diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..eba6d586f --- /dev/null +++ b/.clang-format @@ -0,0 +1,127 @@ +# Commented out parameters are those with the same value as base LLVM style +# We can uncomment them if we want to change their value, or enforce the +# chosen value in case the base style changes (last sync: Clang 6.0.1). +--- +### General config, applies to all languages ### +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +# AlignConsecutiveAssignments: false +# AlignConsecutiveDeclarations: false +# AlignEscapedNewlines: Right +# AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +# AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +# AllowShortLoopsOnASingleLine: false +# AlwaysBreakAfterDefinitionReturnType: None +# AlwaysBreakAfterReturnType: None +# AlwaysBreakBeforeMultilineStrings: false +# AlwaysBreakTemplateDeclarations: false +# BinPackArguments: true +# BinPackParameters: true +# BraceWrapping: +# AfterClass: false +# AfterControlStatement: false +# AfterEnum: false +# AfterFunction: false +# AfterNamespace: false +# AfterObjCDeclaration: false +# AfterStruct: false +# AfterUnion: false +# AfterExternBlock: false +# BeforeCatch: false +# BeforeElse: false +# IndentBraces: false +# SplitEmptyFunction: true +# SplitEmptyRecord: true +# SplitEmptyNamespace: true +# BreakBeforeBinaryOperators: None +# BreakBeforeBraces: Attach +# BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +# BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon +# BreakStringLiterals: true +ColumnLimit: 0 +# CommentPragmas: '^ IWYU pragma:' +# CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +# DerivePointerAlignment: false +# DisableFormat: false +# ExperimentalAutoDetectBinPacking: false +# FixNamespaceComments: true +# ForEachMacros: +# - foreach +# - Q_FOREACH +# - BOOST_FOREACH +# IncludeBlocks: Preserve +IncludeCategories: + - Regex: '".*"' + Priority: 1 + - Regex: '^<.*\.h>' + Priority: 2 + - Regex: '^<.*' + Priority: 3 +# IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: true +# IndentPPDirectives: None +IndentWidth: 4 +# IndentWrappedFunctionNames: false +# JavaScriptQuotes: Leave +# JavaScriptWrapImports: true +# KeepEmptyLinesAtTheStartOfBlocks: true +# MacroBlockBegin: '' +# MacroBlockEnd: '' +# MaxEmptyLinesToKeep: 1 +# NamespaceIndentation: None +# PenaltyBreakAssignment: 2 +# PenaltyBreakBeforeFirstCallParameter: 19 +# PenaltyBreakComment: 300 +# PenaltyBreakFirstLessLess: 120 +# PenaltyBreakString: 1000 +# PenaltyExcessCharacter: 1000000 +# PenaltyReturnTypeOnItsOwnLine: 60 +# PointerAlignment: Right +# RawStringFormats: +# - Delimiter: pb +# Language: TextProto +# BasedOnStyle: google +# ReflowComments: true +# SortIncludes: true +# SortUsingDeclarations: true +# SpaceAfterCStyleCast: false +# SpaceAfterTemplateKeyword: true +# SpaceBeforeAssignmentOperators: true +# SpaceBeforeParens: ControlStatements +# SpaceInEmptyParentheses: false +# SpacesBeforeTrailingComments: 1 +# SpacesInAngles: false +# SpacesInContainerLiterals: true +# SpacesInCStyleCastParentheses: false +# SpacesInParentheses: false +# SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Always +--- +### C++ specific config ### +Language: Cpp +Standard: Cpp11 +--- +### ObjC specific config ### +Language: ObjC +Standard: Cpp11 +ObjCBlockIndentWidth: 4 +# ObjCSpaceAfterProperty: false +# ObjCSpaceBeforeProtocolList: true +--- +### Java specific config ### +Language: Java +# BreakAfterJavaFieldAnnotations: false +... diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..e750822ab --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +patreon: assimp +ko_fi: kimkulling diff --git a/.gitignore b/.gitignore index 9dcb6623d..e975976bf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,11 @@ build .project *.kdev4* +.DS_Store + +# build artefacts +*.o +*.a # Visual Studio *.sln diff --git a/.travis.sh b/.travis.sh index fb42bd40d..f0f0f5d70 100755 --- a/.travis.sh +++ b/.travis.sh @@ -7,7 +7,8 @@ # function generate() { OPTIONS="-DASSIMP_WERROR=ON" - + OPTIONS="$OPTIONS -DASSIMP_NO_EXPORT=NO" + if [ "$DISABLE_EXPORTERS" = "YES" ] ; then OPTIONS="$OPTIONS -DASSIMP_NO_EXPORT=YES" else diff --git a/BUILDBINARIES_EXAMPLE.bat b/BUILDBINARIES_EXAMPLE.bat new file mode 100644 index 000000000..2dd13a542 --- /dev/null +++ b/BUILDBINARIES_EXAMPLE.bat @@ -0,0 +1,26 @@ +:: This is an example file to generate binaries using Windows Operating System +:: This script is configured to be executed from the source directory + +:: Compiled binaries will be placed in BINARIES_DIR\code\CONFIG + +:: NOTE +:: The build process will generate a config.h file that is placed in BINARIES_DIR\include +:: This file must be merged with SOURCE_DIR\include +:: You should write yourself a script that copies the files where you want them. +:: Also see: https://github.com/assimp/assimp/pull/2646 + +SET SOURCE_DIR=. + +:: For generators see "cmake --help" +SET GENERATOR=Visual Studio 15 2017 + +SET BINARIES_DIR="./BINARIES/Win32" +cmake CMakeLists.txt -G "%GENERATOR%" -S %SOURCE_DIR% -B %BINARIES_DIR% +cmake --build %BINARIES_DIR% --config release + +SET BINARIES_DIR="./BINARIES/x64" +cmake CMakeLists.txt -G "%GENERATOR% Win64" -S %SOURCE_DIR% -B %BINARIES_DIR% +cmake --build %BINARIES_DIR% --config debug +cmake --build %BINARIES_DIR% --config release + +PAUSE diff --git a/Build.md b/Build.md index 7c908606d..2db47798d 100644 --- a/Build.md +++ b/Build.md @@ -1,17 +1,31 @@ # Build Instructions -## Install CMake + +## Build on all platforms using vcpkg +You can download and install assimp using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager: +```bash + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + vcpkg install assimp +``` +The assimp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + +## Manual build instructions + +### Install CMake Asset-Importer-Lib can be build for a lot of different platforms. We are using cmake to generate the build environment for these via cmake. So you have to make sure that you have a working cmake-installation on your system. You can download it at https://cmake.org/ or for linux install it via ```bash sudo apt-get install cmake ``` -## Get the source +### Get the source Make sure you have a working git-installation. Open a command prompt and clone the Asset-Importer-Lib via: ```bash git clone https://github.com/assimp/assimp.git ``` -## Build instructions for Windows with Visual-Studio +### Build instructions for Windows with Visual-Studio First you have to install Visual-Studio on your windows-system. You can get the Community-Version for free here: https://visualstudio.microsoft.com/de/downloads/ To generate the build environment for your IDE open a command prompt, navigate to your repo and type: @@ -20,10 +34,10 @@ cmake CMakeLists.txt ``` This will generate the project files for the visual studio. All dependencies used to build Asset-IMporter-Lib shall be part of the repo. If you want to use you own zlib.installation this is possible as well. Check the options for it. -## Build instructions for Windows with UWP +### Build instructions for Windows with UWP See -## Build instructions for Linux / Unix +### Build instructions for Linux / Unix Open a terminal and got to your repository. You can generate the makefiles and build the library via: ```bash @@ -34,7 +48,7 @@ The option -j descripes the number of parallel processes for the build. In this If you want to use a IDE for linux you can try QTCreator for instance. -## Build instructions for MinGW +### Build instructions for MinGW Older versions of MinGW's compiler (e.g. 5.1.0) do not support the -mbig_obj flag required to compile some of assimp's files, especially for debug builds. Version 7.3.0 of g++-mingw-w64 & gcc-mingw-w64 appears to work. @@ -50,7 +64,7 @@ The following toolchain may or may not be helpful for building assimp using MinG Besides the toolchain, compilation should be the same as for Linux / Unix. -## CMake build options +### CMake build options The cmake-build-environment provides options to configure the build. The following options can be used: - **BUILD_SHARED_LIBS ( default ON )**: Generation of shared libs ( dll for windows, so for Linux ). Set this to OFF to get a static lib. - **BUILD_FRAMEWORK ( default OFF, MacOnly)**: Build package as Mac OS X Framework bundle diff --git a/CMakeLists.txt b/CMakeLists.txt index ad0462531..693d6f16a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,7 +173,6 @@ SET (ASSIMP_VERSION ${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}.${ASSIMP_VER SET (ASSIMP_SOVERSION 5) SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" ) - if(NOT HUNTER_ENABLED) # Enable C++11 support globally set_property( GLOBAL PROPERTY CXX_STANDARD 11 ) @@ -254,6 +253,7 @@ ELSEIF(MSVC) IF(MSVC12) ADD_COMPILE_OPTIONS(/wd4351) ENDIF() + SET(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Ob2 /Zi") ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" ) IF(NOT HUNTER_ENABLED) SET(CMAKE_CXX_FLAGS "-fPIC -std=c++11 ${CMAKE_CXX_FLAGS}") @@ -271,22 +271,20 @@ ELSEIF( CMAKE_COMPILER_IS_MINGW ) SET(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}") SET(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}") ENDIF() - SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long -Wa,-mbig-obj ${CMAKE_CXX_FLAGS}") + SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long -Wa,-mbig-obj -O3 ${CMAKE_CXX_FLAGS}") SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}") ADD_DEFINITIONS( -U__STRICT_ANSI__ ) ENDIF() IF ( IOS AND NOT HUNTER_ENABLED) - -IF (CMAKE_BUILD_TYPE STREQUAL "Debug") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -Og") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -Og") -ELSE() - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -O3") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3") - # Experimental for pdb generation -ENDIF() - + IF (CMAKE_BUILD_TYPE STREQUAL "Debug") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -Og") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -Og") + ELSE() + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -O3") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3") + # Experimental for pdb generation + ENDIF() ENDIF( IOS AND NOT HUNTER_ENABLED) IF (ASSIMP_COVERALLS) @@ -341,7 +339,7 @@ SET( ASSIMP_BIN_INSTALL_DIR "bin" CACHE STRING get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) -IF (is_multi_config OR (CMAKE_BUILD_TYPE STREQUAL "Debug")) +IF (INJECT_DEBUG_POSTFIX AND (is_multi_config OR CMAKE_BUILD_TYPE STREQUAL "Debug")) SET(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "Debug Postfix for lib, samples and tools") ELSE() SET(CMAKE_DEBUG_POSTFIX "" CACHE STRING "Debug Postfix for lib, samples and tools") @@ -393,6 +391,11 @@ IF(HUNTER_ENABLED) ) ELSE(HUNTER_ENABLED) # cmake configuration files + if(${BUILD_SHARED_LIBS}) + set(BUILD_LIB_TYPE SHARED) + else() + set(BUILD_LIB_TYPE STATIC) + endif() CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimp-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake" @ONLY IMMEDIATE) CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimpTargets.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets.cmake" @ONLY IMMEDIATE) IF (is_multi_config) @@ -559,17 +562,15 @@ ENDIF(NOT HUNTER_ENABLED) ADD_SUBDIRECTORY( code/ ) IF ( ASSIMP_BUILD_ASSIMP_TOOLS ) + # The viewer for windows only IF ( WIN32 AND DirectX_D3DX9_LIBRARY ) OPTION ( ASSIMP_BUILD_ASSIMP_VIEW "If the Assimp view tool is built. (requires DirectX)" ${DirectX_FOUND} ) IF ( ASSIMP_BUILD_ASSIMP_VIEW ) ADD_SUBDIRECTORY( tools/assimp_view/ ) ENDIF ( ASSIMP_BUILD_ASSIMP_VIEW ) ENDIF ( WIN32 AND DirectX_D3DX9_LIBRARY ) - + # Te command line tool ADD_SUBDIRECTORY( tools/assimp_cmd/ ) -IF (NOT IOS) - ADD_SUBDIRECTORY( tools/assimp_qt_viewer/ ) -ENDIF (NOT IOS) ENDIF ( ASSIMP_BUILD_ASSIMP_TOOLS ) IF ( ASSIMP_BUILD_SAMPLES) diff --git a/Readme.md b/Readme.md index 52a195a64..863e64f08 100644 --- a/Readme.md +++ b/Readme.md @@ -60,13 +60,19 @@ __Importers__: - ENFF - [FBX](https://en.wikipedia.org/wiki/FBX) - [glTF 1.0](https://en.wikipedia.org/wiki/GlTF#glTF_1.0) + GLB -- [glTF 2.0](https://en.wikipedia.org/wiki/GlTF#glTF_2.0) +- [glTF 2.0](https://en.wikipedia.org/wiki/GlTF#glTF_2.0): + At the moment for glTF2.0 the following extensions are supported: + + KHR_lights_punctual ( 5.0 ) + + KHR_materials_pbrSpecularGlossiness ( 5.0 ) + + KHR_materials_unlit ( 5.0 ) + + KHR_texture_transform ( 5.1 under test ) - HMB - IFC-STEP - IRR / IRRMESH - [LWO](https://en.wikipedia.org/wiki/LightWave_3D) - LWS - LXO +- [M3D](https://bztsrc.gitlab.io/model3d) - MD2 - MD3 - MD5 @@ -120,7 +126,7 @@ __Exporters__: - FBX ( experimental ) ### Building ### -Take a look into the https://github.com/assimp/assimp/blob/master/Build.md file. Our build system is CMake, if you used CMake before there is a good chance you know what to do. +Take a look into the https://github.com/assimp/assimp/blob/master/Build.md file. We are available in vcpkg, and our build system is CMake; if you used CMake before there is a good chance you know what to do. ### Ports ### * [Android](port/AndroidJNI/README.md) diff --git a/appveyor.yml b/appveyor.yml index 851d9c096..fa0a125f0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,6 +17,8 @@ image: - Visual Studio 2013 - Visual Studio 2015 - Visual Studio 2017 + - Visual Studio 2019 + - MinGW platform: - Win32 @@ -27,11 +29,15 @@ configuration: Release install: - set PATH=C:\Ruby24-x64\bin;%PATH% - set CMAKE_DEFINES -DASSIMP_WERROR=ON + - if [%COMPILER%]==[MinGW] set PATH=C:\MinGW\bin;%PATH% - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2013" set CMAKE_GENERATOR_NAME=Visual Studio 12 2013 - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015 - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" set CMAKE_GENERATOR_NAME=Visual Studio 15 2017 - - if "%platform%"=="x64" set CMAKE_GENERATOR_NAME=%CMAKE_GENERATOR_NAME% Win64 - - cmake %CMAKE_DEFINES% -G "%CMAKE_GENERATOR_NAME%" . + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2019" set CMAKE_GENERATOR_NAME=Visual Studio 16 2019 + - cmake %CMAKE_DEFINES% -G "%CMAKE_GENERATOR_NAME%" -A %platform% . + # Rename sh.exe as sh.exe in PATH interferes with MinGW - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015 + + - rename "C:\Program Files\Git\usr\bin\sh.exe" "sh2.exe" - set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5" - ps: Invoke-WebRequest -Uri https://download.microsoft.com/download/5/7/b/57b2947c-7221-4f33-b35e-2fc78cb10df4/vc_redist.x64.exe -OutFile .\packaging\windows-innosetup\vc_redist.x64.exe - ps: Invoke-WebRequest -Uri https://download.microsoft.com/download/1/d/8/1d8137db-b5bb-4925-8c5d-927424a2e4de/vc_redist.x86.exe -OutFile .\packaging\windows-innosetup\vc_redist.x86.exe diff --git a/assimpTargets-debug.cmake.in b/assimpTargets-debug.cmake.in index 1ebe2a608..e4ccbfba9 100644 --- a/assimpTargets-debug.cmake.in +++ b/assimpTargets-debug.cmake.in @@ -35,6 +35,8 @@ if(MSVC) endif() set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" ) + file(TO_NATIVE_PATH ${_IMPORT_PREFIX} _IMPORT_PREFIX) + if(ASSIMP_BUILD_SHARED_LIBS) set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@") set(importLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_IMPORT_LIBRARY_SUFFIX@") @@ -63,7 +65,14 @@ if(MSVC) else() set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" ) if(ASSIMP_BUILD_SHARED_LIBS) - set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") + if(WIN32) + # Handle MinGW compiler. + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@@CMAKE_STATIC_LIBRARY_SUFFIX@") + elseif(APPLE) + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@.@ASSIMP_VERSION_MAJOR@@CMAKE_SHARED_LIBRARY_SUFFIX@") + else() + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") + endif() set_target_properties(assimp::assimp PROPERTIES IMPORTED_SONAME_DEBUG "${sharedLibraryName}" IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/${sharedLibraryName}" diff --git a/assimpTargets-release.cmake.in b/assimpTargets-release.cmake.in index b09b881f7..79b643a9a 100644 --- a/assimpTargets-release.cmake.in +++ b/assimpTargets-release.cmake.in @@ -34,6 +34,8 @@ if(MSVC) endif() endif() set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" ) + + file(TO_NATIVE_PATH ${_IMPORT_PREFIX} _IMPORT_PREFIX) if(ASSIMP_BUILD_SHARED_LIBS) set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@") @@ -63,9 +65,17 @@ if(MSVC) else() set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" ) if(ASSIMP_BUILD_SHARED_LIBS) - set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") + if(WIN32) + # Handle MinGW compiler. + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@@CMAKE_STATIC_LIBRARY_SUFFIX@") + elseif(APPLE) + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}.@ASSIMP_VERSION_MAJOR@@CMAKE_SHARED_LIBRARY_SUFFIX@") + else() + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") + endif() set_target_properties(assimp::assimp PROPERTIES IMPORTED_SONAME_RELEASE "${sharedLibraryName}" + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/${sharedLibraryName}" ) list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp ) diff --git a/assimpTargets.cmake.in b/assimpTargets.cmake.in index ab1a8d2c7..b1c618c3a 100644 --- a/assimpTargets.cmake.in +++ b/assimpTargets.cmake.in @@ -5,6 +5,9 @@ if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.5) endif() cmake_policy(PUSH) cmake_policy(VERSION 2.6) +# Required for the evaluation of "if(@BUILD_SHARED_LIBS@)" below to function +cmake_policy(SET CMP0012 NEW) + #---------------------------------------------------------------- # Generated CMake target import file. #---------------------------------------------------------------- @@ -51,11 +54,7 @@ if(_IMPORT_PREFIX STREQUAL "/") endif() # Create imported target assimp::assimp -if(@BUILD_SHARED_LIBS@) - add_library(assimp::assimp SHARED IMPORTED) -else() - add_library(assimp::assimp STATIC IMPORTED) -endif() +add_library(assimp::assimp @BUILD_LIB_TYPE@ IMPORTED) set_target_properties(assimp::assimp PROPERTIES COMPATIBLE_INTERFACE_STRING "assimp_MAJOR_VERSION" diff --git a/cmake-modules/Findassimp.cmake b/cmake-modules/Findassimp.cmake index 95f3250b3..663645574 100644 --- a/cmake-modules/Findassimp.cmake +++ b/cmake-modules/Findassimp.cmake @@ -54,14 +54,18 @@ else(WIN32) find_path( assimp_INCLUDE_DIRS - NAMES postprocess.h scene.h version.h config.h cimport.h - PATHS /usr/local/include/ + NAMES assimp/postprocess.h assimp/scene.h assimp/version.h assimp/config.h assimp/cimport.h + PATHS /usr/local/include + PATHS /usr/include/ + ) find_library( assimp_LIBRARIES NAMES assimp PATHS /usr/local/lib/ + PATHS /usr/lib64/ + PATHS /usr/lib/ ) if (assimp_INCLUDE_DIRS AND assimp_LIBRARIES) @@ -78,4 +82,4 @@ else(WIN32) endif (assimp_FIND_REQUIRED) endif (assimp_FOUND) -endif(WIN32) \ No newline at end of file +endif(WIN32) diff --git a/code/3DS/3DSConverter.cpp b/code/3DS/3DSConverter.cpp index 2176b75fc..3c3da36a3 100644 --- a/code/3DS/3DSConverter.cpp +++ b/code/3DS/3DSConverter.cpp @@ -72,7 +72,7 @@ void Discreet3DSImporter::ReplaceDefaultMaterial() unsigned int idx( NotSet ); for (unsigned int i = 0; i < mScene->mMaterials.size();++i) { - std::string &s = mScene->mMaterials[i].mName; + std::string s = mScene->mMaterials[i].mName; for ( std::string::iterator it = s.begin(); it != s.end(); ++it ) { *it = static_cast< char >( ::tolower( *it ) ); } diff --git a/code/3DS/3DSLoader.cpp b/code/3DS/3DSLoader.cpp index 96b80c962..3c659d0b0 100644 --- a/code/3DS/3DSLoader.cpp +++ b/code/3DS/3DSLoader.cpp @@ -50,9 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER -// internal headers #include "3DSLoader.h" -#include #include #include #include diff --git a/code/3MF/D3MFImporter.cpp b/code/3MF/D3MFImporter.cpp index 2898f8ac2..682de684a 100644 --- a/code/3MF/D3MFImporter.cpp +++ b/code/3MF/D3MFImporter.cpp @@ -50,6 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #include @@ -58,11 +59,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "D3MFOpcPackage.h" -#ifdef ASSIMP_USE_HUNTER -# include -#else -# include -#endif #include #include "3MFXmlTags.h" #include @@ -453,7 +449,7 @@ bool D3MFImporter::CanRead(const std::string &filename, IOSystem *pIOHandler, bo if ( nullptr == pIOHandler ) { return false; } - if ( !D3MF::D3MFOpcPackage::isZipArchive( pIOHandler, filename ) ) { + if ( !ZipArchiveIOSystem::isZipArchive( pIOHandler, filename ) ) { return false; } D3MF::D3MFOpcPackage opcPackage( pIOHandler, filename ); diff --git a/code/3MF/D3MFOpcPackage.cpp b/code/3MF/D3MFOpcPackage.cpp index 3a6e4dbc9..873ba8ee8 100644 --- a/code/3MF/D3MFOpcPackage.cpp +++ b/code/3MF/D3MFOpcPackage.cpp @@ -49,6 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #include @@ -56,344 +57,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#ifdef ASSIMP_USE_HUNTER -# include -#else -# include -#endif #include "3MFXmlTags.h" namespace Assimp { namespace D3MF { - -class IOSystem2Unzip { -public: - static voidpf open(voidpf opaque, const char* filename, int mode); - static uLong read(voidpf opaque, voidpf stream, void* buf, uLong size); - static uLong write(voidpf opaque, voidpf stream, const void* buf, uLong size); - static long tell(voidpf opaque, voidpf stream); - static long seek(voidpf opaque, voidpf stream, uLong offset, int origin); - static int close(voidpf opaque, voidpf stream); - static int testerror(voidpf opaque, voidpf stream); - static zlib_filefunc_def get(IOSystem* pIOHandler); -}; - -voidpf IOSystem2Unzip::open(voidpf opaque, const char* filename, int mode) { - IOSystem* io_system = reinterpret_cast(opaque); - - const char* mode_fopen = NULL; - if((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) { - mode_fopen = "rb"; - } else { - if(mode & ZLIB_FILEFUNC_MODE_EXISTING) { - mode_fopen = "r+b"; - } else { - if(mode & ZLIB_FILEFUNC_MODE_CREATE) { - mode_fopen = "wb"; - } - } - } - - return (voidpf) io_system->Open(filename, mode_fopen); -} - -uLong IOSystem2Unzip::read(voidpf /*opaque*/, voidpf stream, void* buf, uLong size) { - IOStream* io_stream = (IOStream*) stream; - - return static_cast(io_stream->Read(buf, 1, size)); -} - -uLong IOSystem2Unzip::write(voidpf /*opaque*/, voidpf stream, const void* buf, uLong size) { - IOStream* io_stream = (IOStream*) stream; - - return static_cast(io_stream->Write(buf, 1, size)); -} - -long IOSystem2Unzip::tell(voidpf /*opaque*/, voidpf stream) { - IOStream* io_stream = (IOStream*) stream; - - return static_cast(io_stream->Tell()); -} - -long IOSystem2Unzip::seek(voidpf /*opaque*/, voidpf stream, uLong offset, int origin) { - IOStream* io_stream = (IOStream*) stream; - - aiOrigin assimp_origin; - switch (origin) { - default: - case ZLIB_FILEFUNC_SEEK_CUR: - assimp_origin = aiOrigin_CUR; - break; - case ZLIB_FILEFUNC_SEEK_END: - assimp_origin = aiOrigin_END; - break; - case ZLIB_FILEFUNC_SEEK_SET: - assimp_origin = aiOrigin_SET; - break; - } - - return (io_stream->Seek(offset, assimp_origin) == aiReturn_SUCCESS ? 0 : -1); -} - -int IOSystem2Unzip::close(voidpf opaque, voidpf stream) { - IOSystem* io_system = (IOSystem*) opaque; - IOStream* io_stream = (IOStream*) stream; - - io_system->Close(io_stream); - - return 0; -} - -int IOSystem2Unzip::testerror(voidpf /*opaque*/, voidpf /*stream*/) { - return 0; -} - -zlib_filefunc_def IOSystem2Unzip::get(IOSystem* pIOHandler) { - zlib_filefunc_def mapping; - -#ifdef ASSIMP_USE_HUNTER - mapping.zopen_file = (open_file_func)open; - mapping.zread_file = (read_file_func)read; - mapping.zwrite_file = (write_file_func)write; - mapping.ztell_file = (tell_file_func)tell; - mapping.zseek_file = (seek_file_func)seek; - mapping.zclose_file = (close_file_func)close; - mapping.zerror_file = (error_file_func)testerror; -#else - mapping.zopen_file = open; - mapping.zread_file = read; - mapping.zwrite_file = write; - mapping.ztell_file = tell; - mapping.zseek_file = seek; - mapping.zclose_file = close; - mapping.zerror_file = testerror; -#endif - mapping.opaque = reinterpret_cast(pIOHandler); - - return mapping; -} - -class ZipFile : public IOStream { - friend class D3MFZipArchive; - -public: - explicit ZipFile(size_t size); - virtual ~ZipFile(); - size_t Read(void* pvBuffer, size_t pSize, size_t pCount ); - size_t Write(const void* /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/); - size_t FileSize() const; - aiReturn Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/); - size_t Tell() const; - void Flush(); - -private: - void *m_Buffer; - size_t m_Size; -}; - -ZipFile::ZipFile(size_t size) -: m_Buffer( nullptr ) -, m_Size(size) { - ai_assert(m_Size != 0); - m_Buffer = ::malloc(m_Size); -} - -ZipFile::~ZipFile() { - ::free(m_Buffer); - m_Buffer = NULL; -} - -size_t ZipFile::Read(void* pvBuffer, size_t pSize, size_t pCount) { - const size_t size = pSize * pCount; - ai_assert(size <= m_Size); - - std::memcpy(pvBuffer, m_Buffer, size); - - return size; -} - -size_t ZipFile::Write(const void* pvBuffer, size_t size, size_t pCount ) { - const size_t size_to_write( size * pCount ); - if ( 0 == size_to_write ) { - return 0U; - } - return 0U; -} - -size_t ZipFile::FileSize() const { - return m_Size; -} - -aiReturn ZipFile::Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/) { - return aiReturn_FAILURE; -} - -size_t ZipFile::Tell() const { - return 0; -} - -void ZipFile::Flush() { - // empty -} - -class D3MFZipArchive : public IOSystem { -public: - static const unsigned int FileNameSize = 256; - - D3MFZipArchive(IOSystem* pIOHandler, const std::string & rFile); - ~D3MFZipArchive(); - bool Exists(const char* pFile) const; - char getOsSeparator() const; - IOStream* Open(const char* pFile, const char* pMode = "rb"); - void Close(IOStream* pFile); - bool isOpen() const; - void getFileList(std::vector &rFileList); - -private: - bool mapArchive(); - -private: - unzFile m_ZipFileHandle; - std::map m_ArchiveMap; -}; - -// ------------------------------------------------------------------------------------------------ -// Constructor. -D3MFZipArchive::D3MFZipArchive(IOSystem* pIOHandler, const std::string& rFile) -: m_ZipFileHandle( nullptr ) -, m_ArchiveMap() { - if (! rFile.empty()) { - zlib_filefunc_def mapping = IOSystem2Unzip::get(pIOHandler); - - m_ZipFileHandle = unzOpen2(rFile.c_str(), &mapping); - if(m_ZipFileHandle != nullptr ) { - mapArchive(); - } - } -} - -// ------------------------------------------------------------------------------------------------ -// Destructor. -D3MFZipArchive::~D3MFZipArchive() { - for(auto &file : m_ArchiveMap) { - delete file.second; - } - m_ArchiveMap.clear(); - - if(m_ZipFileHandle != nullptr) { - unzClose(m_ZipFileHandle); - m_ZipFileHandle = nullptr; - } -} - -// ------------------------------------------------------------------------------------------------ -// Returns true, if the archive is already open. -bool D3MFZipArchive::isOpen() const { - return (m_ZipFileHandle != nullptr ); -} - -// ------------------------------------------------------------------------------------------------ -// Returns true, if the filename is part of the archive. -bool D3MFZipArchive::Exists(const char* pFile) const { - ai_assert(pFile != nullptr ); - - if ( pFile == nullptr ) { - return false; - } - - std::string filename(pFile); - std::map::const_iterator it = m_ArchiveMap.find(filename); - bool exist( false ); - if(it != m_ArchiveMap.end()) { - exist = true; - } - - return exist; -} - -// ------------------------------------------------------------------------------------------------ -// Returns the separator delimiter. -char D3MFZipArchive::getOsSeparator() const { -#ifndef _WIN32 - return '/'; -#else - return '\\'; -#endif -} - -// ------------------------------------------------------------------------------------------------ -// Opens a file, which is part of the archive. -IOStream *D3MFZipArchive::Open(const char* pFile, const char* /*pMode*/) { - ai_assert(pFile != NULL); - - IOStream* result = NULL; - - std::map::iterator it = m_ArchiveMap.find(pFile); - - if(it != m_ArchiveMap.end()) { - result = static_cast(it->second); - } - - return result; -} - -// ------------------------------------------------------------------------------------------------ -// Close a filestream. -void D3MFZipArchive::Close(IOStream *pFile) { - (void)(pFile); - ai_assert(pFile != NULL); - - // We don't do anything in case the file would be opened again in the future -} -// ------------------------------------------------------------------------------------------------ -// Returns the file-list of the archive. -void D3MFZipArchive::getFileList(std::vector &rFileList) { - rFileList.clear(); - - for(const auto &file : m_ArchiveMap) { - rFileList.push_back(file.first); - } -} - -// ------------------------------------------------------------------------------------------------ -// Maps the archive content. -bool D3MFZipArchive::mapArchive() { - bool success = false; - - if(m_ZipFileHandle != NULL) { - if(m_ArchiveMap.empty()) { - // At first ensure file is already open - if(unzGoToFirstFile(m_ZipFileHandle) == UNZ_OK) { - // Loop over all files - do { - char filename[FileNameSize]; - unz_file_info fileInfo; - - if(unzGetCurrentFileInfo(m_ZipFileHandle, &fileInfo, filename, FileNameSize, NULL, 0, NULL, 0) == UNZ_OK) { - // The file has EXACTLY the size of uncompressed_size. In C - // you need to mark the last character with '\0', so add - // another character - if(fileInfo.uncompressed_size != 0 && unzOpenCurrentFile(m_ZipFileHandle) == UNZ_OK) { - std::pair::iterator, bool> result = m_ArchiveMap.insert(std::make_pair(filename, new ZipFile(fileInfo.uncompressed_size))); - - if(unzReadCurrentFile(m_ZipFileHandle, result.first->second->m_Buffer, fileInfo.uncompressed_size) == (long int) fileInfo.uncompressed_size) { - if(unzCloseCurrentFile(m_ZipFileHandle) == UNZ_OK) { - // Nothing to do anymore... - } - } - } - } - } while(unzGoToNextFile(m_ZipFileHandle) != UNZ_END_OF_LIST_OF_FILE); - } - } - - success = true; - } - - return success; -} - // ------------------------------------------------------------------------------------------------ typedef std::shared_ptr OpcPackageRelationshipPtr; @@ -453,7 +121,7 @@ public: D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile) : mRootStream(nullptr) , mZipArchive() { - mZipArchive.reset( new D3MF::D3MFZipArchive( pIOHandler, rFile ) ); + mZipArchive.reset( new ZipArchiveIOSystem( pIOHandler, rFile ) ); if(!mZipArchive->isOpen()) { throw DeadlyImportError("Failed to open file " + rFile+ "."); } @@ -481,14 +149,14 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile) ASSIMP_LOG_DEBUG(rootFile); + mZipArchive->Close(fileStream); + mRootStream = mZipArchive->Open(rootFile.c_str()); ai_assert( mRootStream != nullptr ); if ( nullptr == mRootStream ) { throw DeadlyExportError( "Cannot open root-file in archive : " + rootFile ); } - mZipArchive->Close( fileStream ); - } else if( file == D3MF::XmlTag::CONTENT_TYPES_ARCHIVE) { ASSIMP_LOG_WARN_F("Ignored file of unsupported type CONTENT_TYPES_ARCHIVES",file); } else { @@ -499,7 +167,7 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile) } D3MFOpcPackage::~D3MFOpcPackage() { - // empty + mZipArchive->Close(mRootStream); } IOStream* D3MFOpcPackage::RootStream() const { @@ -516,15 +184,6 @@ bool D3MFOpcPackage::validate() { return mZipArchive->Exists( ModelRef.c_str() ); } -bool D3MFOpcPackage::isZipArchive( IOSystem* pIOHandler, const std::string& rFile ) { - D3MF::D3MFZipArchive ar( pIOHandler, rFile ); - if ( !ar.isOpen() ) { - return false; - } - - return true; -} - std::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream* stream) { std::unique_ptr xmlStream(new CIrrXML_IOStreamReader(stream)); std::unique_ptr xml(irr::io::createIrrXMLReader(xmlStream.get())); diff --git a/code/3MF/D3MFOpcPackage.h b/code/3MF/D3MFOpcPackage.h index 47c67f45f..87d172116 100644 --- a/code/3MF/D3MFOpcPackage.h +++ b/code/3MF/D3MFOpcPackage.h @@ -49,6 +49,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include namespace Assimp { + class ZipArchiveIOSystem; + namespace D3MF { using XmlReader = irr::io::IrrXMLReader ; @@ -60,22 +62,19 @@ struct OpcPackageRelationship { std::string target; }; -class D3MFZipArchive; - class D3MFOpcPackage { public: D3MFOpcPackage( IOSystem* pIOHandler, const std::string& rFile ); ~D3MFOpcPackage(); IOStream* RootStream() const; bool validate(); - static bool isZipArchive( IOSystem* pIOHandler, const std::string& rFile ); protected: std::string ReadPackageRootRelationship(IOStream* stream); private: IOStream* mRootStream; - std::unique_ptr mZipArchive; + std::unique_ptr mZipArchive; }; } // Namespace D3MF diff --git a/code/AMF/AMFImporter.cpp b/code/AMF/AMFImporter.cpp index d173bd0f5..dedb6dcdd 100644 --- a/code/AMF/AMFImporter.cpp +++ b/code/AMF/AMFImporter.cpp @@ -83,7 +83,7 @@ void AMFImporter::Clear() mMaterial_Converted.clear(); mTexture_Converted.clear(); // Delete all elements - if(mNodeElement_List.size()) + if(!mNodeElement_List.empty()) { for(CAMFImporter_NodeElement* ne: mNodeElement_List) { delete ne; } diff --git a/code/AMF/AMFImporter_Postprocess.cpp b/code/AMF/AMFImporter_Postprocess.cpp index 2bfe3f78c..79b5e15e2 100644 --- a/code/AMF/AMFImporter_Postprocess.cpp +++ b/code/AMF/AMFImporter_Postprocess.cpp @@ -66,7 +66,7 @@ aiColor4D AMFImporter::SPP_Material::GetColor(const float /*pX*/, const float /* aiColor4D tcol; // Check if stored data are supported. - if(Composition.size() != 0) + if(!Composition.empty()) { throw DeadlyImportError("IME. GetColor for composition"); } @@ -321,7 +321,7 @@ void AMFImporter::PostprocessHelper_SplitFacesByTextureID(std::list 0) pOutputList_Separated.push_back(face_list_cur); + if(!face_list_cur.empty()) pOutputList_Separated.push_back(face_list_cur); - } while(pInputList.size() > 0); + } while(!pInputList.empty()); } void AMFImporter::Postprocess_AddMetadata(const std::list& metadataList, aiNode& sceneNode) const @@ -712,7 +712,7 @@ std::list mesh_idx; }// for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child) // if meshes was created then assign new indices with current aiNode - if(mesh_idx.size() > 0) + if(!mesh_idx.empty()) { std::list::const_iterator mit = mesh_idx.begin(); @@ -787,7 +787,7 @@ std::list ch_node; }// for(const CAMFImporter_NodeElement* ne: pConstellation.Child) // copy found aiNode's as children - if(ch_node.size() == 0) throw DeadlyImportError(" must have at least one ."); + if(ch_node.empty()) throw DeadlyImportError(" must have at least one ."); size_t ch_idx = 0; @@ -883,13 +883,13 @@ nl_clean_loop: if(node_list.size() > 1) { // walk through all nodes - for(std::list::iterator nl_it = node_list.begin(); nl_it != node_list.end(); nl_it++) + for(std::list::iterator nl_it = node_list.begin(); nl_it != node_list.end(); ++nl_it) { // and try to find them in another top nodes. std::list::const_iterator next_it = nl_it; - next_it++; - for(; next_it != node_list.end(); next_it++) + ++next_it; + for(; next_it != node_list.end(); ++next_it) { if((*next_it)->FindNode((*nl_it)->mName) != nullptr) { @@ -907,7 +907,7 @@ nl_clean_loop: // // // Nodes - if(node_list.size() > 0) + if(!node_list.empty()) { std::list::const_iterator nl_it = node_list.begin(); @@ -924,7 +924,7 @@ nl_clean_loop: // // Meshes - if(mesh_list.size() > 0) + if(!mesh_list.empty()) { std::list::const_iterator ml_it = mesh_list.begin(); diff --git a/code/Assjson/json_exporter.cpp b/code/Assjson/json_exporter.cpp index cd542b29e..e9fa72496 100644 --- a/code/Assjson/json_exporter.cpp +++ b/code/Assjson/json_exporter.cpp @@ -34,15 +34,6 @@ namespace Assimp { void ExportAssimp2Json(const char*, Assimp::IOSystem*, const aiScene*, const Assimp::ExportProperties*); -Exporter::ExportFormatEntry Assimp2Json_desc = Assimp::Exporter::ExportFormatEntry( - "json", - "Plain JSON representation of the Assimp scene data structure", - "json", - &ExportAssimp2Json, - 0u -); - - // small utility class to simplify serializing the aiScene to Json class JSONWriter { public: diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index a8a514f73..827f43333 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -104,6 +104,7 @@ SET( PUBLIC_HEADERS ${HEADER_PATH}/Exporter.hpp ${HEADER_PATH}/DefaultIOStream.h ${HEADER_PATH}/DefaultIOSystem.h + ${HEADER_PATH}/ZipArchiveIOSystem.h ${HEADER_PATH}/SceneCombiner.h ${HEADER_PATH}/fast_atof.h ${HEADER_PATH}/qnan.h @@ -136,7 +137,6 @@ SET( PUBLIC_HEADERS ${HEADER_PATH}/irrXMLWrapper.h ${HEADER_PATH}/BlobIOSystem.h ${HEADER_PATH}/MathFunctions.h - ${HEADER_PATH}/Macros.h ${HEADER_PATH}/Exceptional.h ${HEADER_PATH}/ByteSwapper.h ) @@ -172,6 +172,7 @@ SET( Common_SRCS Common/DefaultProgressHandler.h Common/DefaultIOStream.cpp Common/DefaultIOSystem.cpp + Common/ZipArchiveIOSystem.cpp Common/PolyTools.h Common/Importer.cpp Common/IFF.h @@ -406,6 +407,18 @@ ADD_ASSIMP_IMPORTER( LWS LWS/LWSLoader.h ) +ADD_ASSIMP_IMPORTER( M3D + M3D/M3DMaterials.h + M3D/M3DImporter.h + M3D/M3DImporter.cpp + M3D/m3d.h +) + +ADD_ASSIMP_EXPORTER( M3D + M3D/M3DExporter.h + M3D/M3DExporter.cpp +) + ADD_ASSIMP_IMPORTER( MD2 MD2/MD2FileData.h MD2/MD2Loader.cpp @@ -669,6 +682,8 @@ SET( PostProcessing_SRCS PostProcessing/MakeVerboseFormat.h PostProcessing/ScaleProcess.cpp PostProcessing/ScaleProcess.h + PostProcessing/ArmaturePopulate.cpp + PostProcessing/ArmaturePopulate.h PostProcessing/GenBoundingBoxesProcess.cpp PostProcessing/GenBoundingBoxesProcess.h ) @@ -688,8 +703,6 @@ ADD_ASSIMP_IMPORTER( Q3BSP Q3BSP/Q3BSPFileParser.cpp Q3BSP/Q3BSPFileImporter.h Q3BSP/Q3BSPFileImporter.cpp - Q3BSP/Q3BSPZipArchive.h - Q3BSP/Q3BSPZipArchive.cpp ) ADD_ASSIMP_IMPORTER( RAW @@ -766,6 +779,8 @@ ADD_ASSIMP_EXPORTER( X3D ) ADD_ASSIMP_IMPORTER( GLTF + glTF/glTFCommon.h + glTF/glTFCommon.cpp glTF/glTFAsset.h glTF/glTFAsset.inl glTF/glTFAssetWriter.h @@ -810,7 +825,7 @@ ADD_ASSIMP_IMPORTER( MMD MMD/MMDVmdParser.h ) -ADD_ASSIMP_EXPORTER( Assjson +ADD_ASSIMP_EXPORTER( ASSJSON Assjson/cencode.c Assjson/cencode.h Assjson/json_exporter.cpp @@ -1057,6 +1072,8 @@ MESSAGE(STATUS "Disabled importer formats:${ASSIMP_IMPORTERS_DISABLED}") MESSAGE(STATUS "Enabled exporter formats:${ASSIMP_EXPORTERS_ENABLED}") MESSAGE(STATUS "Disabled exporter formats:${ASSIMP_EXPORTERS_DISABLED}") +SOURCE_GROUP( include\\assimp FILES ${PUBLIC_HEADERS} ) + SET( assimp_src # Assimp Files ${Core_SRCS} diff --git a/code/Collada/ColladaExporter.cpp b/code/Collada/ColladaExporter.cpp index cace8460e..a93dfa59a 100644 --- a/code/Collada/ColladaExporter.cpp +++ b/code/Collada/ColladaExporter.cpp @@ -45,6 +45,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ColladaExporter.h" #include +#include #include #include #include @@ -91,6 +92,36 @@ void ExportSceneCollada(const char* pFile, IOSystem* pIOSystem, const aiScene* p } // end of namespace Assimp +// ------------------------------------------------------------------------------------------------ +// Encodes a string into a valid XML ID using the xsd:ID schema qualifications. +static const std::string XMLIDEncode(const std::string& name) { + const char XML_ID_CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-."; + const unsigned int XML_ID_CHARS_COUNT = sizeof(XML_ID_CHARS) / sizeof(char); + + if (name.length() == 0) { + return name; + } + + std::stringstream idEncoded; + + // xsd:ID must start with letter or underscore + if (!((name[0] >= 'A' && name[0] <= 'z') || name[0] == '_')) { + idEncoded << '_'; + } + + for (std::string::const_iterator it = name.begin(); it != name.end(); ++it) { + // xsd:ID can only contain letters, digits, underscores, hyphens and periods + if (strchr(XML_ID_CHARS, *it) != nullptr) { + idEncoded << *it; + } else { + // Select placeholder character based on invalid character to prevent name collisions + idEncoded << XML_ID_CHARS[(*it) % XML_ID_CHARS_COUNT]; + } + } + + return idEncoded.str(); +} + // ------------------------------------------------------------------------------------------------ // Constructor for a specific scene to export ColladaExporter::ColladaExporter( const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file) @@ -145,7 +176,7 @@ void ColladaExporter::WriteFile() { // useless Collada fu at the end, just in case we haven't had enough indirections, yet. mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "mRootNode->mName.C_Str()) + "\" />" << endstr; + mOutput << startstr << "mRootNode->mName.C_Str()) + "\" />" << endstr; PopTag(); mOutput << startstr << "" << endstr; PopTag(); @@ -155,7 +186,7 @@ void ColladaExporter::WriteFile() { // ------------------------------------------------------------------------------------------------ // Writes the asset header void ColladaExporter::WriteHeader() { - static const ai_real epsilon = ai_real( 0.00001 ); + static const ai_real epsilon = Math::getEpsilon(); static const aiQuaternion x_rot(aiMatrix3x3( 0, -1, 0, 1, 0, 0, @@ -317,7 +348,7 @@ void ColladaExporter::WriteTextures() { std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char*) texture->achFormatHint); - std::unique_ptr outfile(mIOSystem->Open(mPath + name, "wb")); + std::unique_ptr outfile(mIOSystem->Open(mPath + mIOSystem->getOsSeparator() + name, "wb")); if(outfile == NULL) { throw DeadlyExportError("could not open output texture file: " + mPath + name); } @@ -355,9 +386,10 @@ void ColladaExporter::WriteCamerasLibrary() { void ColladaExporter::WriteCamera(size_t pIndex){ const aiCamera *cam = mScene->mCameras[pIndex]; - const std::string idstrEscaped = XMLEscape(cam->mName.C_Str()); + const std::string cameraName = XMLEscape(cam->mName.C_Str()); + const std::string cameraId = XMLIDEncode(cam->mName.C_Str()); - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); mOutput << startstr << "" << endstr; PushTag(); @@ -411,10 +443,11 @@ void ColladaExporter::WriteLightsLibrary() { void ColladaExporter::WriteLight(size_t pIndex){ const aiLight *light = mScene->mLights[pIndex]; - const std::string idstrEscaped = XMLEscape(light->mName.C_Str()); + const std::string lightName = XMLEscape(light->mName.C_Str()); + const std::string lightId = XMLIDEncode(light->mName.C_Str()); - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); mOutput << startstr << "" << endstr; PushTag(); @@ -585,7 +618,7 @@ static bool isalnum_C(char c) { void ColladaExporter::WriteImageEntry( const Surface& pSurface, const std::string& pNameAdd) { if( !pSurface.texture.empty() ) { - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); mOutput << startstr << ""; @@ -618,7 +651,7 @@ void ColladaExporter::WriteTextureColorEntry( const Surface& pSurface, const std } else { - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; } PopTag(); mOutput << startstr << "" << endstr; @@ -632,21 +665,21 @@ void ColladaExporter::WriteTextureParamEntry( const Surface& pSurface, const std // if surface is a texture, write out the sampler and the surface parameters necessary to reference the texture if( !pSurface.texture.empty() ) { - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "" << XMLEscape(pMatName) << "-" << pTypeName << "-image" << endstr; + mOutput << startstr << "" << XMLIDEncode(pMatName) << "-" << pTypeName << "-image" << endstr; PopTag(); mOutput << startstr << "" << endstr; PopTag(); mOutput << startstr << "" << endstr; - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "" << XMLEscape(pMatName) << "-" << pTypeName << "-surface" << endstr; + mOutput << startstr << "" << XMLIDEncode(pMatName) << "-" << pTypeName << "-surface" << endstr; PopTag(); mOutput << startstr << "" << endstr; PopTag(); @@ -698,11 +731,6 @@ void ColladaExporter::WriteMaterials() materials[a].name = std::string(name.C_Str()) + to_string(materialCountWithThisName); } } - for( std::string::iterator it = materials[a].name.begin(); it != materials[a].name.end(); ++it ) { - if( !isalnum_C( *it ) ) { - *it = '_'; - } - } aiShadingMode shading = aiShadingMode_Flat; materials[a].shading_model = "phong"; @@ -767,7 +795,7 @@ void ColladaExporter::WriteMaterials() { const Material& mat = *it; // this is so ridiculous it must be right - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); mOutput << startstr << "" << endstr; PushTag(); @@ -818,9 +846,9 @@ void ColladaExporter::WriteMaterials() for( std::vector::const_iterator it = materials.begin(); it != materials.end(); ++it ) { const Material& mat = *it; - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PopTag(); mOutput << startstr << "" << endstr; } @@ -849,8 +877,8 @@ void ColladaExporter::WriteControllerLibrary() void ColladaExporter::WriteController( size_t pIndex) { const aiMesh* mesh = mScene->mMeshes[pIndex]; - const std::string idstr = GetMeshId( pIndex); - const std::string idstrEscaped = XMLEscape(idstr); + const std::string idstr = mesh->mName.length == 0 ? GetMeshId(pIndex) : mesh->mName.C_Str(); + const std::string idstrEscaped = XMLIDEncode(idstr); if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 ) return; @@ -885,7 +913,7 @@ void ColladaExporter::WriteController( size_t pIndex) mOutput << startstr << "mNumBones << "\">"; for( size_t i = 0; i < mesh->mNumBones; ++i ) - mOutput << XMLEscape(mesh->mBones[i]->mName.C_Str()) << " "; + mOutput << XMLIDEncode(mesh->mBones[i]->mName.C_Str()) << " "; mOutput << "" << endstr; @@ -1020,14 +1048,15 @@ void ColladaExporter::WriteGeometryLibrary() void ColladaExporter::WriteGeometry( size_t pIndex) { const aiMesh* mesh = mScene->mMeshes[pIndex]; - const std::string idstr = GetMeshId( pIndex); - const std::string idstrEscaped = XMLEscape(idstr); + const std::string idstr = mesh->mName.length == 0 ? GetMeshId(pIndex) : mesh->mName.C_Str(); + const std::string geometryName = XMLEscape(idstr); + const std::string geometryId = XMLIDEncode(idstr); if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 ) return; // opening tag - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); mOutput << startstr << "" << endstr; @@ -1058,9 +1087,9 @@ void ColladaExporter::WriteGeometry( size_t pIndex) // assemble vertex structure // Only write input for POSITION since we will write other as shared inputs in polygon definition - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PopTag(); mOutput << startstr << "" << endstr; @@ -1078,18 +1107,18 @@ void ColladaExporter::WriteGeometry( size_t pIndex) { mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; if( mesh->HasNormals() ) - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) { if( mesh->HasTextureCoords(static_cast(a)) ) - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; } for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a ) { if( mesh->HasVertexColors(static_cast(a) ) ) - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; } mOutput << startstr << "

"; @@ -1112,18 +1141,18 @@ void ColladaExporter::WriteGeometry( size_t pIndex) { mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; if( mesh->HasNormals() ) - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) { if( mesh->HasTextureCoords(static_cast(a)) ) - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; } for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a ) { if( mesh->HasVertexColors(static_cast(a) ) ) - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; } mOutput << startstr << ""; @@ -1172,13 +1201,13 @@ void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataTy return; } - std::string arrayId = pIdString + "-array"; + std::string arrayId = XMLIDEncode(pIdString) + "-array"; - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); // source array - mOutput << startstr << " "; + mOutput << startstr << " "; PushTag(); if( pType == FloatType_TexCoord2 ) @@ -1264,11 +1293,12 @@ void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataTy // Writes the scene library void ColladaExporter::WriteSceneLibrary() { - const std::string scene_name_escaped = XMLEscape(mScene->mRootNode->mName.C_Str()); + const std::string sceneName = XMLEscape(mScene->mRootNode->mName.C_Str()); + const std::string sceneId = XMLIDEncode(mScene->mRootNode->mName.C_Str()); mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); // start recursive write at the root node @@ -1299,7 +1329,7 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex) idstr = idstr + ending; } - const std::string idstrEscaped = XMLEscape(idstr); + const std::string idstrEscaped = XMLIDEncode(idstr); mOutput << startstr << "" << endstr; PushTag(); @@ -1371,13 +1401,13 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex) } const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-interpolation"); - std::string arrayId = node_idstr + "-array"; + std::string arrayId = XMLIDEncode(node_idstr) + "-array"; - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); // source array - mOutput << startstr << " "; + mOutput << startstr << " "; for( size_t a = 0; a < names.size(); ++a ) { mOutput << names[a] << " "; } @@ -1386,7 +1416,7 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex) mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); mOutput << startstr << "" << endstr; @@ -1408,12 +1438,12 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex) { // samplers const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-sampler"); - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "mNodeName.data + std::string("_matrix-input") ) << "\"/>" << endstr; - mOutput << startstr << "mNodeName.data + std::string("_matrix-output") ) << "\"/>" << endstr; - mOutput << startstr << "mNodeName.data + std::string("_matrix-interpolation") ) << "\"/>" << endstr; + mOutput << startstr << "mNodeName.data + std::string("_matrix-input") ) << "\"/>" << endstr; + mOutput << startstr << "mNodeName.data + std::string("_matrix-output") ) << "\"/>" << endstr; + mOutput << startstr << "mNodeName.data + std::string("_matrix-interpolation") ) << "\"/>" << endstr; PopTag(); mOutput << startstr << "" << endstr; @@ -1425,7 +1455,7 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex) { // channels - mOutput << startstr << "mNodeName.data + std::string("_matrix-sampler") ) << "\" target=\"" << XMLEscape(nodeAnim->mNodeName.data) << "/matrix\"/>" << endstr; + mOutput << startstr << "mNodeName.data + std::string("_matrix-sampler") ) << "\" target=\"" << XMLIDEncode(nodeAnim->mNodeName.data) << "/matrix\"/>" << endstr; } } @@ -1436,8 +1466,6 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex) // ------------------------------------------------------------------------------------------------ void ColladaExporter::WriteAnimationsLibrary() { - const std::string scene_name_escaped = XMLEscape(mScene->mRootNode->mName.C_Str()); - if ( mScene->mNumAnimations > 0 ) { mOutput << startstr << "" << endstr; PushTag(); @@ -1545,16 +1573,17 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode) } } - const std::string node_name_escaped = XMLEscape(pNode->mName.data); + const std::string node_id = XMLIDEncode(pNode->mName.data); + const std::string node_name = XMLEscape(pNode->mName.data); mOutput << startstr << "" << endstr; PushTag(); @@ -1593,14 +1622,14 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode) //check if it is a camera node for(size_t i=0; imNumCameras; i++){ if(mScene->mCameras[i]->mName == pNode->mName){ - mOutput << startstr <<"" << endstr; + mOutput << startstr <<"" << endstr; break; } } //check if it is a light node for(size_t i=0; imNumLights; i++){ if(mScene->mLights[i]->mName == pNode->mName){ - mOutput << startstr <<"" << endstr; + mOutput << startstr <<"" << endstr; break; } } @@ -1614,15 +1643,17 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode) if( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 ) continue; + const std::string meshName = mesh->mName.length == 0 ? GetMeshId(pNode->mMeshes[a]) : mesh->mName.C_Str(); + if( mesh->mNumBones == 0 ) { - mOutput << startstr << "mMeshes[a])) << "\">" << endstr; + mOutput << startstr << "" << endstr; PushTag(); } else { mOutput << startstr - << "mMeshes[a])) << "-skin\">" + << "" << endstr; PushTag(); @@ -1630,7 +1661,7 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode) // use the first bone to find skeleton root const aiNode * skeletonRootBoneNode = findSkeletonRootNode( pScene, mesh ); if ( skeletonRootBoneNode ) { - mFoundSkeletonRootNodeID = XMLEscape( skeletonRootBoneNode->mName.C_Str() ); + mFoundSkeletonRootNodeID = XMLIDEncode( skeletonRootBoneNode->mName.C_Str() ); } mOutput << startstr << "#" << mFoundSkeletonRootNodeID << "" << endstr; } @@ -1638,7 +1669,7 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode) PushTag(); mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "mMaterialIndex].name) << "\">" << endstr; + mOutput << startstr << "mMaterialIndex].name) << "\">" << endstr; PushTag(); for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) { @@ -1671,4 +1702,4 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode) } #endif -#endif \ No newline at end of file +#endif diff --git a/code/Collada/ColladaHelper.h b/code/Collada/ColladaHelper.h index ffab6226d..b7d47da15 100644 --- a/code/Collada/ColladaHelper.h +++ b/code/Collada/ColladaHelper.h @@ -580,15 +580,11 @@ struct Image { std::string mFileName; - /** If image file name is zero, embedded image data - */ + /** Embedded image data */ std::vector mImageData; - /** If image file name is zero, file format of - * embedded image data. - */ + /** File format hint of embedded image data */ std::string mEmbeddedFormat; - }; /** An animation channel. */ diff --git a/code/Collada/ColladaLoader.cpp b/code/Collada/ColladaLoader.cpp index 81db957d5..37529bc98 100644 --- a/code/Collada/ColladaLoader.cpp +++ b/code/Collada/ColladaLoader.cpp @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -43,7 +41,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file Implementation of the Collada loader */ - #ifndef ASSIMP_BUILD_NO_COLLADA_IMPORTER #include "ColladaLoader.h" @@ -60,6 +57,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include "time.h" #include "math.h" @@ -67,7 +65,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace Assimp; +namespace Assimp { + using namespace Assimp::Formatter; static const aiImporterDesc desc = { @@ -75,30 +74,30 @@ static const aiImporterDesc desc = { "", "", "http://collada.org", - aiImporterFlags_SupportTextFlavour, + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportCompressedFlavour, 1, 3, 1, 5, - "dae" + "dae zae" }; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer ColladaLoader::ColladaLoader() -: mFileName() -, mMeshIndexByID() -, mMaterialIndexByName() -, mMeshes() -, newMats() -, mCameras() -, mLights() -, mTextures() -, mAnims() -, noSkeletonMesh( false ) -, ignoreUpDirection(false) -, useColladaName( false ) -, mNodeNameCounter( 0 ) { + : mFileName() + , mMeshIndexByID() + , mMaterialIndexByName() + , mMeshes() + , newMats() + , mCameras() + , mLights() + , mTextures() + , mAnims() + , noSkeletonMesh(false) + , ignoreUpDirection(false) + , useColladaName(false) + , mNodeNameCounter(0) { // empty } @@ -110,16 +109,27 @@ ColladaLoader::~ColladaLoader() { // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. -bool ColladaLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const { +bool ColladaLoader::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const { // check file extension - std::string extension = GetExtension(pFile); + const std::string extension = GetExtension(pFile); - if (extension == "dae") { - return true; + bool readSig = checkSig && (pIOHandler != nullptr); + + if (!readSig) { + if (extension == "dae" || extension == "zae") { + return true; + } + } + + if (readSig) { + // Look for a DAE file inside, but don't extract it + ZipArchiveIOSystem zip_archive(pIOHandler, pFile); + if (zip_archive.isOpen()) + return !ColladaParser::ReadZaeManifest(zip_archive).empty(); } // XML - too generic, we need to open the file and search for typical keywords - if( extension == "xml" || !extension.length() || checkSig) { + if (extension == "xml" || !extension.length() || checkSig) { /* If CanRead() is called in order to check whether we * support a specific file extension in general pIOHandler * might be NULL and it's our duty to return true here. @@ -127,8 +137,8 @@ bool ColladaLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler, boo if (!pIOHandler) { return true; } - static const char* tokens[] = {"GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0; - ignoreUpDirection = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION,0) != 0; - useColladaName = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES,0) != 0; + noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, 0) != 0; + ignoreUpDirection = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION, 0) != 0; + useColladaName = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES, 0) != 0; } // ------------------------------------------------------------------------------------------------ // Get file extension list -const aiImporterDesc* ColladaLoader::GetInfo () const { +const aiImporterDesc* ColladaLoader::GetInfo() const { return &desc; } // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. -void ColladaLoader::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) { +void ColladaLoader::InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) { mFileName = pFile; // clean all member arrays - just for safety, it should work even if we did not @@ -164,46 +174,50 @@ void ColladaLoader::InternReadFile( const std::string& pFile, aiScene* pScene, I mAnims.clear(); // parse the input file - ColladaParser parser( pIOHandler, pFile); + ColladaParser parser(pIOHandler, pFile); - if( !parser.mRootNode) + + if( !parser.mRootNode) { throw DeadlyImportError( "Collada: File came out empty. Something is wrong here."); + } // reserve some storage to avoid unnecessary reallocs - newMats.reserve(parser.mMaterialLibrary.size()*2); - mMeshes.reserve(parser.mMeshLibrary.size()*2); + newMats.reserve(parser.mMaterialLibrary.size()*2u); + mMeshes.reserve(parser.mMeshLibrary.size()*2u); mCameras.reserve(parser.mCameraLibrary.size()); mLights.reserve(parser.mLightLibrary.size()); // create the materials first, for the meshes to find - BuildMaterials( parser, pScene); + BuildMaterials(parser, pScene); // build the node hierarchy from it - pScene->mRootNode = BuildHierarchy( parser, parser.mRootNode); + pScene->mRootNode = BuildHierarchy(parser, parser.mRootNode); // ... then fill the materials with the now adjusted settings FillMaterials(parser, pScene); // Apply unit-size scale calculation + pScene->mRootNode->mTransformation *= aiMatrix4x4(parser.mUnitSize, 0, 0, 0, 0, parser.mUnitSize, 0, 0, 0, 0, parser.mUnitSize, 0, 0, 0, 0, 1); if( !ignoreUpDirection ) { - // Convert to Y_UP, if different orientation - if( parser.mUpDirection == ColladaParser::UP_X) - pScene->mRootNode->mTransformation *= aiMatrix4x4( - 0, -1, 0, 0, - 1, 0, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - else if( parser.mUpDirection == ColladaParser::UP_Z) - pScene->mRootNode->mTransformation *= aiMatrix4x4( - 1, 0, 0, 0, - 0, 0, 1, 0, - 0, -1, 0, 0, - 0, 0, 0, 1); + // Convert to Y_UP, if different orientation + if( parser.mUpDirection == ColladaParser::UP_X) { + pScene->mRootNode->mTransformation *= aiMatrix4x4( + 0, -1, 0, 0, + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + } else if( parser.mUpDirection == ColladaParser::UP_Z) { + pScene->mRootNode->mTransformation *= aiMatrix4x4( + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, -1, 0, 0, + 0, 0, 0, 1); + } } // Store scene metadata @@ -211,30 +225,31 @@ void ColladaLoader::InternReadFile( const std::string& pFile, aiScene* pScene, I const size_t numMeta(parser.mAssetMetaData.size()); pScene->mMetaData = aiMetadata::Alloc(static_cast(numMeta)); size_t i = 0; - for (auto it = parser.mAssetMetaData.cbegin(); it != parser.mAssetMetaData.cend(); ++it, ++i) - { + for (auto it = parser.mAssetMetaData.cbegin(); it != parser.mAssetMetaData.cend(); ++it, ++i) { pScene->mMetaData->Set(static_cast(i), (*it).first, (*it).second); } } // store all meshes - StoreSceneMeshes( pScene); + StoreSceneMeshes(pScene); // store all materials - StoreSceneMaterials( pScene); + StoreSceneMaterials(pScene); + + // store all textures + StoreSceneTextures(pScene); // store all lights - StoreSceneLights( pScene); + StoreSceneLights(pScene); // store all cameras - StoreSceneCameras( pScene); + StoreSceneCameras(pScene); // store all animations - StoreAnimations( pScene, parser); - + StoreAnimations(pScene, parser); // If no meshes have been loaded, it's probably just an animated skeleton. - if (!pScene->mNumMeshes) { + if ( 0u == pScene->mNumMeshes) { if (!noSkeletonMesh) { SkeletonMeshBuilder hero(pScene); @@ -245,56 +260,56 @@ void ColladaLoader::InternReadFile( const std::string& pFile, aiScene* pScene, I // ------------------------------------------------------------------------------------------------ // Recursively constructs a scene node for the given parser node and returns it. -aiNode* ColladaLoader::BuildHierarchy( const ColladaParser& pParser, const Collada::Node* pNode) { +aiNode* ColladaLoader::BuildHierarchy(const ColladaParser& pParser, const Collada::Node* pNode) { // create a node for it aiNode* node = new aiNode(); // find a name for the new node. It's more complicated than you might think - node->mName.Set( FindNameForNode( pNode)); + node->mName.Set(FindNameForNode(pNode)); // calculate the transformation matrix for it - node->mTransformation = pParser.CalculateResultTransform( pNode->mTransforms); + node->mTransformation = pParser.CalculateResultTransform(pNode->mTransforms); // now resolve node instances std::vector instances; - ResolveNodeInstances(pParser,pNode,instances); + ResolveNodeInstances(pParser, pNode, instances); // add children. first the *real* ones - node->mNumChildren = static_cast(pNode->mChildren.size()+instances.size()); + node->mNumChildren = static_cast(pNode->mChildren.size() + instances.size()); node->mChildren = new aiNode*[node->mNumChildren]; - for( size_t a = 0; a < pNode->mChildren.size(); ++a) { - node->mChildren[a] = BuildHierarchy( pParser, pNode->mChildren[a]); + for (size_t a = 0; a < pNode->mChildren.size(); ++a) { + node->mChildren[a] = BuildHierarchy(pParser, pNode->mChildren[a]); node->mChildren[a]->mParent = node; } // ... and finally the resolved node instances - for( size_t a = 0; a < instances.size(); ++a) { - node->mChildren[pNode->mChildren.size() + a] = BuildHierarchy( pParser, instances[a]); + for (size_t a = 0; a < instances.size(); ++a) { + node->mChildren[pNode->mChildren.size() + a] = BuildHierarchy(pParser, instances[a]); node->mChildren[pNode->mChildren.size() + a]->mParent = node; } // construct meshes - BuildMeshesForNode( pParser, pNode, node); + BuildMeshesForNode(pParser, pNode, node); // construct cameras BuildCamerasForNode(pParser, pNode, node); // construct lights BuildLightsForNode(pParser, pNode, node); - + return node; } // ------------------------------------------------------------------------------------------------ // Resolve node instances -void ColladaLoader::ResolveNodeInstances( const ColladaParser& pParser, const Collada::Node* pNode, - std::vector& resolved) { +void ColladaLoader::ResolveNodeInstances(const ColladaParser& pParser, const Collada::Node* pNode, + std::vector& resolved) { // reserve enough storage resolved.reserve(pNode->mNodeInstances.size()); // ... and iterate through all nodes to be instanced as children of pNode - for (const auto &nodeInst: pNode->mNodeInstances) { + for (const auto &nodeInst : pNode->mNodeInstances) { // find the corresponding node in the library const ColladaParser::NodeLibrary::const_iterator itt = pParser.mNodeLibrary.find(nodeInst.mNode); const Collada::Node* nd = itt == pParser.mNodeLibrary.end() ? NULL : (*itt).second; @@ -302,13 +317,12 @@ void ColladaLoader::ResolveNodeInstances( const ColladaParser& pParser, const Co // FIX for http://sourceforge.net/tracker/?func=detail&aid=3054873&group_id=226462&atid=1067632 // need to check for both name and ID to catch all. To avoid breaking valid files, // the workaround is only enabled when the first attempt to resolve the node has failed. - if (!nd) { + if (nullptr == nd) { nd = FindNode(pParser.mRootNode, nodeInst.mNode); } - if (!nd) + if (nullptr == nd) { ASSIMP_LOG_ERROR_F("Collada: Unable to resolve reference to instanced node ", nodeInst.mNode); - - else { + } else { // attach this node to the list of children resolved.push_back(nd); } @@ -318,7 +332,8 @@ void ColladaLoader::ResolveNodeInstances( const ColladaParser& pParser, const Co // ------------------------------------------------------------------------------------------------ // Resolve UV channels void ColladaLoader::ApplyVertexToEffectSemanticMapping(Collada::Sampler& sampler, - const Collada::SemanticMappingTable& table) { + + const Collada::SemanticMappingTable& table) { std::map::const_iterator it = table.mMap.find(sampler.mUVChannel); if (it != table.mMap.end()) { if (it->second.mType != Collada::IT_Texcoord) { @@ -331,12 +346,12 @@ void ColladaLoader::ApplyVertexToEffectSemanticMapping(Collada::Sampler& sampler // ------------------------------------------------------------------------------------------------ // Builds lights for the given node and references them -void ColladaLoader::BuildLightsForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget) { - for( const Collada::LightInstance& lid : pNode->mLights) { +void ColladaLoader::BuildLightsForNode(const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget) { + for (const Collada::LightInstance& lid : pNode->mLights) { // find the referred light - ColladaParser::LightLibrary::const_iterator srcLightIt = pParser.mLightLibrary.find( lid.mLight); - if( srcLightIt == pParser.mLightLibrary.end()) { - ASSIMP_LOG_WARN_F("Collada: Unable to find light for ID \"" , lid.mLight , "\". Skipping."); + ColladaParser::LightLibrary::const_iterator srcLightIt = pParser.mLightLibrary.find(lid.mLight); + if (srcLightIt == pParser.mLightLibrary.end()) { + ASSIMP_LOG_WARN_F("Collada: Unable to find light for ID \"", lid.mLight, "\". Skipping."); continue; } const Collada::Light* srcLight = &srcLightIt->second; @@ -347,7 +362,7 @@ void ColladaLoader::BuildLightsForNode( const ColladaParser& pParser, const Coll out->mType = (aiLightSourceType)srcLight->mType; // collada lights point in -Z by default, rest is specified in node transform - out->mDirection = aiVector3D(0.f,0.f,-1.f); + out->mDirection = aiVector3D(0.f, 0.f, -1.f); out->mAttenuationConstant = srcLight->mAttConstant; out->mAttenuationLinear = srcLight->mAttLinear; @@ -357,7 +372,8 @@ void ColladaLoader::BuildLightsForNode( const ColladaParser& pParser, const Coll if (out->mType == aiLightSource_AMBIENT) { out->mColorDiffuse = out->mColorSpecular = aiColor3D(0, 0, 0); out->mColorAmbient = srcLight->mColor*srcLight->mIntensity; - } else { + } + else { // collada doesn't differentiate between these color types out->mColorDiffuse = out->mColorSpecular = srcLight->mColor*srcLight->mIntensity; out->mColorAmbient = aiColor3D(0, 0, 0); @@ -365,23 +381,25 @@ void ColladaLoader::BuildLightsForNode( const ColladaParser& pParser, const Coll // convert falloff angle and falloff exponent in our representation, if given if (out->mType == aiLightSource_SPOT) { - out->mAngleInnerCone = AI_DEG_TO_RAD( srcLight->mFalloffAngle ); + out->mAngleInnerCone = AI_DEG_TO_RAD(srcLight->mFalloffAngle); // ... some extension magic. - if (srcLight->mOuterAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET*(1-1e-6f)) { + if (srcLight->mOuterAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET * (1 - 1e-6f)) { // ... some deprecation magic. - if (srcLight->mPenumbraAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET*(1-1e-6f)) { + if (srcLight->mPenumbraAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET * (1 - 1e-6f)) { // Need to rely on falloff_exponent. I don't know how to interpret it, so I need to guess .... // epsilon chosen to be 0.1 - out->mAngleOuterCone = std::acos(std::pow(0.1f,1.f/srcLight->mFalloffExponent))+ - out->mAngleInnerCone; - } else { - out->mAngleOuterCone = out->mAngleInnerCone + AI_DEG_TO_RAD( srcLight->mPenumbraAngle ); - if (out->mAngleOuterCone < out->mAngleInnerCone) - std::swap(out->mAngleInnerCone,out->mAngleOuterCone); + out->mAngleOuterCone = std::acos(std::pow(0.1f, 1.f / srcLight->mFalloffExponent)) + + out->mAngleInnerCone; } - } else { - out->mAngleOuterCone = AI_DEG_TO_RAD( srcLight->mOuterAngle ); + else { + out->mAngleOuterCone = out->mAngleInnerCone + AI_DEG_TO_RAD(srcLight->mPenumbraAngle); + if (out->mAngleOuterCone < out->mAngleInnerCone) + std::swap(out->mAngleInnerCone, out->mAngleOuterCone); + } + } + else { + out->mAngleOuterCone = AI_DEG_TO_RAD(srcLight->mOuterAngle); } } @@ -392,12 +410,12 @@ void ColladaLoader::BuildLightsForNode( const ColladaParser& pParser, const Coll // ------------------------------------------------------------------------------------------------ // Builds cameras for the given node and references them -void ColladaLoader::BuildCamerasForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget) { - for( const Collada::CameraInstance& cid : pNode->mCameras) { +void ColladaLoader::BuildCamerasForNode(const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget) { + for (const Collada::CameraInstance& cid : pNode->mCameras) { // find the referred light - ColladaParser::CameraLibrary::const_iterator srcCameraIt = pParser.mCameraLibrary.find( cid.mCamera); - if( srcCameraIt == pParser.mCameraLibrary.end()) { - ASSIMP_LOG_WARN_F("Collada: Unable to find camera for ID \"" , cid.mCamera , "\". Skipping."); + ColladaParser::CameraLibrary::const_iterator srcCameraIt = pParser.mCameraLibrary.find(cid.mCamera); + if (srcCameraIt == pParser.mCameraLibrary.end()) { + ASSIMP_LOG_WARN_F("Collada: Unable to find camera for ID \"", cid.mCamera, "\". Skipping."); continue; } const Collada::Camera* srcCamera = &srcCameraIt->second; @@ -412,7 +430,7 @@ void ColladaLoader::BuildCamerasForNode( const ColladaParser& pParser, const Col out->mName = pTarget->mName; // collada cameras point in -Z by default, rest is specified in node transform - out->mLookAt = aiVector3D(0.f,0.f,-1.f); + out->mLookAt = aiVector3D(0.f, 0.f, -1.f); // near/far z is already ok out->mClipPlaneFar = srcCamera->mZFar; @@ -431,8 +449,8 @@ void ColladaLoader::BuildCamerasForNode( const ColladaParser& pParser, const Col out->mAspect = std::tan(AI_DEG_TO_RAD(srcCamera->mHorFov)) / std::tan(AI_DEG_TO_RAD(srcCamera->mVerFov)); } - } - else if (srcCamera->mAspect != 10e10f && srcCamera->mVerFov != 10e10f) { + + } else if (srcCamera->mAspect != 10e10f && srcCamera->mVerFov != 10e10f) { out->mHorizontalFOV = 2.0f * AI_RAD_TO_DEG(std::atan(srcCamera->mAspect * std::tan(AI_DEG_TO_RAD(srcCamera->mVerFov) * 0.5f))); } @@ -447,103 +465,107 @@ void ColladaLoader::BuildCamerasForNode( const ColladaParser& pParser, const Col // ------------------------------------------------------------------------------------------------ // Builds meshes for the given node and references them -void ColladaLoader::BuildMeshesForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget) { +void ColladaLoader::BuildMeshesForNode(const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget) { // accumulated mesh references by this node std::vector newMeshRefs; newMeshRefs.reserve(pNode->mMeshes.size()); // add a mesh for each subgroup in each collada mesh - for( const Collada::MeshInstance& mid : pNode->mMeshes) { + for (const Collada::MeshInstance& mid : pNode->mMeshes) { const Collada::Mesh* srcMesh = nullptr; const Collada::Controller* srcController = nullptr; // find the referred mesh - ColladaParser::MeshLibrary::const_iterator srcMeshIt = pParser.mMeshLibrary.find( mid.mMeshOrController); - if( srcMeshIt == pParser.mMeshLibrary.end()) { + ColladaParser::MeshLibrary::const_iterator srcMeshIt = pParser.mMeshLibrary.find(mid.mMeshOrController); + if (srcMeshIt == pParser.mMeshLibrary.end()) { // if not found in the mesh-library, it might also be a controller referring to a mesh - ColladaParser::ControllerLibrary::const_iterator srcContrIt = pParser.mControllerLibrary.find( mid.mMeshOrController); - if( srcContrIt != pParser.mControllerLibrary.end()) { + ColladaParser::ControllerLibrary::const_iterator srcContrIt = pParser.mControllerLibrary.find(mid.mMeshOrController); + if (srcContrIt != pParser.mControllerLibrary.end()) { srcController = &srcContrIt->second; - srcMeshIt = pParser.mMeshLibrary.find( srcController->mMeshId); - if( srcMeshIt != pParser.mMeshLibrary.end()) { + srcMeshIt = pParser.mMeshLibrary.find(srcController->mMeshId); + if (srcMeshIt != pParser.mMeshLibrary.end()) { srcMesh = srcMeshIt->second; } } - if( !srcMesh) { + + if( nullptr == srcMesh) { ASSIMP_LOG_WARN_F( "Collada: Unable to find geometry for ID \"", mid.mMeshOrController, "\". Skipping." ); continue; } - } else { + } + else { // ID found in the mesh library -> direct reference to an unskinned mesh srcMesh = srcMeshIt->second; } // build a mesh for each of its subgroups size_t vertexStart = 0, faceStart = 0; - for( size_t sm = 0; sm < srcMesh->mSubMeshes.size(); ++sm) { + for (size_t sm = 0; sm < srcMesh->mSubMeshes.size(); ++sm) { const Collada::SubMesh& submesh = srcMesh->mSubMeshes[sm]; - if( submesh.mNumFaces == 0) { + if (submesh.mNumFaces == 0) { continue; } // find material assigned to this submesh std::string meshMaterial; - std::map::const_iterator meshMatIt = mid.mMaterials.find( submesh.mMaterial); + std::map::const_iterator meshMatIt = mid.mMaterials.find(submesh.mMaterial); const Collada::SemanticMappingTable* table = nullptr; - if( meshMatIt != mid.mMaterials.end()) { + if (meshMatIt != mid.mMaterials.end()) { table = &meshMatIt->second; meshMaterial = table->mMatName; - } else { - ASSIMP_LOG_WARN_F( "Collada: No material specified for subgroup <", submesh.mMaterial, "> in geometry <", - mid.mMeshOrController, ">." ); - if( !mid.mMaterials.empty() ) { + } + else { + ASSIMP_LOG_WARN_F("Collada: No material specified for subgroup <", submesh.mMaterial, "> in geometry <", + mid.mMeshOrController, ">."); + if (!mid.mMaterials.empty()) { meshMaterial = mid.mMaterials.begin()->second.mMatName; } } // OK ... here the *real* fun starts ... we have the vertex-input-to-effect-semantic-table // given. The only mapping stuff which we do actually support is the UV channel. - std::map::const_iterator matIt = mMaterialIndexByName.find( meshMaterial); + std::map::const_iterator matIt = mMaterialIndexByName.find(meshMaterial); unsigned int matIdx = 0; - if( matIt != mMaterialIndexByName.end()) { + if (matIt != mMaterialIndexByName.end()) { matIdx = static_cast(matIt->second); } - if (table && !table->mMap.empty() ) { + if (table && !table->mMap.empty()) { std::pair& mat = newMats[matIdx]; // Iterate through all texture channels assigned to the effect and // check whether we have mapping information for it. - ApplyVertexToEffectSemanticMapping(mat.first->mTexDiffuse, *table); - ApplyVertexToEffectSemanticMapping(mat.first->mTexAmbient, *table); - ApplyVertexToEffectSemanticMapping(mat.first->mTexSpecular, *table); - ApplyVertexToEffectSemanticMapping(mat.first->mTexEmissive, *table); - ApplyVertexToEffectSemanticMapping(mat.first->mTexTransparent,*table); - ApplyVertexToEffectSemanticMapping(mat.first->mTexBump, *table); + ApplyVertexToEffectSemanticMapping(mat.first->mTexDiffuse, *table); + ApplyVertexToEffectSemanticMapping(mat.first->mTexAmbient, *table); + ApplyVertexToEffectSemanticMapping(mat.first->mTexSpecular, *table); + ApplyVertexToEffectSemanticMapping(mat.first->mTexEmissive, *table); + ApplyVertexToEffectSemanticMapping(mat.first->mTexTransparent, *table); + ApplyVertexToEffectSemanticMapping(mat.first->mTexBump, *table); } // built lookup index of the Mesh-Submesh-Material combination - ColladaMeshIndex index( mid.mMeshOrController, sm, meshMaterial); + ColladaMeshIndex index(mid.mMeshOrController, sm, meshMaterial); // if we already have the mesh at the library, just add its index to the node's array - std::map::const_iterator dstMeshIt = mMeshIndexByID.find( index); - if( dstMeshIt != mMeshIndexByID.end()) { - newMeshRefs.push_back( dstMeshIt->second); - } else { + std::map::const_iterator dstMeshIt = mMeshIndexByID.find(index); + if (dstMeshIt != mMeshIndexByID.end()) { + newMeshRefs.push_back(dstMeshIt->second); + } + else { // else we have to add the mesh to the collection and store its newly assigned index at the node - aiMesh* dstMesh = CreateMesh( pParser, srcMesh, submesh, srcController, vertexStart, faceStart); + aiMesh* dstMesh = CreateMesh(pParser, srcMesh, submesh, srcController, vertexStart, faceStart); // store the mesh, and store its new index in the node - newMeshRefs.push_back( mMeshes.size()); + newMeshRefs.push_back(mMeshes.size()); mMeshIndexByID[index] = mMeshes.size(); - mMeshes.push_back( dstMesh); + mMeshes.push_back(dstMesh); vertexStart += dstMesh->mNumVertices; faceStart += submesh.mNumFaces; // assign the material index dstMesh->mMaterialIndex = matIdx; - if(dstMesh->mName.length == 0) { + if (dstMesh->mName.length == 0) { dstMesh->mName = mid.mMeshOrController; } } @@ -552,7 +574,7 @@ void ColladaLoader::BuildMeshesForNode( const ColladaParser& pParser, const Coll // now place all mesh references we gathered in the target node pTarget->mNumMeshes = static_cast(newMeshRefs.size()); - if( newMeshRefs.size()) { + if (newMeshRefs.size()) { struct UIntTypeConverter { unsigned int operator()(const size_t& v) const { return static_cast(v); @@ -560,76 +582,76 @@ void ColladaLoader::BuildMeshesForNode( const ColladaParser& pParser, const Coll }; pTarget->mMeshes = new unsigned int[pTarget->mNumMeshes]; - std::transform( newMeshRefs.begin(), newMeshRefs.end(), pTarget->mMeshes, UIntTypeConverter()); + std::transform(newMeshRefs.begin(), newMeshRefs.end(), pTarget->mMeshes, UIntTypeConverter()); } } // ------------------------------------------------------------------------------------------------ // Find mesh from either meshes or morph target meshes -aiMesh *ColladaLoader::findMesh(std::string meshid) { - for (unsigned int i = 0; i < mMeshes.size(); ++i ) { +aiMesh *ColladaLoader::findMesh(const std::string& meshid) { + for (unsigned int i = 0; i < mMeshes.size(); ++i) { if (std::string(mMeshes[i]->mName.data) == meshid) { return mMeshes[i]; } } - for (unsigned int i = 0; i < mTargetMeshes.size(); ++i ) { + for (unsigned int i = 0; i < mTargetMeshes.size(); ++i) { if (std::string(mTargetMeshes[i]->mName.data) == meshid) { return mTargetMeshes[i]; } } - + return nullptr; } // ------------------------------------------------------------------------------------------------ // Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh -aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada::Mesh* pSrcMesh, const Collada::SubMesh& pSubMesh, - const Collada::Controller* pSrcController, size_t pStartVertex, size_t pStartFace) { +aiMesh* ColladaLoader::CreateMesh(const ColladaParser& pParser, const Collada::Mesh* pSrcMesh, const Collada::SubMesh& pSubMesh, + const Collada::Controller* pSrcController, size_t pStartVertex, size_t pStartFace) { std::unique_ptr dstMesh(new aiMesh); dstMesh->mName = pSrcMesh->mName; // count the vertices addressed by its faces - const size_t numVertices = std::accumulate( pSrcMesh->mFaceSize.begin() + pStartFace, + const size_t numVertices = std::accumulate(pSrcMesh->mFaceSize.begin() + pStartFace, pSrcMesh->mFaceSize.begin() + pStartFace + pSubMesh.mNumFaces, size_t(0)); // copy positions dstMesh->mNumVertices = static_cast(numVertices); dstMesh->mVertices = new aiVector3D[numVertices]; - std::copy( pSrcMesh->mPositions.begin() + pStartVertex, pSrcMesh->mPositions.begin() + + std::copy(pSrcMesh->mPositions.begin() + pStartVertex, pSrcMesh->mPositions.begin() + pStartVertex + numVertices, dstMesh->mVertices); // normals, if given. HACK: (thom) Due to the glorious Collada spec we never // know if we have the same number of normals as there are positions. So we // also ignore any vertex attribute if it has a different count - if( pSrcMesh->mNormals.size() >= pStartVertex + numVertices) { + if (pSrcMesh->mNormals.size() >= pStartVertex + numVertices) { dstMesh->mNormals = new aiVector3D[numVertices]; - std::copy( pSrcMesh->mNormals.begin() + pStartVertex, pSrcMesh->mNormals.begin() + + std::copy(pSrcMesh->mNormals.begin() + pStartVertex, pSrcMesh->mNormals.begin() + pStartVertex + numVertices, dstMesh->mNormals); } // tangents, if given. - if( pSrcMesh->mTangents.size() >= pStartVertex + numVertices) { + if (pSrcMesh->mTangents.size() >= pStartVertex + numVertices) { dstMesh->mTangents = new aiVector3D[numVertices]; - std::copy( pSrcMesh->mTangents.begin() + pStartVertex, pSrcMesh->mTangents.begin() + + std::copy(pSrcMesh->mTangents.begin() + pStartVertex, pSrcMesh->mTangents.begin() + pStartVertex + numVertices, dstMesh->mTangents); } // bitangents, if given. - if( pSrcMesh->mBitangents.size() >= pStartVertex + numVertices) { + if (pSrcMesh->mBitangents.size() >= pStartVertex + numVertices) { dstMesh->mBitangents = new aiVector3D[numVertices]; - std::copy( pSrcMesh->mBitangents.begin() + pStartVertex, pSrcMesh->mBitangents.begin() + + std::copy(pSrcMesh->mBitangents.begin() + pStartVertex, pSrcMesh->mBitangents.begin() + pStartVertex + numVertices, dstMesh->mBitangents); } // same for texturecoords, as many as we have // empty slots are not allowed, need to pack and adjust UV indexes accordingly - for( size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) { - if( pSrcMesh->mTexCoords[a].size() >= pStartVertex + numVertices) { + for (size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { + if (pSrcMesh->mTexCoords[a].size() >= pStartVertex + numVertices) { dstMesh->mTextureCoords[real] = new aiVector3D[numVertices]; - for( size_t b = 0; b < numVertices; ++b) { - dstMesh->mTextureCoords[real][b] = pSrcMesh->mTexCoords[a][pStartVertex+b]; + for (size_t b = 0; b < numVertices; ++b) { + dstMesh->mTextureCoords[real][b] = pSrcMesh->mTexCoords[a][pStartVertex + b]; } dstMesh->mNumUVComponents[real] = pSrcMesh->mNumUVComponents[a]; @@ -638,10 +660,10 @@ aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada:: } // same for vertex colors, as many as we have. again the same packing to avoid empty slots - for( size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a ) { - if( pSrcMesh->mColors[a].size() >= pStartVertex + numVertices) { + for (size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) { + if (pSrcMesh->mColors[a].size() >= pStartVertex + numVertices) { dstMesh->mColors[real] = new aiColor4D[numVertices]; - std::copy( pSrcMesh->mColors[a].begin() + pStartVertex, pSrcMesh->mColors[a].begin() + pStartVertex + numVertices,dstMesh->mColors[real]); + std::copy(pSrcMesh->mColors[a].begin() + pStartVertex, pSrcMesh->mColors[a].begin() + pStartVertex + numVertices, dstMesh->mColors[real]); ++real; } } @@ -650,12 +672,12 @@ aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada:: size_t vertex = 0; dstMesh->mNumFaces = static_cast(pSubMesh.mNumFaces); dstMesh->mFaces = new aiFace[dstMesh->mNumFaces]; - for( size_t a = 0; a < dstMesh->mNumFaces; ++a) { - size_t s = pSrcMesh->mFaceSize[ pStartFace + a]; + for (size_t a = 0; a < dstMesh->mNumFaces; ++a) { + size_t s = pSrcMesh->mFaceSize[pStartFace + a]; aiFace& face = dstMesh->mFaces[a]; face.mNumIndices = static_cast(s); face.mIndices = new unsigned int[s]; - for( size_t b = 0; b < s; ++b) { + for (size_t b = 0; b < s; ++b) { face.mIndices[b] = static_cast(vertex++); } } @@ -665,25 +687,25 @@ aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada:: std::vector targetWeights; Collada::MorphMethod method = Collada::Normalized; - for(std::map::const_iterator it = pParser.mControllerLibrary.begin(); - it != pParser.mControllerLibrary.end(); it++) { + for (std::map::const_iterator it = pParser.mControllerLibrary.begin(); + it != pParser.mControllerLibrary.end(); ++it) { const Collada::Controller &c = it->second; - const Collada::Mesh* baseMesh = pParser.ResolveLibraryReference( pParser.mMeshLibrary, c.mMeshId); + const Collada::Mesh* baseMesh = pParser.ResolveLibraryReference(pParser.mMeshLibrary, c.mMeshId); if (c.mType == Collada::Morph && baseMesh->mName == pSrcMesh->mName) { - const Collada::Accessor& targetAccessor = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, c.mMorphTarget); - const Collada::Accessor& weightAccessor = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, c.mMorphWeight); - const Collada::Data& targetData = pParser.ResolveLibraryReference( pParser.mDataLibrary, targetAccessor.mSource); - const Collada::Data& weightData = pParser.ResolveLibraryReference( pParser.mDataLibrary, weightAccessor.mSource); + const Collada::Accessor& targetAccessor = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, c.mMorphTarget); + const Collada::Accessor& weightAccessor = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, c.mMorphWeight); + const Collada::Data& targetData = pParser.ResolveLibraryReference(pParser.mDataLibrary, targetAccessor.mSource); + const Collada::Data& weightData = pParser.ResolveLibraryReference(pParser.mDataLibrary, weightAccessor.mSource); // take method method = c.mMethod; if (!targetData.mIsStringArray) { - throw DeadlyImportError( "target data must contain id. "); + throw DeadlyImportError("target data must contain id. "); } if (weightData.mIsStringArray) { - throw DeadlyImportError( "target weight data must not be textual "); + throw DeadlyImportError("target weight data must not be textual "); } for (unsigned int i = 0; i < targetData.mStrings.size(); ++i) { @@ -692,7 +714,7 @@ aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada:: aiMesh *aimesh = findMesh(targetMesh->mName); if (!aimesh) { if (targetMesh->mSubMeshes.size() > 1) { - throw DeadlyImportError( "Morhing target mesh must be a single"); + throw DeadlyImportError("Morhing target mesh must be a single"); } aimesh = CreateMesh(pParser, targetMesh, targetMesh->mSubMeshes.at(0), NULL, 0, 0); mTargetMeshes.push_back(aimesh); @@ -706,7 +728,7 @@ aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada:: } if (targetMeshes.size() > 0 && targetWeights.size() == targetMeshes.size()) { std::vector animMeshes; - for (unsigned int i = 0; i < targetMeshes.size(); ++i ) { + for (unsigned int i = 0; i < targetMeshes.size(); ++i) { aiMesh* targetMesh = targetMeshes.at(i); aiAnimMesh *animMesh = aiCreateAnimMesh(targetMesh); float weight = targetWeights[i]; @@ -715,54 +737,54 @@ aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada:: animMeshes.push_back(animMesh); } dstMesh->mMethod = (method == Collada::Relative) - ? aiMorphingMethod_MORPH_RELATIVE - : aiMorphingMethod_MORPH_NORMALIZED; + ? aiMorphingMethod_MORPH_RELATIVE + : aiMorphingMethod_MORPH_NORMALIZED; dstMesh->mAnimMeshes = new aiAnimMesh*[animMeshes.size()]; dstMesh->mNumAnimMeshes = static_cast(animMeshes.size()); - for (unsigned int i = 0; i < animMeshes.size(); ++i ) { + for (unsigned int i = 0; i < animMeshes.size(); ++i) { dstMesh->mAnimMeshes[i] = animMeshes.at(i); } } // create bones if given - if( pSrcController && pSrcController->mType == Collada::Skin) { + if (pSrcController && pSrcController->mType == Collada::Skin) { // resolve references - joint names - const Collada::Accessor& jointNamesAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mJointNameSource); - const Collada::Data& jointNames = pParser.ResolveLibraryReference( pParser.mDataLibrary, jointNamesAcc.mSource); + const Collada::Accessor& jointNamesAcc = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, pSrcController->mJointNameSource); + const Collada::Data& jointNames = pParser.ResolveLibraryReference(pParser.mDataLibrary, jointNamesAcc.mSource); // joint offset matrices - const Collada::Accessor& jointMatrixAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mJointOffsetMatrixSource); - const Collada::Data& jointMatrices = pParser.ResolveLibraryReference( pParser.mDataLibrary, jointMatrixAcc.mSource); + const Collada::Accessor& jointMatrixAcc = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, pSrcController->mJointOffsetMatrixSource); + const Collada::Data& jointMatrices = pParser.ResolveLibraryReference(pParser.mDataLibrary, jointMatrixAcc.mSource); // joint vertex_weight name list - should refer to the same list as the joint names above. If not, report and reconsider - const Collada::Accessor& weightNamesAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mWeightInputJoints.mAccessor); - if( &weightNamesAcc != &jointNamesAcc) - throw DeadlyImportError( "Temporary implementational laziness. If you read this, please report to the author."); + const Collada::Accessor& weightNamesAcc = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, pSrcController->mWeightInputJoints.mAccessor); + if (&weightNamesAcc != &jointNamesAcc) + throw DeadlyImportError("Temporary implementational laziness. If you read this, please report to the author."); // vertex weights - const Collada::Accessor& weightsAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mWeightInputWeights.mAccessor); - const Collada::Data& weights = pParser.ResolveLibraryReference( pParser.mDataLibrary, weightsAcc.mSource); + const Collada::Accessor& weightsAcc = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, pSrcController->mWeightInputWeights.mAccessor); + const Collada::Data& weights = pParser.ResolveLibraryReference(pParser.mDataLibrary, weightsAcc.mSource); - if( !jointNames.mIsStringArray || jointMatrices.mIsStringArray || weights.mIsStringArray) - throw DeadlyImportError( "Data type mismatch while resolving mesh joints"); + if (!jointNames.mIsStringArray || jointMatrices.mIsStringArray || weights.mIsStringArray) + throw DeadlyImportError("Data type mismatch while resolving mesh joints"); // sanity check: we rely on the vertex weights always coming as pairs of BoneIndex-WeightIndex - if( pSrcController->mWeightInputJoints.mOffset != 0 || pSrcController->mWeightInputWeights.mOffset != 1) - throw DeadlyImportError( "Unsupported vertex_weight addressing scheme. "); + if (pSrcController->mWeightInputJoints.mOffset != 0 || pSrcController->mWeightInputWeights.mOffset != 1) + throw DeadlyImportError("Unsupported vertex_weight addressing scheme. "); // create containers to collect the weights for each bone size_t numBones = jointNames.mStrings.size(); - std::vector > dstBones( numBones); + std::vector > dstBones(numBones); // build a temporary array of pointers to the start of each vertex's weights typedef std::vector< std::pair > IndexPairVector; std::vector weightStartPerVertex; - weightStartPerVertex.resize(pSrcController->mWeightCounts.size(),pSrcController->mWeights.end()); + weightStartPerVertex.resize(pSrcController->mWeightCounts.size(), pSrcController->mWeights.end()); IndexPairVector::const_iterator pit = pSrcController->mWeights.begin(); - for( size_t a = 0; a < pSrcController->mWeightCounts.size(); ++a) { + for (size_t a = 0; a < pSrcController->mWeightCounts.size(); ++a) { weightStartPerVertex[a] = pit; pit += pSrcController->mWeightCounts[a]; } // now for each vertex put the corresponding vertex weights into each bone's weight collection - for( size_t a = pStartVertex; a < pStartVertex + numVertices; ++a) { + for (size_t a = pStartVertex; a < pStartVertex + numVertices; ++a) { // which position index was responsible for this vertex? that's also the index by which // the controller assigns the vertex weights size_t orgIndex = pSrcMesh->mFacePosIndices[a]; @@ -770,58 +792,62 @@ aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada:: IndexPairVector::const_iterator iit = weightStartPerVertex[orgIndex]; size_t pairCount = pSrcController->mWeightCounts[orgIndex]; - for( size_t b = 0; b < pairCount; ++b, ++iit) - { - size_t jointIndex = iit->first; - size_t vertexIndex = iit->second; - ai_real weight = ReadFloat( weightsAcc, weights, vertexIndex, 0); + for( size_t b = 0; b < pairCount; ++b, ++iit) { + const size_t jointIndex = iit->first; + const size_t vertexIndex = iit->second; + ai_real weight = 1.0f; + if (!weights.mValues.empty()) { + weight = ReadFloat(weightsAcc, weights, vertexIndex, 0); + } // one day I gonna kill that XSI Collada exporter - if( weight > 0.0f) + if (weight > 0.0f) { aiVertexWeight w; w.mVertexId = static_cast(a - pStartVertex); w.mWeight = weight; - dstBones[jointIndex].push_back( w); + dstBones[jointIndex].push_back(w); } } } // count the number of bones which influence vertices of the current submesh size_t numRemainingBones = 0; - for( std::vector >::const_iterator it = dstBones.begin(); it != dstBones.end(); ++it) - if( it->size() > 0) - numRemainingBones++; + for( std::vector >::const_iterator it = dstBones.begin(); it != dstBones.end(); ++it) { + if( it->size() > 0) { + ++numRemainingBones; + } + } // create bone array and copy bone weights one by one dstMesh->mNumBones = static_cast(numRemainingBones); dstMesh->mBones = new aiBone*[numRemainingBones]; size_t boneCount = 0; - for( size_t a = 0; a < numBones; ++a) - { + for( size_t a = 0; a < numBones; ++a) { // omit bones without weights - if( dstBones[a].size() == 0) + if( dstBones[a].empty() ) { continue; + } // create bone with its weights aiBone* bone = new aiBone; - bone->mName = ReadString( jointNamesAcc, jointNames, a); - bone->mOffsetMatrix.a1 = ReadFloat( jointMatrixAcc, jointMatrices, a, 0); - bone->mOffsetMatrix.a2 = ReadFloat( jointMatrixAcc, jointMatrices, a, 1); - bone->mOffsetMatrix.a3 = ReadFloat( jointMatrixAcc, jointMatrices, a, 2); - bone->mOffsetMatrix.a4 = ReadFloat( jointMatrixAcc, jointMatrices, a, 3); - bone->mOffsetMatrix.b1 = ReadFloat( jointMatrixAcc, jointMatrices, a, 4); - bone->mOffsetMatrix.b2 = ReadFloat( jointMatrixAcc, jointMatrices, a, 5); - bone->mOffsetMatrix.b3 = ReadFloat( jointMatrixAcc, jointMatrices, a, 6); - bone->mOffsetMatrix.b4 = ReadFloat( jointMatrixAcc, jointMatrices, a, 7); - bone->mOffsetMatrix.c1 = ReadFloat( jointMatrixAcc, jointMatrices, a, 8); - bone->mOffsetMatrix.c2 = ReadFloat( jointMatrixAcc, jointMatrices, a, 9); - bone->mOffsetMatrix.c3 = ReadFloat( jointMatrixAcc, jointMatrices, a, 10); - bone->mOffsetMatrix.c4 = ReadFloat( jointMatrixAcc, jointMatrices, a, 11); + bone->mName = ReadString(jointNamesAcc, jointNames, a); + bone->mOffsetMatrix.a1 = ReadFloat(jointMatrixAcc, jointMatrices, a, 0); + bone->mOffsetMatrix.a2 = ReadFloat(jointMatrixAcc, jointMatrices, a, 1); + bone->mOffsetMatrix.a3 = ReadFloat(jointMatrixAcc, jointMatrices, a, 2); + bone->mOffsetMatrix.a4 = ReadFloat(jointMatrixAcc, jointMatrices, a, 3); + bone->mOffsetMatrix.b1 = ReadFloat(jointMatrixAcc, jointMatrices, a, 4); + bone->mOffsetMatrix.b2 = ReadFloat(jointMatrixAcc, jointMatrices, a, 5); + bone->mOffsetMatrix.b3 = ReadFloat(jointMatrixAcc, jointMatrices, a, 6); + bone->mOffsetMatrix.b4 = ReadFloat(jointMatrixAcc, jointMatrices, a, 7); + bone->mOffsetMatrix.c1 = ReadFloat(jointMatrixAcc, jointMatrices, a, 8); + bone->mOffsetMatrix.c2 = ReadFloat(jointMatrixAcc, jointMatrices, a, 9); + bone->mOffsetMatrix.c3 = ReadFloat(jointMatrixAcc, jointMatrices, a, 10); + bone->mOffsetMatrix.c4 = ReadFloat(jointMatrixAcc, jointMatrices, a, 11); bone->mNumWeights = static_cast(dstBones[a].size()); bone->mWeights = new aiVertexWeight[bone->mNumWeights]; - std::copy( dstBones[a].begin(), dstBones[a].end(), bone->mWeights); + std::copy(dstBones[a].begin(), dstBones[a].end(), bone->mWeights); // apply bind shape matrix to offset matrix aiMatrix4x4 bindShapeMatrix; @@ -848,14 +874,16 @@ aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada:: // and replace the bone's name by the node's name so that the user can use the standard // find-by-name method to associate nodes with bones. const Collada::Node* bnode = FindNode( pParser.mRootNode, bone->mName.data); - if( !bnode) + if( !bnode) { bnode = FindNodeBySID( pParser.mRootNode, bone->mName.data); + } // assign the name that we would have assigned for the source node - if( bnode) + if( bnode) { bone->mName.Set( FindNameForNode( bnode)); - else + } else { ASSIMP_LOG_WARN_F( "ColladaLoader::CreateMesh(): could not find corresponding node for joint \"", bone->mName.data, "\"." ); + } // and insert bone dstMesh->mBones[boneCount++] = bone; @@ -867,100 +895,91 @@ aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada:: // ------------------------------------------------------------------------------------------------ // Stores all meshes in the given scene -void ColladaLoader::StoreSceneMeshes( aiScene* pScene) -{ +void ColladaLoader::StoreSceneMeshes( aiScene* pScene) { pScene->mNumMeshes = static_cast(mMeshes.size()); - if( mMeshes.size() > 0) - { - pScene->mMeshes = new aiMesh*[mMeshes.size()]; - std::copy( mMeshes.begin(), mMeshes.end(), pScene->mMeshes); - mMeshes.clear(); + if( mMeshes.empty() ) { + return; } + pScene->mMeshes = new aiMesh*[mMeshes.size()]; + std::copy( mMeshes.begin(), mMeshes.end(), pScene->mMeshes); + mMeshes.clear(); } // ------------------------------------------------------------------------------------------------ // Stores all cameras in the given scene -void ColladaLoader::StoreSceneCameras( aiScene* pScene) -{ +void ColladaLoader::StoreSceneCameras( aiScene* pScene) { pScene->mNumCameras = static_cast(mCameras.size()); - if( mCameras.size() > 0) - { - pScene->mCameras = new aiCamera*[mCameras.size()]; - std::copy( mCameras.begin(), mCameras.end(), pScene->mCameras); - mCameras.clear(); + if( mCameras.empty() ) { + return; } + pScene->mCameras = new aiCamera*[mCameras.size()]; + std::copy( mCameras.begin(), mCameras.end(), pScene->mCameras); + mCameras.clear(); } // ------------------------------------------------------------------------------------------------ // Stores all lights in the given scene -void ColladaLoader::StoreSceneLights( aiScene* pScene) -{ +void ColladaLoader::StoreSceneLights( aiScene* pScene) { pScene->mNumLights = static_cast(mLights.size()); - if( mLights.size() > 0) - { - pScene->mLights = new aiLight*[mLights.size()]; - std::copy( mLights.begin(), mLights.end(), pScene->mLights); - mLights.clear(); + if( mLights.empty() ) { + return; } + pScene->mLights = new aiLight*[mLights.size()]; + std::copy( mLights.begin(), mLights.end(), pScene->mLights); + mLights.clear(); } // ------------------------------------------------------------------------------------------------ // Stores all textures in the given scene -void ColladaLoader::StoreSceneTextures( aiScene* pScene) -{ +void ColladaLoader::StoreSceneTextures( aiScene* pScene) { pScene->mNumTextures = static_cast(mTextures.size()); - if( mTextures.size() > 0) - { - pScene->mTextures = new aiTexture*[mTextures.size()]; - std::copy( mTextures.begin(), mTextures.end(), pScene->mTextures); - mTextures.clear(); + if( mTextures.empty() ) { + return; } + pScene->mTextures = new aiTexture*[mTextures.size()]; + std::copy( mTextures.begin(), mTextures.end(), pScene->mTextures); + mTextures.clear(); } // ------------------------------------------------------------------------------------------------ // Stores all materials in the given scene -void ColladaLoader::StoreSceneMaterials( aiScene* pScene) -{ +void ColladaLoader::StoreSceneMaterials( aiScene* pScene) { pScene->mNumMaterials = static_cast(newMats.size()); - - if (newMats.size() > 0) { - pScene->mMaterials = new aiMaterial*[newMats.size()]; - for (unsigned int i = 0; i < newMats.size();++i) - pScene->mMaterials[i] = newMats[i].second; - - newMats.clear(); + if (newMats.empty() ) { + return; } + pScene->mMaterials = new aiMaterial*[newMats.size()]; + for (unsigned int i = 0; i < newMats.size();++i) { + pScene->mMaterials[i] = newMats[i].second; + } + newMats.clear(); } // ------------------------------------------------------------------------------------------------ // Stores all animations -void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pParser) -{ +void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pParser) { // recursively collect all animations from the collada scene - StoreAnimations( pScene, pParser, &pParser.mAnims, ""); + StoreAnimations(pScene, pParser, &pParser.mAnims, ""); // catch special case: many animations with the same length, each affecting only a single node. // we need to unite all those single-node-anims to a proper combined animation - for( size_t a = 0; a < mAnims.size(); ++a) - { + for( size_t a = 0; a < mAnims.size(); ++a) { aiAnimation* templateAnim = mAnims[a]; - if( templateAnim->mNumChannels == 1) - { + if( templateAnim->mNumChannels == 1) { // search for other single-channel-anims with the same duration std::vector collectedAnimIndices; - for( size_t b = a+1; b < mAnims.size(); ++b) - { + for( size_t b = a+1; b < mAnims.size(); ++b) { aiAnimation* other = mAnims[b]; - if( other->mNumChannels == 1 && other->mDuration == templateAnim->mDuration && - other->mTicksPerSecond == templateAnim->mTicksPerSecond ) - collectedAnimIndices.push_back( b); + if (other->mNumChannels == 1 && other->mDuration == templateAnim->mDuration && + other->mTicksPerSecond == templateAnim->mTicksPerSecond) + collectedAnimIndices.push_back(b); } // if there are other animations which fit the template anim, combine all channels into a single anim - if( !collectedAnimIndices.empty() ) + if (!collectedAnimIndices.empty()) { aiAnimation* combinedAnim = new aiAnimation(); - combinedAnim->mName = aiString( std::string( "combinedAnim_") + char( '0' + a)); + combinedAnim->mName = aiString(std::string("combinedAnim_") + char('0' + a)); combinedAnim->mDuration = templateAnim->mDuration; combinedAnim->mTicksPerSecond = templateAnim->mTicksPerSecond; combinedAnim->mNumChannels = static_cast(collectedAnimIndices.size() + 1); @@ -973,7 +992,7 @@ void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pPars mAnims[a] = combinedAnim; // move the memory of all other anims to the combined anim and erase them from the source anims - for( size_t b = 0; b < collectedAnimIndices.size(); ++b) + for (size_t b = 0; b < collectedAnimIndices.size(); ++b) { aiAnimation* srcAnimation = mAnims[collectedAnimIndices[b]]; combinedAnim->mChannels[1 + b] = srcAnimation->mChannels[0]; @@ -983,9 +1002,9 @@ void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pPars // in a second go, delete all the single-channel-anims that we've stripped from their channels // back to front to preserve indices - you know, removing an element from a vector moves all elements behind the removed one - while( !collectedAnimIndices.empty() ) + while (!collectedAnimIndices.empty()) { - mAnims.erase( mAnims.begin() + collectedAnimIndices.back()); + mAnims.erase(mAnims.begin() + collectedAnimIndices.back()); collectedAnimIndices.pop_back(); } } @@ -993,11 +1012,11 @@ void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pPars } // now store all anims in the scene - if( !mAnims.empty()) + if (!mAnims.empty()) { pScene->mNumAnimations = static_cast(mAnims.size()); pScene->mAnimations = new aiAnimation*[mAnims.size()]; - std::copy( mAnims.begin(), mAnims.end(), pScene->mAnimations); + std::copy(mAnims.begin(), mAnims.end(), pScene->mAnimations); } mAnims.clear(); @@ -1005,17 +1024,17 @@ void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pPars // ------------------------------------------------------------------------------------------------ // Constructs the animations for the given source anim -void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pParser, const Collada::Animation* pSrcAnim, const std::string &pPrefix) +void ColladaLoader::StoreAnimations(aiScene* pScene, const ColladaParser& pParser, const Collada::Animation* pSrcAnim, const std::string &pPrefix) { std::string animName = pPrefix.empty() ? pSrcAnim->mName : pPrefix + "_" + pSrcAnim->mName; // create nested animations, if given - for( std::vector::const_iterator it = pSrcAnim->mSubAnims.begin(); it != pSrcAnim->mSubAnims.end(); ++it) - StoreAnimations( pScene, pParser, *it, animName); + for (std::vector::const_iterator it = pSrcAnim->mSubAnims.begin(); it != pSrcAnim->mSubAnims.end(); ++it) + StoreAnimations(pScene, pParser, *it, animName); // create animation channels, if any - if( !pSrcAnim->mChannels.empty()) - CreateAnimation( pScene, pParser, pSrcAnim, animName); + if (!pSrcAnim->mChannels.empty()) + CreateAnimation(pScene, pParser, pSrcAnim, animName); } struct MorphTimeValues @@ -1056,7 +1075,8 @@ void insertMorphTimeValue(std::vector &values, float time, floa { values[i].mKeys.push_back(k); return; - } else if (time > values[i].mTime && time < values[i+1].mTime) + } + else if (time > values[i].mTime && time < values[i + 1].mTime) { MorphTimeValues val; val.mTime = time; @@ -1082,30 +1102,30 @@ float getWeightAtKey(const std::vector &values, int key, unsign // ------------------------------------------------------------------------------------------------ // Constructs the animation for the given source anim -void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pParser, const Collada::Animation* pSrcAnim, const std::string& pName) +void ColladaLoader::CreateAnimation(aiScene* pScene, const ColladaParser& pParser, const Collada::Animation* pSrcAnim, const std::string& pName) { // collect a list of animatable nodes std::vector nodes; - CollectNodes( pScene->mRootNode, nodes); + CollectNodes(pScene->mRootNode, nodes); std::vector anims; std::vector morphAnims; - for( std::vector::const_iterator nit = nodes.begin(); nit != nodes.end(); ++nit) + for (std::vector::const_iterator nit = nodes.begin(); nit != nodes.end(); ++nit) { // find all the collada anim channels which refer to the current node std::vector entries; std::string nodeName = (*nit)->mName.data; // find the collada node corresponding to the aiNode - const Collada::Node* srcNode = FindNode( pParser.mRootNode, nodeName); -// ai_assert( srcNode != NULL); - if( !srcNode) + const Collada::Node* srcNode = FindNode(pParser.mRootNode, nodeName); + // ai_assert( srcNode != NULL); + if (!srcNode) continue; // now check all channels if they affect the current node std::string targetID, subElement; - for( std::vector::const_iterator cit = pSrcAnim->mChannels.begin(); + for (std::vector::const_iterator cit = pSrcAnim->mChannels.begin(); cit != pSrcAnim->mChannels.end(); ++cit) { const Collada::AnimationChannel& srcChannel = *cit; @@ -1113,8 +1133,8 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars // we expect the animation target to be of type "nodeName/transformID.subElement". Ignore all others // find the slash that separates the node name - there should be only one - std::string::size_type slashPos = srcChannel.mTarget.find( '/'); - if( slashPos == std::string::npos) + std::string::size_type slashPos = srcChannel.mTarget.find('/'); + if (slashPos == std::string::npos) { std::string::size_type targetPos = srcChannel.mTarget.find(srcNode->mID); if (targetPos == std::string::npos) @@ -1123,44 +1143,45 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars // not node transform, but something else. store as unknown animation channel for now entry.mChannel = &(*cit); entry.mTargetId = srcChannel.mTarget.substr(targetPos + pSrcAnim->mName.length(), - srcChannel.mTarget.length() - targetPos - pSrcAnim->mName.length()); + srcChannel.mTarget.length() - targetPos - pSrcAnim->mName.length()); if (entry.mTargetId.front() == '-') entry.mTargetId = entry.mTargetId.substr(1); entries.push_back(entry); continue; } - if( srcChannel.mTarget.find( '/', slashPos+1) != std::string::npos) + if (srcChannel.mTarget.find('/', slashPos + 1) != std::string::npos) continue; targetID.clear(); - targetID = srcChannel.mTarget.substr( 0, slashPos); - if( targetID != srcNode->mID) + targetID = srcChannel.mTarget.substr(0, slashPos); + if (targetID != srcNode->mID) continue; // find the dot that separates the transformID - there should be only one or zero - std::string::size_type dotPos = srcChannel.mTarget.find( '.'); - if( dotPos != std::string::npos) + std::string::size_type dotPos = srcChannel.mTarget.find('.'); + if (dotPos != std::string::npos) { - if( srcChannel.mTarget.find( '.', dotPos+1) != std::string::npos) + if (srcChannel.mTarget.find('.', dotPos + 1) != std::string::npos) continue; - entry.mTransformId = srcChannel.mTarget.substr( slashPos+1, dotPos - slashPos - 1); + entry.mTransformId = srcChannel.mTarget.substr(slashPos + 1, dotPos - slashPos - 1); subElement.clear(); - subElement = srcChannel.mTarget.substr( dotPos+1); - if( subElement == "ANGLE") + subElement = srcChannel.mTarget.substr(dotPos + 1); + if (subElement == "ANGLE") entry.mSubElement = 3; // last number in an Axis-Angle-Transform is the angle - else if( subElement == "X") + else if (subElement == "X") entry.mSubElement = 0; - else if( subElement == "Y") + else if (subElement == "Y") entry.mSubElement = 1; - else if( subElement == "Z") + else if (subElement == "Z") entry.mSubElement = 2; else - ASSIMP_LOG_WARN_F( "Unknown anim subelement <", subElement, ">. Ignoring" ); - } else { + ASSIMP_LOG_WARN_F("Unknown anim subelement <", subElement, ">. Ignoring"); + } + else { // no subelement following, transformId is remaining string - entry.mTransformId = srcChannel.mTarget.substr( slashPos+1); + entry.mTransformId = srcChannel.mTarget.substr(slashPos + 1); } std::string::size_type bracketPos = srcChannel.mTarget.find('('); @@ -1206,194 +1227,196 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars // determine which transform step is affected by this channel entry.mTransformIndex = SIZE_MAX; - for( size_t a = 0; a < srcNode->mTransforms.size(); ++a) - if( srcNode->mTransforms[a].mID == entry.mTransformId) + for (size_t a = 0; a < srcNode->mTransforms.size(); ++a) + if (srcNode->mTransforms[a].mID == entry.mTransformId) entry.mTransformIndex = a; - if( entry.mTransformIndex == SIZE_MAX) + if (entry.mTransformIndex == SIZE_MAX) { if (entry.mTransformId.find("morph-weights") != std::string::npos) { entry.mTargetId = entry.mTransformId; entry.mTransformId = ""; - } else + } + else continue; } entry.mChannel = &(*cit); - entries.push_back( entry); + entries.push_back(entry); } // if there's no channel affecting the current node, we skip it - if( entries.empty()) + if (entries.empty()) continue; // resolve the data pointers for all anim channels. Find the minimum time while we're at it - ai_real startTime = ai_real( 1e20 ), endTime = ai_real( -1e20 ); - for( std::vector::iterator it = entries.begin(); it != entries.end(); ++it) + ai_real startTime = ai_real(1e20), endTime = ai_real(-1e20); + for (std::vector::iterator it = entries.begin(); it != entries.end(); ++it) { Collada::ChannelEntry& e = *it; - e.mTimeAccessor = &pParser.ResolveLibraryReference( pParser.mAccessorLibrary, e.mChannel->mSourceTimes); - e.mTimeData = &pParser.ResolveLibraryReference( pParser.mDataLibrary, e.mTimeAccessor->mSource); - e.mValueAccessor = &pParser.ResolveLibraryReference( pParser.mAccessorLibrary, e.mChannel->mSourceValues); - e.mValueData = &pParser.ResolveLibraryReference( pParser.mDataLibrary, e.mValueAccessor->mSource); + e.mTimeAccessor = &pParser.ResolveLibraryReference(pParser.mAccessorLibrary, e.mChannel->mSourceTimes); + e.mTimeData = &pParser.ResolveLibraryReference(pParser.mDataLibrary, e.mTimeAccessor->mSource); + e.mValueAccessor = &pParser.ResolveLibraryReference(pParser.mAccessorLibrary, e.mChannel->mSourceValues); + e.mValueData = &pParser.ResolveLibraryReference(pParser.mDataLibrary, e.mValueAccessor->mSource); // time count and value count must match - if( e.mTimeAccessor->mCount != e.mValueAccessor->mCount) - throw DeadlyImportError( format() << "Time count / value count mismatch in animation channel \"" << e.mChannel->mTarget << "\"." ); + if (e.mTimeAccessor->mCount != e.mValueAccessor->mCount) + throw DeadlyImportError(format() << "Time count / value count mismatch in animation channel \"" << e.mChannel->mTarget << "\"."); - if( e.mTimeAccessor->mCount > 0 ) - { - // find bounding times - startTime = std::min( startTime, ReadFloat( *e.mTimeAccessor, *e.mTimeData, 0, 0)); - endTime = std::max( endTime, ReadFloat( *e.mTimeAccessor, *e.mTimeData, e.mTimeAccessor->mCount-1, 0)); - } + if (e.mTimeAccessor->mCount > 0) + { + // find bounding times + startTime = std::min(startTime, ReadFloat(*e.mTimeAccessor, *e.mTimeData, 0, 0)); + endTime = std::max(endTime, ReadFloat(*e.mTimeAccessor, *e.mTimeData, e.mTimeAccessor->mCount - 1, 0)); + } } - std::vector resultTrafos; - if( !entries.empty() && entries.front().mTimeAccessor->mCount > 0 ) - { - // create a local transformation chain of the node's transforms - std::vector transforms = srcNode->mTransforms; + std::vector resultTrafos; + if (!entries.empty() && entries.front().mTimeAccessor->mCount > 0) + { + // create a local transformation chain of the node's transforms + std::vector transforms = srcNode->mTransforms; - // now for every unique point in time, find or interpolate the key values for that time - // and apply them to the transform chain. Then the node's present transformation can be calculated. - ai_real time = startTime; - while( 1) - { - for( std::vector::iterator it = entries.begin(); it != entries.end(); ++it) - { - Collada::ChannelEntry& e = *it; + // now for every unique point in time, find or interpolate the key values for that time + // and apply them to the transform chain. Then the node's present transformation can be calculated. + ai_real time = startTime; + while (1) + { + for (std::vector::iterator it = entries.begin(); it != entries.end(); ++it) + { + Collada::ChannelEntry& e = *it; - // find the keyframe behind the current point in time - size_t pos = 0; - ai_real postTime = 0.0; - while( 1) - { - if( pos >= e.mTimeAccessor->mCount) - break; - postTime = ReadFloat( *e.mTimeAccessor, *e.mTimeData, pos, 0); - if( postTime >= time) - break; - ++pos; - } + // find the keyframe behind the current point in time + size_t pos = 0; + ai_real postTime = 0.0; + while (1) + { + if (pos >= e.mTimeAccessor->mCount) + break; + postTime = ReadFloat(*e.mTimeAccessor, *e.mTimeData, pos, 0); + if (postTime >= time) + break; + ++pos; + } - pos = std::min( pos, e.mTimeAccessor->mCount-1); + pos = std::min(pos, e.mTimeAccessor->mCount - 1); - // read values from there - ai_real temp[16]; - for( size_t c = 0; c < e.mValueAccessor->mSize; ++c) - temp[c] = ReadFloat( *e.mValueAccessor, *e.mValueData, pos, c); + // read values from there + ai_real temp[16]; + for (size_t c = 0; c < e.mValueAccessor->mSize; ++c) + temp[c] = ReadFloat(*e.mValueAccessor, *e.mValueData, pos, c); - // if not exactly at the key time, interpolate with previous value set - if( postTime > time && pos > 0) - { - ai_real preTime = ReadFloat( *e.mTimeAccessor, *e.mTimeData, pos-1, 0); - ai_real factor = (time - postTime) / (preTime - postTime); + // if not exactly at the key time, interpolate with previous value set + if (postTime > time && pos > 0) + { + ai_real preTime = ReadFloat(*e.mTimeAccessor, *e.mTimeData, pos - 1, 0); + ai_real factor = (time - postTime) / (preTime - postTime); - for( size_t c = 0; c < e.mValueAccessor->mSize; ++c) - { - ai_real v = ReadFloat( *e.mValueAccessor, *e.mValueData, pos-1, c); - temp[c] += (v - temp[c]) * factor; - } - } + for (size_t c = 0; c < e.mValueAccessor->mSize; ++c) + { + ai_real v = ReadFloat(*e.mValueAccessor, *e.mValueData, pos - 1, c); + temp[c] += (v - temp[c]) * factor; + } + } - // Apply values to current transformation - std::copy( temp, temp + e.mValueAccessor->mSize, transforms[e.mTransformIndex].f + e.mSubElement); - } + // Apply values to current transformation + std::copy(temp, temp + e.mValueAccessor->mSize, transforms[e.mTransformIndex].f + e.mSubElement); + } - // Calculate resulting transformation - aiMatrix4x4 mat = pParser.CalculateResultTransform( transforms); + // Calculate resulting transformation + aiMatrix4x4 mat = pParser.CalculateResultTransform(transforms); - // out of laziness: we store the time in matrix.d4 - mat.d4 = time; - resultTrafos.push_back( mat); + // out of laziness: we store the time in matrix.d4 + mat.d4 = time; + resultTrafos.push_back(mat); - // find next point in time to evaluate. That's the closest frame larger than the current in any channel - ai_real nextTime = ai_real( 1e20 ); - for( std::vector::iterator it = entries.begin(); it != entries.end(); ++it) - { - Collada::ChannelEntry& channelElement = *it; + // find next point in time to evaluate. That's the closest frame larger than the current in any channel + ai_real nextTime = ai_real(1e20); + for (std::vector::iterator it = entries.begin(); it != entries.end(); ++it) + { + Collada::ChannelEntry& channelElement = *it; - // find the next time value larger than the current - size_t pos = 0; - while( pos < channelElement.mTimeAccessor->mCount) - { - const ai_real t = ReadFloat( *channelElement.mTimeAccessor, *channelElement.mTimeData, pos, 0); - if( t > time) - { - nextTime = std::min( nextTime, t); - break; - } - ++pos; - } + // find the next time value larger than the current + size_t pos = 0; + while (pos < channelElement.mTimeAccessor->mCount) + { + const ai_real t = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos, 0); + if (t > time) + { + nextTime = std::min(nextTime, t); + break; + } + ++pos; + } - // https://github.com/assimp/assimp/issues/458 - // Sub-sample axis-angle channels if the delta between two consecutive - // key-frame angles is >= 180 degrees. - if (transforms[channelElement.mTransformIndex].mType == Collada::TF_ROTATE && channelElement.mSubElement == 3 && pos > 0 && pos < channelElement.mTimeAccessor->mCount) { - const ai_real cur_key_angle = ReadFloat(*channelElement.mValueAccessor, *channelElement.mValueData, pos, 0); - const ai_real last_key_angle = ReadFloat(*channelElement.mValueAccessor, *channelElement.mValueData, pos - 1, 0); - const ai_real cur_key_time = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos, 0); - const ai_real last_key_time = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos - 1, 0); - const ai_real last_eval_angle = last_key_angle + (cur_key_angle - last_key_angle) * (time - last_key_time) / (cur_key_time - last_key_time); - const ai_real delta = std::abs(cur_key_angle - last_eval_angle); - if (delta >= 180.0) { - const int subSampleCount = static_cast(std::floor(delta / 90.0)); - if (cur_key_time != time) { - const ai_real nextSampleTime = time + (cur_key_time - time) / subSampleCount; - nextTime = std::min(nextTime, nextSampleTime); - } - } - } - } + // https://github.com/assimp/assimp/issues/458 + // Sub-sample axis-angle channels if the delta between two consecutive + // key-frame angles is >= 180 degrees. + if (transforms[channelElement.mTransformIndex].mType == Collada::TF_ROTATE && channelElement.mSubElement == 3 && pos > 0 && pos < channelElement.mTimeAccessor->mCount) { + const ai_real cur_key_angle = ReadFloat(*channelElement.mValueAccessor, *channelElement.mValueData, pos, 0); + const ai_real last_key_angle = ReadFloat(*channelElement.mValueAccessor, *channelElement.mValueData, pos - 1, 0); + const ai_real cur_key_time = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos, 0); + const ai_real last_key_time = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos - 1, 0); + const ai_real last_eval_angle = last_key_angle + (cur_key_angle - last_key_angle) * (time - last_key_time) / (cur_key_time - last_key_time); + const ai_real delta = std::abs(cur_key_angle - last_eval_angle); + if (delta >= 180.0) { + const int subSampleCount = static_cast(std::floor(delta / 90.0)); + if (cur_key_time != time) { + const ai_real nextSampleTime = time + (cur_key_time - time) / subSampleCount; + nextTime = std::min(nextTime, nextSampleTime); + } + } + } + } - // no more keys on any channel after the current time -> we're done - if( nextTime > 1e19) - break; + // no more keys on any channel after the current time -> we're done + if (nextTime > 1e19) + break; - // else construct next keyframe at this following time point - time = nextTime; - } - } + // else construct next keyframe at this following time point + time = nextTime; + } + } // there should be some keyframes, but we aren't that fixated on valid input data // ai_assert( resultTrafos.size() > 0); // build an animation channel for the given node out of these trafo keys - if( !resultTrafos.empty() ) + if (!resultTrafos.empty()) { - aiNodeAnim* dstAnim = new aiNodeAnim; - dstAnim->mNodeName = nodeName; - dstAnim->mNumPositionKeys = static_cast(resultTrafos.size()); - dstAnim->mNumRotationKeys = static_cast(resultTrafos.size()); - dstAnim->mNumScalingKeys = static_cast(resultTrafos.size()); - dstAnim->mPositionKeys = new aiVectorKey[resultTrafos.size()]; - dstAnim->mRotationKeys = new aiQuatKey[resultTrafos.size()]; - dstAnim->mScalingKeys = new aiVectorKey[resultTrafos.size()]; + aiNodeAnim* dstAnim = new aiNodeAnim; + dstAnim->mNodeName = nodeName; + dstAnim->mNumPositionKeys = static_cast(resultTrafos.size()); + dstAnim->mNumRotationKeys = static_cast(resultTrafos.size()); + dstAnim->mNumScalingKeys = static_cast(resultTrafos.size()); + dstAnim->mPositionKeys = new aiVectorKey[resultTrafos.size()]; + dstAnim->mRotationKeys = new aiQuatKey[resultTrafos.size()]; + dstAnim->mScalingKeys = new aiVectorKey[resultTrafos.size()]; - for( size_t a = 0; a < resultTrafos.size(); ++a) - { - aiMatrix4x4 mat = resultTrafos[a]; - double time = double( mat.d4); // remember? time is stored in mat.d4 - mat.d4 = 1.0f; + for (size_t a = 0; a < resultTrafos.size(); ++a) + { + aiMatrix4x4 mat = resultTrafos[a]; + double time = double(mat.d4); // remember? time is stored in mat.d4 + mat.d4 = 1.0f; - dstAnim->mPositionKeys[a].mTime = time; - dstAnim->mRotationKeys[a].mTime = time; - dstAnim->mScalingKeys[a].mTime = time; - mat.Decompose( dstAnim->mScalingKeys[a].mValue, dstAnim->mRotationKeys[a].mValue, dstAnim->mPositionKeys[a].mValue); - } + dstAnim->mPositionKeys[a].mTime = time; + dstAnim->mRotationKeys[a].mTime = time; + dstAnim->mScalingKeys[a].mTime = time; + mat.Decompose(dstAnim->mScalingKeys[a].mValue, dstAnim->mRotationKeys[a].mValue, dstAnim->mPositionKeys[a].mValue); + } - anims.push_back( dstAnim); - } else + anims.push_back(dstAnim); + } + else { - ASSIMP_LOG_WARN( "Collada loader: found empty animation channel, ignored. Please check your exporter."); + ASSIMP_LOG_WARN("Collada loader: found empty animation channel, ignored. Please check your exporter."); } - if( !entries.empty() && entries.front().mTimeAccessor->mCount > 0 ) + if (!entries.empty() && entries.front().mTimeAccessor->mCount > 0) { std::vector morphChannels; - for( std::vector::iterator it = entries.begin(); it != entries.end(); ++it) + for (std::vector::iterator it = entries.begin(); it != entries.end(); ++it) { Collada::ChannelEntry& e = *it; @@ -1416,7 +1439,7 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars std::vector morphTimeValues; int morphAnimChannelIndex = 0; - for( std::vector::iterator it = morphChannels.begin(); it != morphChannels.end(); ++it) + for (std::vector::iterator it = morphChannels.begin(); it != morphChannels.end(); ++it) { Collada::ChannelEntry& e = *it; std::string::size_type apos = e.mTargetId.find('('); @@ -1438,8 +1461,8 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars for (unsigned int key = 0; key < morphAnim->mNumKeys; key++) { morphAnim->mKeys[key].mNumValuesAndWeights = static_cast(morphChannels.size()); - morphAnim->mKeys[key].mValues = new unsigned int [morphChannels.size()]; - morphAnim->mKeys[key].mWeights = new double [morphChannels.size()]; + morphAnim->mKeys[key].mValues = new unsigned int[morphChannels.size()]; + morphAnim->mKeys[key].mWeights = new double[morphChannels.size()]; morphAnim->mKeys[key].mTime = morphTimeValues[key].mTime; for (unsigned int valueIndex = 0; valueIndex < morphChannels.size(); valueIndex++) @@ -1454,48 +1477,48 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars } } - if( !anims.empty() || !morphAnims.empty()) + if (!anims.empty() || !morphAnims.empty()) { aiAnimation* anim = new aiAnimation; - anim->mName.Set( pName); + anim->mName.Set(pName); anim->mNumChannels = static_cast(anims.size()); if (anim->mNumChannels > 0) { anim->mChannels = new aiNodeAnim*[anims.size()]; - std::copy( anims.begin(), anims.end(), anim->mChannels); + std::copy(anims.begin(), anims.end(), anim->mChannels); } anim->mNumMorphMeshChannels = static_cast(morphAnims.size()); if (anim->mNumMorphMeshChannels > 0) { anim->mMorphMeshChannels = new aiMeshMorphAnim*[anim->mNumMorphMeshChannels]; - std::copy( morphAnims.begin(), morphAnims.end(), anim->mMorphMeshChannels); + std::copy(morphAnims.begin(), morphAnims.end(), anim->mMorphMeshChannels); } anim->mDuration = 0.0f; - for( size_t a = 0; a < anims.size(); ++a) + for (size_t a = 0; a < anims.size(); ++a) { - anim->mDuration = std::max( anim->mDuration, anims[a]->mPositionKeys[anims[a]->mNumPositionKeys-1].mTime); - anim->mDuration = std::max( anim->mDuration, anims[a]->mRotationKeys[anims[a]->mNumRotationKeys-1].mTime); - anim->mDuration = std::max( anim->mDuration, anims[a]->mScalingKeys[anims[a]->mNumScalingKeys-1].mTime); + anim->mDuration = std::max(anim->mDuration, anims[a]->mPositionKeys[anims[a]->mNumPositionKeys - 1].mTime); + anim->mDuration = std::max(anim->mDuration, anims[a]->mRotationKeys[anims[a]->mNumRotationKeys - 1].mTime); + anim->mDuration = std::max(anim->mDuration, anims[a]->mScalingKeys[anims[a]->mNumScalingKeys - 1].mTime); } for (size_t a = 0; a < morphAnims.size(); ++a) { - anim->mDuration = std::max(anim->mDuration, morphAnims[a]->mKeys[morphAnims[a]->mNumKeys-1].mTime); + anim->mDuration = std::max(anim->mDuration, morphAnims[a]->mKeys[morphAnims[a]->mNumKeys - 1].mTime); } anim->mTicksPerSecond = 1; - mAnims.push_back( anim); + mAnims.push_back(anim); } } // ------------------------------------------------------------------------------------------------ // Add a texture to a material structure -void ColladaLoader::AddTexture ( aiMaterial& mat, const ColladaParser& pParser, +void ColladaLoader::AddTexture(aiMaterial& mat, const ColladaParser& pParser, const Collada::Effect& effect, const Collada::Sampler& sampler, aiTextureType type, unsigned int idx) { // first of all, basic file name - const aiString name = FindFilenameForEffectTexture( pParser, effect, sampler.mName ); - mat.AddProperty( &name, _AI_MATKEY_TEXTURE_BASE, type, idx ); + const aiString name = FindFilenameForEffectTexture(pParser, effect, sampler.mName); + mat.AddProperty(&name, _AI_MATKEY_TEXTURE_BASE, type, idx); // mapping mode int map = aiTextureMapMode_Clamp; @@ -1504,7 +1527,7 @@ void ColladaLoader::AddTexture ( aiMaterial& mat, const ColladaParser& pParser, if (sampler.mWrapU && sampler.mMirrorU) map = aiTextureMapMode_Mirror; - mat.AddProperty( &map, 1, _AI_MATKEY_MAPPINGMODE_U_BASE, type, idx); + mat.AddProperty(&map, 1, _AI_MATKEY_MAPPINGMODE_U_BASE, type, idx); map = aiTextureMapMode_Clamp; if (sampler.mWrapV) @@ -1512,18 +1535,18 @@ void ColladaLoader::AddTexture ( aiMaterial& mat, const ColladaParser& pParser, if (sampler.mWrapV && sampler.mMirrorV) map = aiTextureMapMode_Mirror; - mat.AddProperty( &map, 1, _AI_MATKEY_MAPPINGMODE_V_BASE, type, idx); + mat.AddProperty(&map, 1, _AI_MATKEY_MAPPINGMODE_V_BASE, type, idx); // UV transformation mat.AddProperty(&sampler.mTransform, 1, _AI_MATKEY_UVTRANSFORM_BASE, type, idx); // Blend mode - mat.AddProperty((int*)&sampler.mOp , 1, + mat.AddProperty((int*)&sampler.mOp, 1, _AI_MATKEY_TEXBLEND_BASE, type, idx); // Blend factor - mat.AddProperty((ai_real*)&sampler.mWeighting , 1, + mat.AddProperty((ai_real*)&sampler.mWeighting, 1, _AI_MATKEY_TEXBLEND_BASE, type, idx); // UV source index ... if we didn't resolve the mapping, it is actually just @@ -1535,7 +1558,7 @@ void ColladaLoader::AddTexture ( aiMaterial& mat, const ColladaParser& pParser, map = sampler.mUVId; else { map = -1; - for (std::string::const_iterator it = sampler.mUVChannel.begin();it != sampler.mUVChannel.end(); ++it){ + for (std::string::const_iterator it = sampler.mUVChannel.begin(); it != sampler.mUVChannel.end(); ++it) { if (IsNumeric(*it)) { map = strtoul10(&(*it)); break; @@ -1546,12 +1569,12 @@ void ColladaLoader::AddTexture ( aiMaterial& mat, const ColladaParser& pParser, map = 0; } } - mat.AddProperty(&map,1,_AI_MATKEY_UVWSRC_BASE,type,idx); + mat.AddProperty(&map, 1, _AI_MATKEY_UVWSRC_BASE, type, idx); } // ------------------------------------------------------------------------------------------------ // Fills materials from the collada material definitions -void ColladaLoader::FillMaterials( const ColladaParser& pParser, aiScene* /*pScene*/) +void ColladaLoader::FillMaterials(const ColladaParser& pParser, aiScene* /*pScene*/) { for (auto &elem : newMats) { @@ -1563,7 +1586,7 @@ void ColladaLoader::FillMaterials( const ColladaParser& pParser, aiScene* /*pSce if (effect.mFaceted) /* fixme */ shadeMode = aiShadingMode_Flat; else { - switch( effect.mShadeType) + switch (effect.mShadeType) { case Collada::Shade_Constant: shadeMode = aiShadingMode_NoShading; @@ -1584,56 +1607,57 @@ void ColladaLoader::FillMaterials( const ColladaParser& pParser, aiScene* /*pSce break; } } - mat.AddProperty( &shadeMode, 1, AI_MATKEY_SHADING_MODEL); + mat.AddProperty(&shadeMode, 1, AI_MATKEY_SHADING_MODEL); // double-sided? shadeMode = effect.mDoubleSided; - mat.AddProperty( &shadeMode, 1, AI_MATKEY_TWOSIDED); + mat.AddProperty(&shadeMode, 1, AI_MATKEY_TWOSIDED); // wireframe? shadeMode = effect.mWireframe; - mat.AddProperty( &shadeMode, 1, AI_MATKEY_ENABLE_WIREFRAME); + mat.AddProperty(&shadeMode, 1, AI_MATKEY_ENABLE_WIREFRAME); // add material colors - mat.AddProperty( &effect.mAmbient, 1,AI_MATKEY_COLOR_AMBIENT); - mat.AddProperty( &effect.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); - mat.AddProperty( &effect.mSpecular, 1,AI_MATKEY_COLOR_SPECULAR); - mat.AddProperty( &effect.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); - mat.AddProperty( &effect.mReflective, 1, AI_MATKEY_COLOR_REFLECTIVE); + mat.AddProperty(&effect.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT); + mat.AddProperty(&effect.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + mat.AddProperty(&effect.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); + mat.AddProperty(&effect.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); + mat.AddProperty(&effect.mReflective, 1, AI_MATKEY_COLOR_REFLECTIVE); // scalar properties - mat.AddProperty( &effect.mShininess, 1, AI_MATKEY_SHININESS); - mat.AddProperty( &effect.mReflectivity, 1, AI_MATKEY_REFLECTIVITY); - mat.AddProperty( &effect.mRefractIndex, 1, AI_MATKEY_REFRACTI); + mat.AddProperty(&effect.mShininess, 1, AI_MATKEY_SHININESS); + mat.AddProperty(&effect.mReflectivity, 1, AI_MATKEY_REFLECTIVITY); + mat.AddProperty(&effect.mRefractIndex, 1, AI_MATKEY_REFRACTI); // transparency, a very hard one. seemingly not all files are following the // specification here (1.0 transparency => completely opaque)... // therefore, we let the opportunity for the user to manually invert // the transparency if necessary and we add preliminary support for RGB_ZERO mode - if(effect.mTransparency >= 0.f && effect.mTransparency <= 1.f) { + if (effect.mTransparency >= 0.f && effect.mTransparency <= 1.f) { // handle RGB transparency completely, cf Collada specs 1.5.0 pages 249 and 304 - if(effect.mRGBTransparency) { - // use luminance as defined by ISO/CIE color standards (see ITU-R Recommendation BT.709-4) + if (effect.mRGBTransparency) { + // use luminance as defined by ISO/CIE color standards (see ITU-R Recommendation BT.709-4) effect.mTransparency *= ( 0.212671f * effect.mTransparent.r + 0.715160f * effect.mTransparent.g + 0.072169f * effect.mTransparent.b - ); + ); effect.mTransparent.a = 1.f; - mat.AddProperty( &effect.mTransparent, 1, AI_MATKEY_COLOR_TRANSPARENT ); - } else { - effect.mTransparency *= effect.mTransparent.a; + mat.AddProperty(&effect.mTransparent, 1, AI_MATKEY_COLOR_TRANSPARENT); + } + else { + effect.mTransparency *= effect.mTransparent.a; } - if(effect.mInvertTransparency) { + if (effect.mInvertTransparency) { effect.mTransparency = 1.f - effect.mTransparency; } // Is the material finally transparent ? if (effect.mHasTransparency || effect.mTransparency < 1.f) { - mat.AddProperty( &effect.mTransparency, 1, AI_MATKEY_OPACITY ); + mat.AddProperty(&effect.mTransparency, 1, AI_MATKEY_OPACITY); } } @@ -1643,87 +1667,88 @@ void ColladaLoader::FillMaterials( const ColladaParser& pParser, aiScene* /*pSce AddTexture(mat, pParser, effect, effect.mTexAmbient, aiTextureType_LIGHTMAP); } - if( !effect.mTexEmissive.mName.empty()) - AddTexture( mat, pParser, effect, effect.mTexEmissive, aiTextureType_EMISSIVE); + if (!effect.mTexEmissive.mName.empty()) + AddTexture(mat, pParser, effect, effect.mTexEmissive, aiTextureType_EMISSIVE); - if( !effect.mTexSpecular.mName.empty()) - AddTexture( mat, pParser, effect, effect.mTexSpecular, aiTextureType_SPECULAR); + if (!effect.mTexSpecular.mName.empty()) + AddTexture(mat, pParser, effect, effect.mTexSpecular, aiTextureType_SPECULAR); - if( !effect.mTexDiffuse.mName.empty()) - AddTexture( mat, pParser, effect, effect.mTexDiffuse, aiTextureType_DIFFUSE); + if (!effect.mTexDiffuse.mName.empty()) + AddTexture(mat, pParser, effect, effect.mTexDiffuse, aiTextureType_DIFFUSE); - if( !effect.mTexBump.mName.empty()) - AddTexture( mat, pParser, effect, effect.mTexBump, aiTextureType_NORMALS); + if (!effect.mTexBump.mName.empty()) + AddTexture(mat, pParser, effect, effect.mTexBump, aiTextureType_NORMALS); - if( !effect.mTexTransparent.mName.empty()) - AddTexture( mat, pParser, effect, effect.mTexTransparent, aiTextureType_OPACITY); + if (!effect.mTexTransparent.mName.empty()) + AddTexture(mat, pParser, effect, effect.mTexTransparent, aiTextureType_OPACITY); - if( !effect.mTexReflective.mName.empty()) - AddTexture( mat, pParser, effect, effect.mTexReflective, aiTextureType_REFLECTION); + if (!effect.mTexReflective.mName.empty()) + AddTexture(mat, pParser, effect, effect.mTexReflective, aiTextureType_REFLECTION); } } // ------------------------------------------------------------------------------------------------ // Constructs materials from the collada material definitions -void ColladaLoader::BuildMaterials( ColladaParser& pParser, aiScene* /*pScene*/) +void ColladaLoader::BuildMaterials(ColladaParser& pParser, aiScene* /*pScene*/) { newMats.reserve(pParser.mMaterialLibrary.size()); - for( ColladaParser::MaterialLibrary::const_iterator matIt = pParser.mMaterialLibrary.begin(); + for (ColladaParser::MaterialLibrary::const_iterator matIt = pParser.mMaterialLibrary.begin(); matIt != pParser.mMaterialLibrary.end(); ++matIt) { const Collada::Material& material = matIt->second; // a material is only a reference to an effect - ColladaParser::EffectLibrary::iterator effIt = pParser.mEffectLibrary.find( material.mEffect); - if( effIt == pParser.mEffectLibrary.end()) + ColladaParser::EffectLibrary::iterator effIt = pParser.mEffectLibrary.find(material.mEffect); + if (effIt == pParser.mEffectLibrary.end()) continue; Collada::Effect& effect = effIt->second; // create material aiMaterial* mat = new aiMaterial; - aiString name( material.mName.empty() ? matIt->first : material.mName ); - mat->AddProperty(&name,AI_MATKEY_NAME); + aiString name(material.mName.empty() ? matIt->first : material.mName); + mat->AddProperty(&name, AI_MATKEY_NAME); // store the material mMaterialIndexByName[matIt->first] = newMats.size(); - newMats.push_back( std::pair( &effect,mat) ); + newMats.push_back(std::pair(&effect, mat)); } // ScenePreprocessor generates a default material automatically if none is there. // All further code here in this loader works well without a valid material so // we can safely let it to ScenePreprocessor. #if 0 - if( newMats.size() == 0) + if (newMats.size() == 0) { aiMaterial* mat = new aiMaterial; - aiString name( AI_DEFAULT_MATERIAL_NAME ); - mat->AddProperty( &name, AI_MATKEY_NAME); + aiString name(AI_DEFAULT_MATERIAL_NAME); + mat->AddProperty(&name, AI_MATKEY_NAME); const int shadeMode = aiShadingMode_Phong; - mat->AddProperty( &shadeMode, 1, AI_MATKEY_SHADING_MODEL); - aiColor4D colAmbient( 0.2, 0.2, 0.2, 1.0), colDiffuse( 0.8, 0.8, 0.8, 1.0), colSpecular( 0.5, 0.5, 0.5, 0.5); - mat->AddProperty( &colAmbient, 1, AI_MATKEY_COLOR_AMBIENT); - mat->AddProperty( &colDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); - mat->AddProperty( &colSpecular, 1, AI_MATKEY_COLOR_SPECULAR); + mat->AddProperty(&shadeMode, 1, AI_MATKEY_SHADING_MODEL); + aiColor4D colAmbient(0.2, 0.2, 0.2, 1.0), colDiffuse(0.8, 0.8, 0.8, 1.0), colSpecular(0.5, 0.5, 0.5, 0.5); + mat->AddProperty(&colAmbient, 1, AI_MATKEY_COLOR_AMBIENT); + mat->AddProperty(&colDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty(&colSpecular, 1, AI_MATKEY_COLOR_SPECULAR); const ai_real specExp = 5.0; - mat->AddProperty( &specExp, 1, AI_MATKEY_SHININESS); - } + mat->AddProperty(&specExp, 1, AI_MATKEY_SHININESS); +} #endif } // ------------------------------------------------------------------------------------------------ // Resolves the texture name for the given effect texture entry -aiString ColladaLoader::FindFilenameForEffectTexture( const ColladaParser& pParser, +// and loads the texture data +aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParser, const Collada::Effect& pEffect, const std::string& pName) { aiString result; // recurse through the param references until we end up at an image std::string name = pName; - while( 1) + while (1) { // the given string is a param entry. Find it - Collada::Effect::ParamLibrary::const_iterator it = pEffect.mParams.find( name); + Collada::Effect::ParamLibrary::const_iterator it = pEffect.mParams.find(name); // if not found, we're at the end of the recursion. The resulting string should be the image ID - if( it == pEffect.mParams.end()) + if (it == pEffect.mParams.end()) break; // else recurse on @@ -1731,131 +1756,81 @@ aiString ColladaLoader::FindFilenameForEffectTexture( const ColladaParser& pPars } // find the image referred by this name in the image library of the scene - ColladaParser::ImageLibrary::const_iterator imIt = pParser.mImageLibrary.find( name); - if( imIt == pParser.mImageLibrary.end()) + ColladaParser::ImageLibrary::const_iterator imIt = pParser.mImageLibrary.find(name); + if (imIt == pParser.mImageLibrary.end()) { ASSIMP_LOG_WARN_F("Collada: Unable to resolve effect texture entry \"", pName, "\", ended up at ID \"", name, "\"."); //set default texture file name result.Set(name + ".jpg"); - ConvertPath(result); + ColladaParser::UriDecodePath(result); return result; } // if this is an embedded texture image setup an aiTexture for it - if (imIt->second.mFileName.empty()) + if (!imIt->second.mImageData.empty()) { - if (imIt->second.mImageData.empty()) { - throw DeadlyImportError("Collada: Invalid texture, no data or file reference given"); - } - aiTexture* tex = new aiTexture(); + // Store embedded texture name reference + tex->mFilename.Set(imIt->second.mFileName.c_str()); + result.Set(imIt->second.mFileName); + + // TODO: check the possibility of using the flag "AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING" +// result.data[0] = '*'; +// result.length = 1 + ASSIMP_itoa10(result.data + 1, static_cast(MAXLEN - 1), static_cast(mTextures.size())); + + // setup format hint - if (imIt->second.mEmbeddedFormat.length() > 3) { + if (imIt->second.mEmbeddedFormat.length() >= HINTMAXTEXTURELEN) { ASSIMP_LOG_WARN("Collada: texture format hint is too long, truncating to 3 characters"); } - strncpy(tex->achFormatHint,imIt->second.mEmbeddedFormat.c_str(),3); + strncpy(tex->achFormatHint, imIt->second.mEmbeddedFormat.c_str(), 3); // and copy texture data tex->mHeight = 0; tex->mWidth = static_cast(imIt->second.mImageData.size()); tex->pcData = (aiTexel*)new char[tex->mWidth]; - memcpy(tex->pcData,&imIt->second.mImageData[0],tex->mWidth); - - // TODO: check the possibility of using the flag "AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING" - // In FBX files textures are now stored internally by Assimp with their filename included - // Now Assimp can lookup through the loaded textures after all data is processed - // We need to load all textures before referencing them, as FBX file format order may reference a texture before loading it - // This may occur on this case too, it has to be studied - // setup texture reference string - result.data[0] = '*'; - result.length = 1 + ASSIMP_itoa10(result.data+1,static_cast(MAXLEN-1),static_cast(mTextures.size())); + memcpy(tex->pcData, &imIt->second.mImageData[0], tex->mWidth); // and add this texture to the list mTextures.push_back(tex); } else { - result.Set( imIt->second.mFileName ); - ConvertPath(result); + if (imIt->second.mFileName.empty()) { + throw DeadlyImportError("Collada: Invalid texture, no data or file reference given"); + } + + result.Set(imIt->second.mFileName); } return result; } -// ------------------------------------------------------------------------------------------------ -// Convert a path read from a collada file to the usual representation -void ColladaLoader::ConvertPath (aiString& ss) -{ - // TODO: collada spec, p 22. Handle URI correctly. - // For the moment we're just stripping the file:// away to make it work. - // Windows doesn't seem to be able to find stuff like - // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg' - if (0 == strncmp(ss.data,"file://",7)) - { - ss.length -= 7; - memmove(ss.data,ss.data+7,ss.length); - ss.data[ss.length] = '\0'; - } - - // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes... - // I need to filter it without destroying linux paths starting with "/somewhere" -#if defined( _MSC_VER ) - if( ss.data[0] == '/' && isalpha( (unsigned char) ss.data[1]) && ss.data[2] == ':' ) { -#else - if (ss.data[ 0 ] == '/' && isalpha( ss.data[ 1 ] ) && ss.data[ 2 ] == ':') { -#endif - --ss.length; - ::memmove( ss.data, ss.data+1, ss.length); - ss.data[ss.length] = 0; - } - - // find and convert all %xy special chars - char* out = ss.data; - for( const char* it = ss.data; it != ss.data + ss.length; /**/ ) - { - if( *it == '%' && (it + 3) < ss.data + ss.length ) - { - // separate the number to avoid dragging in chars from behind into the parsing - char mychar[3] = { it[1], it[2], 0 }; - size_t nbr = strtoul16( mychar); - it += 3; - *out++ = (char)(nbr & 0xFF); - } else - { - *out++ = *it++; - } - } - - // adjust length and terminator of the shortened string - *out = 0; - ss.length = (ptrdiff_t) (out - ss.data); -} - // ------------------------------------------------------------------------------------------------ // Reads a float value from an accessor and its data array. -ai_real ColladaLoader::ReadFloat( const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex, size_t pOffset) const +ai_real ColladaLoader::ReadFloat(const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex, size_t pOffset) const { // FIXME: (thom) Test for data type here in every access? For the moment, I leave this to the caller size_t pos = pAccessor.mStride * pIndex + pAccessor.mOffset + pOffset; - ai_assert( pos < pData.mValues.size()); + ai_assert(pos < pData.mValues.size()); return pData.mValues[pos]; } // ------------------------------------------------------------------------------------------------ // Reads a string value from an accessor and its data array. -const std::string& ColladaLoader::ReadString( const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex) const +const std::string& ColladaLoader::ReadString(const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex) const { size_t pos = pAccessor.mStride * pIndex + pAccessor.mOffset; - ai_assert( pos < pData.mStrings.size()); + ai_assert(pos < pData.mStrings.size()); return pData.mStrings[pos]; } // ------------------------------------------------------------------------------------------------ // Collects all nodes into the given array -void ColladaLoader::CollectNodes( const aiNode* pNode, std::vector& poNodes) const +void ColladaLoader::CollectNodes(const aiNode* pNode, std::vector& poNodes) const { - poNodes.push_back( pNode); + poNodes.push_back(pNode); for (size_t a = 0; a < pNode->mNumChildren; ++a) { CollectNodes(pNode->mChildren[a], poNodes); } @@ -1863,15 +1838,15 @@ void ColladaLoader::CollectNodes( const aiNode* pNode, std::vectormName == pName || pNode->mID == pName) + if (pNode->mName == pName || pNode->mID == pName) return pNode; - for( size_t a = 0; a < pNode->mChildren.size(); ++a) + for (size_t a = 0; a < pNode->mChildren.size(); ++a) { - const Collada::Node* node = FindNode( pNode->mChildren[a], pName); - if( node) + const Collada::Node* node = FindNode(pNode->mChildren[a], pName); + if (node) return node; } @@ -1880,32 +1855,37 @@ const Collada::Node* ColladaLoader::FindNode( const Collada::Node* pNode, const // ------------------------------------------------------------------------------------------------ // Finds a node in the collada scene by the given SID -const Collada::Node* ColladaLoader::FindNodeBySID( const Collada::Node* pNode, const std::string& pSID) const -{ - if( pNode->mSID == pSID) - return pNode; +const Collada::Node* ColladaLoader::FindNodeBySID( const Collada::Node* pNode, const std::string& pSID) const { + if (nullptr == pNode) { + return nullptr; + } - for( size_t a = 0; a < pNode->mChildren.size(); ++a) - { - const Collada::Node* node = FindNodeBySID( pNode->mChildren[a], pSID); - if( node) - return node; - } + if (pNode->mSID == pSID) { + return pNode; + } - return NULL; + for( size_t a = 0; a < pNode->mChildren.size(); ++a) { + const Collada::Node* node = FindNodeBySID( pNode->mChildren[a], pSID); + if (node) { + return node; + } + } + + return nullptr; } // ------------------------------------------------------------------------------------------------ // Finds a proper unique name for a node derived from the collada-node's properties. // The name must be unique for proper node-bone association. -std::string ColladaLoader::FindNameForNode( const Collada::Node* pNode) +std::string ColladaLoader::FindNameForNode(const Collada::Node* pNode) { // If explicitly requested, just use the collada name. if (useColladaName) { if (!pNode->mName.empty()) { return pNode->mName; - } else { + } + else { return format() << "$ColladaAutoName$_" << mNodeNameCounter++; } } @@ -1926,4 +1906,6 @@ std::string ColladaLoader::FindNameForNode( const Collada::Node* pNode) } } +} // Namespace Assimp + #endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER diff --git a/code/Collada/ColladaLoader.h b/code/Collada/ColladaLoader.h index 72c2dd8e7..d8d3f4f52 100644 --- a/code/Collada/ColladaLoader.h +++ b/code/Collada/ColladaLoader.h @@ -94,20 +94,20 @@ public: public: /** Returns whether the class can handle the format of the given file. * See BaseImporter::CanRead() for details. */ - bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const; + bool CanRead(const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override; protected: /** Return importer meta information. * See #BaseImporter::GetInfo for the details */ - const aiImporterDesc* GetInfo () const; + const aiImporterDesc* GetInfo () const override; - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; /** Imports the given file into the given scene structure. * See BaseImporter::InternReadFile() for details */ - void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); + void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) override; /** Recursively constructs a scene node for the given parser node and returns it. */ aiNode* BuildHierarchy( const ColladaParser& pParser, const Collada::Node* pNode); @@ -120,7 +120,7 @@ protected: void BuildMeshesForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget); - aiMesh *findMesh(std::string meshid); + aiMesh *findMesh(const std::string& meshid); /** Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh */ aiMesh* CreateMesh( const ColladaParser& pParser, const Collada::Mesh* pSrcMesh, const Collada::SubMesh& pSubMesh, @@ -184,9 +184,6 @@ protected: aiString FindFilenameForEffectTexture( const ColladaParser& pParser, const Collada::Effect& pEffect, const std::string& pName); - /** Converts a path read from a collada file to the usual representation */ - void ConvertPath( aiString& ss); - /** Reads a float value from an accessor and its data array. * @param pAccessor The accessor to use for reading * @param pData The data array to read from diff --git a/code/Collada/ColladaParser.cpp b/code/Collada/ColladaParser.cpp index 860ae2ae9..e2e6626c3 100644 --- a/code/Collada/ColladaParser.cpp +++ b/code/Collada/ColladaParser.cpp @@ -57,6 +57,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include @@ -66,9 +67,9 @@ using namespace Assimp::Formatter; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -ColladaParser::ColladaParser( IOSystem* pIOHandler, const std::string& pFile) - : mFileName( pFile ) - , mReader( nullptr ) +ColladaParser::ColladaParser(IOSystem* pIOHandler, const std::string& pFile) + : mFileName(pFile) + , mReader(nullptr) , mDataLibrary() , mAccessorLibrary() , mMeshLibrary() @@ -79,32 +80,60 @@ ColladaParser::ColladaParser( IOSystem* pIOHandler, const std::string& pFile) , mLightLibrary() , mCameraLibrary() , mControllerLibrary() - , mRootNode( nullptr ) + , mRootNode(nullptr) , mAnims() - , mUnitSize( 1.0f ) - , mUpDirection( UP_Y ) - , mFormat(FV_1_5_n ) // We assume the newest file format by default + , mUnitSize(1.0f) + , mUpDirection(UP_Y) + , mFormat(FV_1_5_n) // We assume the newest file format by default { // validate io-handler instance - if (nullptr == pIOHandler ) { - throw DeadlyImportError("IOSystem is NULL." ); + if (nullptr == pIOHandler) { + throw DeadlyImportError("IOSystem is NULL."); } - // open the file - std::unique_ptr file( pIOHandler->Open(pFile ) ); - if (file.get() == nullptr) { - throw DeadlyImportError( "Failed to open file " + pFile + "." ); + std::unique_ptr daefile; + std::unique_ptr zip_archive; + + // Determine type + std::string extension = BaseImporter::GetExtension(pFile); + if (extension != "dae") { + zip_archive.reset(new ZipArchiveIOSystem(pIOHandler, pFile)); + } + + if (zip_archive && zip_archive->isOpen()) { + std::string dae_filename = ReadZaeManifest(*zip_archive); + + if (dae_filename.empty()) { + ThrowException(std::string("Invalid ZAE")); + } + + daefile.reset(zip_archive->Open(dae_filename.c_str())); + if (daefile == nullptr) { + ThrowException(std::string("Invalid ZAE manifest: '") + std::string(dae_filename) + std::string("' is missing")); + } + } + else { + // attempt to open the file directly + daefile.reset(pIOHandler->Open(pFile)); + if (daefile.get() == nullptr) { + throw DeadlyImportError("Failed to open file '" + pFile + "'."); + } } // generate a XML reader for it - std::unique_ptr mIOWrapper(new CIrrXML_IOStreamReader(file.get())); - mReader = irr::io::createIrrXMLReader( mIOWrapper.get()); + std::unique_ptr mIOWrapper(new CIrrXML_IOStreamReader(daefile.get())); + mReader = irr::io::createIrrXMLReader(mIOWrapper.get()); if (!mReader) { - ThrowException("Collada: Unable to open file."); + ThrowException("Unable to read file, malformed XML"); } // start reading ReadContents(); + + // read embedded textures + if (zip_archive && zip_archive->isOpen()) { + ReadEmbeddedTextures(*zip_archive); + } } // ------------------------------------------------------------------------------------------------ @@ -112,18 +141,115 @@ ColladaParser::ColladaParser( IOSystem* pIOHandler, const std::string& pFile) ColladaParser::~ColladaParser() { delete mReader; - for( NodeLibrary::iterator it = mNodeLibrary.begin(); it != mNodeLibrary.end(); ++it) + for (NodeLibrary::iterator it = mNodeLibrary.begin(); it != mNodeLibrary.end(); ++it) delete it->second; - for( MeshLibrary::iterator it = mMeshLibrary.begin(); it != mMeshLibrary.end(); ++it) + for (MeshLibrary::iterator it = mMeshLibrary.begin(); it != mMeshLibrary.end(); ++it) delete it->second; } +// ------------------------------------------------------------------------------------------------ +// Read a ZAE manifest and return the filename to attempt to open +std::string ColladaParser::ReadZaeManifest(ZipArchiveIOSystem &zip_archive) { + // Open the manifest + std::unique_ptr manifestfile(zip_archive.Open("manifest.xml")); + if (manifestfile == nullptr) + { + // No manifest, hope there is only one .DAE inside + std::vector file_list; + zip_archive.getFileListExtension(file_list, "dae"); + + if (file_list.empty()) + return std::string(); + + return file_list.front(); + } + + std::unique_ptr mIOWrapper(new CIrrXML_IOStreamReader(manifestfile.get())); + std::unique_ptr manifest_reader(irr::io::createIrrXMLReader(mIOWrapper.get())); + + while (manifest_reader->read()) + { + // find the manifest "dae_root" element + if (manifest_reader->getNodeType() == irr::io::EXN_ELEMENT) + { + if (::strcmp(manifest_reader->getNodeName(), "dae_root") == 0) + { + if (!manifest_reader->read()) + return std::string(); + if (manifest_reader->getNodeType() != irr::io::EXN_TEXT && manifest_reader->getNodeType() != irr::io::EXN_CDATA) + return std::string(); + + const char* filepath = manifest_reader->getNodeData(); + if (filepath == nullptr) + return std::string(); + + aiString ai_str(filepath); + UriDecodePath(ai_str); + + return std::string(ai_str.C_Str()); + } + } + } + return std::string(); +} + +// ------------------------------------------------------------------------------------------------ +// Convert a path read from a collada file to the usual representation +void ColladaParser::UriDecodePath(aiString& ss) +{ + // TODO: collada spec, p 22. Handle URI correctly. + // For the moment we're just stripping the file:// away to make it work. + // Windows doesn't seem to be able to find stuff like + // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg' + if (0 == strncmp(ss.data, "file://", 7)) + { + ss.length -= 7; + memmove(ss.data, ss.data + 7, ss.length); + ss.data[ss.length] = '\0'; + } + + // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes... + // I need to filter it without destroying linux paths starting with "/somewhere" +#if defined( _MSC_VER ) + if (ss.data[0] == '/' && isalpha((unsigned char)ss.data[1]) && ss.data[2] == ':') { +#else + if (ss.data[0] == '/' && isalpha(ss.data[1]) && ss.data[2] == ':') { +#endif + --ss.length; + ::memmove(ss.data, ss.data + 1, ss.length); + ss.data[ss.length] = 0; + } + + // find and convert all %xy special chars + char* out = ss.data; + for (const char* it = ss.data; it != ss.data + ss.length; /**/) + { + if (*it == '%' && (it + 3) < ss.data + ss.length) + { + // separate the number to avoid dragging in chars from behind into the parsing + char mychar[3] = { it[1], it[2], 0 }; + size_t nbr = strtoul16(mychar); + it += 3; + *out++ = (char)(nbr & 0xFF); + } + else + { + *out++ = *it++; + } + } + + // adjust length and terminator of the shortened string + *out = 0; + ai_assert(out > ss.data); + ss.length = static_cast(out - ss.data); +} + // ------------------------------------------------------------------------------------------------ // Read bool from text contents of current element bool ColladaParser::ReadBoolFromTextContent() { const char* cur = GetTextContent(); - return (!ASSIMP_strincmp(cur,"true",4) || '0' != *cur); + return (!ASSIMP_strincmp(cur, "true", 4) || '0' != *cur); } // ------------------------------------------------------------------------------------------------ @@ -138,39 +264,41 @@ ai_real ColladaParser::ReadFloatFromTextContent() // Reads the contents of the file void ColladaParser::ReadContents() { - while( mReader->read()) + while (mReader->read()) { // handle the root element "COLLADA" - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "COLLADA")) + if (IsElement("COLLADA")) { // check for 'version' attribute const int attrib = TestAttribute("version"); if (attrib != -1) { const char* version = mReader->getAttributeValue(attrib); - if (!::strncmp(version,"1.5",3)) { - mFormat = FV_1_5_n; + if (!::strncmp(version, "1.5", 3)) { + mFormat = FV_1_5_n; ASSIMP_LOG_DEBUG("Collada schema version is 1.5.n"); } - else if (!::strncmp(version,"1.4",3)) { - mFormat = FV_1_4_n; + else if (!::strncmp(version, "1.4", 3)) { + mFormat = FV_1_4_n; ASSIMP_LOG_DEBUG("Collada schema version is 1.4.n"); } - else if (!::strncmp(version,"1.3",3)) { - mFormat = FV_1_3_n; + else if (!::strncmp(version, "1.3", 3)) { + mFormat = FV_1_3_n; ASSIMP_LOG_DEBUG("Collada schema version is 1.3.n"); } } ReadStructure(); - } else + } + else { - ASSIMP_LOG_DEBUG_F( "Ignoring global element <", mReader->getNodeName(), ">." ); + ASSIMP_LOG_DEBUG_F("Ignoring global element <", mReader->getNodeName(), ">."); SkipElement(); } - } else + } + else { // skip everything else silently } @@ -181,47 +309,47 @@ void ColladaParser::ReadContents() // Reads the structure of the file void ColladaParser::ReadStructure() { - while( mReader->read()) + while (mReader->read()) { // beginning of elements - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "asset")) + if (IsElement("asset")) ReadAssetInfo(); - else if( IsElement( "library_animations")) + else if (IsElement("library_animations")) ReadAnimationLibrary(); - else if (IsElement("library_animation_clips")) - ReadAnimationClipLibrary(); - else if( IsElement( "library_controllers")) + else if (IsElement("library_animation_clips")) + ReadAnimationClipLibrary(); + else if (IsElement("library_controllers")) ReadControllerLibrary(); - else if( IsElement( "library_images")) + else if (IsElement("library_images")) ReadImageLibrary(); - else if( IsElement( "library_materials")) + else if (IsElement("library_materials")) ReadMaterialLibrary(); - else if( IsElement( "library_effects")) + else if (IsElement("library_effects")) ReadEffectLibrary(); - else if( IsElement( "library_geometries")) + else if (IsElement("library_geometries")) ReadGeometryLibrary(); - else if( IsElement( "library_visual_scenes")) + else if (IsElement("library_visual_scenes")) ReadSceneLibrary(); - else if( IsElement( "library_lights")) + else if (IsElement("library_lights")) ReadLightLibrary(); - else if( IsElement( "library_cameras")) + else if (IsElement("library_cameras")) ReadCameraLibrary(); - else if( IsElement( "library_nodes")) + else if (IsElement("library_nodes")) ReadSceneNode(NULL); /* some hacking to reuse this piece of code */ - else if( IsElement( "scene")) + else if (IsElement("scene")) ReadScene(); else SkipElement(); } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { break; } } - PostProcessRootAnimations(); + PostProcessRootAnimations(); PostProcessControllers(); } @@ -229,43 +357,43 @@ void ColladaParser::ReadStructure() // Reads asset information such as coordinate system information and legal blah void ColladaParser::ReadAssetInfo() { - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) return; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "unit")) + if (IsElement("unit")) { // read unit data from the element's attributes - const int attrIndex = TestAttribute( "meter"); + const int attrIndex = TestAttribute("meter"); if (attrIndex == -1) { mUnitSize = 1.f; } else { - mUnitSize = mReader->getAttributeValueAsFloat( attrIndex); + mUnitSize = mReader->getAttributeValueAsFloat(attrIndex); } // consume the trailing stuff - if( !mReader->isEmptyElement()) + if (!mReader->isEmptyElement()) SkipElement(); } - else if( IsElement( "up_axis")) + else if (IsElement("up_axis")) { // read content, strip whitespace, compare const char* content = GetTextContent(); - if( strncmp( content, "X_UP", 4) == 0) + if (strncmp(content, "X_UP", 4) == 0) mUpDirection = UP_X; - else if( strncmp( content, "Z_UP", 4) == 0) + else if (strncmp(content, "Z_UP", 4) == 0) mUpDirection = UP_Z; else mUpDirection = UP_Y; // check element end - TestClosing( "up_axis"); + TestClosing("up_axis"); } - else if(IsElement("contributor")) + else if (IsElement("contributor")) { ReadContributorInfo(); } @@ -274,10 +402,10 @@ void ColladaParser::ReadAssetInfo() ReadMetaDataItem(mAssetMetaData); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if (strcmp( mReader->getNodeName(), "asset") != 0) - ThrowException( "Expected end of element."); + if (strcmp(mReader->getNodeName(), "asset") != 0) + ThrowException("Expected end of element."); break; } @@ -355,82 +483,82 @@ void ColladaParser::ToCamelCase(std::string &text) // Reads the animation clips void ColladaParser::ReadAnimationClipLibrary() { - if (mReader->isEmptyElement()) - return; + if (mReader->isEmptyElement()) + return; - while (mReader->read()) - { - if (mReader->getNodeType() == irr::io::EXN_ELEMENT) - { - if (IsElement("animation_clip")) - { - // optional name given as an attribute - std::string animName; - int indexName = TestAttribute("name"); - int indexID = TestAttribute("id"); - if (indexName >= 0) - animName = mReader->getAttributeValue(indexName); - else if (indexID >= 0) - animName = mReader->getAttributeValue(indexID); - else - animName = std::string("animation_") + to_string(mAnimationClipLibrary.size()); + while (mReader->read()) + { + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) + { + if (IsElement("animation_clip")) + { + // optional name given as an attribute + std::string animName; + int indexName = TestAttribute("name"); + int indexID = TestAttribute("id"); + if (indexName >= 0) + animName = mReader->getAttributeValue(indexName); + else if (indexID >= 0) + animName = mReader->getAttributeValue(indexID); + else + animName = std::string("animation_") + to_string(mAnimationClipLibrary.size()); - std::pair > clip; + std::pair > clip; - clip.first = animName; + clip.first = animName; - while (mReader->read()) - { - if (mReader->getNodeType() == irr::io::EXN_ELEMENT) - { - if (IsElement("instance_animation")) - { - int indexUrl = TestAttribute("url"); - if (indexUrl >= 0) - { - const char* url = mReader->getAttributeValue(indexUrl); - if (url[0] != '#') - ThrowException("Unknown reference format"); + while (mReader->read()) + { + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) + { + if (IsElement("instance_animation")) + { + int indexUrl = TestAttribute("url"); + if (indexUrl >= 0) + { + const char* url = mReader->getAttributeValue(indexUrl); + if (url[0] != '#') + ThrowException("Unknown reference format"); - url++; + url++; - clip.second.push_back(url); - } - } - else - { - // ignore the rest - SkipElement(); - } - } - else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) - { - if (strcmp(mReader->getNodeName(), "animation_clip") != 0) - ThrowException("Expected end of element."); + clip.second.push_back(url); + } + } + else + { + // ignore the rest + SkipElement(); + } + } + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + { + if (strcmp(mReader->getNodeName(), "animation_clip") != 0) + ThrowException("Expected end of element."); - break; - } - } + break; + } + } - if (clip.second.size() > 0) - { - mAnimationClipLibrary.push_back(clip); - } - } - else - { - // ignore the rest - SkipElement(); - } - } - else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) - { - if (strcmp(mReader->getNodeName(), "library_animation_clips") != 0) - ThrowException("Expected end of element."); + if (clip.second.size() > 0) + { + mAnimationClipLibrary.push_back(clip); + } + } + else + { + // ignore the rest + SkipElement(); + } + } + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + { + if (strcmp(mReader->getNodeName(), "library_animation_clips") != 0) + ThrowException("Expected end of element."); - break; - } - } + break; + } + } } void ColladaParser::PostProcessControllers() @@ -439,11 +567,11 @@ void ColladaParser::PostProcessControllers() for (ControllerLibrary::iterator it = mControllerLibrary.begin(); it != mControllerLibrary.end(); ++it) { meshId = it->second.mMeshId; ControllerLibrary::iterator findItr = mControllerLibrary.find(meshId); - while(findItr != mControllerLibrary.end()) { + while (findItr != mControllerLibrary.end()) { meshId = findItr->second.mMeshId; findItr = mControllerLibrary.find(meshId); } - + it->second.mMeshId = meshId; } } @@ -452,43 +580,43 @@ void ColladaParser::PostProcessControllers() // Re-build animations from animation clip library, if present, otherwise combine single-channel animations void ColladaParser::PostProcessRootAnimations() { - if (mAnimationClipLibrary.size() > 0) - { - Animation temp; + if (mAnimationClipLibrary.size() > 0) + { + Animation temp; - for (AnimationClipLibrary::iterator it = mAnimationClipLibrary.begin(); it != mAnimationClipLibrary.end(); ++it) - { - std::string clipName = it->first; + for (AnimationClipLibrary::iterator it = mAnimationClipLibrary.begin(); it != mAnimationClipLibrary.end(); ++it) + { + std::string clipName = it->first; - Animation *clip = new Animation(); - clip->mName = clipName; + Animation *clip = new Animation(); + clip->mName = clipName; - temp.mSubAnims.push_back(clip); + temp.mSubAnims.push_back(clip); - for (std::vector::iterator a = it->second.begin(); a != it->second.end(); ++a) - { - std::string animationID = *a; + for (std::vector::iterator a = it->second.begin(); a != it->second.end(); ++a) + { + std::string animationID = *a; - AnimationLibrary::iterator animation = mAnimationLibrary.find(animationID); + AnimationLibrary::iterator animation = mAnimationLibrary.find(animationID); - if (animation != mAnimationLibrary.end()) - { - Animation *pSourceAnimation = animation->second; + if (animation != mAnimationLibrary.end()) + { + Animation *pSourceAnimation = animation->second; - pSourceAnimation->CollectChannelsRecursively(clip->mChannels); - } - } - } + pSourceAnimation->CollectChannelsRecursively(clip->mChannels); + } + } + } - mAnims = temp; + mAnims = temp; - // Ensure no double deletes. - temp.mSubAnims.clear(); - } - else - { - mAnims.CombineSingleChannelAnimations(); - } + // Ensure no double deletes. + temp.mSubAnims.clear(); + } + else + { + mAnims.CombineSingleChannelAnimations(); + } } // ------------------------------------------------------------------------------------------------ @@ -498,24 +626,25 @@ void ColladaParser::ReadAnimationLibrary() if (mReader->isEmptyElement()) return; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "animation")) + if (IsElement("animation")) { // delegate the reading. Depending on the inner elements it will be a container or a anim channel - ReadAnimation( &mAnims); - } else + ReadAnimation(&mAnims); + } + else { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "library_animations") != 0) - ThrowException( "Expected end of element."); + if (strcmp(mReader->getNodeName(), "library_animations") != 0) + ThrowException("Expected end of element."); break; } @@ -524,9 +653,9 @@ void ColladaParser::ReadAnimationLibrary() // ------------------------------------------------------------------------------------------------ // Reads an animation into the given parent structure -void ColladaParser::ReadAnimation( Collada::Animation* pParent) +void ColladaParser::ReadAnimation(Collada::Animation* pParent) { - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) return; // an element may be a container for grouping sub-elements or an animation channel @@ -538,68 +667,68 @@ void ColladaParser::ReadAnimation( Collada::Animation* pParent) // optional name given as an attribute std::string animName; - std::string animID; - int indexName = TestAttribute( "name"); - int indexID = TestAttribute( "id"); + std::string animID; + int indexName = TestAttribute("name"); + int indexID = TestAttribute("id"); - if (indexID >= 0) - animID = mReader->getAttributeValue(indexID); + if (indexID >= 0) + animID = mReader->getAttributeValue(indexID); - if( indexName >= 0) - animName = mReader->getAttributeValue( indexName); - else if( indexID >= 0) + if (indexName >= 0) + animName = mReader->getAttributeValue(indexName); + else if (indexID >= 0) animName = animID; else animName = "animation"; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { // we have subanimations - if( IsElement( "animation")) + if (IsElement("animation")) { // create container from our element - if( !anim) + if (!anim) { anim = new Animation; anim->mName = animName; - pParent->mSubAnims.push_back( anim); + pParent->mSubAnims.push_back(anim); } // recurse into the subelement - ReadAnimation( anim); + ReadAnimation(anim); } - else if( IsElement( "source")) + else if (IsElement("source")) { // possible animation data - we'll never know. Better store it ReadSource(); } - else if( IsElement( "sampler")) + else if (IsElement("sampler")) { // read the ID to assign the corresponding collada channel afterwards. - int indexID = GetAttribute( "id"); - std::string id = mReader->getAttributeValue( indexID); - ChannelMap::iterator newChannel = channels.insert( std::make_pair( id, AnimationChannel())).first; + int indexID = GetAttribute("id"); + std::string id = mReader->getAttributeValue(indexID); + ChannelMap::iterator newChannel = channels.insert(std::make_pair(id, AnimationChannel())).first; // have it read into a channel - ReadAnimationSampler( newChannel->second); + ReadAnimationSampler(newChannel->second); } - else if( IsElement( "channel")) + else if (IsElement("channel")) { // the binding element whose whole purpose is to provide the target to animate // Thanks, Collada! A directly posted information would have been too simple, I guess. // Better add another indirection to that! Can't have enough of those. - int indexTarget = GetAttribute( "target"); - int indexSource = GetAttribute( "source"); - const char* sourceId = mReader->getAttributeValue( indexSource); - if( sourceId[0] == '#') + int indexTarget = GetAttribute("target"); + int indexSource = GetAttribute("source"); + const char* sourceId = mReader->getAttributeValue(indexSource); + if (sourceId[0] == '#') sourceId++; - ChannelMap::iterator cit = channels.find( sourceId); - if( cit != channels.end()) - cit->second.mTarget = mReader->getAttributeValue( indexTarget); + ChannelMap::iterator cit = channels.find(sourceId); + if (cit != channels.end()) + cit->second.mTarget = mReader->getAttributeValue(indexTarget); - if( !mReader->isEmptyElement()) + if (!mReader->isEmptyElement()) SkipElement(); } else @@ -608,24 +737,24 @@ void ColladaParser::ReadAnimation( Collada::Animation* pParent) SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "animation") != 0) - ThrowException( "Expected end of element."); + if (strcmp(mReader->getNodeName(), "animation") != 0) + ThrowException("Expected end of element."); break; } } // it turned out to have channels - add them - if( !channels.empty()) + if (!channels.empty()) { - // FIXME: Is this essentially doing the same as "single-anim-node" codepath in - // ColladaLoader::StoreAnimations? For now, this has been deferred to after - // all animations and all clips have been read. Due to handling of - // this cannot be done here, as the channel owner - // is lost, and some exporters make up animations by referring to multiple - // single-channel animations from an . + // FIXME: Is this essentially doing the same as "single-anim-node" codepath in + // ColladaLoader::StoreAnimations? For now, this has been deferred to after + // all animations and all clips have been read. Due to handling of + // this cannot be done here, as the channel owner + // is lost, and some exporters make up animations by referring to multiple + // single-channel animations from an . /* // special filtering for stupid exporters packing each channel into a separate animation if( channels.size() == 1) @@ -635,53 +764,53 @@ void ColladaParser::ReadAnimation( Collada::Animation* pParent) */ { // else create the animation, if not done yet, and store the channels - if( !anim) + if (!anim) { anim = new Animation; anim->mName = animName; - pParent->mSubAnims.push_back( anim); + pParent->mSubAnims.push_back(anim); } - for( ChannelMap::const_iterator it = channels.begin(); it != channels.end(); ++it) - anim->mChannels.push_back( it->second); + for (ChannelMap::const_iterator it = channels.begin(); it != channels.end(); ++it) + anim->mChannels.push_back(it->second); - if (indexID >= 0) - { - mAnimationLibrary[animID] = anim; - } + if (indexID >= 0) + { + mAnimationLibrary[animID] = anim; + } } } } // ------------------------------------------------------------------------------------------------ // Reads an animation sampler into the given anim channel -void ColladaParser::ReadAnimationSampler( Collada::AnimationChannel& pChannel) +void ColladaParser::ReadAnimationSampler(Collada::AnimationChannel& pChannel) { - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "input")) + if (IsElement("input")) { - int indexSemantic = GetAttribute( "semantic"); - const char* semantic = mReader->getAttributeValue( indexSemantic); - int indexSource = GetAttribute( "source"); - const char* source = mReader->getAttributeValue( indexSource); - if( source[0] != '#') - ThrowException( "Unsupported URL format"); + int indexSemantic = GetAttribute("semantic"); + const char* semantic = mReader->getAttributeValue(indexSemantic); + int indexSource = GetAttribute("source"); + const char* source = mReader->getAttributeValue(indexSource); + if (source[0] != '#') + ThrowException("Unsupported URL format"); source++; - if( strcmp( semantic, "INPUT") == 0) + if (strcmp(semantic, "INPUT") == 0) pChannel.mSourceTimes = source; - else if( strcmp( semantic, "OUTPUT") == 0) + else if (strcmp(semantic, "OUTPUT") == 0) pChannel.mSourceValues = source; - else if( strcmp( semantic, "IN_TANGENT") == 0) + else if (strcmp(semantic, "IN_TANGENT") == 0) pChannel.mInTanValues = source; - else if( strcmp( semantic, "OUT_TANGENT") == 0) + else if (strcmp(semantic, "OUT_TANGENT") == 0) pChannel.mOutTanValues = source; - else if( strcmp( semantic, "INTERPOLATION") == 0) + else if (strcmp(semantic, "INTERPOLATION") == 0) pChannel.mInterpolationValues = source; - if( !mReader->isEmptyElement()) + if (!mReader->isEmptyElement()) SkipElement(); } else @@ -690,10 +819,10 @@ void ColladaParser::ReadAnimationSampler( Collada::AnimationChannel& pChannel) SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "sampler") != 0) - ThrowException( "Expected end of element."); + if (strcmp(mReader->getNodeName(), "sampler") != 0) + ThrowException("Expected end of element."); break; } @@ -707,31 +836,32 @@ void ColladaParser::ReadControllerLibrary() if (mReader->isEmptyElement()) return; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "controller")) + if (IsElement("controller")) { // read ID. Ask the spec if it's necessary or optional... you might be surprised. - int attrID = GetAttribute( "id"); - std::string id = mReader->getAttributeValue( attrID); + int attrID = GetAttribute("id"); + std::string id = mReader->getAttributeValue(attrID); // create an entry and store it in the library under its ID mControllerLibrary[id] = Controller(); // read on from there - ReadController( mControllerLibrary[id]); - } else + ReadController(mControllerLibrary[id]); + } + else { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "library_controllers") != 0) - ThrowException( "Expected end of element."); + if (strcmp(mReader->getNodeName(), "library_controllers") != 0) + ThrowException("Expected end of element."); break; } @@ -740,17 +870,17 @@ void ColladaParser::ReadControllerLibrary() // ------------------------------------------------------------------------------------------------ // Reads a controller into the given mesh structure -void ColladaParser::ReadController( Collada::Controller& pController) +void ColladaParser::ReadController(Collada::Controller& pController) { // initial values pController.mType = Skin; pController.mMethod = Normalized; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { // two types of controllers: "skin" and "morph". Only the first one is relevant, we skip the other - if( IsElement( "morph")) + if (IsElement("morph")) { pController.mType = Morph; int baseIndex = GetAttribute("source"); @@ -762,47 +892,47 @@ void ColladaParser::ReadController( Collada::Controller& pController) pController.mMethod = Relative; } } - else if( IsElement( "skin")) + else if (IsElement("skin")) { // read the mesh it refers to. According to the spec this could also be another // controller, but I refuse to implement every single idea they've come up with - int sourceIndex = GetAttribute( "source"); - pController.mMeshId = mReader->getAttributeValue( sourceIndex) + 1; + int sourceIndex = GetAttribute("source"); + pController.mMeshId = mReader->getAttributeValue(sourceIndex) + 1; } - else if( IsElement( "bind_shape_matrix")) + else if (IsElement("bind_shape_matrix")) { // content is 16 floats to define a matrix... it seems to be important for some models const char* content = GetTextContent(); // read the 16 floats - for( unsigned int a = 0; a < 16; a++) + for (unsigned int a = 0; a < 16; a++) { // read a number - content = fast_atoreal_move( content, pController.mBindShapeMatrix[a]); + content = fast_atoreal_move(content, pController.mBindShapeMatrix[a]); // skip whitespace after it - SkipSpacesAndLineEnd( &content); + SkipSpacesAndLineEnd(&content); } - TestClosing( "bind_shape_matrix"); + TestClosing("bind_shape_matrix"); } - else if( IsElement( "source")) + else if (IsElement("source")) { // data array - we have specialists to handle this ReadSource(); } - else if( IsElement( "joints")) + else if (IsElement("joints")) { - ReadControllerJoints( pController); + ReadControllerJoints(pController); } - else if( IsElement( "vertex_weights")) + else if (IsElement("vertex_weights")) { - ReadControllerWeights( pController); + ReadControllerWeights(pController); } - else if ( IsElement( "targets" )) + else if (IsElement("targets")) { while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if ( IsElement( "input")) { + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (IsElement("input")) { int semanticsIndex = GetAttribute("semantic"); int sourceIndex = GetAttribute("source"); @@ -816,11 +946,12 @@ void ColladaParser::ReadController( Collada::Controller& pController) pController.mMorphWeight = source + 1; } } - } else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "targets") == 0) + } + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (strcmp(mReader->getNodeName(), "targets") == 0) break; else - ThrowException( "Expected end of element."); + ThrowException("Expected end of element."); } } } @@ -830,47 +961,47 @@ void ColladaParser::ReadController( Collada::Controller& pController) SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "controller") == 0) + if (strcmp(mReader->getNodeName(), "controller") == 0) break; - else if( strcmp( mReader->getNodeName(), "skin") != 0 && strcmp( mReader->getNodeName(), "morph") != 0) - ThrowException( "Expected end of element."); + else if (strcmp(mReader->getNodeName(), "skin") != 0 && strcmp(mReader->getNodeName(), "morph") != 0) + ThrowException("Expected end of element."); } } } // ------------------------------------------------------------------------------------------------ // Reads the joint definitions for the given controller -void ColladaParser::ReadControllerJoints( Collada::Controller& pController) +void ColladaParser::ReadControllerJoints(Collada::Controller& pController) { - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { // Input channels for joint data. Two possible semantics: "JOINT" and "INV_BIND_MATRIX" - if( IsElement( "input")) + if (IsElement("input")) { - int indexSemantic = GetAttribute( "semantic"); - const char* attrSemantic = mReader->getAttributeValue( indexSemantic); - int indexSource = GetAttribute( "source"); - const char* attrSource = mReader->getAttributeValue( indexSource); + int indexSemantic = GetAttribute("semantic"); + const char* attrSemantic = mReader->getAttributeValue(indexSemantic); + int indexSource = GetAttribute("source"); + const char* attrSource = mReader->getAttributeValue(indexSource); // local URLS always start with a '#'. We don't support global URLs - if( attrSource[0] != '#') - ThrowException( format() << "Unsupported URL format in \"" << attrSource << "\" in source attribute of data element" ); + if (attrSource[0] != '#') + ThrowException(format() << "Unsupported URL format in \"" << attrSource << "\" in source attribute of data element"); attrSource++; // parse source URL to corresponding source - if( strcmp( attrSemantic, "JOINT") == 0) + if (strcmp(attrSemantic, "JOINT") == 0) pController.mJointNameSource = attrSource; - else if( strcmp( attrSemantic, "INV_BIND_MATRIX") == 0) + else if (strcmp(attrSemantic, "INV_BIND_MATRIX") == 0) pController.mJointOffsetMatrixSource = attrSource; else - ThrowException( format() << "Unknown semantic \"" << attrSemantic << "\" in data element" ); + ThrowException(format() << "Unknown semantic \"" << attrSemantic << "\" in data element"); // skip inner data, if present - if( !mReader->isEmptyElement()) + if (!mReader->isEmptyElement()) SkipElement(); } else @@ -879,10 +1010,10 @@ void ColladaParser::ReadControllerJoints( Collada::Controller& pController) SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "joints") != 0) - ThrowException( "Expected end of element."); + if (strcmp(mReader->getNodeName(), "joints") != 0) + ThrowException("Expected end of element."); break; } @@ -891,85 +1022,85 @@ void ColladaParser::ReadControllerJoints( Collada::Controller& pController) // ------------------------------------------------------------------------------------------------ // Reads the joint weights for the given controller -void ColladaParser::ReadControllerWeights( Collada::Controller& pController) +void ColladaParser::ReadControllerWeights(Collada::Controller& pController) { // read vertex count from attributes and resize the array accordingly - int indexCount = GetAttribute( "count"); - size_t vertexCount = (size_t) mReader->getAttributeValueAsInt( indexCount); - pController.mWeightCounts.resize( vertexCount); + int indexCount = GetAttribute("count"); + size_t vertexCount = (size_t)mReader->getAttributeValueAsInt(indexCount); + pController.mWeightCounts.resize(vertexCount); - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { // Input channels for weight data. Two possible semantics: "JOINT" and "WEIGHT" - if( IsElement( "input") && vertexCount > 0 ) + if (IsElement("input") && vertexCount > 0) { InputChannel channel; - int indexSemantic = GetAttribute( "semantic"); - const char* attrSemantic = mReader->getAttributeValue( indexSemantic); - int indexSource = GetAttribute( "source"); - const char* attrSource = mReader->getAttributeValue( indexSource); - int indexOffset = TestAttribute( "offset"); - if( indexOffset >= 0) - channel.mOffset = mReader->getAttributeValueAsInt( indexOffset); + int indexSemantic = GetAttribute("semantic"); + const char* attrSemantic = mReader->getAttributeValue(indexSemantic); + int indexSource = GetAttribute("source"); + const char* attrSource = mReader->getAttributeValue(indexSource); + int indexOffset = TestAttribute("offset"); + if (indexOffset >= 0) + channel.mOffset = mReader->getAttributeValueAsInt(indexOffset); // local URLS always start with a '#'. We don't support global URLs - if( attrSource[0] != '#') - ThrowException( format() << "Unsupported URL format in \"" << attrSource << "\" in source attribute of data element" ); + if (attrSource[0] != '#') + ThrowException(format() << "Unsupported URL format in \"" << attrSource << "\" in source attribute of data element"); channel.mAccessor = attrSource + 1; // parse source URL to corresponding source - if( strcmp( attrSemantic, "JOINT") == 0) + if (strcmp(attrSemantic, "JOINT") == 0) pController.mWeightInputJoints = channel; - else if( strcmp( attrSemantic, "WEIGHT") == 0) + else if (strcmp(attrSemantic, "WEIGHT") == 0) pController.mWeightInputWeights = channel; else - ThrowException( format() << "Unknown semantic \"" << attrSemantic << "\" in data element" ); + ThrowException(format() << "Unknown semantic \"" << attrSemantic << "\" in data element"); // skip inner data, if present - if( !mReader->isEmptyElement()) + if (!mReader->isEmptyElement()) SkipElement(); } - else if( IsElement( "vcount") && vertexCount > 0 ) + else if (IsElement("vcount") && vertexCount > 0) { // read weight count per vertex const char* text = GetTextContent(); size_t numWeights = 0; - for( std::vector::iterator it = pController.mWeightCounts.begin(); it != pController.mWeightCounts.end(); ++it) + for (std::vector::iterator it = pController.mWeightCounts.begin(); it != pController.mWeightCounts.end(); ++it) { - if( *text == 0) - ThrowException( "Out of data while reading "); + if (*text == 0) + ThrowException("Out of data while reading "); - *it = strtoul10( text, &text); + *it = strtoul10(text, &text); numWeights += *it; - SkipSpacesAndLineEnd( &text); + SkipSpacesAndLineEnd(&text); } - TestClosing( "vcount"); + TestClosing("vcount"); // reserve weight count - pController.mWeights.resize( numWeights); + pController.mWeights.resize(numWeights); } - else if( IsElement( "v") && vertexCount > 0 ) + else if (IsElement("v") && vertexCount > 0) { // read JointIndex - WeightIndex pairs const char* text = GetTextContent(); - for( std::vector< std::pair >::iterator it = pController.mWeights.begin(); it != pController.mWeights.end(); ++it) + for (std::vector< std::pair >::iterator it = pController.mWeights.begin(); it != pController.mWeights.end(); ++it) { - if( *text == 0) - ThrowException( "Out of data while reading "); - it->first = strtoul10( text, &text); - SkipSpacesAndLineEnd( &text); - if( *text == 0) - ThrowException( "Out of data while reading "); - it->second = strtoul10( text, &text); - SkipSpacesAndLineEnd( &text); + if (*text == 0) + ThrowException("Out of data while reading "); + it->first = strtoul10(text, &text); + SkipSpacesAndLineEnd(&text); + if (*text == 0) + ThrowException("Out of data while reading "); + it->second = strtoul10(text, &text); + SkipSpacesAndLineEnd(&text); } - TestClosing( "v"); + TestClosing("v"); } else { @@ -977,10 +1108,10 @@ void ColladaParser::ReadControllerWeights( Collada::Controller& pController) SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "vertex_weights") != 0) - ThrowException( "Expected end of element."); + if (strcmp(mReader->getNodeName(), "vertex_weights") != 0) + ThrowException("Expected end of element."); break; } @@ -991,32 +1122,33 @@ void ColladaParser::ReadControllerWeights( Collada::Controller& pController) // Reads the image library contents void ColladaParser::ReadImageLibrary() { - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) return; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "image")) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (IsElement("image")) { // read ID. Another entry which is "optional" by design but obligatory in reality - int attrID = GetAttribute( "id"); - std::string id = mReader->getAttributeValue( attrID); + int attrID = GetAttribute("id"); + std::string id = mReader->getAttributeValue(attrID); // create an entry and store it in the library under its ID mImageLibrary[id] = Image(); // read on from there - ReadImage( mImageLibrary[id]); - } else + ReadImage(mImageLibrary[id]); + } + else { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "library_images") != 0) - ThrowException( "Expected end of element."); + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (strcmp(mReader->getNodeName(), "library_images") != 0) + ThrowException("Expected end of element."); break; } @@ -1025,16 +1157,16 @@ void ColladaParser::ReadImageLibrary() // ------------------------------------------------------------------------------------------------ // Reads an image entry into the given image -void ColladaParser::ReadImage( Collada::Image& pImage) +void ColladaParser::ReadImage(Collada::Image& pImage) { - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT){ + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { // Need to run different code paths here, depending on the Collada XSD version if (IsElement("image")) { SkipElement(); } - else if( IsElement( "init_from")) + else if (IsElement("init_from")) { if (mFormat == FV_1_4_n) { @@ -1042,8 +1174,13 @@ void ColladaParser::ReadImage( Collada::Image& pImage) if (!mReader->isEmptyElement()) { // element content is filename - hopefully const char* sz = TestTextContent(); - if (sz)pImage.mFileName = sz; - TestClosing( "init_from"); + if (sz) + { + aiString filepath(sz); + UriDecodePath(filepath); + pImage.mFileName = filepath.C_Str(); + } + TestClosing("init_from"); } if (!pImage.mFileName.length()) { pImage.mFileName = "unknown_texture"; @@ -1071,14 +1208,19 @@ void ColladaParser::ReadImage( Collada::Image& pImage) } else if (mFormat == FV_1_5_n) { - if( IsElement( "ref")) + if (IsElement("ref")) { // element content is filename - hopefully const char* sz = TestTextContent(); - if (sz)pImage.mFileName = sz; - TestClosing( "ref"); + if (sz) + { + aiString filepath(sz); + UriDecodePath(filepath); + pImage.mFileName = filepath.C_Str(); + } + TestClosing("ref"); } - else if( IsElement( "hex") && !pImage.mFileName.length()) + else if (IsElement("hex") && !pImage.mFileName.length()) { // embedded image. get format const int attrib = TestAttribute("format"); @@ -1093,12 +1235,12 @@ void ColladaParser::ReadImage( Collada::Image& pImage) const char* cur = data; while (!IsSpaceOrNewLine(*cur)) cur++; - const unsigned int size = (unsigned int)(cur-data) * 2; + const unsigned int size = (unsigned int)(cur - data) * 2; pImage.mImageData.resize(size); - for (unsigned int i = 0; i < size;++i) - pImage.mImageData[i] = HexOctetToDecimal(data+(i<<1)); + for (unsigned int i = 0; i < size; ++i) + pImage.mImageData[i] = HexOctetToDecimal(data + (i << 1)); - TestClosing( "hex"); + TestClosing("hex"); } } else @@ -1107,8 +1249,8 @@ void ColladaParser::ReadImage( Collada::Image& pImage) SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "image") == 0) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (strcmp(mReader->getNodeName(), "image") == 0) break; } } @@ -1118,36 +1260,36 @@ void ColladaParser::ReadImage( Collada::Image& pImage) // Reads the material library void ColladaParser::ReadMaterialLibrary() { - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) return; std::map names; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "material")) + if (IsElement("material")) { // read ID. By now you probably know my opinion about this "specification" - int attrID = GetAttribute( "id"); - std::string id = mReader->getAttributeValue( attrID); + int attrID = GetAttribute("id"); + std::string id = mReader->getAttributeValue(attrID); std::string name; int attrName = TestAttribute("name"); if (attrName >= 0) - name = mReader->getAttributeValue( attrName); + name = mReader->getAttributeValue(attrName); // create an entry and store it in the library under its ID mMaterialLibrary[id] = Material(); - if( !name.empty()) + if (!name.empty()) { - std::map::iterator it = names.find( name); - if( it != names.end()) + std::map::iterator it = names.find(name); + if (it != names.end()) { std::ostringstream strStream; strStream << ++it->second; - name.append( " " + strStream.str()); + name.append(" " + strStream.str()); } else { @@ -1157,17 +1299,18 @@ void ColladaParser::ReadMaterialLibrary() mMaterialLibrary[id].mName = name; } - ReadMaterial( mMaterialLibrary[id]); - } else + ReadMaterial(mMaterialLibrary[id]); + } + else { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "library_materials") != 0) - ThrowException( "Expected end of element."); + if (strcmp(mReader->getNodeName(), "library_materials") != 0) + ThrowException("Expected end of element."); break; } @@ -1178,30 +1321,31 @@ void ColladaParser::ReadMaterialLibrary() // Reads the light library void ColladaParser::ReadLightLibrary() { - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) return; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "light")) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (IsElement("light")) { // read ID. By now you probably know my opinion about this "specification" - int attrID = GetAttribute( "id"); - std::string id = mReader->getAttributeValue( attrID); + int attrID = GetAttribute("id"); + std::string id = mReader->getAttributeValue(attrID); // create an entry and store it in the library under its ID ReadLight(mLightLibrary[id] = Light()); - } else + } + else { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "library_lights") != 0) - ThrowException( "Expected end of element."); + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (strcmp(mReader->getNodeName(), "library_lights") != 0) + ThrowException("Expected end of element."); break; } @@ -1212,35 +1356,36 @@ void ColladaParser::ReadLightLibrary() // Reads the camera library void ColladaParser::ReadCameraLibrary() { - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) return; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "camera")) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (IsElement("camera")) { // read ID. By now you probably know my opinion about this "specification" - int attrID = GetAttribute( "id"); - std::string id = mReader->getAttributeValue( attrID); + int attrID = GetAttribute("id"); + std::string id = mReader->getAttributeValue(attrID); // create an entry and store it in the library under its ID Camera& cam = mCameraLibrary[id]; - attrID = TestAttribute( "name"); + attrID = TestAttribute("name"); if (attrID != -1) - cam.mName = mReader->getAttributeValue( attrID); + cam.mName = mReader->getAttributeValue(attrID); ReadCamera(cam); - } else + } + else { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "library_cameras") != 0) - ThrowException( "Expected end of element."); + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (strcmp(mReader->getNodeName(), "library_cameras") != 0) + ThrowException("Expected end of element."); break; } @@ -1249,34 +1394,35 @@ void ColladaParser::ReadCameraLibrary() // ------------------------------------------------------------------------------------------------ // Reads a material entry into the given material -void ColladaParser::ReadMaterial( Collada::Material& pMaterial) +void ColladaParser::ReadMaterial(Collada::Material& pMaterial) { - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { if (IsElement("material")) { SkipElement(); } - else if( IsElement( "instance_effect")) + else if (IsElement("instance_effect")) { // referred effect by URL - int attrUrl = GetAttribute( "url"); - const char* url = mReader->getAttributeValue( attrUrl); - if( url[0] != '#') - ThrowException( "Unknown reference format"); + int attrUrl = GetAttribute("url"); + const char* url = mReader->getAttributeValue(attrUrl); + if (url[0] != '#') + ThrowException("Unknown reference format"); - pMaterial.mEffect = url+1; + pMaterial.mEffect = url + 1; SkipElement(); - } else + } + else { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "material") != 0) - ThrowException( "Expected end of element."); + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (strcmp(mReader->getNodeName(), "material") != 0) + ThrowException("Expected end of element."); break; } @@ -1285,11 +1431,11 @@ void ColladaParser::ReadMaterial( Collada::Material& pMaterial) // ------------------------------------------------------------------------------------------------ // Reads a light entry into the given light -void ColladaParser::ReadLight( Collada::Light& pLight) +void ColladaParser::ReadLight(Collada::Light& pLight) { - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { if (IsElement("light")) { SkipElement(); } @@ -1309,16 +1455,16 @@ void ColladaParser::ReadLight( Collada::Light& pLight) // text content contains 3 floats const char* content = GetTextContent(); - content = fast_atoreal_move( content, (ai_real&)pLight.mColor.r); - SkipSpacesAndLineEnd( &content); + content = fast_atoreal_move(content, (ai_real&)pLight.mColor.r); + SkipSpacesAndLineEnd(&content); - content = fast_atoreal_move( content, (ai_real&)pLight.mColor.g); - SkipSpacesAndLineEnd( &content); + content = fast_atoreal_move(content, (ai_real&)pLight.mColor.g); + SkipSpacesAndLineEnd(&content); - content = fast_atoreal_move( content, (ai_real&)pLight.mColor.b); - SkipSpacesAndLineEnd( &content); + content = fast_atoreal_move(content, (ai_real&)pLight.mColor.b); + SkipSpacesAndLineEnd(&content); - TestClosing( "color"); + TestClosing("color"); } else if (IsElement("constant_attenuation")) { pLight.mAttConstant = ReadFloatFromTextContent(); @@ -1370,8 +1516,8 @@ void ColladaParser::ReadLight( Collada::Light& pLight) TestClosing("decay_falloff"); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "light") == 0) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (strcmp(mReader->getNodeName(), "light") == 0) break; } } @@ -1379,11 +1525,11 @@ void ColladaParser::ReadLight( Collada::Light& pLight) // ------------------------------------------------------------------------------------------------ // Reads a camera entry into the given light -void ColladaParser::ReadCamera( Collada::Camera& pCamera) +void ColladaParser::ReadCamera(Collada::Camera& pCamera) { - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { if (IsElement("camera")) { SkipElement(); } @@ -1411,8 +1557,8 @@ void ColladaParser::ReadCamera( Collada::Camera& pCamera) TestClosing("zfar"); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "camera") == 0) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (strcmp(mReader->getNodeName(), "camera") == 0) break; } } @@ -1426,28 +1572,29 @@ void ColladaParser::ReadEffectLibrary() return; } - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "effect")) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (IsElement("effect")) { // read ID. Do I have to repeat my ranting about "optional" attributes? - int attrID = GetAttribute( "id"); - std::string id = mReader->getAttributeValue( attrID); + int attrID = GetAttribute("id"); + std::string id = mReader->getAttributeValue(attrID); // create an entry and store it in the library under its ID mEffectLibrary[id] = Effect(); // read on from there - ReadEffect( mEffectLibrary[id]); - } else + ReadEffect(mEffectLibrary[id]); + } + else { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "library_effects") != 0) - ThrowException( "Expected end of element."); + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (strcmp(mReader->getNodeName(), "library_effects") != 0) + ThrowException("Expected end of element."); break; } @@ -1456,22 +1603,22 @@ void ColladaParser::ReadEffectLibrary() // ------------------------------------------------------------------------------------------------ // Reads an effect entry into the given effect -void ColladaParser::ReadEffect( Collada::Effect& pEffect) +void ColladaParser::ReadEffect(Collada::Effect& pEffect) { // for the moment we don't support any other type of effect. - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "profile_COMMON")) - ReadEffectProfileCommon( pEffect); + if (IsElement("profile_COMMON")) + ReadEffectProfileCommon(pEffect); else SkipElement(); } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "effect") != 0) - ThrowException( "Expected end of element."); + if (strcmp(mReader->getNodeName(), "effect") != 0) + ThrowException("Expected end of element."); break; } @@ -1480,107 +1627,107 @@ void ColladaParser::ReadEffect( Collada::Effect& pEffect) // ------------------------------------------------------------------------------------------------ // Reads an COMMON effect profile -void ColladaParser::ReadEffectProfileCommon( Collada::Effect& pEffect) +void ColladaParser::ReadEffectProfileCommon(Collada::Effect& pEffect) { - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "newparam")) { + if (IsElement("newparam")) { // save ID - int attrSID = GetAttribute( "sid"); - std::string sid = mReader->getAttributeValue( attrSID); + int attrSID = GetAttribute("sid"); + std::string sid = mReader->getAttributeValue(attrSID); pEffect.mParams[sid] = EffectParam(); - ReadEffectParam( pEffect.mParams[sid]); + ReadEffectParam(pEffect.mParams[sid]); } - else if( IsElement( "technique") || IsElement( "extra")) + else if (IsElement("technique") || IsElement("extra")) { // just syntactic sugar } - else if( mFormat == FV_1_4_n && IsElement( "image")) + else if (mFormat == FV_1_4_n && IsElement("image")) { // read ID. Another entry which is "optional" by design but obligatory in reality - int attrID = GetAttribute( "id"); - std::string id = mReader->getAttributeValue( attrID); + int attrID = GetAttribute("id"); + std::string id = mReader->getAttributeValue(attrID); // create an entry and store it in the library under its ID mImageLibrary[id] = Image(); // read on from there - ReadImage( mImageLibrary[id]); + ReadImage(mImageLibrary[id]); } /* Shading modes */ - else if( IsElement( "phong")) + else if (IsElement("phong")) pEffect.mShadeType = Shade_Phong; - else if( IsElement( "constant")) + else if (IsElement("constant")) pEffect.mShadeType = Shade_Constant; - else if( IsElement( "lambert")) + else if (IsElement("lambert")) pEffect.mShadeType = Shade_Lambert; - else if( IsElement( "blinn")) + else if (IsElement("blinn")) pEffect.mShadeType = Shade_Blinn; /* Color + texture properties */ - else if( IsElement( "emission")) - ReadEffectColor( pEffect.mEmissive, pEffect.mTexEmissive); - else if( IsElement( "ambient")) - ReadEffectColor( pEffect.mAmbient, pEffect.mTexAmbient); - else if( IsElement( "diffuse")) - ReadEffectColor( pEffect.mDiffuse, pEffect.mTexDiffuse); - else if( IsElement( "specular")) - ReadEffectColor( pEffect.mSpecular, pEffect.mTexSpecular); - else if( IsElement( "reflective")) { - ReadEffectColor( pEffect.mReflective, pEffect.mTexReflective); + else if (IsElement("emission")) + ReadEffectColor(pEffect.mEmissive, pEffect.mTexEmissive); + else if (IsElement("ambient")) + ReadEffectColor(pEffect.mAmbient, pEffect.mTexAmbient); + else if (IsElement("diffuse")) + ReadEffectColor(pEffect.mDiffuse, pEffect.mTexDiffuse); + else if (IsElement("specular")) + ReadEffectColor(pEffect.mSpecular, pEffect.mTexSpecular); + else if (IsElement("reflective")) { + ReadEffectColor(pEffect.mReflective, pEffect.mTexReflective); } - else if( IsElement( "transparent")) { + else if (IsElement("transparent")) { pEffect.mHasTransparency = true; const char* opaque = mReader->getAttributeValueSafe("opaque"); - if(::strcmp(opaque, "RGB_ZERO") == 0 || ::strcmp(opaque, "RGB_ONE") == 0) { + if (::strcmp(opaque, "RGB_ZERO") == 0 || ::strcmp(opaque, "RGB_ONE") == 0) { pEffect.mRGBTransparency = true; } // In RGB_ZERO mode, the transparency is interpreted in reverse, go figure... - if(::strcmp(opaque, "RGB_ZERO") == 0 || ::strcmp(opaque, "A_ZERO") == 0) { - pEffect.mInvertTransparency = true; - } + if (::strcmp(opaque, "RGB_ZERO") == 0 || ::strcmp(opaque, "A_ZERO") == 0) { + pEffect.mInvertTransparency = true; + } - ReadEffectColor( pEffect.mTransparent,pEffect.mTexTransparent); + ReadEffectColor(pEffect.mTransparent, pEffect.mTexTransparent); } - else if( IsElement( "shininess")) - ReadEffectFloat( pEffect.mShininess); - else if( IsElement( "reflectivity")) - ReadEffectFloat( pEffect.mReflectivity); + else if (IsElement("shininess")) + ReadEffectFloat(pEffect.mShininess); + else if (IsElement("reflectivity")) + ReadEffectFloat(pEffect.mReflectivity); /* Single scalar properties */ - else if( IsElement( "transparency")) - ReadEffectFloat( pEffect.mTransparency); - else if( IsElement( "index_of_refraction")) - ReadEffectFloat( pEffect.mRefractIndex); + else if (IsElement("transparency")) + ReadEffectFloat(pEffect.mTransparency); + else if (IsElement("index_of_refraction")) + ReadEffectFloat(pEffect.mRefractIndex); // GOOGLEEARTH/OKINO extensions // ------------------------------------------------------- - else if( IsElement( "double_sided")) + else if (IsElement("double_sided")) pEffect.mDoubleSided = ReadBoolFromTextContent(); // FCOLLADA extensions // ------------------------------------------------------- - else if( IsElement( "bump")) { + else if (IsElement("bump")) { aiColor4D dummy; - ReadEffectColor( dummy,pEffect.mTexBump); + ReadEffectColor(dummy, pEffect.mTexBump); } // MAX3D extensions // ------------------------------------------------------- - else if( IsElement( "wireframe")) { + else if (IsElement("wireframe")) { pEffect.mWireframe = ReadBoolFromTextContent(); - TestClosing( "wireframe"); + TestClosing("wireframe"); } - else if( IsElement( "faceted")) { + else if (IsElement("faceted")) { pEffect.mFaceted = ReadBoolFromTextContent(); - TestClosing( "faceted"); + TestClosing("faceted"); } else { @@ -1588,8 +1735,8 @@ void ColladaParser::ReadEffectProfileCommon( Collada::Effect& pEffect) SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "profile_COMMON") == 0) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (strcmp(mReader->getNodeName(), "profile_COMMON") == 0) { break; } @@ -1599,92 +1746,92 @@ void ColladaParser::ReadEffectProfileCommon( Collada::Effect& pEffect) // ------------------------------------------------------------------------------------------------ // Read texture wrapping + UV transform settings from a profile==Maya chunk -void ColladaParser::ReadSamplerProperties( Sampler& out ) +void ColladaParser::ReadSamplerProperties(Sampler& out) { if (mReader->isEmptyElement()) { return; } - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { // MAYA extensions // ------------------------------------------------------- - if( IsElement( "wrapU")) { + if (IsElement("wrapU")) { out.mWrapU = ReadBoolFromTextContent(); - TestClosing( "wrapU"); + TestClosing("wrapU"); } - else if( IsElement( "wrapV")) { + else if (IsElement("wrapV")) { out.mWrapV = ReadBoolFromTextContent(); - TestClosing( "wrapV"); + TestClosing("wrapV"); } - else if( IsElement( "mirrorU")) { + else if (IsElement("mirrorU")) { out.mMirrorU = ReadBoolFromTextContent(); - TestClosing( "mirrorU"); + TestClosing("mirrorU"); } - else if( IsElement( "mirrorV")) { + else if (IsElement("mirrorV")) { out.mMirrorV = ReadBoolFromTextContent(); - TestClosing( "mirrorV"); + TestClosing("mirrorV"); } - else if( IsElement( "repeatU")) { + else if (IsElement("repeatU")) { out.mTransform.mScaling.x = ReadFloatFromTextContent(); - TestClosing( "repeatU"); + TestClosing("repeatU"); } - else if( IsElement( "repeatV")) { + else if (IsElement("repeatV")) { out.mTransform.mScaling.y = ReadFloatFromTextContent(); - TestClosing( "repeatV"); + TestClosing("repeatV"); } - else if( IsElement( "offsetU")) { + else if (IsElement("offsetU")) { out.mTransform.mTranslation.x = ReadFloatFromTextContent(); - TestClosing( "offsetU"); + TestClosing("offsetU"); } - else if( IsElement( "offsetV")) { + else if (IsElement("offsetV")) { out.mTransform.mTranslation.y = ReadFloatFromTextContent(); - TestClosing( "offsetV"); + TestClosing("offsetV"); } - else if( IsElement( "rotateUV")) { + else if (IsElement("rotateUV")) { out.mTransform.mRotation = ReadFloatFromTextContent(); - TestClosing( "rotateUV"); + TestClosing("rotateUV"); } - else if( IsElement( "blend_mode")) { + else if (IsElement("blend_mode")) { const char* sz = GetTextContent(); // http://www.feelingsoftware.com/content/view/55/72/lang,en/ // NONE, OVER, IN, OUT, ADD, SUBTRACT, MULTIPLY, DIFFERENCE, LIGHTEN, DARKEN, SATURATE, DESATURATE and ILLUMINATE - if (0 == ASSIMP_strincmp(sz,"ADD",3)) + if (0 == ASSIMP_strincmp(sz, "ADD", 3)) out.mOp = aiTextureOp_Add; - else if (0 == ASSIMP_strincmp(sz,"SUBTRACT",8)) + else if (0 == ASSIMP_strincmp(sz, "SUBTRACT", 8)) out.mOp = aiTextureOp_Subtract; - else if (0 == ASSIMP_strincmp(sz,"MULTIPLY",8)) + else if (0 == ASSIMP_strincmp(sz, "MULTIPLY", 8)) out.mOp = aiTextureOp_Multiply; - else { + else { ASSIMP_LOG_WARN("Collada: Unsupported MAYA texture blend mode"); } - TestClosing( "blend_mode"); + TestClosing("blend_mode"); } // OKINO extensions // ------------------------------------------------------- - else if( IsElement( "weighting")) { + else if (IsElement("weighting")) { out.mWeighting = ReadFloatFromTextContent(); - TestClosing( "weighting"); + TestClosing("weighting"); } - else if( IsElement( "mix_with_previous_layer")) { + else if (IsElement("mix_with_previous_layer")) { out.mMixWithPrevious = ReadFloatFromTextContent(); - TestClosing( "mix_with_previous_layer"); + TestClosing("mix_with_previous_layer"); } // MAX3D extensions // ------------------------------------------------------- - else if( IsElement( "amount")) { + else if (IsElement("amount")) { out.mWeighting = ReadFloatFromTextContent(); - TestClosing( "amount"); + TestClosing("amount"); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "technique") == 0) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (strcmp(mReader->getNodeName(), "technique") == 0) break; } } @@ -1692,7 +1839,7 @@ void ColladaParser::ReadSamplerProperties( Sampler& out ) // ------------------------------------------------------------------------------------------------ // Reads an effect entry containing a color or a texture defining that color -void ColladaParser::ReadEffectColor( aiColor4D& pColor, Sampler& pSampler) +void ColladaParser::ReadEffectColor(aiColor4D& pColor, Sampler& pSampler) { if (mReader->isEmptyElement()) return; @@ -1700,64 +1847,64 @@ void ColladaParser::ReadEffectColor( aiColor4D& pColor, Sampler& pSampler) // Save current element name const std::string curElem = mReader->getNodeName(); - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "color")) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (IsElement("color")) { // text content contains 4 floats const char* content = GetTextContent(); - content = fast_atoreal_move( content, (ai_real&)pColor.r); - SkipSpacesAndLineEnd( &content); + content = fast_atoreal_move(content, (ai_real&)pColor.r); + SkipSpacesAndLineEnd(&content); - content = fast_atoreal_move( content, (ai_real&)pColor.g); - SkipSpacesAndLineEnd( &content); + content = fast_atoreal_move(content, (ai_real&)pColor.g); + SkipSpacesAndLineEnd(&content); - content = fast_atoreal_move( content, (ai_real&)pColor.b); - SkipSpacesAndLineEnd( &content); + content = fast_atoreal_move(content, (ai_real&)pColor.b); + SkipSpacesAndLineEnd(&content); - content = fast_atoreal_move( content, (ai_real&)pColor.a); - SkipSpacesAndLineEnd( &content); - TestClosing( "color"); + content = fast_atoreal_move(content, (ai_real&)pColor.a); + SkipSpacesAndLineEnd(&content); + TestClosing("color"); } - else if( IsElement( "texture")) + else if (IsElement("texture")) { // get name of source texture/sampler - int attrTex = GetAttribute( "texture"); - pSampler.mName = mReader->getAttributeValue( attrTex); + int attrTex = GetAttribute("texture"); + pSampler.mName = mReader->getAttributeValue(attrTex); // get name of UV source channel. Specification demands it to be there, but some exporters // don't write it. It will be the default UV channel in case it's missing. - attrTex = TestAttribute( "texcoord"); - if( attrTex >= 0 ) - pSampler.mUVChannel = mReader->getAttributeValue( attrTex); + attrTex = TestAttribute("texcoord"); + if (attrTex >= 0) + pSampler.mUVChannel = mReader->getAttributeValue(attrTex); //SkipElement(); // as we've read texture, the color needs to be 1,1,1,1 pColor = aiColor4D(1.f, 1.f, 1.f, 1.f); } - else if( IsElement( "technique")) + else if (IsElement("technique")) { - const int _profile = GetAttribute( "profile"); - const char* profile = mReader->getAttributeValue( _profile ); + const int _profile = GetAttribute("profile"); + const char* profile = mReader->getAttributeValue(_profile); // Some extensions are quite useful ... ReadSamplerProperties processes // several extensions in MAYA, OKINO and MAX3D profiles. - if (!::strcmp(profile,"MAYA") || !::strcmp(profile,"MAX3D") || !::strcmp(profile,"OKINO")) + if (!::strcmp(profile, "MAYA") || !::strcmp(profile, "MAX3D") || !::strcmp(profile, "OKINO")) { // get more information on this sampler ReadSamplerProperties(pSampler); } else SkipElement(); } - else if( !IsElement( "extra")) + else if (!IsElement("extra")) { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END){ + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { if (mReader->getNodeName() == curElem) break; } @@ -1766,26 +1913,27 @@ void ColladaParser::ReadEffectColor( aiColor4D& pColor, Sampler& pSampler) // ------------------------------------------------------------------------------------------------ // Reads an effect entry containing a float -void ColladaParser::ReadEffectFloat( ai_real& pFloat) +void ColladaParser::ReadEffectFloat(ai_real& pFloat) { - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT){ - if( IsElement( "float")) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (IsElement("float")) { // text content contains a single floats const char* content = GetTextContent(); - content = fast_atoreal_move( content, pFloat); - SkipSpacesAndLineEnd( &content); + content = fast_atoreal_move(content, pFloat); + SkipSpacesAndLineEnd(&content); - TestClosing( "float"); - } else + TestClosing("float"); + } + else { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END){ + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { break; } } @@ -1793,54 +1941,55 @@ void ColladaParser::ReadEffectFloat( ai_real& pFloat) // ------------------------------------------------------------------------------------------------ // Reads an effect parameter specification of any kind -void ColladaParser::ReadEffectParam( Collada::EffectParam& pParam) +void ColladaParser::ReadEffectParam(Collada::EffectParam& pParam) { - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "surface")) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (IsElement("surface")) { // image ID given inside tags - TestOpening( "init_from"); + TestOpening("init_from"); const char* content = GetTextContent(); pParam.mType = Param_Surface; pParam.mReference = content; - TestClosing( "init_from"); + TestClosing("init_from"); // don't care for remaining stuff - SkipElement( "surface"); + SkipElement("surface"); } - else if( IsElement( "sampler2D") && (FV_1_4_n == mFormat || FV_1_3_n == mFormat)) + else if (IsElement("sampler2D") && (FV_1_4_n == mFormat || FV_1_3_n == mFormat)) { // surface ID is given inside tags - TestOpening( "source"); + TestOpening("source"); const char* content = GetTextContent(); pParam.mType = Param_Sampler; pParam.mReference = content; - TestClosing( "source"); + TestClosing("source"); // don't care for remaining stuff - SkipElement( "sampler2D"); + SkipElement("sampler2D"); } - else if( IsElement( "sampler2D")) + else if (IsElement("sampler2D")) { // surface ID is given inside tags - TestOpening( "instance_image"); + TestOpening("instance_image"); int attrURL = GetAttribute("url"); - const char* url = mReader->getAttributeValue( attrURL); - if( url[0] != '#') - ThrowException( "Unsupported URL format in instance_image"); + const char* url = mReader->getAttributeValue(attrURL); + if (url[0] != '#') + ThrowException("Unsupported URL format in instance_image"); url++; pParam.mType = Param_Sampler; pParam.mReference = url; - SkipElement( "sampler2D"); - } else + SkipElement("sampler2D"); + } + else { // ignore unknown element SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { break; } } @@ -1850,18 +1999,18 @@ void ColladaParser::ReadEffectParam( Collada::EffectParam& pParam) // Reads the geometry library contents void ColladaParser::ReadGeometryLibrary() { - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) return; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "geometry")) + if (IsElement("geometry")) { // read ID. Another entry which is "optional" by design but obligatory in reality - int indexID = GetAttribute( "id"); - std::string id = mReader->getAttributeValue( indexID); + int indexID = GetAttribute("id"); + std::string id = mReader->getAttributeValue(indexID); // TODO: (thom) support SIDs // ai_assert( TestAttribute( "sid") == -1); @@ -1872,23 +2021,24 @@ void ColladaParser::ReadGeometryLibrary() // read the mesh name if it exists const int nameIndex = TestAttribute("name"); - if(nameIndex != -1) + if (nameIndex != -1) { mesh->mName = mReader->getAttributeValue(nameIndex); } // read on from there - ReadGeometry( mesh); - } else + ReadGeometry(mesh); + } + else { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "library_geometries") != 0) - ThrowException( "Expected end of element."); + if (strcmp(mReader->getNodeName(), "library_geometries") != 0) + ThrowException("Expected end of element."); break; } @@ -1897,29 +2047,30 @@ void ColladaParser::ReadGeometryLibrary() // ------------------------------------------------------------------------------------------------ // Reads a geometry from the geometry library. -void ColladaParser::ReadGeometry( Collada::Mesh* pMesh) +void ColladaParser::ReadGeometry(Collada::Mesh* pMesh) { - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) return; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "mesh")) + if (IsElement("mesh")) { // read on from there - ReadMesh( pMesh); - } else + ReadMesh(pMesh); + } + else { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "geometry") != 0) - ThrowException( "Expected end of element."); + if (strcmp(mReader->getNodeName(), "geometry") != 0) + ThrowException("Expected end of element."); break; } @@ -1928,50 +2079,52 @@ void ColladaParser::ReadGeometry( Collada::Mesh* pMesh) // ------------------------------------------------------------------------------------------------ // Reads a mesh from the geometry library -void ColladaParser::ReadMesh( Mesh* pMesh) +void ColladaParser::ReadMesh(Mesh* pMesh) { - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) return; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "source")) + if (IsElement("source")) { // we have professionals dealing with this ReadSource(); } - else if( IsElement( "vertices")) + else if (IsElement("vertices")) { // read per-vertex mesh data - ReadVertexData( pMesh); + ReadVertexData(pMesh); } - else if( IsElement( "triangles") || IsElement( "lines") || IsElement( "linestrips") - || IsElement( "polygons") || IsElement( "polylist") || IsElement( "trifans") || IsElement( "tristrips")) + else if (IsElement("triangles") || IsElement("lines") || IsElement("linestrips") + || IsElement("polygons") || IsElement("polylist") || IsElement("trifans") || IsElement("tristrips")) { // read per-index mesh data and faces setup - ReadIndexData( pMesh); - } else + ReadIndexData(pMesh); + } + else { // ignore the restf SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "technique_common") == 0) + if (strcmp(mReader->getNodeName(), "technique_common") == 0) { // end of another meaningless element - read over it } - else if( strcmp( mReader->getNodeName(), "mesh") == 0) + else if (strcmp(mReader->getNodeName(), "mesh") == 0) { // end of element - we're done here break; - } else + } + else { // everything else should be punished - ThrowException( "Expected end of element."); + ThrowException("Expected end of element."); } } } @@ -1981,44 +2134,46 @@ void ColladaParser::ReadMesh( Mesh* pMesh) // Reads a source element void ColladaParser::ReadSource() { - int indexID = GetAttribute( "id"); - std::string sourceID = mReader->getAttributeValue( indexID); + int indexID = GetAttribute("id"); + std::string sourceID = mReader->getAttributeValue(indexID); - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "float_array") || IsElement( "IDREF_array") || IsElement( "Name_array")) + if (IsElement("float_array") || IsElement("IDREF_array") || IsElement("Name_array")) { ReadDataArray(); } - else if( IsElement( "technique_common")) + else if (IsElement("technique_common")) { // I don't care for your profiles } - else if( IsElement( "accessor")) + else if (IsElement("accessor")) { - ReadAccessor( sourceID); - } else + ReadAccessor(sourceID); + } + else { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "source") == 0) + if (strcmp(mReader->getNodeName(), "source") == 0) { // end of - we're done break; } - else if( strcmp( mReader->getNodeName(), "technique_common") == 0) + else if (strcmp(mReader->getNodeName(), "technique_common") == 0) { // end of another meaningless element - read over it - } else + } + else { // everything else should be punished - ThrowException( "Expected end of element."); + ThrowException("Expected end of element."); } } } @@ -2030,83 +2185,84 @@ void ColladaParser::ReadDataArray() { std::string elmName = mReader->getNodeName(); bool isStringArray = (elmName == "IDREF_array" || elmName == "Name_array"); - bool isEmptyElement = mReader->isEmptyElement(); + bool isEmptyElement = mReader->isEmptyElement(); // read attributes - int indexID = GetAttribute( "id"); - std::string id = mReader->getAttributeValue( indexID); - int indexCount = GetAttribute( "count"); - unsigned int count = (unsigned int) mReader->getAttributeValueAsInt( indexCount); + int indexID = GetAttribute("id"); + std::string id = mReader->getAttributeValue(indexID); + int indexCount = GetAttribute("count"); + unsigned int count = (unsigned int)mReader->getAttributeValueAsInt(indexCount); const char* content = TestTextContent(); - // read values and store inside an array in the data library - mDataLibrary[id] = Data(); - Data& data = mDataLibrary[id]; - data.mIsStringArray = isStringArray; + // read values and store inside an array in the data library + mDataLibrary[id] = Data(); + Data& data = mDataLibrary[id]; + data.mIsStringArray = isStringArray; - // some exporters write empty data arrays, but we need to conserve them anyways because others might reference them - if (content) - { - if( isStringArray) + // some exporters write empty data arrays, but we need to conserve them anyways because others might reference them + if (content) + { + if (isStringArray) { - data.mStrings.reserve( count); + data.mStrings.reserve(count); std::string s; - for( unsigned int a = 0; a < count; a++) + for (unsigned int a = 0; a < count; a++) { - if( *content == 0) - ThrowException( "Expected more values while reading IDREF_array contents."); + if (*content == 0) + ThrowException("Expected more values while reading IDREF_array contents."); s.clear(); - while( !IsSpaceOrNewLine( *content)) + while (!IsSpaceOrNewLine(*content)) s += *content++; - data.mStrings.push_back( s); + data.mStrings.push_back(s); - SkipSpacesAndLineEnd( &content); + SkipSpacesAndLineEnd(&content); } - } else + } + else { - data.mValues.reserve( count); + data.mValues.reserve(count); - for( unsigned int a = 0; a < count; a++) + for (unsigned int a = 0; a < count; a++) { - if( *content == 0) - ThrowException( "Expected more values while reading float_array contents."); + if (*content == 0) + ThrowException("Expected more values while reading float_array contents."); ai_real value; // read a number - content = fast_atoreal_move( content, value); - data.mValues.push_back( value); + content = fast_atoreal_move(content, value); + data.mValues.push_back(value); // skip whitespace after it - SkipSpacesAndLineEnd( &content); + SkipSpacesAndLineEnd(&content); } } } - // test for closing tag - if( !isEmptyElement ) - TestClosing( elmName.c_str()); + // test for closing tag + if (!isEmptyElement) + TestClosing(elmName.c_str()); } // ------------------------------------------------------------------------------------------------ // Reads an accessor and stores it in the global library -void ColladaParser::ReadAccessor( const std::string& pID) +void ColladaParser::ReadAccessor(const std::string& pID) { // read accessor attributes - int attrSource = GetAttribute( "source"); - const char* source = mReader->getAttributeValue( attrSource); - if( source[0] != '#') - ThrowException( format() << "Unknown reference format in url \"" << source << "\" in source attribute of element." ); - int attrCount = GetAttribute( "count"); - unsigned int count = (unsigned int) mReader->getAttributeValueAsInt( attrCount); - int attrOffset = TestAttribute( "offset"); + int attrSource = GetAttribute("source"); + const char* source = mReader->getAttributeValue(attrSource); + if (source[0] != '#') + ThrowException(format() << "Unknown reference format in url \"" << source << "\" in source attribute of element."); + int attrCount = GetAttribute("count"); + unsigned int count = (unsigned int)mReader->getAttributeValueAsInt(attrCount); + int attrOffset = TestAttribute("offset"); unsigned int offset = 0; - if( attrOffset > -1) - offset = (unsigned int) mReader->getAttributeValueAsInt( attrOffset); - int attrStride = TestAttribute( "stride"); + if (attrOffset > -1) + offset = (unsigned int)mReader->getAttributeValueAsInt(attrOffset); + int attrStride = TestAttribute("stride"); unsigned int stride = 1; - if( attrStride > -1) - stride = (unsigned int) mReader->getAttributeValueAsInt( attrStride); + if (attrStride > -1) + stride = (unsigned int)mReader->getAttributeValueAsInt(attrStride); // store in the library under the given ID mAccessorLibrary[pID] = Accessor(); @@ -2114,77 +2270,78 @@ void ColladaParser::ReadAccessor( const std::string& pID) acc.mCount = count; acc.mOffset = offset; acc.mStride = stride; - acc.mSource = source+1; // ignore the leading '#' + acc.mSource = source + 1; // ignore the leading '#' acc.mSize = 0; // gets incremented with every param // and read the components - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "param")) + if (IsElement("param")) { // read data param - int attrName = TestAttribute( "name"); + int attrName = TestAttribute("name"); std::string name; - if( attrName > -1) + if (attrName > -1) { - name = mReader->getAttributeValue( attrName); + name = mReader->getAttributeValue(attrName); // analyse for common type components and store it's sub-offset in the corresponding field /* Cartesian coordinates */ - if( name == "X") acc.mSubOffset[0] = acc.mParams.size(); - else if( name == "Y") acc.mSubOffset[1] = acc.mParams.size(); - else if( name == "Z") acc.mSubOffset[2] = acc.mParams.size(); + if (name == "X") acc.mSubOffset[0] = acc.mParams.size(); + else if (name == "Y") acc.mSubOffset[1] = acc.mParams.size(); + else if (name == "Z") acc.mSubOffset[2] = acc.mParams.size(); /* RGBA colors */ - else if( name == "R") acc.mSubOffset[0] = acc.mParams.size(); - else if( name == "G") acc.mSubOffset[1] = acc.mParams.size(); - else if( name == "B") acc.mSubOffset[2] = acc.mParams.size(); - else if( name == "A") acc.mSubOffset[3] = acc.mParams.size(); + else if (name == "R") acc.mSubOffset[0] = acc.mParams.size(); + else if (name == "G") acc.mSubOffset[1] = acc.mParams.size(); + else if (name == "B") acc.mSubOffset[2] = acc.mParams.size(); + else if (name == "A") acc.mSubOffset[3] = acc.mParams.size(); /* UVWQ (STPQ) texture coordinates */ - else if( name == "S") acc.mSubOffset[0] = acc.mParams.size(); - else if( name == "T") acc.mSubOffset[1] = acc.mParams.size(); - else if( name == "P") acc.mSubOffset[2] = acc.mParams.size(); - // else if( name == "Q") acc.mSubOffset[3] = acc.mParams.size(); - /* 4D uv coordinates are not supported in Assimp */ + else if (name == "S") acc.mSubOffset[0] = acc.mParams.size(); + else if (name == "T") acc.mSubOffset[1] = acc.mParams.size(); + else if (name == "P") acc.mSubOffset[2] = acc.mParams.size(); + // else if( name == "Q") acc.mSubOffset[3] = acc.mParams.size(); + /* 4D uv coordinates are not supported in Assimp */ - /* Generic extra data, interpreted as UV data, too*/ - else if( name == "U") acc.mSubOffset[0] = acc.mParams.size(); - else if( name == "V") acc.mSubOffset[1] = acc.mParams.size(); + /* Generic extra data, interpreted as UV data, too*/ + else if (name == "U") acc.mSubOffset[0] = acc.mParams.size(); + else if (name == "V") acc.mSubOffset[1] = acc.mParams.size(); //else // DefaultLogger::get()->warn( format() << "Unknown accessor parameter \"" << name << "\". Ignoring data channel." ); } // read data type - int attrType = TestAttribute( "type"); - if( attrType > -1) + int attrType = TestAttribute("type"); + if (attrType > -1) { // for the moment we only distinguish between a 4x4 matrix and anything else. // TODO: (thom) I don't have a spec here at work. Check if there are other multi-value types // which should be tested for here. - std::string type = mReader->getAttributeValue( attrType); - if( type == "float4x4") + std::string type = mReader->getAttributeValue(attrType); + if (type == "float4x4") acc.mSize += 16; else acc.mSize += 1; } - acc.mParams.push_back( name); + acc.mParams.push_back(name); // skip remaining stuff of this element, if any SkipElement(); - } else + } + else { - ThrowException( format() << "Unexpected sub element <" << mReader->getNodeName() << "> in tag " ); + ThrowException(format() << "Unexpected sub element <" << mReader->getNodeName() << "> in tag "); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "accessor") != 0) - ThrowException( "Expected end of element."); + if (strcmp(mReader->getNodeName(), "accessor") != 0) + ThrowException("Expected end of element."); break; } } @@ -2192,29 +2349,30 @@ void ColladaParser::ReadAccessor( const std::string& pID) // ------------------------------------------------------------------------------------------------ // Reads input declarations of per-vertex mesh data into the given mesh -void ColladaParser::ReadVertexData( Mesh* pMesh) +void ColladaParser::ReadVertexData(Mesh* pMesh) { // extract the ID of the element. Not that we care, but to catch strange referencing schemes we should warn about - int attrID= GetAttribute( "id"); - pMesh->mVertexID = mReader->getAttributeValue( attrID); + int attrID = GetAttribute("id"); + pMesh->mVertexID = mReader->getAttributeValue(attrID); // a number of elements - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "input")) + if (IsElement("input")) { - ReadInputChannel( pMesh->mPerVertexData); - } else + ReadInputChannel(pMesh->mPerVertexData); + } + else { - ThrowException( format() << "Unexpected sub element <" << mReader->getNodeName() << "> in tag " ); + ThrowException(format() << "Unexpected sub element <" << mReader->getNodeName() << "> in tag "); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "vertices") != 0) - ThrowException( "Expected end of element."); + if (strcmp(mReader->getNodeName(), "vertices") != 0) + ThrowException("Expected end of element."); break; } @@ -2223,79 +2381,79 @@ void ColladaParser::ReadVertexData( Mesh* pMesh) // ------------------------------------------------------------------------------------------------ // Reads input declarations of per-index mesh data into the given mesh -void ColladaParser::ReadIndexData( Mesh* pMesh) +void ColladaParser::ReadIndexData(Mesh* pMesh) { std::vector vcount; std::vector perIndexData; // read primitive count from the attribute - int attrCount = GetAttribute( "count"); - size_t numPrimitives = (size_t) mReader->getAttributeValueAsInt( attrCount); + int attrCount = GetAttribute("count"); + size_t numPrimitives = (size_t)mReader->getAttributeValueAsInt(attrCount); // some mesh types (e.g. tristrips) don't specify primitive count upfront, // so we need to sum up the actual number of primitives while we read the

-tags size_t actualPrimitives = 0; // material subgroup - int attrMaterial = TestAttribute( "material"); + int attrMaterial = TestAttribute("material"); SubMesh subgroup; - if( attrMaterial > -1) - subgroup.mMaterial = mReader->getAttributeValue( attrMaterial); + if (attrMaterial > -1) + subgroup.mMaterial = mReader->getAttributeValue(attrMaterial); // distinguish between polys and triangles std::string elementName = mReader->getNodeName(); PrimitiveType primType = Prim_Invalid; - if( IsElement( "lines")) + if (IsElement("lines")) primType = Prim_Lines; - else if( IsElement( "linestrips")) + else if (IsElement("linestrips")) primType = Prim_LineStrip; - else if( IsElement( "polygons")) + else if (IsElement("polygons")) primType = Prim_Polygon; - else if( IsElement( "polylist")) + else if (IsElement("polylist")) primType = Prim_Polylist; - else if( IsElement( "triangles")) + else if (IsElement("triangles")) primType = Prim_Triangles; - else if( IsElement( "trifans")) + else if (IsElement("trifans")) primType = Prim_TriFans; - else if( IsElement( "tristrips")) + else if (IsElement("tristrips")) primType = Prim_TriStrips; - ai_assert( primType != Prim_Invalid); + ai_assert(primType != Prim_Invalid); // also a number of elements, but in addition a

primitive collection and probably index counts for all primitives - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "input")) + if (IsElement("input")) { - ReadInputChannel( perIndexData); + ReadInputChannel(perIndexData); } - else if( IsElement( "vcount")) + else if (IsElement("vcount")) { - if( !mReader->isEmptyElement()) + if (!mReader->isEmptyElement()) { if (numPrimitives) // It is possible to define a mesh without any primitives { // case - specifies the number of indices for each polygon const char* content = GetTextContent(); - vcount.reserve( numPrimitives); - for( unsigned int a = 0; a < numPrimitives; a++) + vcount.reserve(numPrimitives); + for (unsigned int a = 0; a < numPrimitives; a++) { - if( *content == 0) - ThrowException( "Expected more values while reading contents."); + if (*content == 0) + ThrowException("Expected more values while reading contents."); // read a number - vcount.push_back( (size_t) strtoul10( content, &content)); + vcount.push_back((size_t)strtoul10(content, &content)); // skip whitespace after it - SkipSpacesAndLineEnd( &content); + SkipSpacesAndLineEnd(&content); } } - TestClosing( "vcount"); + TestClosing("vcount"); } } - else if( IsElement( "p")) + else if (IsElement("p")) { - if( !mReader->isEmptyElement()) + if (!mReader->isEmptyElement()) { // now here the actual fun starts - these are the indices to construct the mesh data from actualPrimitives += ReadPrimitives(pMesh, perIndexData, numPrimitives, vcount, primType); @@ -2304,23 +2462,25 @@ void ColladaParser::ReadIndexData( Mesh* pMesh) else if (IsElement("extra")) { SkipElement("extra"); - } else if ( IsElement("ph")) { + } + else if (IsElement("ph")) { SkipElement("ph"); - } else { - ThrowException( format() << "Unexpected sub element <" << mReader->getNodeName() << "> in tag <" << elementName << ">" ); + } + else { + ThrowException(format() << "Unexpected sub element <" << mReader->getNodeName() << "> in tag <" << elementName << ">"); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( mReader->getNodeName() != elementName) - ThrowException( format() << "Expected end of <" << elementName << "> element." ); + if (mReader->getNodeName() != elementName) + ThrowException(format() << "Expected end of <" << elementName << "> element."); break; } } #ifdef ASSIMP_BUILD_DEBUG - if (primType != Prim_TriFans && primType != Prim_TriStrips && primType != Prim_LineStrip && + if (primType != Prim_TriFans && primType != Prim_TriStrips && primType != Prim_LineStrip && primType != Prim_Lines) { // this is ONLY to workaround a bug in SketchUp 15.3.331 where it writes the wrong 'count' when it writes out the 'lines'. ai_assert(actualPrimitives == numPrimitives); } @@ -2333,42 +2493,42 @@ void ColladaParser::ReadIndexData( Mesh* pMesh) // ------------------------------------------------------------------------------------------------ // Reads a single input channel element and stores it in the given array, if valid -void ColladaParser::ReadInputChannel( std::vector& poChannels) +void ColladaParser::ReadInputChannel(std::vector& poChannels) { InputChannel channel; // read semantic - int attrSemantic = GetAttribute( "semantic"); - std::string semantic = mReader->getAttributeValue( attrSemantic); - channel.mType = GetTypeForSemantic( semantic); + int attrSemantic = GetAttribute("semantic"); + std::string semantic = mReader->getAttributeValue(attrSemantic); + channel.mType = GetTypeForSemantic(semantic); // read source - int attrSource = GetAttribute( "source"); - const char* source = mReader->getAttributeValue( attrSource); - if( source[0] != '#') - ThrowException( format() << "Unknown reference format in url \"" << source << "\" in source attribute of element." ); - channel.mAccessor = source+1; // skipping the leading #, hopefully the remaining text is the accessor ID only + int attrSource = GetAttribute("source"); + const char* source = mReader->getAttributeValue(attrSource); + if (source[0] != '#') + ThrowException(format() << "Unknown reference format in url \"" << source << "\" in source attribute of element."); + channel.mAccessor = source + 1; // skipping the leading #, hopefully the remaining text is the accessor ID only // read index offset, if per-index - int attrOffset = TestAttribute( "offset"); - if( attrOffset > -1) - channel.mOffset = mReader->getAttributeValueAsInt( attrOffset); + int attrOffset = TestAttribute("offset"); + if (attrOffset > -1) + channel.mOffset = mReader->getAttributeValueAsInt(attrOffset); // read set if texture coordinates - if(channel.mType == IT_Texcoord || channel.mType == IT_Color){ + if (channel.mType == IT_Texcoord || channel.mType == IT_Color) { int attrSet = TestAttribute("set"); - if(attrSet > -1){ - attrSet = mReader->getAttributeValueAsInt( attrSet); - if(attrSet < 0) - ThrowException( format() << "Invalid index \"" << (attrSet) << "\" in set attribute of element" ); + if (attrSet > -1) { + attrSet = mReader->getAttributeValueAsInt(attrSet); + if (attrSet < 0) + ThrowException(format() << "Invalid index \"" << (attrSet) << "\" in set attribute of element"); channel.mIndex = attrSet; } } // store, if valid type - if( channel.mType != IT_Invalid) - poChannels.push_back( channel); + if (channel.mType != IT_Invalid) + poChannels.push_back(channel); // skip remaining stuff of this element, if any SkipElement(); @@ -2376,116 +2536,118 @@ void ColladaParser::ReadInputChannel( std::vector& poChannels) // ------------------------------------------------------------------------------------------------ // Reads a

primitive index list and assembles the mesh data into the given mesh -size_t ColladaParser::ReadPrimitives( Mesh* pMesh, std::vector& pPerIndexChannels, +size_t ColladaParser::ReadPrimitives(Mesh* pMesh, std::vector& pPerIndexChannels, size_t pNumPrimitives, const std::vector& pVCount, PrimitiveType pPrimType) { // determine number of indices coming per vertex // find the offset index for all per-vertex channels size_t numOffsets = 1; size_t perVertexOffset = SIZE_MAX; // invalid value - for( const InputChannel& channel : pPerIndexChannels) + for (const InputChannel& channel : pPerIndexChannels) { - numOffsets = std::max( numOffsets, channel.mOffset+1); - if( channel.mType == IT_Vertex) + numOffsets = std::max(numOffsets, channel.mOffset + 1); + if (channel.mType == IT_Vertex) perVertexOffset = channel.mOffset; } // determine the expected number of indices size_t expectedPointCount = 0; - switch( pPrimType) + switch (pPrimType) { - case Prim_Polylist: - { - for( size_t i : pVCount) - expectedPointCount += i; - break; - } - case Prim_Lines: - expectedPointCount = 2 * pNumPrimitives; - break; - case Prim_Triangles: - expectedPointCount = 3 * pNumPrimitives; - break; - default: - // other primitive types don't state the index count upfront... we need to guess - break; + case Prim_Polylist: + { + for (size_t i : pVCount) + expectedPointCount += i; + break; + } + case Prim_Lines: + expectedPointCount = 2 * pNumPrimitives; + break; + case Prim_Triangles: + expectedPointCount = 3 * pNumPrimitives; + break; + default: + // other primitive types don't state the index count upfront... we need to guess + break; } // and read all indices into a temporary array std::vector indices; - if( expectedPointCount > 0) - indices.reserve( expectedPointCount * numOffsets); + if (expectedPointCount > 0) + indices.reserve(expectedPointCount * numOffsets); if (pNumPrimitives > 0) // It is possible to not contain any indices { const char* content = GetTextContent(); - while( *content != 0) + while (*content != 0) { // read a value. // Hack: (thom) Some exporters put negative indices sometimes. We just try to carry on anyways. - int value = std::max( 0, strtol10( content, &content)); - indices.push_back( size_t( value)); + int value = std::max(0, strtol10(content, &content)); + indices.push_back(size_t(value)); // skip whitespace after it - SkipSpacesAndLineEnd( &content); + SkipSpacesAndLineEnd(&content); } } - // complain if the index count doesn't fit - if( expectedPointCount > 0 && indices.size() != expectedPointCount * numOffsets) { + // complain if the index count doesn't fit + if (expectedPointCount > 0 && indices.size() != expectedPointCount * numOffsets) { if (pPrimType == Prim_Lines) { // HACK: We just fix this number since SketchUp 15.3.331 writes the wrong 'count' for 'lines' - ReportWarning( "Expected different index count in

element, %zu instead of %zu.", indices.size(), expectedPointCount * numOffsets); + ReportWarning("Expected different index count in

element, %zu instead of %zu.", indices.size(), expectedPointCount * numOffsets); pNumPrimitives = (indices.size() / numOffsets) / 2; - } else - ThrowException( "Expected different index count in

element."); + } + else + ThrowException("Expected different index count in

element."); - } else if( expectedPointCount == 0 && (indices.size() % numOffsets) != 0) - ThrowException( "Expected different index count in

element."); + } + else if (expectedPointCount == 0 && (indices.size() % numOffsets) != 0) + ThrowException("Expected different index count in

element."); - // find the data for all sources - for( std::vector::iterator it = pMesh->mPerVertexData.begin(); it != pMesh->mPerVertexData.end(); ++it) + // find the data for all sources + for (std::vector::iterator it = pMesh->mPerVertexData.begin(); it != pMesh->mPerVertexData.end(); ++it) { InputChannel& input = *it; - if( input.mResolved) + if (input.mResolved) continue; // find accessor - input.mResolved = &ResolveLibraryReference( mAccessorLibrary, input.mAccessor); + input.mResolved = &ResolveLibraryReference(mAccessorLibrary, input.mAccessor); // resolve accessor's data pointer as well, if necessary const Accessor* acc = input.mResolved; - if( !acc->mData) - acc->mData = &ResolveLibraryReference( mDataLibrary, acc->mSource); + if (!acc->mData) + acc->mData = &ResolveLibraryReference(mDataLibrary, acc->mSource); } // and the same for the per-index channels - for( std::vector::iterator it = pPerIndexChannels.begin(); it != pPerIndexChannels.end(); ++it) + for (std::vector::iterator it = pPerIndexChannels.begin(); it != pPerIndexChannels.end(); ++it) { InputChannel& input = *it; - if( input.mResolved) + if (input.mResolved) continue; // ignore vertex pointer, it doesn't refer to an accessor - if( input.mType == IT_Vertex) + if (input.mType == IT_Vertex) { // warn if the vertex channel does not refer to the element in the same mesh - if( input.mAccessor != pMesh->mVertexID) - ThrowException( "Unsupported vertex referencing scheme."); + if (input.mAccessor != pMesh->mVertexID) + ThrowException("Unsupported vertex referencing scheme."); continue; } // find accessor - input.mResolved = &ResolveLibraryReference( mAccessorLibrary, input.mAccessor); + input.mResolved = &ResolveLibraryReference(mAccessorLibrary, input.mAccessor); // resolve accessor's data pointer as well, if necessary const Accessor* acc = input.mResolved; - if( !acc->mData) - acc->mData = &ResolveLibraryReference( mDataLibrary, acc->mSource); + if (!acc->mData) + acc->mData = &ResolveLibraryReference(mDataLibrary, acc->mSource); } // For continued primitives, the given count does not come all in one

, but only one primitive per

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

's inside the parent tag - if ( pPrimType == Prim_TriStrips){ + if (pPrimType == Prim_TriStrips) { size_t numberOfVertices = indices.size() / numOffsets; numPrimitives = numberOfVertices - 2; } @@ -2494,66 +2656,66 @@ size_t ColladaParser::ReadPrimitives( Mesh* pMesh, std::vector& pP numPrimitives = numberOfVertices - 1; } - pMesh->mFaceSize.reserve( numPrimitives); - pMesh->mFacePosIndices.reserve( indices.size() / numOffsets); + pMesh->mFaceSize.reserve(numPrimitives); + pMesh->mFacePosIndices.reserve(indices.size() / numOffsets); size_t polylistStartVertex = 0; for (size_t currentPrimitive = 0; currentPrimitive < numPrimitives; currentPrimitive++) { // determine number of points for this primitive size_t numPoints = 0; - switch( pPrimType) + switch (pPrimType) { - case Prim_Lines: - numPoints = 2; - for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) - CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); - break; - case Prim_LineStrip: - numPoints = 2; - for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) - CopyVertex(currentVertex, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); - break; - case Prim_Triangles: - numPoints = 3; - for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) - CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); - break; - case Prim_TriStrips: - numPoints = 3; - ReadPrimTriStrips(numOffsets, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); - break; - case Prim_Polylist: - numPoints = pVCount[currentPrimitive]; - for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) - CopyVertex(polylistStartVertex + currentVertex, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, 0, indices); - polylistStartVertex += numPoints; - break; - case Prim_TriFans: - case Prim_Polygon: - numPoints = indices.size() / numOffsets; - for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) - CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); - break; - default: - // LineStrip is not supported due to expected index unmangling - ThrowException( "Unsupported primitive type."); - break; + case Prim_Lines: + numPoints = 2; + for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) + CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + break; + case Prim_LineStrip: + numPoints = 2; + for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) + CopyVertex(currentVertex, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + break; + case Prim_Triangles: + numPoints = 3; + for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) + CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + break; + case Prim_TriStrips: + numPoints = 3; + ReadPrimTriStrips(numOffsets, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + break; + case Prim_Polylist: + numPoints = pVCount[currentPrimitive]; + for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) + CopyVertex(polylistStartVertex + currentVertex, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, 0, indices); + polylistStartVertex += numPoints; + break; + case Prim_TriFans: + case Prim_Polygon: + numPoints = indices.size() / numOffsets; + for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++) + CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); + break; + default: + // LineStrip is not supported due to expected index unmangling + ThrowException("Unsupported primitive type."); + break; } // store the face size to later reconstruct the face from - pMesh->mFaceSize.push_back( numPoints); + pMesh->mFaceSize.push_back(numPoints); } // if I ever get my hands on that guy who invented this steaming pile of indirection... - TestClosing( "p"); + TestClosing("p"); return numPrimitives; } ///@note This function willn't work correctly if both PerIndex and PerVertex channels have same channels. ///For example if TEXCOORD present in both and tags this function will create wrong uv coordinates. ///It's not clear from COLLADA documentation is this allowed or not. For now only exporter fixed to avoid such behavior -void ColladaParser::CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset, Mesh* pMesh, std::vector& pPerIndexChannels, size_t currentPrimitive, const std::vector& indices){ +void ColladaParser::CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset, Mesh* pMesh, std::vector& pPerIndexChannels, size_t currentPrimitive, const std::vector& indices) { // calculate the base offset of the vertex whose attributes we ant to copy size_t baseOffset = currentPrimitive * numOffsets * numPoints + currentVertex * numOffsets; @@ -2571,8 +2733,8 @@ void ColladaParser::CopyVertex(size_t currentVertex, size_t numOffsets, size_t n pMesh->mFacePosIndices.push_back(indices[baseOffset + perVertexOffset]); } -void ColladaParser::ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Mesh* pMesh, std::vector& pPerIndexChannels, size_t currentPrimitive, const std::vector& indices){ - if (currentPrimitive % 2 != 0){ +void ColladaParser::ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Mesh* pMesh, std::vector& pPerIndexChannels, size_t currentPrimitive, const std::vector& indices) { + if (currentPrimitive % 2 != 0) { //odd tristrip triangles need their indices mangled, to preserve winding direction CopyVertex(1, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); CopyVertex(0, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); @@ -2587,108 +2749,110 @@ void ColladaParser::ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, // ------------------------------------------------------------------------------------------------ // Extracts a single object from an input channel and stores it in the appropriate mesh data array -void ColladaParser::ExtractDataObjectFromChannel( const InputChannel& pInput, size_t pLocalIndex, Mesh* pMesh) +void ColladaParser::ExtractDataObjectFromChannel(const InputChannel& pInput, size_t pLocalIndex, Mesh* pMesh) { // ignore vertex referrer - we handle them that separate - if( pInput.mType == IT_Vertex) + if (pInput.mType == IT_Vertex) return; const Accessor& acc = *pInput.mResolved; - if( pLocalIndex >= acc.mCount) - ThrowException( format() << "Invalid data index (" << pLocalIndex << "/" << acc.mCount << ") in primitive specification" ); + if (pLocalIndex >= acc.mCount) + ThrowException(format() << "Invalid data index (" << pLocalIndex << "/" << acc.mCount << ") in primitive specification"); // get a pointer to the start of the data object referred to by the accessor and the local index - const ai_real* dataObject = &(acc.mData->mValues[0]) + acc.mOffset + pLocalIndex* acc.mStride; + const ai_real* dataObject = &(acc.mData->mValues[0]) + acc.mOffset + pLocalIndex * acc.mStride; // assemble according to the accessors component sub-offset list. We don't care, yet, // what kind of object exactly we're extracting here ai_real obj[4]; - for( size_t c = 0; c < 4; ++c) + for (size_t c = 0; c < 4; ++c) obj[c] = dataObject[acc.mSubOffset[c]]; // now we reinterpret it according to the type we're reading here - switch( pInput.mType) + switch (pInput.mType) { - case IT_Position: // ignore all position streams except 0 - there can be only one position - if( pInput.mIndex == 0) - pMesh->mPositions.push_back( aiVector3D( obj[0], obj[1], obj[2])); - else - ASSIMP_LOG_ERROR("Collada: just one vertex position stream supported"); - break; - case IT_Normal: - // pad to current vertex count if necessary - if( pMesh->mNormals.size() < pMesh->mPositions.size()-1) - pMesh->mNormals.insert( pMesh->mNormals.end(), pMesh->mPositions.size() - pMesh->mNormals.size() - 1, aiVector3D( 0, 1, 0)); + case IT_Position: // ignore all position streams except 0 - there can be only one position + if (pInput.mIndex == 0) + pMesh->mPositions.push_back(aiVector3D(obj[0], obj[1], obj[2])); + else + ASSIMP_LOG_ERROR("Collada: just one vertex position stream supported"); + break; + case IT_Normal: + // pad to current vertex count if necessary + if (pMesh->mNormals.size() < pMesh->mPositions.size() - 1) + pMesh->mNormals.insert(pMesh->mNormals.end(), pMesh->mPositions.size() - pMesh->mNormals.size() - 1, aiVector3D(0, 1, 0)); - // ignore all normal streams except 0 - there can be only one normal - if( pInput.mIndex == 0) - pMesh->mNormals.push_back( aiVector3D( obj[0], obj[1], obj[2])); - else - ASSIMP_LOG_ERROR("Collada: just one vertex normal stream supported"); - break; - case IT_Tangent: - // pad to current vertex count if necessary - if( pMesh->mTangents.size() < pMesh->mPositions.size()-1) - pMesh->mTangents.insert( pMesh->mTangents.end(), pMesh->mPositions.size() - pMesh->mTangents.size() - 1, aiVector3D( 1, 0, 0)); + // ignore all normal streams except 0 - there can be only one normal + if (pInput.mIndex == 0) + pMesh->mNormals.push_back(aiVector3D(obj[0], obj[1], obj[2])); + else + ASSIMP_LOG_ERROR("Collada: just one vertex normal stream supported"); + break; + case IT_Tangent: + // pad to current vertex count if necessary + if (pMesh->mTangents.size() < pMesh->mPositions.size() - 1) + pMesh->mTangents.insert(pMesh->mTangents.end(), pMesh->mPositions.size() - pMesh->mTangents.size() - 1, aiVector3D(1, 0, 0)); - // ignore all tangent streams except 0 - there can be only one tangent - if( pInput.mIndex == 0) - pMesh->mTangents.push_back( aiVector3D( obj[0], obj[1], obj[2])); - else - ASSIMP_LOG_ERROR("Collada: just one vertex tangent stream supported"); - break; - case IT_Bitangent: - // pad to current vertex count if necessary - if( pMesh->mBitangents.size() < pMesh->mPositions.size()-1) - pMesh->mBitangents.insert( pMesh->mBitangents.end(), pMesh->mPositions.size() - pMesh->mBitangents.size() - 1, aiVector3D( 0, 0, 1)); + // ignore all tangent streams except 0 - there can be only one tangent + if (pInput.mIndex == 0) + pMesh->mTangents.push_back(aiVector3D(obj[0], obj[1], obj[2])); + else + ASSIMP_LOG_ERROR("Collada: just one vertex tangent stream supported"); + break; + case IT_Bitangent: + // pad to current vertex count if necessary + if (pMesh->mBitangents.size() < pMesh->mPositions.size() - 1) + pMesh->mBitangents.insert(pMesh->mBitangents.end(), pMesh->mPositions.size() - pMesh->mBitangents.size() - 1, aiVector3D(0, 0, 1)); - // ignore all bitangent streams except 0 - there can be only one bitangent - if( pInput.mIndex == 0) - pMesh->mBitangents.push_back( aiVector3D( obj[0], obj[1], obj[2])); - else - ASSIMP_LOG_ERROR("Collada: just one vertex bitangent stream supported"); - break; - case IT_Texcoord: - // up to 4 texture coord sets are fine, ignore the others - if( pInput.mIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS) + // ignore all bitangent streams except 0 - there can be only one bitangent + if (pInput.mIndex == 0) + pMesh->mBitangents.push_back(aiVector3D(obj[0], obj[1], obj[2])); + else + ASSIMP_LOG_ERROR("Collada: just one vertex bitangent stream supported"); + break; + case IT_Texcoord: + // up to 4 texture coord sets are fine, ignore the others + if (pInput.mIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS) + { + // pad to current vertex count if necessary + if (pMesh->mTexCoords[pInput.mIndex].size() < pMesh->mPositions.size() - 1) + pMesh->mTexCoords[pInput.mIndex].insert(pMesh->mTexCoords[pInput.mIndex].end(), + pMesh->mPositions.size() - pMesh->mTexCoords[pInput.mIndex].size() - 1, aiVector3D(0, 0, 0)); + + pMesh->mTexCoords[pInput.mIndex].push_back(aiVector3D(obj[0], obj[1], obj[2])); + if (0 != acc.mSubOffset[2] || 0 != acc.mSubOffset[3]) /* hack ... consider cleaner solution */ + pMesh->mNumUVComponents[pInput.mIndex] = 3; + } + else + { + ASSIMP_LOG_ERROR("Collada: too many texture coordinate sets. Skipping."); + } + break; + case IT_Color: + // up to 4 color sets are fine, ignore the others + if (pInput.mIndex < AI_MAX_NUMBER_OF_COLOR_SETS) + { + // pad to current vertex count if necessary + if (pMesh->mColors[pInput.mIndex].size() < pMesh->mPositions.size() - 1) + pMesh->mColors[pInput.mIndex].insert(pMesh->mColors[pInput.mIndex].end(), + pMesh->mPositions.size() - pMesh->mColors[pInput.mIndex].size() - 1, aiColor4D(0, 0, 0, 1)); + + aiColor4D result(0, 0, 0, 1); + for (size_t i = 0; i < pInput.mResolved->mSize; ++i) { - // pad to current vertex count if necessary - if( pMesh->mTexCoords[pInput.mIndex].size() < pMesh->mPositions.size()-1) - pMesh->mTexCoords[pInput.mIndex].insert( pMesh->mTexCoords[pInput.mIndex].end(), - pMesh->mPositions.size() - pMesh->mTexCoords[pInput.mIndex].size() - 1, aiVector3D( 0, 0, 0)); - - pMesh->mTexCoords[pInput.mIndex].push_back( aiVector3D( obj[0], obj[1], obj[2])); - if (0 != acc.mSubOffset[2] || 0 != acc.mSubOffset[3]) /* hack ... consider cleaner solution */ - pMesh->mNumUVComponents[pInput.mIndex]=3; - } else - { - ASSIMP_LOG_ERROR("Collada: too many texture coordinate sets. Skipping."); + result[static_cast(i)] = obj[pInput.mResolved->mSubOffset[i]]; } - break; - case IT_Color: - // up to 4 color sets are fine, ignore the others - if( pInput.mIndex < AI_MAX_NUMBER_OF_COLOR_SETS) - { - // pad to current vertex count if necessary - if( pMesh->mColors[pInput.mIndex].size() < pMesh->mPositions.size()-1) - pMesh->mColors[pInput.mIndex].insert( pMesh->mColors[pInput.mIndex].end(), - pMesh->mPositions.size() - pMesh->mColors[pInput.mIndex].size() - 1, aiColor4D( 0, 0, 0, 1)); + pMesh->mColors[pInput.mIndex].push_back(result); + } + else + { + ASSIMP_LOG_ERROR("Collada: too many vertex color sets. Skipping."); + } - aiColor4D result(0, 0, 0, 1); - for (size_t i = 0; i < pInput.mResolved->mSize; ++i) - { - result[static_cast(i)] = obj[pInput.mResolved->mSubOffset[i]]; - } - pMesh->mColors[pInput.mIndex].push_back(result); - } else - { - ASSIMP_LOG_ERROR("Collada: too many vertex color sets. Skipping."); - } - - break; - default: - // IT_Invalid and IT_Vertex - ai_assert(false && "shouldn't ever get here"); + break; + default: + // IT_Invalid and IT_Vertex + ai_assert(false && "shouldn't ever get here"); } } @@ -2696,25 +2860,25 @@ void ColladaParser::ExtractDataObjectFromChannel( const InputChannel& pInput, si // Reads the library of node hierarchies and scene parts void ColladaParser::ReadSceneLibrary() { - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) return; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { // a visual scene - generate root node under its ID and let ReadNode() do the recursive work - if( IsElement( "visual_scene")) + if (IsElement("visual_scene")) { // read ID. Is optional according to the spec, but how on earth should a scene_instance refer to it then? - int indexID = GetAttribute( "id"); - const char* attrID = mReader->getAttributeValue( indexID); + int indexID = GetAttribute("id"); + const char* attrID = mReader->getAttributeValue(indexID); // read name if given. - int indexName = TestAttribute( "name"); + int indexName = TestAttribute("name"); const char* attrName = "unnamed"; - if( indexName > -1) - attrName = mReader->getAttributeValue( indexName); + if (indexName > -1) + attrName = mReader->getAttributeValue(indexName); // create a node and store it in the library under its ID Node* node = new Node; @@ -2722,55 +2886,56 @@ void ColladaParser::ReadSceneLibrary() node->mName = attrName; mNodeLibrary[node->mID] = node; - ReadSceneNode( node); - } else + ReadSceneNode(node); + } + else { // ignore the rest SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "library_visual_scenes") == 0) + if (strcmp(mReader->getNodeName(), "library_visual_scenes") == 0) //ThrowException( "Expected end of \"library_visual_scenes\" element."); - break; + break; } } } // ------------------------------------------------------------------------------------------------ // Reads a scene node's contents including children and stores it in the given node -void ColladaParser::ReadSceneNode( Node* pNode) +void ColladaParser::ReadSceneNode(Node* pNode) { // quit immediately on elements - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) return; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "node")) + if (IsElement("node")) { Node* child = new Node; - int attrID = TestAttribute( "id"); - if( attrID > -1) - child->mID = mReader->getAttributeValue( attrID); - int attrSID = TestAttribute( "sid"); - if( attrSID > -1) - child->mSID = mReader->getAttributeValue( attrSID); + int attrID = TestAttribute("id"); + if (attrID > -1) + child->mID = mReader->getAttributeValue(attrID); + int attrSID = TestAttribute("sid"); + if (attrSID > -1) + child->mSID = mReader->getAttributeValue(attrSID); - int attrName = TestAttribute( "name"); - if( attrName > -1) - child->mName = mReader->getAttributeValue( attrName); + int attrName = TestAttribute("name"); + if (attrName > -1) + child->mName = mReader->getAttributeValue(attrName); // TODO: (thom) support SIDs // ai_assert( TestAttribute( "sid") == -1); if (pNode) { - pNode->mChildren.push_back( child); + pNode->mChildren.push_back(child); child->mParent = pNode; } else @@ -2781,26 +2946,26 @@ void ColladaParser::ReadSceneNode( Node* pNode) } // read on recursively from there - ReadSceneNode( child); + ReadSceneNode(child); continue; } // For any further stuff we need a valid node to work on else if (!pNode) continue; - if( IsElement( "lookat")) - ReadNodeTransformation( pNode, TF_LOOKAT); - else if( IsElement( "matrix")) - ReadNodeTransformation( pNode, TF_MATRIX); - else if( IsElement( "rotate")) - ReadNodeTransformation( pNode, TF_ROTATE); - else if( IsElement( "scale")) - ReadNodeTransformation( pNode, TF_SCALE); - else if( IsElement( "skew")) - ReadNodeTransformation( pNode, TF_SKEW); - else if( IsElement( "translate")) - ReadNodeTransformation( pNode, TF_TRANSLATE); - else if( IsElement( "render") && pNode->mParent == NULL && 0 == pNode->mPrimaryCamera.length()) + if (IsElement("lookat")) + ReadNodeTransformation(pNode, TF_LOOKAT); + else if (IsElement("matrix")) + ReadNodeTransformation(pNode, TF_MATRIX); + else if (IsElement("rotate")) + ReadNodeTransformation(pNode, TF_ROTATE); + else if (IsElement("scale")) + ReadNodeTransformation(pNode, TF_SCALE); + else if (IsElement("skew")) + ReadNodeTransformation(pNode, TF_SKEW); + else if (IsElement("translate")) + ReadNodeTransformation(pNode, TF_TRANSLATE); + else if (IsElement("render") && pNode->mParent == NULL && 0 == pNode->mPrimaryCamera.length()) { // ... scene evaluation or, in other words, postprocessing pipeline, // or, again in other words, a turing-complete description how to @@ -2813,14 +2978,14 @@ void ColladaParser::ReadSceneNode( Node* pNode) if (s[0] != '#') ASSIMP_LOG_ERROR("Collada: Unresolved reference format of camera"); else - pNode->mPrimaryCamera = s+1; + pNode->mPrimaryCamera = s + 1; } } - else if( IsElement( "instance_node")) + else if (IsElement("instance_node")) { // find the node in the library - int attrID = TestAttribute( "url"); - if( attrID != -1) + int attrID = TestAttribute("url"); + if (attrID != -1) { const char* s = mReader->getAttributeValue(attrID); if (s[0] != '#') @@ -2828,16 +2993,16 @@ void ColladaParser::ReadSceneNode( Node* pNode) else { pNode->mNodeInstances.push_back(NodeInstance()); - pNode->mNodeInstances.back().mNode = s+1; + pNode->mNodeInstances.back().mNode = s + 1; } } } - else if( IsElement( "instance_geometry") || IsElement( "instance_controller")) + else if (IsElement("instance_geometry") || IsElement("instance_controller")) { // Reference to a mesh or controller, with possible material associations - ReadNodeGeometry( pNode); + ReadNodeGeometry(pNode); } - else if( IsElement( "instance_light")) + else if (IsElement("instance_light")) { // Reference to a light, name given in 'url' attribute int attrID = TestAttribute("url"); @@ -2845,15 +3010,15 @@ void ColladaParser::ReadSceneNode( Node* pNode) ASSIMP_LOG_WARN("Collada: Expected url attribute in element"); else { - const char* url = mReader->getAttributeValue( attrID); - if( url[0] != '#') - ThrowException( "Unknown reference format in element"); + const char* url = mReader->getAttributeValue(attrID); + if (url[0] != '#') + ThrowException("Unknown reference format in element"); pNode->mLights.push_back(LightInstance()); - pNode->mLights.back().mLight = url+1; + pNode->mLights.back().mLight = url + 1; } } - else if( IsElement( "instance_camera")) + else if (IsElement("instance_camera")) { // Reference to a camera, name given in 'url' attribute int attrID = TestAttribute("url"); @@ -2861,12 +3026,12 @@ void ColladaParser::ReadSceneNode( Node* pNode) ASSIMP_LOG_WARN("Collada: Expected url attribute in element"); else { - const char* url = mReader->getAttributeValue( attrID); - if( url[0] != '#') - ThrowException( "Unknown reference format in element"); + const char* url = mReader->getAttributeValue(attrID); + if (url[0] != '#') + ThrowException("Unknown reference format in element"); pNode->mCameras.push_back(CameraInstance()); - pNode->mCameras.back().mCamera = url+1; + pNode->mCameras.back().mCamera = url + 1; } } else @@ -2875,7 +3040,7 @@ void ColladaParser::ReadSceneNode( Node* pNode) SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { break; } } @@ -2883,9 +3048,9 @@ void ColladaParser::ReadSceneNode( Node* pNode) // ------------------------------------------------------------------------------------------------ // Reads a node transformation entry of the given type and adds it to the given node's transformation list. -void ColladaParser::ReadNodeTransformation( Node* pNode, TransformType pType) +void ColladaParser::ReadNodeTransformation(Node* pNode, TransformType pType) { - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) return; std::string tagName = mReader->getNodeName(); @@ -2894,38 +3059,38 @@ void ColladaParser::ReadNodeTransformation( Node* pNode, TransformType pType) tf.mType = pType; // read SID - int indexSID = TestAttribute( "sid"); - if( indexSID >= 0) - tf.mID = mReader->getAttributeValue( indexSID); + int indexSID = TestAttribute("sid"); + if (indexSID >= 0) + tf.mID = mReader->getAttributeValue(indexSID); // how many parameters to read per transformation type static const unsigned int sNumParameters[] = { 9, 4, 3, 3, 7, 16 }; const char* content = GetTextContent(); // read as many parameters and store in the transformation - for( unsigned int a = 0; a < sNumParameters[pType]; a++) + for (unsigned int a = 0; a < sNumParameters[pType]; a++) { // read a number - content = fast_atoreal_move( content, tf.f[a]); + content = fast_atoreal_move(content, tf.f[a]); // skip whitespace after it - SkipSpacesAndLineEnd( &content); + SkipSpacesAndLineEnd(&content); } // place the transformation at the queue of the node - pNode->mTransforms.push_back( tf); + pNode->mTransforms.push_back(tf); // and consume the closing tag - TestClosing( tagName.c_str()); + TestClosing(tagName.c_str()); } // ------------------------------------------------------------------------------------------------ // Processes bind_vertex_input and bind elements -void ColladaParser::ReadMaterialVertexInputBinding( Collada::SemanticMappingTable& tbl) +void ColladaParser::ReadMaterialVertexInputBinding(Collada::SemanticMappingTable& tbl) { - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "bind_vertex_input")) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (IsElement("bind_vertex_input")) { Collada::InputSemanticMapEntry vn; @@ -2935,7 +3100,7 @@ void ColladaParser::ReadMaterialVertexInputBinding( Collada::SemanticMappingTabl // input semantic n = GetAttribute("input_semantic"); - vn.mType = GetTypeForSemantic( mReader->getAttributeValue(n) ); + vn.mType = GetTypeForSemantic(mReader->getAttributeValue(n)); // index of input set n = TestAttribute("input_set"); @@ -2944,103 +3109,124 @@ void ColladaParser::ReadMaterialVertexInputBinding( Collada::SemanticMappingTabl tbl.mMap[s] = vn; } - else if( IsElement( "bind")) { + else if (IsElement("bind")) { ASSIMP_LOG_WARN("Collada: Found unsupported element"); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "instance_material") == 0) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (strcmp(mReader->getNodeName(), "instance_material") == 0) break; } } } +void ColladaParser::ReadEmbeddedTextures(ZipArchiveIOSystem& zip_archive) +{ + // Attempt to load any undefined Collada::Image in ImageLibrary + for (ImageLibrary::iterator it = mImageLibrary.begin(); it != mImageLibrary.end(); ++it) { + Collada::Image &image = (*it).second; + + if (image.mImageData.empty()) { + std::unique_ptr image_file(zip_archive.Open(image.mFileName.c_str())); + if (image_file) { + image.mImageData.resize(image_file->FileSize()); + image_file->Read(image.mImageData.data(), image_file->FileSize(), 1); + image.mEmbeddedFormat = BaseImporter::GetExtension(image.mFileName); + if (image.mEmbeddedFormat == "jpeg") { + image.mEmbeddedFormat = "jpg"; + } + } + } + } +} + // ------------------------------------------------------------------------------------------------ // Reads a mesh reference in a node and adds it to the node's mesh list -void ColladaParser::ReadNodeGeometry( Node* pNode) +void ColladaParser::ReadNodeGeometry(Node* pNode) { // referred mesh is given as an attribute of the element - int attrUrl = GetAttribute( "url"); - const char* url = mReader->getAttributeValue( attrUrl); - if( url[0] != '#') - ThrowException( "Unknown reference format"); + int attrUrl = GetAttribute("url"); + const char* url = mReader->getAttributeValue(attrUrl); + if (url[0] != '#') + ThrowException("Unknown reference format"); Collada::MeshInstance instance; - instance.mMeshOrController = url+1; // skipping the leading # + instance.mMeshOrController = url + 1; // skipping the leading # - if( !mReader->isEmptyElement()) + if (!mReader->isEmptyElement()) { // read material associations. Ignore additional elements in between - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "instance_material")) + if (IsElement("instance_material")) { // read ID of the geometry subgroup and the target material - int attrGroup = GetAttribute( "symbol"); - std::string group = mReader->getAttributeValue( attrGroup); - int attrMaterial = GetAttribute( "target"); - const char* urlMat = mReader->getAttributeValue( attrMaterial); + int attrGroup = GetAttribute("symbol"); + std::string group = mReader->getAttributeValue(attrGroup); + int attrMaterial = GetAttribute("target"); + const char* urlMat = mReader->getAttributeValue(attrMaterial); Collada::SemanticMappingTable s; - if( urlMat[0] == '#') + if (urlMat[0] == '#') urlMat++; s.mMatName = urlMat; // resolve further material details + THIS UGLY AND NASTY semantic mapping stuff - if( !mReader->isEmptyElement()) + if (!mReader->isEmptyElement()) ReadMaterialVertexInputBinding(s); // store the association instance.mMaterials[group] = s; } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { - if( strcmp( mReader->getNodeName(), "instance_geometry") == 0 - || strcmp( mReader->getNodeName(), "instance_controller") == 0) + if (strcmp(mReader->getNodeName(), "instance_geometry") == 0 + || strcmp(mReader->getNodeName(), "instance_controller") == 0) break; } } } // store it - pNode->mMeshes.push_back( instance); + pNode->mMeshes.push_back(instance); } // ------------------------------------------------------------------------------------------------ // Reads the collada scene void ColladaParser::ReadScene() { - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) return; - while( mReader->read()) + while (mReader->read()) { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT) { - if( IsElement( "instance_visual_scene")) + if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { + if (IsElement("instance_visual_scene")) { // should be the first and only occurrence - if( mRootNode) - ThrowException( "Invalid scene containing multiple root nodes in element"); + if (mRootNode) + ThrowException("Invalid scene containing multiple root nodes in element"); // read the url of the scene to instance. Should be of format "#some_name" - int urlIndex = GetAttribute( "url"); - const char* url = mReader->getAttributeValue( urlIndex); - if( url[0] != '#') - ThrowException( "Unknown reference format in element"); + int urlIndex = GetAttribute("url"); + const char* url = mReader->getAttributeValue(urlIndex); + if (url[0] != '#') + ThrowException("Unknown reference format in element"); // find the referred scene, skip the leading # - NodeLibrary::const_iterator sit = mNodeLibrary.find( url+1); - if( sit == mNodeLibrary.end()) - ThrowException( "Unable to resolve visual_scene reference \"" + std::string(url) + "\" in element."); + NodeLibrary::const_iterator sit = mNodeLibrary.find(url + 1); + if (sit == mNodeLibrary.end()) + ThrowException("Unable to resolve visual_scene reference \"" + std::string(url) + "\" in element."); mRootNode = sit->second; - } else { + } + else { SkipElement(); } } - else if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END){ + else if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { break; } } @@ -3048,108 +3234,119 @@ void ColladaParser::ReadScene() // ------------------------------------------------------------------------------------------------ // Aborts the file reading with an exception -AI_WONT_RETURN void ColladaParser::ThrowException( const std::string& pError) const -{ - throw DeadlyImportError( format() << "Collada: " << mFileName << " - " << pError ); +AI_WONT_RETURN void ColladaParser::ThrowException(const std::string& pError) const { + throw DeadlyImportError(format() << "Collada: " << mFileName << " - " << pError); } -void ColladaParser::ReportWarning(const char* msg,...) -{ - ai_assert(NULL != msg); + +void ColladaParser::ReportWarning(const char* msg, ...) { + ai_assert(nullptr != msg); va_list args; - va_start(args,msg); + va_start(args, msg); char szBuffer[3000]; - const int iLen = vsprintf(szBuffer,msg,args); + const int iLen = vsprintf(szBuffer, msg, args); ai_assert(iLen > 0); va_end(args); - ASSIMP_LOG_WARN_F("Validation warning: ", std::string(szBuffer,iLen)); + ASSIMP_LOG_WARN_F("Validation warning: ", std::string(szBuffer, iLen)); } // ------------------------------------------------------------------------------------------------ // Skips all data until the end node of the current element -void ColladaParser::SkipElement() -{ +void ColladaParser::SkipElement() { // nothing to skip if it's an - if( mReader->isEmptyElement()) + if (mReader->isEmptyElement()) { return; + } // reroute - SkipElement( mReader->getNodeName()); + SkipElement(mReader->getNodeName()); } // ------------------------------------------------------------------------------------------------ // Skips all data until the end node of the given element -void ColladaParser::SkipElement( const char* pElement) -{ +void ColladaParser::SkipElement(const char* pElement) { // copy the current node's name because it'a pointer to the reader's internal buffer, // which is going to change with the upcoming parsing std::string element = pElement; - while( mReader->read()) - { - if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END) - if( mReader->getNodeName() == element) + while (mReader->read()) { + if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (mReader->getNodeName() == element) { break; + } + } } } // ------------------------------------------------------------------------------------------------ // Tests for an opening element of the given name, throws an exception if not found -void ColladaParser::TestOpening( const char* pName) -{ +void ColladaParser::TestOpening(const char* pName) { // read element start - if( !mReader->read()) - ThrowException( format() << "Unexpected end of file while beginning of <" << pName << "> element." ); + if (!mReader->read()) { + ThrowException(format() << "Unexpected end of file while beginning of <" << pName << "> element."); + } // whitespace in front is ok, just read again if found - if( mReader->getNodeType() == irr::io::EXN_TEXT) - if( !mReader->read()) - ThrowException( format() << "Unexpected end of file while reading beginning of <" << pName << "> element." ); + if (mReader->getNodeType() == irr::io::EXN_TEXT) { + if (!mReader->read()) { + ThrowException(format() << "Unexpected end of file while reading beginning of <" << pName << "> element."); + } + } - if( mReader->getNodeType() != irr::io::EXN_ELEMENT || strcmp( mReader->getNodeName(), pName) != 0) - ThrowException( format() << "Expected start of <" << pName << "> element." ); + if (mReader->getNodeType() != irr::io::EXN_ELEMENT || strcmp(mReader->getNodeName(), pName) != 0) { + ThrowException(format() << "Expected start of <" << pName << "> element."); + } } // ------------------------------------------------------------------------------------------------ // Tests for the closing tag of the given element, throws an exception if not found -void ColladaParser::TestClosing( const char* pName) -{ - // check if we're already on the closing tag and return right away - if( mReader->getNodeType() == irr::io::EXN_ELEMENT_END && strcmp( mReader->getNodeName(), pName) == 0) +void ColladaParser::TestClosing(const char* pName) { + // check if we have an empty (self-closing) element + if (mReader->isEmptyElement()) { return; + } + + // check if we're already on the closing tag and return right away + if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END && strcmp(mReader->getNodeName(), pName) == 0) { + return; + } // if not, read some more - if( !mReader->read()) - ThrowException( format() << "Unexpected end of file while reading end of <" << pName << "> element." ); + if (!mReader->read()) { + ThrowException(format() << "Unexpected end of file while reading end of <" << pName << "> element."); + } // whitespace in front is ok, just read again if found - if( mReader->getNodeType() == irr::io::EXN_TEXT) - if( !mReader->read()) - ThrowException( format() << "Unexpected end of file while reading end of <" << pName << "> element." ); + if (mReader->getNodeType() == irr::io::EXN_TEXT) { + if (!mReader->read()) { + ThrowException(format() << "Unexpected end of file while reading end of <" << pName << "> element."); + } + } // but this has the be the closing tag, or we're lost - if( mReader->getNodeType() != irr::io::EXN_ELEMENT_END || strcmp( mReader->getNodeName(), pName) != 0) - ThrowException( format() << "Expected end of <" << pName << "> element." ); + if (mReader->getNodeType() != irr::io::EXN_ELEMENT_END || strcmp(mReader->getNodeName(), pName) != 0) { + ThrowException(format() << "Expected end of <" << pName << "> element."); + } } // ------------------------------------------------------------------------------------------------ // Returns the index of the named attribute or -1 if not found. Does not throw, therefore useful for optional attributes -int ColladaParser::GetAttribute( const char* pAttr) const -{ - int index = TestAttribute( pAttr); - if( index != -1) +int ColladaParser::GetAttribute(const char* pAttr) const { + int index = TestAttribute(pAttr); + if (index != -1) { return index; + } // attribute not found -> throw an exception - ThrowException( format() << "Expected attribute \"" << pAttr << "\" for element <" << mReader->getNodeName() << ">." ); + ThrowException(format() << "Expected attribute \"" << pAttr << "\" for element <" << mReader->getNodeName() << ">."); return -1; } // ------------------------------------------------------------------------------------------------ // Tests the present element for the presence of one attribute, returns its index or throws an exception if not found -int ColladaParser::TestAttribute( const char* pAttr) const +int ColladaParser::TestAttribute(const char* pAttr) const { - for( int a = 0; a < mReader->getAttributeCount(); a++) - if( strcmp( mReader->getAttributeName( a), pAttr) == 0) + for (int a = 0; a < mReader->getAttributeCount(); a++) + if (strcmp(mReader->getAttributeName(a), pAttr) == 0) return a; return -1; @@ -3160,8 +3357,8 @@ int ColladaParser::TestAttribute( const char* pAttr) const const char* ColladaParser::GetTextContent() { const char* sz = TestTextContent(); - if(!sz) { - ThrowException( "Invalid contents in element \"n\"."); + if (!sz) { + ThrowException("Invalid contents in element \"n\"."); } return sz; } @@ -3171,85 +3368,85 @@ const char* ColladaParser::GetTextContent() const char* ColladaParser::TestTextContent() { // present node should be the beginning of an element - if( mReader->getNodeType() != irr::io::EXN_ELEMENT || mReader->isEmptyElement()) + if (mReader->getNodeType() != irr::io::EXN_ELEMENT || mReader->isEmptyElement()) return NULL; // read contents of the element - if( !mReader->read() ) + if (!mReader->read()) return NULL; - if( mReader->getNodeType() != irr::io::EXN_TEXT && mReader->getNodeType() != irr::io::EXN_CDATA) + if (mReader->getNodeType() != irr::io::EXN_TEXT && mReader->getNodeType() != irr::io::EXN_CDATA) return NULL; // skip leading whitespace const char* text = mReader->getNodeData(); - SkipSpacesAndLineEnd( &text); + SkipSpacesAndLineEnd(&text); return text; } // ------------------------------------------------------------------------------------------------ // Calculates the resulting transformation fromm all the given transform steps -aiMatrix4x4 ColladaParser::CalculateResultTransform( const std::vector& pTransforms) const +aiMatrix4x4 ColladaParser::CalculateResultTransform(const std::vector& pTransforms) const { aiMatrix4x4 res; - for( std::vector::const_iterator it = pTransforms.begin(); it != pTransforms.end(); ++it) + for (std::vector::const_iterator it = pTransforms.begin(); it != pTransforms.end(); ++it) { const Transform& tf = *it; - switch( tf.mType) + switch (tf.mType) { - case TF_LOOKAT: - { - aiVector3D pos( tf.f[0], tf.f[1], tf.f[2]); - aiVector3D dstPos( tf.f[3], tf.f[4], tf.f[5]); - aiVector3D up = aiVector3D( tf.f[6], tf.f[7], tf.f[8]).Normalize(); - aiVector3D dir = aiVector3D( dstPos - pos).Normalize(); - aiVector3D right = (dir ^ up).Normalize(); + case TF_LOOKAT: + { + aiVector3D pos(tf.f[0], tf.f[1], tf.f[2]); + aiVector3D dstPos(tf.f[3], tf.f[4], tf.f[5]); + aiVector3D up = aiVector3D(tf.f[6], tf.f[7], tf.f[8]).Normalize(); + aiVector3D dir = aiVector3D(dstPos - pos).Normalize(); + aiVector3D right = (dir ^ up).Normalize(); - res *= aiMatrix4x4( - right.x, up.x, -dir.x, pos.x, - right.y, up.y, -dir.y, pos.y, - right.z, up.z, -dir.z, pos.z, - 0, 0, 0, 1); - break; - } - case TF_ROTATE: - { - aiMatrix4x4 rot; - ai_real angle = tf.f[3] * ai_real( AI_MATH_PI) / ai_real( 180.0 ); - aiVector3D axis( tf.f[0], tf.f[1], tf.f[2]); - aiMatrix4x4::Rotation( angle, axis, rot); - res *= rot; - break; - } - case TF_TRANSLATE: - { - aiMatrix4x4 trans; - aiMatrix4x4::Translation( aiVector3D( tf.f[0], tf.f[1], tf.f[2]), trans); - res *= trans; - break; - } - case TF_SCALE: - { - aiMatrix4x4 scale( tf.f[0], 0.0f, 0.0f, 0.0f, 0.0f, tf.f[1], 0.0f, 0.0f, 0.0f, 0.0f, tf.f[2], 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f); - res *= scale; - break; - } - case TF_SKEW: - // TODO: (thom) - ai_assert( false); - break; - case TF_MATRIX: - { - aiMatrix4x4 mat( tf.f[0], tf.f[1], tf.f[2], tf.f[3], tf.f[4], tf.f[5], tf.f[6], tf.f[7], - tf.f[8], tf.f[9], tf.f[10], tf.f[11], tf.f[12], tf.f[13], tf.f[14], tf.f[15]); - res *= mat; - break; - } - default: - ai_assert( false); - break; + res *= aiMatrix4x4( + right.x, up.x, -dir.x, pos.x, + right.y, up.y, -dir.y, pos.y, + right.z, up.z, -dir.z, pos.z, + 0, 0, 0, 1); + break; + } + case TF_ROTATE: + { + aiMatrix4x4 rot; + ai_real angle = tf.f[3] * ai_real(AI_MATH_PI) / ai_real(180.0); + aiVector3D axis(tf.f[0], tf.f[1], tf.f[2]); + aiMatrix4x4::Rotation(angle, axis, rot); + res *= rot; + break; + } + case TF_TRANSLATE: + { + aiMatrix4x4 trans; + aiMatrix4x4::Translation(aiVector3D(tf.f[0], tf.f[1], tf.f[2]), trans); + res *= trans; + break; + } + case TF_SCALE: + { + aiMatrix4x4 scale(tf.f[0], 0.0f, 0.0f, 0.0f, 0.0f, tf.f[1], 0.0f, 0.0f, 0.0f, 0.0f, tf.f[2], 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + res *= scale; + break; + } + case TF_SKEW: + // TODO: (thom) + ai_assert(false); + break; + case TF_MATRIX: + { + aiMatrix4x4 mat(tf.f[0], tf.f[1], tf.f[2], tf.f[3], tf.f[4], tf.f[5], tf.f[6], tf.f[7], + tf.f[8], tf.f[9], tf.f[10], tf.f[11], tf.f[12], tf.f[13], tf.f[14], tf.f[15]); + res *= mat; + break; + } + default: + ai_assert(false); + break; } } @@ -3258,29 +3455,29 @@ aiMatrix4x4 ColladaParser::CalculateResultTransform( const std::vector StringMetaData; /** Constructor from XML file */ - ColladaParser( IOSystem* pIOHandler, const std::string& pFile); + ColladaParser(IOSystem* pIOHandler, const std::string& pFile); /** Destructor */ ~ColladaParser(); + /** Attempts to read the ZAE manifest and returns the DAE to open */ + static std::string ReadZaeManifest(ZipArchiveIOSystem &zip_archive); + /** Reads the contents of the file */ void ReadContents(); @@ -235,6 +242,9 @@ namespace Assimp // Processes bind_vertex_input and bind elements void ReadMaterialVertexInputBinding( Collada::SemanticMappingTable& tbl); + /** Reads embedded textures from a ZAE archive*/ + void ReadEmbeddedTextures(ZipArchiveIOSystem &zip_archive); + protected: /** Aborts the file reading with an exception */ AI_WONT_RETURN void ThrowException( const std::string& pError) const AI_WONT_RETURN_SUFFIX; diff --git a/code/Common/BaseImporter.cpp b/code/Common/BaseImporter.cpp index 0a5694aa0..5c1e60554 100644 --- a/code/Common/BaseImporter.cpp +++ b/code/Common/BaseImporter.cpp @@ -67,7 +67,20 @@ using namespace Assimp; // Constructor to be privately used by Importer BaseImporter::BaseImporter() AI_NO_EXCEPT : m_progress() { - // nothing to do here + /** + * Assimp Importer + * unit conversions available + * if you need another measurment unit add it below. + * it's currently defined in assimp that we prefer meters. + * + * NOTE: Initialised here rather than in the header file + * to workaround a VS2013 bug with brace initialisers + * */ + importerUnits[ImporterUnits::M] = 1.0; + importerUnits[ImporterUnits::CM] = 0.01; + importerUnits[ImporterUnits::MM] = 0.001; + importerUnits[ImporterUnits::INCHES] = 0.0254; + importerUnits[ImporterUnits::FEET] = 0.3048; } // ------------------------------------------------------------------------------------------------ @@ -76,9 +89,25 @@ BaseImporter::~BaseImporter() { // nothing to do here } +void BaseImporter::UpdateImporterScale( Importer* pImp ) +{ + ai_assert(pImp != nullptr); + ai_assert(importerScale != 0.0); + ai_assert(fileScale != 0.0); + + double activeScale = importerScale * fileScale; + + // Set active scaling + pImp->SetPropertyFloat( AI_CONFIG_APP_SCALE_KEY, static_cast( activeScale) ); + + ASSIMP_LOG_DEBUG_F("UpdateImporterScale scale set: %f", activeScale ); +} + // ------------------------------------------------------------------------------------------------ // Imports the given file and returns the imported data. -aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile, IOSystem* pIOHandler) { +aiScene* BaseImporter::ReadFile(Importer* pImp, const std::string& pFile, IOSystem* pIOHandler) { + + m_progress = pImp->GetProgressHandler(); if (nullptr == m_progress) { return nullptr; @@ -100,6 +129,11 @@ aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile, { InternReadFile( pFile, sc.get(), &filter); + // Calculate import scale hook - required because pImp not available anywhere else + // passes scale into ScaleProcess + UpdateImporterScale(pImp); + + } catch( const std::exception& err ) { // extract error description m_ErrorText = err.what(); @@ -112,7 +146,7 @@ aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile, } // ------------------------------------------------------------------------------------------------ -void BaseImporter::SetupProperties(const Importer* /*pImp*/) +void BaseImporter::SetupProperties(const Importer* pImp) { // the default implementation does nothing } @@ -588,6 +622,8 @@ aiScene* BatchLoader::GetImport( unsigned int which ) return nullptr; } + + // ------------------------------------------------------------------------------------------------ void BatchLoader::LoadAll() { diff --git a/code/Common/DefaultIOStream.cpp b/code/Common/DefaultIOStream.cpp index 1c100b618..829b44731 100644 --- a/code/Common/DefaultIOStream.cpp +++ b/code/Common/DefaultIOStream.cpp @@ -52,6 +52,35 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; +namespace +{ + template + size_t select_ftell(FILE* file) + { + return ::ftell(file); + } + + template + int select_fseek(FILE* file, int64_t offset, int origin) + { + return ::fseek(file, static_cast(offset), origin); + } + +#if defined _WIN32 && (!defined __GNUC__ || __MSVCRT_VERSION__ >= 0x0601) + template<> + size_t select_ftell<8>(FILE* file) + { + return ::_ftelli64(file); + } + + template<> + int select_fseek<8>(FILE* file, int64_t offset, int origin) + { + return ::_fseeki64(file, offset, origin); + } +#endif +} + // ---------------------------------------------------------------------------------- DefaultIOStream::~DefaultIOStream() { @@ -93,7 +122,7 @@ aiReturn DefaultIOStream::Seek(size_t pOffset, aiOrigin_END == SEEK_END && aiOrigin_SET == SEEK_SET"); // do the seek - return (0 == ::fseek(mFile, (long)pOffset,(int)pOrigin) ? AI_SUCCESS : AI_FAILURE); + return (0 == select_fseek(mFile, (int64_t)pOffset,(int)pOrigin) ? AI_SUCCESS : AI_FAILURE); } // ---------------------------------------------------------------------------------- @@ -102,7 +131,7 @@ size_t DefaultIOStream::Tell() const if (!mFile) { return 0; } - return ::ftell(mFile); + return select_ftell(mFile); } // ---------------------------------------------------------------------------------- diff --git a/code/Common/DefaultIOSystem.cpp b/code/Common/DefaultIOSystem.cpp index d40b67de3..6fdc24dd8 100644 --- a/code/Common/DefaultIOSystem.cpp +++ b/code/Common/DefaultIOSystem.cpp @@ -61,83 +61,66 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -// maximum path length -// XXX http://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html -#ifdef PATH_MAX -# define PATHLIMIT PATH_MAX -#else -# define PATHLIMIT 4096 +#ifdef _WIN32 +static std::wstring Utf8ToWide(const char* in) +{ + int size = MultiByteToWideChar(CP_UTF8, 0, in, -1, nullptr, 0); + // size includes terminating null; std::wstring adds null automatically + std::wstring out(static_cast(size) - 1, L'\0'); + MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], size); + return out; +} + +static std::string WideToUtf8(const wchar_t* in) +{ + int size = WideCharToMultiByte(CP_UTF8, 0, in, -1, nullptr, 0, nullptr, nullptr); + // size includes terminating null; std::string adds null automatically + std::string out(static_cast(size) - 1, '\0'); + WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], size, nullptr, nullptr); + return out; +} #endif // ------------------------------------------------------------------------------------------------ // Tests for the existence of a file at the given path. -bool DefaultIOSystem::Exists( const char* pFile) const +bool DefaultIOSystem::Exists(const char* pFile) const { #ifdef _WIN32 - wchar_t fileName16[PATHLIMIT]; - -#ifndef WindowsStore - bool isUnicode = IsTextUnicode(pFile, static_cast(strlen(pFile)), NULL) != 0; - if (isUnicode) { - - MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, pFile, -1, fileName16, PATHLIMIT); - struct __stat64 filestat; - if (0 != _wstat64(fileName16, &filestat)) { - return false; - } - } else { -#endif - FILE* file = ::fopen(pFile, "rb"); - if (!file) - return false; - - ::fclose(file); -#ifndef WindowsStore + struct __stat64 filestat; + if (_wstat64(Utf8ToWide(pFile).c_str(), &filestat) != 0) { + return false; } -#endif #else - FILE* file = ::fopen( pFile, "rb"); - if( !file) + FILE* file = ::fopen(pFile, "rb"); + if (!file) return false; - ::fclose( file); + ::fclose(file); #endif return true; } // ------------------------------------------------------------------------------------------------ // Open a new file with a given path. -IOStream* DefaultIOSystem::Open( const char* strFile, const char* strMode) +IOStream* DefaultIOSystem::Open(const char* strFile, const char* strMode) { - ai_assert(NULL != strFile); - ai_assert(NULL != strMode); + ai_assert(strFile != nullptr); + ai_assert(strMode != nullptr); FILE* file; #ifdef _WIN32 - wchar_t fileName16[PATHLIMIT]; -#ifndef WindowsStore - bool isUnicode = IsTextUnicode(strFile, static_cast(strlen(strFile)), NULL) != 0; - if (isUnicode) { - MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, strFile, -1, fileName16, PATHLIMIT); - std::string mode8(strMode); - file = ::_wfopen(fileName16, std::wstring(mode8.begin(), mode8.end()).c_str()); - } else { -#endif - file = ::fopen(strFile, strMode); -#ifndef WindowsStore - } -#endif + file = ::_wfopen(Utf8ToWide(strFile).c_str(), Utf8ToWide(strMode).c_str()); #else file = ::fopen(strFile, strMode); #endif - if (nullptr == file) + if (!file) return nullptr; - return new DefaultIOStream(file, (std::string) strFile); + return new DefaultIOStream(file, strFile); } // ------------------------------------------------------------------------------------------------ // Closes the given file and releases all resources associated with it. -void DefaultIOSystem::Close( IOStream* pFile) +void DefaultIOSystem::Close(IOStream* pFile) { delete pFile; } @@ -155,78 +138,56 @@ char DefaultIOSystem::getOsSeparator() const // ------------------------------------------------------------------------------------------------ // IOSystem default implementation (ComparePaths isn't a pure virtual function) -bool IOSystem::ComparePaths (const char* one, const char* second) const +bool IOSystem::ComparePaths(const char* one, const char* second) const { - return !ASSIMP_stricmp(one,second); + return !ASSIMP_stricmp(one, second); } // ------------------------------------------------------------------------------------------------ // Convert a relative path into an absolute path -inline static void MakeAbsolutePath (const char* in, char* _out) +inline static std::string MakeAbsolutePath(const char* in) { - ai_assert(in && _out); -#if defined( _MSC_VER ) || defined( __MINGW32__ ) -#ifndef WindowsStore - bool isUnicode = IsTextUnicode(in, static_cast(strlen(in)), NULL) != 0; - if (isUnicode) { - wchar_t out16[PATHLIMIT]; - wchar_t in16[PATHLIMIT]; - MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, in, -1, out16, PATHLIMIT); - wchar_t* ret = ::_wfullpath(out16, in16, PATHLIMIT); - if (ret) { - WideCharToMultiByte(CP_UTF8, MB_PRECOMPOSED, out16, -1, _out, PATHLIMIT, nullptr, nullptr); - } - if (!ret) { - // preserve the input path, maybe someone else is able to fix - // the path before it is accessed (e.g. our file system filter) - ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in)); - strcpy(_out, in); - } - - } else { -#endif - char* ret = :: _fullpath(_out, in, PATHLIMIT); - if (!ret) { - // preserve the input path, maybe someone else is able to fix - // the path before it is accessed (e.g. our file system filter) - ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in)); - strcpy(_out, in); - } -#ifndef WindowsStore + ai_assert(in); + std::string out; +#ifdef _WIN32 + wchar_t* ret = ::_wfullpath(nullptr, Utf8ToWide(in).c_str(), 0); + if (ret) { + out = WideToUtf8(ret); + free(ret); + } +#else + char* ret = realpath(in, nullptr); + if (ret) { + out = ret; + free(ret); } #endif -#else - // use realpath - char* ret = realpath(in, _out); - if(!ret) { + if (!ret) { // preserve the input path, maybe someone else is able to fix // the path before it is accessed (e.g. our file system filter) ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in)); - strcpy(_out,in); + out = in; } -#endif + return out; } // ------------------------------------------------------------------------------------------------ // DefaultIOSystem's more specialized implementation -bool DefaultIOSystem::ComparePaths (const char* one, const char* second) const +bool DefaultIOSystem::ComparePaths(const char* one, const char* second) const { // chances are quite good both paths are formatted identically, // so we can hopefully return here already - if( !ASSIMP_stricmp(one,second) ) + if (!ASSIMP_stricmp(one, second)) return true; - char temp1[PATHLIMIT]; - char temp2[PATHLIMIT]; + std::string temp1 = MakeAbsolutePath(one); + std::string temp2 = MakeAbsolutePath(second); - MakeAbsolutePath (one, temp1); - MakeAbsolutePath (second, temp2); - - return !ASSIMP_stricmp(temp1,temp2); + return !ASSIMP_stricmp(temp1, temp2); } // ------------------------------------------------------------------------------------------------ -std::string DefaultIOSystem::fileName( const std::string &path ) +std::string DefaultIOSystem::fileName(const std::string& path) { std::string ret = path; std::size_t last = ret.find_last_of("\\/"); @@ -235,16 +196,16 @@ std::string DefaultIOSystem::fileName( const std::string &path ) } // ------------------------------------------------------------------------------------------------ -std::string DefaultIOSystem::completeBaseName( const std::string &path ) +std::string DefaultIOSystem::completeBaseName(const std::string& path) { std::string ret = fileName(path); std::size_t pos = ret.find_last_of('.'); - if(pos != ret.npos) ret = ret.substr(0, pos); + if (pos != std::string::npos) ret = ret.substr(0, pos); return ret; } // ------------------------------------------------------------------------------------------------ -std::string DefaultIOSystem::absolutePath( const std::string &path ) +std::string DefaultIOSystem::absolutePath(const std::string& path) { std::string ret = path; std::size_t last = ret.find_last_of("\\/"); @@ -253,5 +214,3 @@ std::string DefaultIOSystem::absolutePath( const std::string &path ) } // ------------------------------------------------------------------------------------------------ - -#undef PATHLIMIT diff --git a/code/Common/DefaultLogger.cpp b/code/Common/DefaultLogger.cpp index de3528d2b..eee53bd7c 100644 --- a/code/Common/DefaultLogger.cpp +++ b/code/Common/DefaultLogger.cpp @@ -107,7 +107,7 @@ LogStream* LogStream::createDefaultStream(aiDefaultLogStream streams, return nullptr; #endif - // Platform-independent default streams + // Platform-independent default streams case aiDefaultLogStream_STDERR: return new StdOStreamLogStream(std::cerr); case aiDefaultLogStream_STDOUT: @@ -121,7 +121,7 @@ LogStream* LogStream::createDefaultStream(aiDefaultLogStream streams, }; // For compilers without dead code path detection - return NULL; + return nullptr; } // ---------------------------------------------------------------------------------- diff --git a/code/Common/Exporter.cpp b/code/Common/Exporter.cpp index 58cad3be8..8a95ceae5 100644 --- a/code/Common/Exporter.cpp +++ b/code/Common/Exporter.cpp @@ -102,94 +102,92 @@ void ExportSceneX3D(const char*, IOSystem*, const aiScene*, const ExportProperti void ExportSceneFBX(const char*, IOSystem*, const aiScene*, const ExportProperties*); void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*); void ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* ); +void ExportSceneM3D(const char*, IOSystem*, const aiScene*, const ExportProperties*); +void ExportSceneA3D(const char*, IOSystem*, const aiScene*, const ExportProperties*); void ExportAssimp2Json(const char* , IOSystem*, const aiScene* , const Assimp::ExportProperties*); -// ------------------------------------------------------------------------------------------------ -// global array of all export formats which Assimp supports in its current build -Exporter::ExportFormatEntry gExporters[] = -{ + +static void setupExporterArray(std::vector &exporters) { #ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER - Exporter::ExportFormatEntry( "collada", "COLLADA - Digital Asset Exchange Schema", "dae", &ExportSceneCollada ), + exporters.push_back(Exporter::ExportFormatEntry("collada", "COLLADA - Digital Asset Exchange Schema", "dae", &ExportSceneCollada)); #endif #ifndef ASSIMP_BUILD_NO_X_EXPORTER - Exporter::ExportFormatEntry( "x", "X Files", "x", &ExportSceneXFile, - aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs ), + exporters.push_back(Exporter::ExportFormatEntry("x", "X Files", "x", &ExportSceneXFile, + aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs)); #endif #ifndef ASSIMP_BUILD_NO_STEP_EXPORTER - Exporter::ExportFormatEntry( "stp", "Step Files", "stp", &ExportSceneStep, 0 ), + exporters.push_back(Exporter::ExportFormatEntry("stp", "Step Files", "stp", &ExportSceneStep, 0)); #endif #ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER - Exporter::ExportFormatEntry( "obj", "Wavefront OBJ format", "obj", &ExportSceneObj, - aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */ ), - Exporter::ExportFormatEntry( "objnomtl", "Wavefront OBJ format without material file", "obj", &ExportSceneObjNoMtl, - aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */ ), + exporters.push_back(Exporter::ExportFormatEntry("obj", "Wavefront OBJ format", "obj", &ExportSceneObj, + aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */)); + exporters.push_back(Exporter::ExportFormatEntry("objnomtl", "Wavefront OBJ format without material file", "obj", &ExportSceneObjNoMtl, + aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */)); #endif #ifndef ASSIMP_BUILD_NO_STL_EXPORTER - Exporter::ExportFormatEntry( "stl", "Stereolithography", "stl" , &ExportSceneSTL, - aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices - ), - Exporter::ExportFormatEntry( "stlb", "Stereolithography (binary)", "stl" , &ExportSceneSTLBinary, - aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices - ), + exporters.push_back(Exporter::ExportFormatEntry("stl", "Stereolithography", "stl", &ExportSceneSTL, + aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices)); + exporters.push_back(Exporter::ExportFormatEntry("stlb", "Stereolithography (binary)", "stl", &ExportSceneSTLBinary, + aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices)); #endif #ifndef ASSIMP_BUILD_NO_PLY_EXPORTER - Exporter::ExportFormatEntry( "ply", "Stanford Polygon Library", "ply" , &ExportScenePly, - aiProcess_PreTransformVertices - ), - Exporter::ExportFormatEntry( "plyb", "Stanford Polygon Library (binary)", "ply", &ExportScenePlyBinary, - aiProcess_PreTransformVertices - ), + exporters.push_back(Exporter::ExportFormatEntry("ply", "Stanford Polygon Library", "ply", &ExportScenePly, + aiProcess_PreTransformVertices)); + exporters.push_back(Exporter::ExportFormatEntry("plyb", "Stanford Polygon Library (binary)", "ply", &ExportScenePlyBinary, + aiProcess_PreTransformVertices)); #endif #ifndef ASSIMP_BUILD_NO_3DS_EXPORTER - Exporter::ExportFormatEntry( "3ds", "Autodesk 3DS (legacy)", "3ds" , &ExportScene3DS, - aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_JoinIdenticalVertices ), + exporters.push_back(Exporter::ExportFormatEntry("3ds", "Autodesk 3DS (legacy)", "3ds", &ExportScene3DS, + aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_JoinIdenticalVertices)); #endif #ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER - Exporter::ExportFormatEntry( "gltf2", "GL Transmission Format v. 2", "gltf", &ExportSceneGLTF2, - aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ), - Exporter::ExportFormatEntry( "glb2", "GL Transmission Format v. 2 (binary)", "glb", &ExportSceneGLB2, - aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ), - Exporter::ExportFormatEntry( "gltf", "GL Transmission Format", "gltf", &ExportSceneGLTF, - aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ), - Exporter::ExportFormatEntry( "glb", "GL Transmission Format (binary)", "glb", &ExportSceneGLB, - aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ), + exporters.push_back(Exporter::ExportFormatEntry("gltf2", "GL Transmission Format v. 2", "gltf", &ExportSceneGLTF2, + aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType)); + exporters.push_back(Exporter::ExportFormatEntry("glb2", "GL Transmission Format v. 2 (binary)", "glb", &ExportSceneGLB2, + aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType)); + exporters.push_back(Exporter::ExportFormatEntry("gltf", "GL Transmission Format", "gltf", &ExportSceneGLTF, + aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType)); + exporters.push_back(Exporter::ExportFormatEntry("glb", "GL Transmission Format (binary)", "glb", &ExportSceneGLB, + aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType)); #endif #ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER - Exporter::ExportFormatEntry( "assbin", "Assimp Binary", "assbin" , &ExportSceneAssbin, 0 ), + exporters.push_back(Exporter::ExportFormatEntry("assbin", "Assimp Binary File", "assbin", &ExportSceneAssbin, 0)); #endif #ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER - Exporter::ExportFormatEntry( "assxml", "Assxml Document", "assxml" , &ExportSceneAssxml, 0 ), + exporters.push_back(Exporter::ExportFormatEntry("assxml", "Assimp XML Document", "assxml", &ExportSceneAssxml, 0)); #endif #ifndef ASSIMP_BUILD_NO_X3D_EXPORTER - Exporter::ExportFormatEntry( "x3d", "Extensible 3D", "x3d" , &ExportSceneX3D, 0 ), + exporters.push_back(Exporter::ExportFormatEntry("x3d", "Extensible 3D", "x3d", &ExportSceneX3D, 0)); #endif #ifndef ASSIMP_BUILD_NO_FBX_EXPORTER - Exporter::ExportFormatEntry( "fbx", "Autodesk FBX (binary)", "fbx", &ExportSceneFBX, 0 ), - Exporter::ExportFormatEntry( "fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0 ), + exporters.push_back(Exporter::ExportFormatEntry("fbx", "Autodesk FBX (binary)", "fbx", &ExportSceneFBX, 0)); + exporters.push_back(Exporter::ExportFormatEntry("fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0)); +#endif + +#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("m3d", "Model 3D (binary)", "m3d", &ExportSceneM3D, 0)); + exporters.push_back(Exporter::ExportFormatEntry("a3d", "Model 3D (ascii)", "m3d", &ExportSceneA3D, 0)); #endif #ifndef ASSIMP_BUILD_NO_3MF_EXPORTER - Exporter::ExportFormatEntry( "3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0 ), + exporters.push_back(Exporter::ExportFormatEntry("3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0)); #endif #ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER - Exporter::ExportFormatEntry("json", "Plain JSON representation of the Assimp scene data structure", "json", &ExportAssimp2Json, 0) + exporters.push_back(Exporter::ExportFormatEntry("assjson", "Assimp JSON Document", "json", &ExportAssimp2Json, 0)); #endif -}; - -#define ASSIMP_NUM_EXPORTERS (sizeof(gExporters)/sizeof(gExporters[0])) - +} class ExporterPimpl { public: @@ -205,10 +203,7 @@ public: GetPostProcessingStepInstanceList(mPostProcessingSteps); // grab all built-in exporters - if ( 0 != ( ASSIMP_NUM_EXPORTERS ) ) { - mExporters.resize( ASSIMP_NUM_EXPORTERS ); - std::copy( gExporters, gExporters + ASSIMP_NUM_EXPORTERS, mExporters.begin() ); - } + setupExporterArray(mExporters); } ~ExporterPimpl() { @@ -252,24 +247,28 @@ Exporter :: Exporter() // ------------------------------------------------------------------------------------------------ Exporter::~Exporter() { - FreeBlob(); + ai_assert(nullptr != pimpl); + FreeBlob(); delete pimpl; } // ------------------------------------------------------------------------------------------------ void Exporter::SetIOHandler( IOSystem* pIOHandler) { - pimpl->mIsDefaultIOHandler = !pIOHandler; + ai_assert(nullptr != pimpl); + pimpl->mIsDefaultIOHandler = !pIOHandler; pimpl->mIOSystem.reset(pIOHandler); } // ------------------------------------------------------------------------------------------------ IOSystem* Exporter::GetIOHandler() const { - return pimpl->mIOSystem.get(); + ai_assert(nullptr != pimpl); + return pimpl->mIOSystem.get(); } // ------------------------------------------------------------------------------------------------ bool Exporter::IsDefaultIOHandler() const { - return pimpl->mIsDefaultIOHandler; + ai_assert(nullptr != pimpl); + return pimpl->mIsDefaultIOHandler; } // ------------------------------------------------------------------------------------------------ @@ -295,6 +294,7 @@ void Exporter::SetProgressHandler(ProgressHandler* pHandler) { // ------------------------------------------------------------------------------------------------ const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const char* pFormatId, unsigned int pPreprocessing, const ExportProperties* pProperties) { + ai_assert(nullptr != pimpl); if (pimpl->blob) { delete pimpl->blob; pimpl->blob = nullptr; @@ -315,44 +315,16 @@ const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const cha return pimpl->blob; } -// ------------------------------------------------------------------------------------------------ -bool IsVerboseFormat(const aiMesh* mesh) { - // avoid slow vector specialization - std::vector seen(mesh->mNumVertices,0); - for(unsigned int i = 0; i < mesh->mNumFaces; ++i) { - const aiFace& f = mesh->mFaces[i]; - for(unsigned int j = 0; j < f.mNumIndices; ++j) { - if(++seen[f.mIndices[j]] == 2) { - // found a duplicate index - return false; - } - } - } - - return true; -} - -// ------------------------------------------------------------------------------------------------ -bool IsVerboseFormat(const aiScene* pScene) { - for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) { - if(!IsVerboseFormat(pScene->mMeshes[i])) { - return false; - } - } - - return true; -} - // ------------------------------------------------------------------------------------------------ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const char* pPath, unsigned int pPreprocessing, const ExportProperties* pProperties) { ASSIMP_BEGIN_EXCEPTION_REGION(); - + ai_assert(nullptr != pimpl); // when they create scenes from scratch, users will likely create them not in verbose // format. They will likely not be aware that there is a flag in the scene to indicate // this, however. To avoid surprises and bug reports, we check for duplicates in // meshes upfront. - const bool is_verbose_format = !(pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) || IsVerboseFormat(pScene); + const bool is_verbose_format = !(pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) || MakeVerboseFormatProcess::IsVerboseFormat(pScene); pimpl->mProgressHandler->UpdateFileWrite(0, 4); @@ -472,7 +444,10 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c } ExportProperties emptyProperties; // Never pass NULL ExportProperties so Exporters don't have to worry. - exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProperties ? pProperties : &emptyProperties); + ExportProperties* pProp = pProperties ? (ExportProperties*)pProperties : &emptyProperties; + pProp->SetPropertyBool("bJoinIdenticalVertices", must_join_again); + exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProp); + exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProp); pimpl->mProgressHandler->UpdateFileWrite(4, 4); } catch (DeadlyExportError& err) { @@ -491,11 +466,13 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c // ------------------------------------------------------------------------------------------------ const char* Exporter::GetErrorString() const { + ai_assert(nullptr != pimpl); return pimpl->mError.c_str(); } // ------------------------------------------------------------------------------------------------ void Exporter::FreeBlob() { + ai_assert(nullptr != pimpl); delete pimpl->blob; pimpl->blob = nullptr; @@ -504,30 +481,34 @@ void Exporter::FreeBlob() { // ------------------------------------------------------------------------------------------------ const aiExportDataBlob* Exporter::GetBlob() const { - return pimpl->blob; + ai_assert(nullptr != pimpl); + return pimpl->blob; } // ------------------------------------------------------------------------------------------------ const aiExportDataBlob* Exporter::GetOrphanedBlob() const { - const aiExportDataBlob* tmp = pimpl->blob; + ai_assert(nullptr != pimpl); + const aiExportDataBlob *tmp = pimpl->blob; pimpl->blob = nullptr; return tmp; } // ------------------------------------------------------------------------------------------------ size_t Exporter::GetExportFormatCount() const { + ai_assert(nullptr != pimpl); return pimpl->mExporters.size(); } // ------------------------------------------------------------------------------------------------ const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) const { - if (index >= GetExportFormatCount()) { + ai_assert(nullptr != pimpl); + if (index >= GetExportFormatCount()) { return nullptr; } // Return from static storage if the requested index is built-in. - if (index < sizeof(gExporters) / sizeof(gExporters[0])) { - return &gExporters[index].mDescription; + if (index < pimpl->mExporters.size()) { + return &pimpl->mExporters[index].mDescription; } return &pimpl->mExporters[index].mDescription; @@ -535,7 +516,8 @@ const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) c // ------------------------------------------------------------------------------------------------ aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) { - for(const ExportFormatEntry& e : pimpl->mExporters) { + ai_assert(nullptr != pimpl); + for (const ExportFormatEntry &e : pimpl->mExporters) { if (!strcmp(e.mDescription.id,desc.mDescription.id)) { return aiReturn_FAILURE; } @@ -547,7 +529,8 @@ aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) { // ------------------------------------------------------------------------------------------------ void Exporter::UnregisterExporter(const char* id) { - for(std::vector::iterator it = pimpl->mExporters.begin(); + ai_assert(nullptr != pimpl); + for (std::vector::iterator it = pimpl->mExporters.begin(); it != pimpl->mExporters.end(); ++it) { if (!strcmp((*it).mDescription.id,id)) { pimpl->mExporters.erase(it); diff --git a/code/Common/ImporterRegistry.cpp b/code/Common/ImporterRegistry.cpp index 32ac3b416..b9f28f035 100644 --- a/code/Common/ImporterRegistry.cpp +++ b/code/Common/ImporterRegistry.cpp @@ -197,6 +197,9 @@ corresponding preprocessor flag to selectively disable formats. #ifndef ASSIMP_BUILD_NO_MMD_IMPORTER # include "MMD/MMDImporter.h" #endif +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER +# include "M3D/M3DImporter.h" +#endif #ifndef ASSIMP_BUILD_NO_STEP_IMPORTER # include "Importer/StepFile/StepFileImporter.h" #endif @@ -223,6 +226,9 @@ void GetImporterInstanceList(std::vector< BaseImporter* >& out) #if (!defined ASSIMP_BUILD_NO_3DS_IMPORTER) out.push_back( new Discreet3DSImporter()); #endif +#if (!defined ASSIMP_BUILD_NO_M3D_IMPORTER) + out.push_back( new M3DImporter()); +#endif #if (!defined ASSIMP_BUILD_NO_MD3_IMPORTER) out.push_back( new MD3Importer()); #endif diff --git a/code/Common/PostStepRegistry.cpp b/code/Common/PostStepRegistry.cpp index ef58f8ddf..8ff4af040 100644 --- a/code/Common/PostStepRegistry.cpp +++ b/code/Common/PostStepRegistry.cpp @@ -131,11 +131,15 @@ corresponding preprocessor flag to selectively disable steps. #if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS) # include "PostProcessing/ScaleProcess.h" #endif +#if (!defined ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS) +# include "PostProcessing/ArmaturePopulate.h" +#endif #if (!defined ASSIMP_BUILD_NO_GENBOUNDINGBOXES_PROCESS) # include "PostProcessing/GenBoundingBoxesProcess.h" #endif + namespace Assimp { // ------------------------------------------------------------------------------------------------ @@ -180,6 +184,9 @@ void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out) #if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS) out.push_back( new ScaleProcess()); #endif +#if (!defined ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS) + out.push_back( new ArmaturePopulate()); +#endif #if (!defined ASSIMP_BUILD_NO_PRETRANSFORMVERTICES_PROCESS) out.push_back( new PretransformVertices()); #endif diff --git a/code/Common/SceneCombiner.cpp b/code/Common/SceneCombiner.cpp index e445bd743..f7b13cc95 100644 --- a/code/Common/SceneCombiner.cpp +++ b/code/Common/SceneCombiner.cpp @@ -1091,6 +1091,35 @@ void SceneCombiner::Copy( aiMesh** _dest, const aiMesh* src ) { aiFace& f = dest->mFaces[i]; GetArrayCopy(f.mIndices,f.mNumIndices); } + + // make a deep copy of all blend shapes + CopyPtrArray(dest->mAnimMeshes, dest->mAnimMeshes, dest->mNumAnimMeshes); +} + +// ------------------------------------------------------------------------------------------------ +void SceneCombiner::Copy(aiAnimMesh** _dest, const aiAnimMesh* src) { + if (nullptr == _dest || nullptr == src) { + return; + } + + aiAnimMesh* dest = *_dest = new aiAnimMesh(); + + // get a flat copy + ::memcpy(dest, src, sizeof(aiAnimMesh)); + + // and reallocate all arrays + GetArrayCopy(dest->mVertices, dest->mNumVertices); + GetArrayCopy(dest->mNormals, dest->mNumVertices); + GetArrayCopy(dest->mTangents, dest->mNumVertices); + GetArrayCopy(dest->mBitangents, dest->mNumVertices); + + unsigned int n = 0; + while (dest->HasTextureCoords(n)) + GetArrayCopy(dest->mTextureCoords[n++], dest->mNumVertices); + + n = 0; + while (dest->HasVertexColors(n)) + GetArrayCopy(dest->mColors[n++], dest->mNumVertices); } // ------------------------------------------------------------------------------------------------ @@ -1167,6 +1196,7 @@ void SceneCombiner::Copy( aiAnimation** _dest, const aiAnimation* src ) { // and reallocate all arrays CopyPtrArray( dest->mChannels, src->mChannels, dest->mNumChannels ); + CopyPtrArray( dest->mMorphMeshChannels, src->mMorphMeshChannels, dest->mNumMorphMeshChannels ); } // ------------------------------------------------------------------------------------------------ @@ -1186,6 +1216,26 @@ void SceneCombiner::Copy(aiNodeAnim** _dest, const aiNodeAnim* src) { GetArrayCopy( dest->mRotationKeys, dest->mNumRotationKeys ); } +void SceneCombiner::Copy(aiMeshMorphAnim** _dest, const aiMeshMorphAnim* src) { + if ( nullptr == _dest || nullptr == src ) { + return; + } + + aiMeshMorphAnim* dest = *_dest = new aiMeshMorphAnim(); + + // get a flat copy + ::memcpy(dest,src,sizeof(aiMeshMorphAnim)); + + // and reallocate all arrays + GetArrayCopy( dest->mKeys, dest->mNumKeys ); + for (ai_uint i = 0; i < dest->mNumKeys;++i) { + dest->mKeys[i].mValues = new unsigned int[dest->mKeys[i].mNumValuesAndWeights]; + dest->mKeys[i].mWeights = new double[dest->mKeys[i].mNumValuesAndWeights]; + ::memcpy(dest->mKeys[i].mValues, src->mKeys[i].mValues, dest->mKeys[i].mNumValuesAndWeights * sizeof(unsigned int)); + ::memcpy(dest->mKeys[i].mWeights, src->mKeys[i].mWeights, dest->mKeys[i].mNumValuesAndWeights * sizeof(double)); + } +} + // ------------------------------------------------------------------------------------------------ void SceneCombiner::Copy( aiCamera** _dest,const aiCamera* src) { if ( nullptr == _dest || nullptr == src ) { diff --git a/code/Common/Version.cpp b/code/Common/Version.cpp index cc94340ac..ea4c996f0 100644 --- a/code/Common/Version.cpp +++ b/code/Common/Version.cpp @@ -46,8 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "ScenePrivate.h" -static const unsigned int MajorVersion = 4; -static const unsigned int MinorVersion = 1; +#include "revision.h" // -------------------------------------------------------------------------------- // Legal information string - don't remove this. @@ -56,9 +55,9 @@ static const char* LEGAL_INFORMATION = "Open Asset Import Library (Assimp).\n" "A free C/C++ library to import various 3D file formats into applications\n\n" -"(c) 2008-2017, assimp team\n" +"(c) 2006-2019, assimp team\n" "License under the terms and conditions of the 3-clause BSD license\n" -"http://assimp.sourceforge.net\n" +"http://assimp.org\n" ; // ------------------------------------------------------------------------------------------------ @@ -67,16 +66,22 @@ ASSIMP_API const char* aiGetLegalString () { return LEGAL_INFORMATION; } +// ------------------------------------------------------------------------------------------------ +// Get Assimp patch version +ASSIMP_API unsigned int aiGetVersionPatch() { + return VER_PATCH; +} + // ------------------------------------------------------------------------------------------------ // Get Assimp minor version ASSIMP_API unsigned int aiGetVersionMinor () { - return MinorVersion; + return VER_MINOR; } // ------------------------------------------------------------------------------------------------ // Get Assimp major version ASSIMP_API unsigned int aiGetVersionMajor () { - return MajorVersion; + return VER_MAJOR; } // ------------------------------------------------------------------------------------------------ @@ -104,9 +109,6 @@ ASSIMP_API unsigned int aiGetCompileFlags () { return flags; } -// include current build revision, which is even updated from time to time -- :-) -#include "revision.h" - // ------------------------------------------------------------------------------------------------ ASSIMP_API unsigned int aiGetVersionRevision() { return GitVersion; diff --git a/code/Common/ZipArchiveIOSystem.cpp b/code/Common/ZipArchiveIOSystem.cpp new file mode 100644 index 000000000..7c37a05f9 --- /dev/null +++ b/code/Common/ZipArchiveIOSystem.cpp @@ -0,0 +1,539 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file ZipArchiveIOSystem.cpp + * @brief Zip File I/O implementation for #Importer + */ + +#include +#include + +#include + +#include +#include + +#ifdef ASSIMP_USE_HUNTER +# include +#else +# include +#endif + +namespace Assimp { + // ---------------------------------------------------------------- + // Wraps an existing Assimp::IOSystem for unzip + class IOSystem2Unzip { + public: + static voidpf open(voidpf opaque, const char* filename, int mode); + static uLong read(voidpf opaque, voidpf stream, void* buf, uLong size); + static uLong write(voidpf opaque, voidpf stream, const void* buf, uLong size); + static long tell(voidpf opaque, voidpf stream); + static long seek(voidpf opaque, voidpf stream, uLong offset, int origin); + static int close(voidpf opaque, voidpf stream); + static int testerror(voidpf opaque, voidpf stream); + static zlib_filefunc_def get(IOSystem* pIOHandler); + }; + + voidpf IOSystem2Unzip::open(voidpf opaque, const char* filename, int mode) { + IOSystem* io_system = reinterpret_cast(opaque); + + const char* mode_fopen = nullptr; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) { + mode_fopen = "rb"; + } + else { + if (mode & ZLIB_FILEFUNC_MODE_EXISTING) { + mode_fopen = "r+b"; + } + else { + if (mode & ZLIB_FILEFUNC_MODE_CREATE) { + mode_fopen = "wb"; + } + } + } + + return (voidpf)io_system->Open(filename, mode_fopen); + } + + uLong IOSystem2Unzip::read(voidpf /*opaque*/, voidpf stream, void* buf, uLong size) { + IOStream* io_stream = (IOStream*)stream; + + return static_cast(io_stream->Read(buf, 1, size)); + } + + uLong IOSystem2Unzip::write(voidpf /*opaque*/, voidpf stream, const void* buf, uLong size) { + IOStream* io_stream = (IOStream*)stream; + + return static_cast(io_stream->Write(buf, 1, size)); + } + + long IOSystem2Unzip::tell(voidpf /*opaque*/, voidpf stream) { + IOStream* io_stream = (IOStream*)stream; + + return static_cast(io_stream->Tell()); + } + + long IOSystem2Unzip::seek(voidpf /*opaque*/, voidpf stream, uLong offset, int origin) { + IOStream* io_stream = (IOStream*)stream; + + aiOrigin assimp_origin; + switch (origin) { + default: + case ZLIB_FILEFUNC_SEEK_CUR: + assimp_origin = aiOrigin_CUR; + break; + case ZLIB_FILEFUNC_SEEK_END: + assimp_origin = aiOrigin_END; + break; + case ZLIB_FILEFUNC_SEEK_SET: + assimp_origin = aiOrigin_SET; + break; + } + + return (io_stream->Seek(offset, assimp_origin) == aiReturn_SUCCESS ? 0 : -1); + } + + int IOSystem2Unzip::close(voidpf opaque, voidpf stream) { + IOSystem* io_system = (IOSystem*)opaque; + IOStream* io_stream = (IOStream*)stream; + + io_system->Close(io_stream); + + return 0; + } + + int IOSystem2Unzip::testerror(voidpf /*opaque*/, voidpf /*stream*/) { + return 0; + } + + zlib_filefunc_def IOSystem2Unzip::get(IOSystem* pIOHandler) { + zlib_filefunc_def mapping; + +#ifdef ASSIMP_USE_HUNTER + mapping.zopen_file = (open_file_func)open; + mapping.zread_file = (read_file_func)read; + mapping.zwrite_file = (write_file_func)write; + mapping.ztell_file = (tell_file_func)tell; + mapping.zseek_file = (seek_file_func)seek; + mapping.zclose_file = (close_file_func)close; + mapping.zerror_file = (error_file_func)testerror; +#else + mapping.zopen_file = open; + mapping.zread_file = read; + mapping.zwrite_file = write; + mapping.ztell_file = tell; + mapping.zseek_file = seek; + mapping.zclose_file = close; + mapping.zerror_file = testerror; +#endif + mapping.opaque = reinterpret_cast(pIOHandler); + + return mapping; + } + + // ---------------------------------------------------------------- + // A read-only file inside a ZIP + + class ZipFile : public IOStream { + friend class ZipFileInfo; + explicit ZipFile(size_t size); + public: + virtual ~ZipFile(); + + // IOStream interface + size_t Read(void* pvBuffer, size_t pSize, size_t pCount) override; + size_t Write(const void* /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/) override { return 0; } + size_t FileSize() const override; + aiReturn Seek(size_t pOffset, aiOrigin pOrigin) override; + size_t Tell() const override; + void Flush() override {} + + private: + size_t m_Size = 0; + size_t m_SeekPtr = 0; + std::unique_ptr m_Buffer; + }; + + + // ---------------------------------------------------------------- + // Info about a read-only file inside a ZIP + class ZipFileInfo + { + public: + explicit ZipFileInfo(unzFile zip_handle, size_t size); + + // Allocate and Extract data from the ZIP + ZipFile * Extract(unzFile zip_handle) const; + + private: + size_t m_Size = 0; + unz_file_pos_s m_ZipFilePos; + }; + + ZipFileInfo::ZipFileInfo(unzFile zip_handle, size_t size) + : m_Size(size) { + ai_assert(m_Size != 0); + // Workaround for MSVC 2013 - C2797 + m_ZipFilePos.num_of_file = 0; + m_ZipFilePos.pos_in_zip_directory = 0; + unzGetFilePos(zip_handle, &(m_ZipFilePos)); + } + + ZipFile * ZipFileInfo::Extract(unzFile zip_handle) const { + // Find in the ZIP. This cannot fail + unz_file_pos_s *filepos = const_cast(&(m_ZipFilePos)); + if (unzGoToFilePos(zip_handle, filepos) != UNZ_OK) + return nullptr; + + if (unzOpenCurrentFile(zip_handle) != UNZ_OK) + return nullptr; + + ZipFile *zip_file = new ZipFile(m_Size); + + if (unzReadCurrentFile(zip_handle, zip_file->m_Buffer.get(), static_cast(m_Size)) != static_cast(m_Size)) + { + // Failed, release the memory + delete zip_file; + zip_file = nullptr; + } + + ai_assert(unzCloseCurrentFile(zip_handle) == UNZ_OK); + return zip_file; + } + + ZipFile::ZipFile(size_t size) + : m_Size(size) { + ai_assert(m_Size != 0); + m_Buffer = std::unique_ptr(new uint8_t[m_Size]); + } + + ZipFile::~ZipFile() { + } + + size_t ZipFile::Read(void* pvBuffer, size_t pSize, size_t pCount) { + // Should be impossible + ai_assert(m_Buffer != nullptr); + ai_assert(NULL != pvBuffer && 0 != pSize && 0 != pCount); + + // Clip down to file size + size_t byteSize = pSize * pCount; + if ((byteSize + m_SeekPtr) > m_Size) + { + pCount = (m_Size - m_SeekPtr) / pSize; + byteSize = pSize * pCount; + if (byteSize == 0) + return 0; + } + + std::memcpy(pvBuffer, m_Buffer.get() + m_SeekPtr, byteSize); + + m_SeekPtr += byteSize; + + return pCount; + } + + size_t ZipFile::FileSize() const { + return m_Size; + } + + aiReturn ZipFile::Seek(size_t pOffset, aiOrigin pOrigin) { + switch (pOrigin) + { + case aiOrigin_SET: { + if (pOffset > m_Size) return aiReturn_FAILURE; + m_SeekPtr = pOffset; + return aiReturn_SUCCESS; + } + + case aiOrigin_CUR: { + if ((pOffset + m_SeekPtr) > m_Size) return aiReturn_FAILURE; + m_SeekPtr += pOffset; + return aiReturn_SUCCESS; + } + + case aiOrigin_END: { + if (pOffset > m_Size) return aiReturn_FAILURE; + m_SeekPtr = m_Size - pOffset; + return aiReturn_SUCCESS; + } + default:; + } + + return aiReturn_FAILURE; + } + + size_t ZipFile::Tell() const { + return m_SeekPtr; + } + + // ---------------------------------------------------------------- + // pImpl of the Zip Archive IO + class ZipArchiveIOSystem::Implement { + public: + static const unsigned int FileNameSize = 256; + + Implement(IOSystem* pIOHandler, const char* pFilename, const char* pMode); + ~Implement(); + + bool isOpen() const; + void getFileList(std::vector& rFileList); + void getFileListExtension(std::vector& rFileList, const std::string& extension); + bool Exists(std::string& filename); + IOStream* OpenFile(std::string& filename); + + static void SimplifyFilename(std::string& filename); + + private: + void MapArchive(); + + private: + typedef std::map ZipFileInfoMap; + + unzFile m_ZipFileHandle = nullptr; + ZipFileInfoMap m_ArchiveMap; + }; + + ZipArchiveIOSystem::Implement::Implement(IOSystem* pIOHandler, const char* pFilename, const char* pMode) { + ai_assert(strcmp(pMode, "r") == 0); + ai_assert(pFilename != nullptr); + if (pFilename[0] == 0) + return; + + zlib_filefunc_def mapping = IOSystem2Unzip::get(pIOHandler); + m_ZipFileHandle = unzOpen2(pFilename, &mapping); + } + + ZipArchiveIOSystem::Implement::~Implement() { + m_ArchiveMap.clear(); + + if (m_ZipFileHandle != nullptr) { + unzClose(m_ZipFileHandle); + m_ZipFileHandle = nullptr; + } + } + + void ZipArchiveIOSystem::Implement::MapArchive() { + if (m_ZipFileHandle == nullptr) + return; + + if (!m_ArchiveMap.empty()) + return; + + // At first ensure file is already open + if (unzGoToFirstFile(m_ZipFileHandle) != UNZ_OK) + return; + + // Loop over all files + do { + char filename[FileNameSize]; + unz_file_info fileInfo; + + if (unzGetCurrentFileInfo(m_ZipFileHandle, &fileInfo, filename, FileNameSize, nullptr, 0, nullptr, 0) == UNZ_OK) { + if (fileInfo.uncompressed_size != 0) { + std::string filename_string(filename, fileInfo.size_filename); + SimplifyFilename(filename_string); + m_ArchiveMap.emplace(filename_string, ZipFileInfo(m_ZipFileHandle, fileInfo.uncompressed_size)); + } + } + } while (unzGoToNextFile(m_ZipFileHandle) != UNZ_END_OF_LIST_OF_FILE); + } + + bool ZipArchiveIOSystem::Implement::isOpen() const { + return (m_ZipFileHandle != nullptr); + } + + void ZipArchiveIOSystem::Implement::getFileList(std::vector& rFileList) { + MapArchive(); + rFileList.clear(); + + for (const auto &file : m_ArchiveMap) { + rFileList.push_back(file.first); + } + } + + void ZipArchiveIOSystem::Implement::getFileListExtension(std::vector& rFileList, const std::string& extension) { + MapArchive(); + rFileList.clear(); + + for (const auto &file : m_ArchiveMap) { + if (extension == BaseImporter::GetExtension(file.first)) + rFileList.push_back(file.first); + } + } + + bool ZipArchiveIOSystem::Implement::Exists(std::string& filename) { + MapArchive(); + + ZipFileInfoMap::const_iterator it = m_ArchiveMap.find(filename); + return (it != m_ArchiveMap.end()); + } + + IOStream * ZipArchiveIOSystem::Implement::OpenFile(std::string& filename) { + MapArchive(); + + SimplifyFilename(filename); + + // Find in the map + ZipFileInfoMap::const_iterator zip_it = m_ArchiveMap.find(filename); + if (zip_it == m_ArchiveMap.cend()) + return nullptr; + + const ZipFileInfo &zip_file = (*zip_it).second; + return zip_file.Extract(m_ZipFileHandle); + } + + inline void ReplaceAll(std::string& data, const std::string& before, const std::string& after) { + size_t pos = data.find(before); + while (pos != std::string::npos) + { + data.replace(pos, before.size(), after); + pos = data.find(before, pos + after.size()); + } + } + + inline void ReplaceAllChar(std::string& data, const char before, const char after) { + size_t pos = data.find(before); + while (pos != std::string::npos) + { + data[pos] = after; + pos = data.find(before, pos + 1); + } + } + + void ZipArchiveIOSystem::Implement::SimplifyFilename(std::string& filename) + { + ReplaceAllChar(filename, '\\', '/'); + + // Remove all . and / from the beginning of the path + size_t pos = filename.find_first_not_of("./"); + if (pos != 0) + filename.erase(0, pos); + + // Simplify "my/folder/../file.png" constructions, if any + static const std::string relative("/../"); + const size_t relsize = relative.size() - 1; + pos = filename.find(relative); + while (pos != std::string::npos) + { + // Previous slash + size_t prevpos = filename.rfind('/', pos - 1); + if (prevpos == pos) + filename.erase(0, pos + relative.size()); + else + filename.erase(prevpos, pos + relsize - prevpos); + + pos = filename.find(relative); + } + } + + ZipArchiveIOSystem::ZipArchiveIOSystem(IOSystem* pIOHandler, const char* pFilename, const char* pMode) + : pImpl(new Implement(pIOHandler, pFilename, pMode)) { + } + + // ---------------------------------------------------------------- + // The ZipArchiveIO + ZipArchiveIOSystem::ZipArchiveIOSystem(IOSystem* pIOHandler, const std::string& rFilename, const char* pMode) + : pImpl(new Implement(pIOHandler, rFilename.c_str(), pMode)) + { + } + + ZipArchiveIOSystem::~ZipArchiveIOSystem() { + delete pImpl; + } + + bool ZipArchiveIOSystem::Exists(const char* pFilename) const { + ai_assert(pFilename != nullptr); + + if (pFilename == nullptr) { + return false; + } + + std::string filename(pFilename); + return pImpl->Exists(filename); + } + + // This is always '/' in a ZIP + char ZipArchiveIOSystem::getOsSeparator() const { + return '/'; + } + + // Only supports Reading + IOStream * ZipArchiveIOSystem::Open(const char* pFilename, const char* pMode) { + ai_assert(pFilename != nullptr); + + for (size_t i = 0; pMode[i] != 0; ++i) + { + ai_assert(pMode[i] != 'w'); + if (pMode[i] == 'w') + return nullptr; + } + + std::string filename(pFilename); + return pImpl->OpenFile(filename); + } + + void ZipArchiveIOSystem::Close(IOStream* pFile) { + delete pFile; + } + + bool ZipArchiveIOSystem::isOpen() const { + return (pImpl->isOpen()); + } + + void ZipArchiveIOSystem::getFileList(std::vector& rFileList) const { + return pImpl->getFileList(rFileList); + } + + void ZipArchiveIOSystem::getFileListExtension(std::vector& rFileList, const std::string& extension) const { + return pImpl->getFileListExtension(rFileList, extension); + } + + bool ZipArchiveIOSystem::isZipArchive(IOSystem* pIOHandler, const char* pFilename) { + Implement tmp(pIOHandler, pFilename, "r"); + return tmp.isOpen(); + } + + bool ZipArchiveIOSystem::isZipArchive(IOSystem* pIOHandler, const std::string& rFilename) { + return isZipArchive(pIOHandler, rFilename.c_str()); + } + +} diff --git a/code/Common/scene.cpp b/code/Common/scene.cpp index 2acb348d8..d15619acf 100644 --- a/code/Common/scene.cpp +++ b/code/Common/scene.cpp @@ -44,23 +44,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. aiNode::aiNode() : mName("") -, mParent(NULL) +, mParent(nullptr) , mNumChildren(0) -, mChildren(NULL) +, mChildren(nullptr) , mNumMeshes(0) -, mMeshes(NULL) -, mMetaData(NULL) { +, mMeshes(nullptr) +, mMetaData(nullptr) { // empty } aiNode::aiNode(const std::string& name) : mName(name) -, mParent(NULL) +, mParent(nullptr) , mNumChildren(0) -, mChildren(NULL) +, mChildren(nullptr) , mNumMeshes(0) -, mMeshes(NULL) -, mMetaData(NULL) { +, mMeshes(nullptr) +, mMetaData(nullptr) { // empty } @@ -68,7 +68,7 @@ aiNode::aiNode(const std::string& name) aiNode::~aiNode() { // delete all children recursively // to make sure we won't crash if the data is invalid ... - if (mChildren && mNumChildren) + if (mNumChildren && mChildren) { for (unsigned int a = 0; a < mNumChildren; a++) delete mChildren[a]; diff --git a/code/FBX/FBXCommon.h b/code/FBX/FBXCommon.h index e51644913..b28601c74 100644 --- a/code/FBX/FBXCommon.h +++ b/code/FBX/FBXCommon.h @@ -50,9 +50,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { namespace FBX { - const std::string NULL_RECORD = { // 13 null bytes - '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0' - }; // who knows why + const std::string NULL_RECORD = { // 25 null bytes in 64-bit and 13 null bytes in 32-bit + '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0', + '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0' + }; // who knows why, it looks like two integers 32/64 bit (compressed and uncompressed sizes?) + 1 byte (might be compression type?) const std::string SEPARATOR = {'\x00', '\x01'}; // for use inside strings const std::string MAGIC_NODE_TAG = "_$AssimpFbx$"; // from import const int64_t SECOND = 46186158000; // FBX's kTime unit diff --git a/code/FBX/FBXCompileConfig.h b/code/FBX/FBXCompileConfig.h index 3a3841fa5..03536a182 100644 --- a/code/FBX/FBXCompileConfig.h +++ b/code/FBX/FBXCompileConfig.h @@ -47,6 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define INCLUDED_AI_FBX_COMPILECONFIG_H #include +#include // #if _MSC_VER > 1500 || (defined __GNUC___) @@ -54,16 +55,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # else # define fbx_unordered_map map # define fbx_unordered_multimap multimap +# define fbx_unordered_set set +# define fbx_unordered_multiset multiset #endif #ifdef ASSIMP_FBX_USE_UNORDERED_MULTIMAP # include +# include # if _MSC_VER > 1600 # define fbx_unordered_map unordered_map # define fbx_unordered_multimap unordered_multimap +# define fbx_unordered_set unordered_set +# define fbx_unordered_multiset unordered_multiset # else # define fbx_unordered_map tr1::unordered_map # define fbx_unordered_multimap tr1::unordered_multimap +# define fbx_unordered_set tr1::unordered_set +# define fbx_unordered_multiset tr1::unordered_multiset # endif #endif diff --git a/code/FBX/FBXConverter.cpp b/code/FBX/FBXConverter.cpp index a4c1015bf..d8a22d9f7 100644 --- a/code/FBX/FBXConverter.cpp +++ b/code/FBX/FBXConverter.cpp @@ -55,6 +55,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "FBXImporter.h" #include +#include #include @@ -66,7 +67,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include - +#include +#include +#include namespace Assimp { namespace FBX { @@ -75,9 +78,9 @@ namespace Assimp { #define MAGIC_NODE_TAG "_$AssimpFbx$" -#define CONVERT_FBX_TIME(time) static_cast(time) / 46186158000L +#define CONVERT_FBX_TIME(time) static_cast(time) / 46186158000LL - FBXConverter::FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones, FbxUnit unit ) + FBXConverter::FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones ) : defaultMaterialIndex() , lights() , cameras() @@ -89,13 +92,19 @@ namespace Assimp { , mNodeNames() , anim_fps() , out(out) - , doc(doc) - , mRemoveEmptyBones( removeEmptyBones ) - , mCurrentUnit(FbxUnit::cm) { + , doc(doc) { // animations need to be converted first since this will // populate the node_anim_chain_bits map, which is needed // to determine which nodes need to be generated. ConvertAnimations(); + // Embedded textures in FBX could be connected to nothing but to itself, + // for instance Texture -> Video connection only but not to the main graph, + // The idea here is to traverse all objects to find these Textures and convert them, + // so later during material conversion it will find converted texture in the textures_converted array. + if (doc.Settings().readTextures) + { + ConvertOrphantEmbeddedTextures(); + } ConvertRootNode(); if (doc.Settings().readAllMaterials) { @@ -119,7 +128,6 @@ namespace Assimp { ConvertGlobalSettings(); TransferDataToScene(); - ConvertToUnitScale(unit); // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE // to make sure the scene passes assimp's validation. FBX files @@ -146,7 +154,7 @@ namespace Assimp { out->mRootNode->mName.Set(unique_name); // root has ID 0 - ConvertNodes(0L, *out->mRootNode); + ConvertNodes(0L, out->mRootNode, out->mRootNode); } static std::string getAncestorBaseName(const aiNode* node) @@ -180,8 +188,11 @@ namespace Assimp { GetUniqueName(original_name, unique_name); return unique_name; } - - void FBXConverter::ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform) { + /// todo: pre-build node hierarchy + /// todo: get bone from stack + /// todo: make map of aiBone* to aiNode* + /// then update convert clusters to the new format + void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) { const std::vector& conns = doc.GetConnectionsByDestinationSequenced(id, "Model"); std::vector nodes; @@ -192,62 +203,69 @@ namespace Assimp { try { for (const Connection* con : conns) { - // ignore object-property links if (con->PropertyName().length()) { - continue; + // really important we document why this is ignored. + FBXImporter::LogInfo("ignoring property link - no docs on why this is ignored"); + continue; //? } + // convert connection source object into Object base class const Object* const object = con->SourceObject(); if (nullptr == object) { - FBXImporter::LogWarn("failed to convert source object for Model link"); + FBXImporter::LogError("failed to convert source object for Model link"); continue; } + // FBX Model::Cube, Model::Bone001, etc elements + // This detects if we can cast the object into this model structure. const Model* const model = dynamic_cast(object); if (nullptr != model) { nodes_chain.clear(); post_nodes_chain.clear(); - aiMatrix4x4 new_abs_transform = parent_transform; - - std::string unique_name = MakeUniqueNodeName(model, parent); - + aiMatrix4x4 new_abs_transform = parent->mTransformation; + std::string node_name = FixNodeName(model->Name()); // even though there is only a single input node, the design of // assimp (or rather: the complicated transformation chain that // is employed by fbx) means that we may need multiple aiNode's // to represent a fbx node's transformation. - const bool need_additional_node = GenerateTransformationNodeChain(*model, unique_name, nodes_chain, post_nodes_chain); + + // generate node transforms - this includes pivot data + // if need_additional_node is true then you t + const bool need_additional_node = GenerateTransformationNodeChain(*model, node_name, nodes_chain, post_nodes_chain); + + // assert that for the current node we must have at least a single transform ai_assert(nodes_chain.size()); if (need_additional_node) { - nodes_chain.push_back(new aiNode(unique_name)); + nodes_chain.push_back(new aiNode(node_name)); } //setup metadata on newest node SetupNodeMetadata(*model, *nodes_chain.back()); // link all nodes in a row - aiNode* last_parent = &parent; - for (aiNode* prenode : nodes_chain) { - ai_assert(prenode); + aiNode* last_parent = parent; + for (aiNode* child : nodes_chain) { + ai_assert(child); - if (last_parent != &parent) { + if (last_parent != parent) { last_parent->mNumChildren = 1; last_parent->mChildren = new aiNode*[1]; - last_parent->mChildren[0] = prenode; + last_parent->mChildren[0] = child; } - prenode->mParent = last_parent; - last_parent = prenode; + child->mParent = last_parent; + last_parent = child; - new_abs_transform *= prenode->mTransformation; + new_abs_transform *= child->mTransformation; } // attach geometry - ConvertModel(*model, *nodes_chain.back(), new_abs_transform); + ConvertModel(*model, nodes_chain.back(), root_node, new_abs_transform); // check if there will be any child nodes const std::vector& child_conns @@ -259,7 +277,7 @@ namespace Assimp { for (aiNode* postnode : post_nodes_chain) { ai_assert(postnode); - if (last_parent != &parent) { + if (last_parent != parent) { last_parent->mNumChildren = 1; last_parent->mChildren = new aiNode*[1]; last_parent->mChildren[0] = postnode; @@ -281,15 +299,15 @@ namespace Assimp { ); } - // attach sub-nodes (if any) - ConvertNodes(model->ID(), *last_parent, new_abs_transform); + // recursion call - child nodes + ConvertNodes(model->ID(), last_parent, root_node); if (doc.Settings().readLights) { - ConvertLights(*model, unique_name); + ConvertLights(*model, node_name); } if (doc.Settings().readCameras) { - ConvertCameras(*model, unique_name); + ConvertCameras(*model, node_name); } nodes.push_back(nodes_chain.front()); @@ -298,11 +316,17 @@ namespace Assimp { } if (nodes.size()) { - parent.mChildren = new aiNode*[nodes.size()](); - parent.mNumChildren = static_cast(nodes.size()); + parent->mChildren = new aiNode*[nodes.size()](); + parent->mNumChildren = static_cast(nodes.size()); - std::swap_ranges(nodes.begin(), nodes.end(), parent.mChildren); + std::swap_ranges(nodes.begin(), nodes.end(), parent->mChildren); } + else + { + parent->mNumChildren = 0; + parent->mChildren = nullptr; + } + } catch (std::exception&) { Util::delete_fun deleter; @@ -555,7 +579,7 @@ namespace Assimp { return; } - const float angle_epsilon = 1e-6f; + const float angle_epsilon = Math::getEpsilon(); out = aiMatrix4x4(); @@ -685,30 +709,37 @@ namespace Assimp { bool ok; aiMatrix4x4 chain[TransformationComp_MAXIMUM]; + + ai_assert(TransformationComp_MAXIMUM < 32); + std::uint32_t chainBits = 0; + // A node won't need a node chain if it only has these. + const std::uint32_t chainMaskSimple = (1 << TransformationComp_Translation) + (1 << TransformationComp_Scaling) + (1 << TransformationComp_Rotation); + // A node will need a node chain if it has any of these. + const std::uint32_t chainMaskComplex = ((1 << (TransformationComp_MAXIMUM)) - 1) - chainMaskSimple; + std::fill_n(chain, static_cast(TransformationComp_MAXIMUM), aiMatrix4x4()); // generate transformation matrices for all the different transformation components - const float zero_epsilon = 1e-6f; + const float zero_epsilon = Math::getEpsilon(); const aiVector3D all_ones(1.0f, 1.0f, 1.0f); - bool is_complex = false; const aiVector3D& PreRotation = PropertyGet(props, "PreRotation", ok); if (ok && PreRotation.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_PreRotation); GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PreRotation, chain[TransformationComp_PreRotation]); } const aiVector3D& PostRotation = PropertyGet(props, "PostRotation", ok); if (ok && PostRotation.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_PostRotation); GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PostRotation, chain[TransformationComp_PostRotation]); } const aiVector3D& RotationPivot = PropertyGet(props, "RotationPivot", ok); if (ok && RotationPivot.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_RotationPivot) | (1 << TransformationComp_RotationPivotInverse); aiMatrix4x4::Translation(RotationPivot, chain[TransformationComp_RotationPivot]); aiMatrix4x4::Translation(-RotationPivot, chain[TransformationComp_RotationPivotInverse]); @@ -716,21 +747,21 @@ namespace Assimp { const aiVector3D& RotationOffset = PropertyGet(props, "RotationOffset", ok); if (ok && RotationOffset.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_RotationOffset); aiMatrix4x4::Translation(RotationOffset, chain[TransformationComp_RotationOffset]); } const aiVector3D& ScalingOffset = PropertyGet(props, "ScalingOffset", ok); if (ok && ScalingOffset.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_ScalingOffset); aiMatrix4x4::Translation(ScalingOffset, chain[TransformationComp_ScalingOffset]); } const aiVector3D& ScalingPivot = PropertyGet(props, "ScalingPivot", ok); if (ok && ScalingPivot.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_ScalingPivot) | (1 << TransformationComp_ScalingPivotInverse); aiMatrix4x4::Translation(ScalingPivot, chain[TransformationComp_ScalingPivot]); aiMatrix4x4::Translation(-ScalingPivot, chain[TransformationComp_ScalingPivotInverse]); @@ -738,22 +769,28 @@ namespace Assimp { const aiVector3D& Translation = PropertyGet(props, "Lcl Translation", ok); if (ok && Translation.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_Translation); + aiMatrix4x4::Translation(Translation, chain[TransformationComp_Translation]); } const aiVector3D& Scaling = PropertyGet(props, "Lcl Scaling", ok); if (ok && (Scaling - all_ones).SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_Scaling); + aiMatrix4x4::Scaling(Scaling, chain[TransformationComp_Scaling]); } const aiVector3D& Rotation = PropertyGet(props, "Lcl Rotation", ok); if (ok && Rotation.SquareLength() > zero_epsilon) { + chainBits = chainBits | (1 << TransformationComp_Rotation); + GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]); } const aiVector3D& GeometricScaling = PropertyGet(props, "GeometricScaling", ok); if (ok && (GeometricScaling - all_ones).SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_GeometricScaling); aiMatrix4x4::Scaling(GeometricScaling, chain[TransformationComp_GeometricScaling]); aiVector3D GeometricScalingInverse = GeometricScaling; bool canscale = true; @@ -768,13 +805,14 @@ namespace Assimp { } } if (canscale) { + chainBits = chainBits | (1 << TransformationComp_GeometricScalingInverse); aiMatrix4x4::Scaling(GeometricScalingInverse, chain[TransformationComp_GeometricScalingInverse]); } } const aiVector3D& GeometricRotation = PropertyGet(props, "GeometricRotation", ok); if (ok && GeometricRotation.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_GeometricRotation) | (1 << TransformationComp_GeometricRotationInverse); GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotation]); GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotationInverse]); chain[TransformationComp_GeometricRotationInverse].Inverse(); @@ -782,7 +820,7 @@ namespace Assimp { const aiVector3D& GeometricTranslation = PropertyGet(props, "GeometricTranslation", ok); if (ok && GeometricTranslation.SquareLength() > zero_epsilon) { - is_complex = true; + chainBits = chainBits | (1 << TransformationComp_GeometricTranslation) | (1 << TransformationComp_GeometricTranslationInverse); aiMatrix4x4::Translation(GeometricTranslation, chain[TransformationComp_GeometricTranslation]); aiMatrix4x4::Translation(-GeometricTranslation, chain[TransformationComp_GeometricTranslationInverse]); } @@ -790,12 +828,12 @@ namespace Assimp { // is_complex needs to be consistent with NeedsComplexTransformationChain() // or the interplay between this code and the animation converter would // not be guaranteed. - ai_assert(NeedsComplexTransformationChain(model) == is_complex); + //ai_assert(NeedsComplexTransformationChain(model) == ((chainBits & chainMaskComplex) != 0)); // now, if we have more than just Translation, Scaling and Rotation, // we need to generate a full node chain to accommodate for assimp's // lack to express pivots and offsets. - if (is_complex && doc.Settings().preservePivots) { + if ((chainBits & chainMaskComplex) && doc.Settings().preservePivots) { FBXImporter::LogInfo("generating full transformation chain for node: " + name); // query the anim_chain_bits dictionary to find out which chain elements @@ -808,7 +846,7 @@ namespace Assimp { for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) { const TransformationComp comp = static_cast(i); - if (chain[i].IsIdentity() && (anim_chain_bitmask & bit) == 0) { + if ((chainBits & bit) == 0 && (anim_chain_bitmask & bit) == 0) { continue; } @@ -892,7 +930,8 @@ namespace Assimp { } } - void FBXConverter::ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform) + void FBXConverter::ConvertModel(const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { const std::vector& geos = model.GetGeometry(); @@ -904,11 +943,12 @@ namespace Assimp { const MeshGeometry* const mesh = dynamic_cast(geo); const LineGeometry* const line = dynamic_cast(geo); if (mesh) { - const std::vector& indices = ConvertMesh(*mesh, model, node_global_transform, nd); + const std::vector& indices = ConvertMesh(*mesh, model, parent, root_node, + absolute_transform); std::copy(indices.begin(), indices.end(), std::back_inserter(meshes)); } else if (line) { - const std::vector& indices = ConvertLine(*line, model, node_global_transform, nd); + const std::vector& indices = ConvertLine(*line, model, parent, root_node); std::copy(indices.begin(), indices.end(), std::back_inserter(meshes)); } else { @@ -917,15 +957,16 @@ namespace Assimp { } if (meshes.size()) { - nd.mMeshes = new unsigned int[meshes.size()](); - nd.mNumMeshes = static_cast(meshes.size()); + parent->mMeshes = new unsigned int[meshes.size()](); + parent->mNumMeshes = static_cast(meshes.size()); - std::swap_ranges(meshes.begin(), meshes.end(), nd.mMeshes); + std::swap_ranges(meshes.begin(), meshes.end(), parent->mMeshes); } } - std::vector FBXConverter::ConvertMesh(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) + std::vector + FBXConverter::ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { std::vector temp; @@ -949,18 +990,18 @@ namespace Assimp { const MatIndexArray::value_type base = mindices[0]; for (MatIndexArray::value_type index : mindices) { if (index != base) { - return ConvertMeshMultiMaterial(mesh, model, node_global_transform, nd); + return ConvertMeshMultiMaterial(mesh, model, parent, root_node, absolute_transform); } } } // faster code-path, just copy the data - temp.push_back(ConvertMeshSingleMaterial(mesh, model, node_global_transform, nd)); + temp.push_back(ConvertMeshSingleMaterial(mesh, model, absolute_transform, parent, root_node)); return temp; } std::vector FBXConverter::ConvertLine(const LineGeometry& line, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) + aiNode *parent, aiNode *root_node) { std::vector temp; @@ -971,7 +1012,7 @@ namespace Assimp { return temp; } - aiMesh* const out_mesh = SetupEmptyMesh(line, nd); + aiMesh* const out_mesh = SetupEmptyMesh(line, root_node); out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; // copy vertices @@ -1006,7 +1047,7 @@ namespace Assimp { return temp; } - aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode& nd) + aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode *parent) { aiMesh* const out_mesh = new aiMesh(); meshes.push_back(out_mesh); @@ -1023,17 +1064,18 @@ namespace Assimp { } else { - out_mesh->mName = nd.mName; + out_mesh->mName = parent->mName; } return out_mesh; } - unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) + unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model, + const aiMatrix4x4 &absolute_transform, aiNode *parent, + aiNode *root_node) { const MatIndexArray& mindices = mesh.GetMaterialIndices(); - aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd); + aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent); const std::vector& vertices = mesh.GetVertices(); const std::vector& faces = mesh.GetFaceIndexCounts(); @@ -1100,7 +1142,7 @@ namespace Assimp { binormals = &tempBinormals; } else { - binormals = NULL; + binormals = nullptr; } } @@ -1150,8 +1192,9 @@ namespace Assimp { ConvertMaterialForMesh(out_mesh, model, mesh, mindices[0]); } - if (doc.Settings().readWeights && mesh.DeformerSkin() != NULL) { - ConvertWeights(out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION); + if (doc.Settings().readWeights && mesh.DeformerSkin() != nullptr) { + ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, NO_MATERIAL_SEPARATION, + nullptr); } std::vector animMeshes; @@ -1196,8 +1239,10 @@ namespace Assimp { return static_cast(meshes.size() - 1); } - std::vector FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) + std::vector + FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent, + aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { const MatIndexArray& mindices = mesh.GetMaterialIndices(); ai_assert(mindices.size()); @@ -1208,7 +1253,7 @@ namespace Assimp { for (MatIndexArray::value_type index : mindices) { if (had.find(index) == had.end()) { - indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, node_global_transform, nd)); + indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, parent, root_node, absolute_transform)); had.insert(index); } } @@ -1216,18 +1261,18 @@ namespace Assimp { return indices; } - unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, - MatIndexArray::value_type index, - const aiMatrix4x4& node_global_transform, - aiNode& nd) + unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, + MatIndexArray::value_type index, + aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { - aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd); + aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent); const MatIndexArray& mindices = mesh.GetMaterialIndices(); const std::vector& vertices = mesh.GetVertices(); const std::vector& faces = mesh.GetFaceIndexCounts(); - const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != NULL; + const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != nullptr; unsigned int count_faces = 0; unsigned int count_vertices = 0; @@ -1247,10 +1292,10 @@ namespace Assimp { ai_assert(count_faces); ai_assert(count_vertices); - // mapping from output indices to DOM indexing, needed to resolve weights + // mapping from output indices to DOM indexing, needed to resolve weights or blendshapes std::vector reverseMapping; std::map translateIndexMap; - if (process_weights) { + if (process_weights || mesh.GetBlendShapes().size() > 0) { reverseMapping.resize(count_vertices); } @@ -1287,7 +1332,7 @@ namespace Assimp { binormals = &tempBinormals; } else { - binormals = NULL; + binormals = nullptr; } } @@ -1386,7 +1431,7 @@ namespace Assimp { ConvertMaterialForMesh(out_mesh, model, mesh, index); if (process_weights) { - ConvertWeights(out_mesh, model, mesh, node_global_transform, index, &reverseMapping); + ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, index, &reverseMapping); } std::vector animMeshes; @@ -1407,7 +1452,10 @@ namespace Assimp { unsigned int count = 0; const unsigned int* outIndices = mesh.ToOutputVertexIndex(index, count); for (unsigned int k = 0; k < count; k++) { - unsigned int index = translateIndexMap[outIndices[k]]; + unsigned int outIndex = outIndices[k]; + if (translateIndexMap.find(outIndex) == translateIndexMap.end()) + continue; + unsigned int index = translateIndexMap[outIndex]; animMesh->mVertices[index] += vertex; if (animMesh->mNormals != nullptr) { animMesh->mNormals[index] += normal; @@ -1421,13 +1469,22 @@ namespace Assimp { } } + const size_t numAnimMeshes = animMeshes.size(); + if (numAnimMeshes > 0) { + out_mesh->mNumAnimMeshes = static_cast(numAnimMeshes); + out_mesh->mAnimMeshes = new aiAnimMesh*[numAnimMeshes]; + for (size_t i = 0; i < numAnimMeshes; i++) { + out_mesh->mAnimMeshes[i] = animMeshes.at(i); + } + } + return static_cast(meshes.size() - 1); } - void FBXConverter::ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo, - const aiMatrix4x4& node_global_transform, - unsigned int materialIndex, - std::vector* outputVertStartIndices) + void FBXConverter::ConvertWeights(aiMesh *out, const Model &model, const MeshGeometry &geo, + const aiMatrix4x4 &absolute_transform, + aiNode *parent, aiNode *root_node, unsigned int materialIndex, + std::vector *outputVertStartIndices) { ai_assert(geo.DeformerSkin()); @@ -1438,41 +1495,35 @@ namespace Assimp { const Skin& sk = *geo.DeformerSkin(); std::vector bones; - bones.reserve(sk.Clusters().size()); const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION; ai_assert(no_mat_check || outputVertStartIndices); try { - + // iterate over the sub deformers for (const Cluster* cluster : sk.Clusters()) { ai_assert(cluster); const WeightIndexArray& indices = cluster->GetIndices(); - if (indices.empty() && mRemoveEmptyBones ) { - continue; - } - const MatIndexArray& mats = geo.GetMaterialIndices(); - bool ok = false; - const size_t no_index_sentinel = std::numeric_limits::max(); count_out_indices.clear(); index_out_indices.clear(); out_indices.clear(); + // now check if *any* of these weights is contained in the output mesh, // taking notes so we don't need to do it twice. for (WeightIndexArray::value_type index : indices) { unsigned int count = 0; const unsigned int* const out_idx = geo.ToOutputVertexIndex(index, count); - // ToOutputVertexIndex only returns NULL if index is out of bounds + // ToOutputVertexIndex only returns nullptr if index is out of bounds // which should never happen - ai_assert(out_idx != NULL); + ai_assert(out_idx != nullptr); index_out_indices.push_back(no_index_sentinel); count_out_indices.push_back(0); @@ -1497,75 +1548,111 @@ namespace Assimp { out_indices.push_back(std::distance(outputVertStartIndices->begin(), it)); } - ++count_out_indices.back(); - ok = true; + ++count_out_indices.back(); } } } - + // if we found at least one, generate the output bones // XXX this could be heavily simplified by collecting the bone // data in a single step. - if (ok && mRemoveEmptyBones) { - ConvertCluster(bones, model, *cluster, out_indices, index_out_indices, - count_out_indices, node_global_transform); - } + ConvertCluster(bones, cluster, out_indices, index_out_indices, + count_out_indices, absolute_transform, parent, root_node); } + + bone_map.clear(); } - catch (std::exception&) { + catch (std::exception&e) { std::for_each(bones.begin(), bones.end(), Util::delete_fun()); throw; } if (bones.empty()) { + out->mBones = nullptr; + out->mNumBones = 0; return; + } else { + out->mBones = new aiBone *[bones.size()](); + out->mNumBones = static_cast(bones.size()); + + std::swap_ranges(bones.begin(), bones.end(), out->mBones); } - - out->mBones = new aiBone*[bones.size()](); - out->mNumBones = static_cast(bones.size()); - - std::swap_ranges(bones.begin(), bones.end(), out->mBones); } - void FBXConverter::ConvertCluster(std::vector& bones, const Model& /*model*/, const Cluster& cl, - std::vector& out_indices, - std::vector& index_out_indices, - std::vector& count_out_indices, - const aiMatrix4x4& node_global_transform) + const aiNode* FBXConverter::GetNodeByName( const aiString& name, aiNode *current_node ) { + aiNode * iter = current_node; + //printf("Child count: %d", iter->mNumChildren); + return iter; + } - aiBone* const bone = new aiBone(); - bones.push_back(bone); + void FBXConverter::ConvertCluster(std::vector &local_mesh_bones, const Cluster *cl, + std::vector &out_indices, std::vector &index_out_indices, + std::vector &count_out_indices, const aiMatrix4x4 &absolute_transform, + aiNode *parent, aiNode *root_node) { + ai_assert(cl); // make sure cluster valid + std::string deformer_name = cl->TargetNode()->Name(); + aiString bone_name = aiString(FixNodeName(deformer_name)); - bone->mName = FixNodeName(cl.TargetNode()->Name()); + aiBone *bone = nullptr; - bone->mOffsetMatrix = cl.TransformLink(); - bone->mOffsetMatrix.Inverse(); + if (bone_map.count(deformer_name)) { + std::cout << "retrieved bone from lookup " << bone_name.C_Str() << ". Deformer: " << deformer_name + << std::endl; + bone = bone_map[deformer_name]; + } else { + std::cout << "created new bone " << bone_name.C_Str() << ". Deformer: " << deformer_name << std::endl; + bone = new aiBone(); + bone->mName = bone_name; - bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform; + // store local transform link for post processing + bone->mOffsetMatrix = cl->TransformLink(); + bone->mOffsetMatrix.Inverse(); - bone->mNumWeights = static_cast(out_indices.size()); - aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[out_indices.size()]; + aiMatrix4x4 matrix = (aiMatrix4x4)absolute_transform; - const size_t no_index_sentinel = std::numeric_limits::max(); - const WeightArray& weights = cl.GetWeights(); + bone->mOffsetMatrix = bone->mOffsetMatrix * matrix; // * mesh_offset - const size_t c = index_out_indices.size(); - for (size_t i = 0; i < c; ++i) { - const size_t index_index = index_out_indices[i]; - if (index_index == no_index_sentinel) { - continue; + // + // Now calculate the aiVertexWeights + // + + aiVertexWeight *cursor = nullptr; + + bone->mNumWeights = static_cast(out_indices.size()); + cursor = bone->mWeights = new aiVertexWeight[out_indices.size()]; + + const size_t no_index_sentinel = std::numeric_limits::max(); + const WeightArray& weights = cl->GetWeights(); + + const size_t c = index_out_indices.size(); + for (size_t i = 0; i < c; ++i) { + const size_t index_index = index_out_indices[i]; + + if (index_index == no_index_sentinel) { + continue; + } + + const size_t cc = count_out_indices[i]; + for (size_t j = 0; j < cc; ++j) { + // cursor runs from first element relative to the start + // or relative to the start of the next indexes. + aiVertexWeight& out_weight = *cursor++; + + out_weight.mVertexId = static_cast(out_indices[index_index + j]); + out_weight.mWeight = weights[i]; + } } - const size_t cc = count_out_indices[i]; - for (size_t j = 0; j < cc; ++j) { - aiVertexWeight& out_weight = *cursor++; - - out_weight.mVertexId = static_cast(out_indices[index_index + j]); - out_weight.mWeight = weights[i]; - } + bone_map.insert(std::pair(deformer_name, bone)); } + + std::cout << "bone research: Indicies size: " << out_indices.size() << std::endl; + + // lookup must be populated in case something goes wrong + // this also allocates bones to mesh instance outside + local_mesh_bones.push_back(bone); } void FBXConverter::ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo, @@ -1695,7 +1782,7 @@ namespace Assimp { bool textureReady = false; //tells if our texture is ready (if it was loaded or if it was found) unsigned int index; - VideoMap::const_iterator it = textures_converted.find(media); + VideoMap::const_iterator it = textures_converted.find(*media); if (it != textures_converted.end()) { index = (*it).second; textureReady = true; @@ -1703,7 +1790,7 @@ namespace Assimp { else { if (media->ContentLength() > 0) { index = ConvertVideo(*media); - textures_converted[media] = index; + textures_converted[*media] = index; textureReady = true; } } @@ -1986,6 +2073,21 @@ namespace Assimp { TrySetTextureProperties(out_mat, textures, "Maya|SpecularTexture", aiTextureType_SPECULAR, mesh); TrySetTextureProperties(out_mat, textures, "Maya|FalloffTexture", aiTextureType_OPACITY, mesh); TrySetTextureProperties(out_mat, textures, "Maya|ReflectionMapTexture", aiTextureType_REFLECTION, mesh); + + // Maya PBR + TrySetTextureProperties(out_mat, textures, "Maya|baseColor|file", aiTextureType_BASE_COLOR, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|normalCamera|file", aiTextureType_NORMAL_CAMERA, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|emissionColor|file", aiTextureType_EMISSION_COLOR, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|metalness|file", aiTextureType_METALNESS, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|diffuseRoughness|file", aiTextureType_DIFFUSE_ROUGHNESS, mesh); + + // Maya stingray + TrySetTextureProperties(out_mat, textures, "Maya|TEX_color_map|file", aiTextureType_BASE_COLOR, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|TEX_normal_map|file", aiTextureType_NORMAL_CAMERA, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|TEX_emissive_map|file", aiTextureType_EMISSION_COLOR, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|TEX_metallic_map|file", aiTextureType_METALNESS, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|TEX_roughness_map|file", aiTextureType_DIFFUSE_ROUGHNESS, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|TEX_ao_map|file", aiTextureType_AMBIENT_OCCLUSION, mesh); } void FBXConverter::SetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh) @@ -2212,13 +2314,13 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa if (media != nullptr && media->ContentLength() > 0) { unsigned int index; - VideoMap::const_iterator it = textures_converted.find(media); + VideoMap::const_iterator it = textures_converted.find(*media); if (it != textures_converted.end()) { index = (*it).second; } else { index = ConvertVideo(*media); - textures_converted[media] = index; + textures_converted[*media] = index; } // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) @@ -2646,7 +2748,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa // sanity check whether the input is ok static void validateAnimCurveNodes(const std::vector& curves, bool strictMode) { - const Object* target(NULL); + const Object* target(nullptr); for (const AnimationCurveNode* node : curves) { if (!target) { target = node->Target(); @@ -2677,7 +2779,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa #ifdef ASSIMP_BUILD_DEBUG validateAnimCurveNodes(curves, doc.Settings().strictMode); #endif - const AnimationCurveNode* curve_node = NULL; + const AnimationCurveNode* curve_node = nullptr; for (const AnimationCurveNode* node : curves) { ai_assert(node); @@ -2937,7 +3039,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa TransformationCompDefaultValue(comp) ); - const float epsilon = 1e-6f; + const float epsilon = Math::getEpsilon(); return (dyn_val - static_val).SquareLength() < epsilon; } @@ -3520,52 +3622,12 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa out->mMetaData->Set(14, "CustomFrameRate", doc.GlobalSettings().CustomFrameRate()); } - void FBXConverter::ConvertToUnitScale( FbxUnit unit ) { - if (mCurrentUnit == unit) { - return; - } - - ai_real scale = 1.0; - if (mCurrentUnit == FbxUnit::cm) { - if (unit == FbxUnit::m) { - scale = (ai_real)0.01; - } else if (unit == FbxUnit::km) { - scale = (ai_real)0.00001; - } - } else if (mCurrentUnit == FbxUnit::m) { - if (unit == FbxUnit::cm) { - scale = (ai_real)100.0; - } else if (unit == FbxUnit::km) { - scale = (ai_real)0.001; - } - } else if (mCurrentUnit == FbxUnit::km) { - if (unit == FbxUnit::cm) { - scale = (ai_real)100000.0; - } else if (unit == FbxUnit::m) { - scale = (ai_real)1000.0; - } - } - - for (auto mesh : meshes) { - if (nullptr == mesh) { - continue; - } - - if (mesh->HasPositions()) { - for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { - aiVector3D &pos = mesh->mVertices[i]; - pos *= scale; - } - } - } - } - void FBXConverter::TransferDataToScene() { ai_assert(!out->mMeshes); ai_assert(!out->mNumMeshes); - // note: the trailing () ensures initialization with NULL - not + // note: the trailing () ensures initialization with nullptr - not // many C++ users seem to know this, so pointing it out to avoid // confusion why this code works. @@ -3612,10 +3674,51 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa } } - // ------------------------------------------------------------------------------------------------ - void ConvertToAssimpScene(aiScene* out, const Document& doc, bool removeEmptyBones, FbxUnit unit) + void FBXConverter::ConvertOrphantEmbeddedTextures() { - FBXConverter converter(out, doc, removeEmptyBones, unit); + // in C++14 it could be: + // for (auto&& [id, object] : objects) + for (auto&& id_and_object : doc.Objects()) + { + auto&& id = std::get<0>(id_and_object); + auto&& object = std::get<1>(id_and_object); + // If an object doesn't have parent + if (doc.ConnectionsBySource().count(id) == 0) + { + const Texture* realTexture = nullptr; + try + { + const auto& element = object->GetElement(); + const Token& key = element.KeyToken(); + const char* obtype = key.begin(); + const size_t length = static_cast(key.end() - key.begin()); + if (strncmp(obtype, "Texture", length) == 0) + { + const Texture* texture = static_cast(object->Get()); + if (texture->Media() && texture->Media()->ContentLength() > 0) + { + realTexture = texture; + } + } + } + catch (...) + { + // do nothing + } + if (realTexture) + { + const Video* media = realTexture->Media(); + unsigned int index = ConvertVideo(*media); + textures_converted[*media] = index; + } + } + } + } + + // ------------------------------------------------------------------------------------------------ + void ConvertToAssimpScene(aiScene* out, const Document& doc, bool removeEmptyBones) + { + FBXConverter converter(out, doc, removeEmptyBones); } } // !FBX diff --git a/code/FBX/FBXConverter.h b/code/FBX/FBXConverter.h index 17a7bc56b..46693bdca 100644 --- a/code/FBX/FBXConverter.h +++ b/code/FBX/FBXConverter.h @@ -76,23 +76,13 @@ namespace Assimp { namespace FBX { class Document; - -enum class FbxUnit { - cm = 0, - m, - km, - NumUnits, - - Undefined -}; - /** * Convert a FBX #Document to #aiScene * @param out Empty scene to be populated * @param doc Parsed FBX document * @param removeEmptyBones Will remove bones, which do not have any references to vertices. */ -void ConvertToAssimpScene(aiScene* out, const Document& doc, bool removeEmptyBones, FbxUnit unit); +void ConvertToAssimpScene(aiScene* out, const Document& doc, bool removeEmptyBones); /** Dummy class to encapsulate the conversion process */ class FBXConverter { @@ -123,7 +113,7 @@ public: }; public: - FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones, FbxUnit unit); + FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones); ~FBXConverter(); private: @@ -133,7 +123,7 @@ private: // ------------------------------------------------------------------------------------------------ // collect and assign child nodes - void ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4()); + void ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node); // ------------------------------------------------------------------------------------------------ void ConvertLights(const Model& model, const std::string &orig_name ); @@ -189,32 +179,35 @@ private: void SetupNodeMetadata(const Model& model, aiNode& nd); // ------------------------------------------------------------------------------------------------ - void ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform); + void ConvertModel(const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform); // ------------------------------------------------------------------------------------------------ // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed - std::vector ConvertMesh(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd); + std::vector + ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform); // ------------------------------------------------------------------------------------------------ std::vector ConvertLine(const LineGeometry& line, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd); + aiNode *parent, aiNode *root_node); // ------------------------------------------------------------------------------------------------ - aiMesh* SetupEmptyMesh(const Geometry& mesh, aiNode& nd); + aiMesh* SetupEmptyMesh(const Geometry& mesh, aiNode *parent); // ------------------------------------------------------------------------------------------------ - unsigned int ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd); + unsigned int ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model, + const aiMatrix4x4 &absolute_transform, aiNode *parent, + aiNode *root_node); // ------------------------------------------------------------------------------------------------ - std::vector ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd); + std::vector + ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform); // ------------------------------------------------------------------------------------------------ - unsigned int ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, - MatIndexArray::value_type index, - const aiMatrix4x4& node_global_transform, aiNode& nd); + unsigned int ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, MatIndexArray::value_type index, + aiNode *parent, aiNode *root_node, const aiMatrix4x4 &absolute_transform); // ------------------------------------------------------------------------------------------------ static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits::max() */ @@ -227,17 +220,17 @@ private: * - outputVertStartIndices is only used when a material index is specified, it gives for * each output vertex the DOM index it maps to. */ - void ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo, - const aiMatrix4x4& node_global_transform = aiMatrix4x4(), - unsigned int materialIndex = NO_MATERIAL_SEPARATION, - std::vector* outputVertStartIndices = NULL); - + void ConvertWeights(aiMesh *out, const Model &model, const MeshGeometry &geo, const aiMatrix4x4 &absolute_transform, + aiNode *parent = NULL, aiNode *root_node = NULL, + unsigned int materialIndex = NO_MATERIAL_SEPARATION, + std::vector *outputVertStartIndices = NULL); + // lookup + static const aiNode* GetNodeByName( const aiString& name, aiNode *current_node ); // ------------------------------------------------------------------------------------------------ - void ConvertCluster(std::vector& bones, const Model& /*model*/, const Cluster& cl, - std::vector& out_indices, - std::vector& index_out_indices, - std::vector& count_out_indices, - const aiMatrix4x4& node_global_transform); + void ConvertCluster(std::vector &local_mesh_bones, const Cluster *cl, + std::vector &out_indices, std::vector &index_out_indices, + std::vector &count_out_indices, const aiMatrix4x4 &absolute_transform, + aiNode *parent, aiNode *root_node); // ------------------------------------------------------------------------------------------------ void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo, @@ -430,14 +423,14 @@ private: void ConvertGlobalSettings(); - // ------------------------------------------------------------------------------------------------ - // Will perform the conversion from a given unit to the requested unit. - void ConvertToUnitScale(FbxUnit unit); - // ------------------------------------------------------------------------------------------------ // copy generated meshes, animations, lights, cameras and textures to the output scene void TransferDataToScene(); + // ------------------------------------------------------------------------------------------------ + // FBX file could have embedded textures not connected to anything + void ConvertOrphantEmbeddedTextures(); + private: // 0: not assigned yet, others: index is value - 1 unsigned int defaultMaterialIndex; @@ -449,31 +442,47 @@ private: std::vector cameras; std::vector textures; - using MaterialMap = std::map; + using MaterialMap = std::fbx_unordered_map; MaterialMap materials_converted; - using VideoMap = std::map; + using VideoMap = std::fbx_unordered_map; VideoMap textures_converted; - using MeshMap = std::map >; + using MeshMap = std::fbx_unordered_map >; MeshMap meshes_converted; // fixed node name -> which trafo chain components have animations? - using NodeAnimBitMap = std::map ; + using NodeAnimBitMap = std::fbx_unordered_map ; NodeAnimBitMap node_anim_chain_bits; // number of nodes with the same name - using NodeNameCache = std::unordered_map; + using NodeNameCache = std::fbx_unordered_map; NodeNameCache mNodeNames; + // Deformer name is not the same as a bone name - it does contain the bone name though :) + // Deformer names in FBX are always unique in an FBX file. + std::map bone_map; + double anim_fps; aiScene* const out; const FBX::Document& doc; - bool mRemoveEmptyBones; + static void BuildBoneList(aiNode *current_node, const aiNode *root_node, const aiScene *scene, + std::vector& bones); - FbxUnit mCurrentUnit; + void BuildBoneStack(aiNode *current_node, const aiNode *root_node, const aiScene *scene, + const std::vector &bones, + std::map &bone_stack, + std::vector &node_stack ); + + static void BuildNodeList(aiNode *current_node, std::vector &nodes); + + static aiNode *GetNodeFromStack(const aiString &node_name, std::vector &nodes); + + static aiNode *GetArmatureRoot(aiNode *bone_node, std::vector &bone_list); + + static bool IsBoneNode(const aiString &bone_name, std::vector &bones); }; } diff --git a/code/FBX/FBXDocument.cpp b/code/FBX/FBXDocument.cpp index 1af08fe6d..506fd978d 100644 --- a/code/FBX/FBXDocument.cpp +++ b/code/FBX/FBXDocument.cpp @@ -90,14 +90,6 @@ const Object* LazyObject::Get(bool dieOnError) return object.get(); } - // if this is the root object, we return a dummy since there - // is no root object int he fbx file - it is just referenced - // with id 0. - if(id == 0L) { - object.reset(new Object(id, element, "Model::RootNode")); - return object.get(); - } - const Token& key = element.KeyToken(); const TokenList& tokens = element.Tokens(); diff --git a/code/FBX/FBXDocument.h b/code/FBX/FBXDocument.h index 18e5c38f1..a60d7d9ef 100644 --- a/code/FBX/FBXDocument.h +++ b/code/FBX/FBXDocument.h @@ -637,6 +637,20 @@ public: return ptr; } + bool operator==(const Video& other) const + { + return ( + type == other.type + && relativeFileName == other.relativeFileName + && fileName == other.fileName + ); + } + + bool operator<(const Video& other) const + { + return std::tie(type, relativeFileName, fileName) < std::tie(other.type, other.relativeFileName, other.fileName); + } + private: std::string type; std::string relativeFileName; @@ -1005,10 +1019,10 @@ public: // during their entire lifetime (Document). FBX files have // up to many thousands of objects (most of which we never use), // so the memory overhead for them should be kept at a minimum. -typedef std::map ObjectMap; +typedef std::fbx_unordered_map ObjectMap; typedef std::fbx_unordered_map > PropertyTemplateMap; -typedef std::multimap ConnectionMap; +typedef std::fbx_unordered_multimap ConnectionMap; /** DOM class for global document settings, a single instance per document can * be accessed via Document.Globals(). */ @@ -1177,4 +1191,25 @@ private: } // Namespace FBX } // Namespace Assimp +namespace std +{ + template <> + struct hash + { + std::size_t operator()(const Assimp::FBX::Video& video) const + { + using std::size_t; + using std::hash; + using std::string; + + size_t res = 17; + res = res * 31 + hash()(video.Name()); + res = res * 31 + hash()(video.RelativeFilename()); + res = res * 31 + hash()(video.Type()); + + return res; + } + }; +} + #endif // INCLUDED_AI_FBX_DOCUMENT_H diff --git a/code/FBX/FBXExportNode.cpp b/code/FBX/FBXExportNode.cpp index 06c89cee4..9b29995cc 100644 --- a/code/FBX/FBXExportNode.cpp +++ b/code/FBX/FBXExportNode.cpp @@ -325,9 +325,9 @@ void FBX::Node::BeginBinary(Assimp::StreamWriterLE &s) this->start_pos = s.Tell(); // placeholders for end pos and property section info - s.PutU4(0); // end pos - s.PutU4(0); // number of properties - s.PutU4(0); // total property section length + s.PutU8(0); // end pos + s.PutU8(0); // number of properties + s.PutU8(0); // total property section length // node name s.PutU1(uint8_t(name.size())); // length of node name @@ -352,9 +352,9 @@ void FBX::Node::EndPropertiesBinary( size_t pos = s.Tell(); ai_assert(pos > property_start); size_t property_section_size = pos - property_start; - s.Seek(start_pos + 4); - s.PutU4(uint32_t(num_properties)); - s.PutU4(uint32_t(property_section_size)); + s.Seek(start_pos + 8); // 8 bytes of uint64_t of end_pos + s.PutU8(num_properties); + s.PutU8(property_section_size); s.Seek(pos); } @@ -375,7 +375,7 @@ void FBX::Node::EndBinary( // now go back and write initial pos this->end_pos = s.Tell(); s.Seek(start_pos); - s.PutU4(uint32_t(end_pos)); + s.PutU8(end_pos); s.Seek(end_pos); } diff --git a/code/FBX/FBXExportProperty.cpp b/code/FBX/FBXExportProperty.cpp index f8593e629..f2a63b72b 100644 --- a/code/FBX/FBXExportProperty.cpp +++ b/code/FBX/FBXExportProperty.cpp @@ -59,11 +59,7 @@ namespace FBX { FBXExportProperty::FBXExportProperty(bool v) : type('C') -, data(1) { - data = { - uint8_t(v) - }; -} +, data(1, uint8_t(v)) {} FBXExportProperty::FBXExportProperty(int16_t v) : type('Y') diff --git a/code/FBX/FBXExporter.cpp b/code/FBX/FBXExporter.cpp index dd34e135f..9767f9a0a 100644 --- a/code/FBX/FBXExporter.cpp +++ b/code/FBX/FBXExporter.cpp @@ -67,6 +67,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include // RESOURCES: // https://code.blender.org/2013/08/fbx-binary-file-format-specification/ @@ -80,8 +81,8 @@ using namespace Assimp::FBX; // some constants that we'll use for writing metadata namespace Assimp { namespace FBX { - const std::string EXPORT_VERSION_STR = "7.4.0"; - const uint32_t EXPORT_VERSION_INT = 7400; // 7.4 == 2014/2015 + const std::string EXPORT_VERSION_STR = "7.5.0"; + const uint32_t EXPORT_VERSION_INT = 7500; // 7.5 == 2016+ // FBX files have some hashed values that depend on the creation time field, // but for now we don't actually know how to generate these. // what we can do is set them to a known-working version. @@ -1005,6 +1006,9 @@ void FBXExporter::WriteObjects () object_node.EndProperties(outstream, binary, indent); object_node.BeginChildren(outstream, binary, indent); + bool bJoinIdenticalVertices = mProperties->GetPropertyBool("bJoinIdenticalVertices", true); + std::vector> vVertexIndice;//save vertex_indices as it is needed later + // geometry (aiMesh) mesh_uids.clear(); indent = 1; @@ -1031,21 +1035,35 @@ void FBXExporter::WriteObjects () std::vector vertex_indices; // map of vertex value to its index in the data vector std::map index_by_vertex_value; - int32_t index = 0; - for (size_t vi = 0; vi < m->mNumVertices; ++vi) { - aiVector3D vtx = m->mVertices[vi]; - auto elem = index_by_vertex_value.find(vtx); - if (elem == index_by_vertex_value.end()) { - vertex_indices.push_back(index); - index_by_vertex_value[vtx] = index; - flattened_vertices.push_back(vtx[0]); - flattened_vertices.push_back(vtx[1]); - flattened_vertices.push_back(vtx[2]); - ++index; - } else { - vertex_indices.push_back(int32_t(elem->second)); + if(bJoinIdenticalVertices){ + int32_t index = 0; + for (size_t vi = 0; vi < m->mNumVertices; ++vi) { + aiVector3D vtx = m->mVertices[vi]; + auto elem = index_by_vertex_value.find(vtx); + if (elem == index_by_vertex_value.end()) { + vertex_indices.push_back(index); + index_by_vertex_value[vtx] = index; + flattened_vertices.push_back(vtx[0]); + flattened_vertices.push_back(vtx[1]); + flattened_vertices.push_back(vtx[2]); + ++index; + } else { + vertex_indices.push_back(int32_t(elem->second)); + } } } + else { // do not join vertex, respect the export flag + vertex_indices.resize(m->mNumVertices); + std::iota(vertex_indices.begin(), vertex_indices.end(), 0); + for(unsigned int v = 0; v < m->mNumVertices; ++ v) { + aiVector3D vtx = m->mVertices[v]; + flattened_vertices.push_back(vtx.x); + flattened_vertices.push_back(vtx.y); + flattened_vertices.push_back(vtx.z); + } + } + vVertexIndice.push_back(vertex_indices); + FBX::Node::WritePropertyNode( "Vertices", flattened_vertices, outstream, binary, indent ); @@ -1116,6 +1134,51 @@ void FBXExporter::WriteObjects () normals.End(outstream, binary, indent, true); } + // colors, if any + // TODO only one color channel currently + const int32_t colorChannelIndex = 0; + if (m->HasVertexColors(colorChannelIndex)) { + FBX::Node vertexcolors("LayerElementColor", int32_t(colorChannelIndex)); + vertexcolors.Begin(outstream, binary, indent); + vertexcolors.DumpProperties(outstream, binary, indent); + vertexcolors.EndProperties(outstream, binary, indent); + vertexcolors.BeginChildren(outstream, binary, indent); + indent = 3; + FBX::Node::WritePropertyNode( + "Version", int32_t(101), outstream, binary, indent + ); + char layerName[8]; + sprintf(layerName, "COLOR_%d", colorChannelIndex); + FBX::Node::WritePropertyNode( + "Name", (const char*)layerName, outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "MappingInformationType", "ByPolygonVertex", + outstream, binary, indent + ); + FBX::Node::WritePropertyNode( + "ReferenceInformationType", "Direct", + outstream, binary, indent + ); + std::vector color_data; + color_data.reserve(4 * polygon_data.size()); + for (size_t fi = 0; fi < m->mNumFaces; ++fi) { + const aiFace &f = m->mFaces[fi]; + for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) { + const aiColor4D &c = m->mColors[colorChannelIndex][f.mIndices[pvi]]; + color_data.push_back(c.r); + color_data.push_back(c.g); + color_data.push_back(c.b); + color_data.push_back(c.a); + } + } + FBX::Node::WritePropertyNode( + "Colors", color_data, outstream, binary, indent + ); + indent = 2; + vertexcolors.End(outstream, binary, indent, true); + } + // uvs, if any for (size_t uvi = 0; uvi < m->GetNumUVChannels(); ++uvi) { if (m->mNumUVComponents[uvi] > 2) { @@ -1209,6 +1272,11 @@ void FBXExporter::WriteObjects () le.AddChild("Type", "LayerElementNormal"); le.AddChild("TypedIndex", int32_t(0)); layer.AddChild(le); + // TODO only 1 color channel currently + le = FBX::Node("LayerElement"); + le.AddChild("Type", "LayerElementColor"); + le.AddChild("TypedIndex", int32_t(0)); + layer.AddChild(le); le = FBX::Node("LayerElement"); le.AddChild("Type", "LayerElementMaterial"); le.AddChild("TypedIndex", int32_t(0)); @@ -1219,6 +1287,16 @@ void FBXExporter::WriteObjects () layer.AddChild(le); layer.Dump(outstream, binary, indent); + for(unsigned int lr = 1; lr < m->GetNumUVChannels(); ++ lr) + { + FBX::Node layerExtra("Layer", int32_t(lr)); + layerExtra.AddChild("Version", int32_t(100)); + FBX::Node leExtra("LayerElement"); + leExtra.AddChild("Type", "LayerElementUV"); + leExtra.AddChild("TypedIndex", int32_t(lr)); + layerExtra.AddChild(leExtra); + layerExtra.Dump(outstream, binary, indent); + } // finish the node record indent = 1; n.End(outstream, binary, indent, true); @@ -1617,6 +1695,17 @@ void FBXExporter::WriteObjects () // at the same time we can build a list of all the skeleton nodes, // which will be used later to mark them as type "limbNode". std::unordered_set limbnodes; + + //actual bone nodes in fbx, without parenting-up + std::unordered_set setAllBoneNamesInScene; + for(unsigned int m = 0; m < mScene->mNumMeshes; ++ m) + { + aiMesh* pMesh = mScene->mMeshes[m]; + for(unsigned int b = 0; b < pMesh->mNumBones; ++ b) + setAllBoneNamesInScene.insert(pMesh->mBones[b]->mName.data); + } + aiMatrix4x4 mxTransIdentity; + // and a map of nodes by bone name, as finding them is annoying. std::map node_by_bone; for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) { @@ -1660,6 +1749,11 @@ void FBXExporter::WriteObjects () if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) { continue; } + //not a bone in scene && no effect in transform + if(setAllBoneNamesInScene.find(node_name)==setAllBoneNamesInScene.end() + && parent->mTransformation == mxTransIdentity) { + continue; + } // otherwise check if this is the root of the skeleton bool end = false; // is the mesh part of this node? @@ -1680,8 +1774,7 @@ void FBXExporter::WriteObjects () } if (end) { break; } } - limbnodes.insert(parent); - skeleton.insert(parent); + // if it was the skeleton root we can finish here if (end) { break; } } @@ -1723,28 +1816,8 @@ void FBXExporter::WriteObjects () // connect it connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]); - // we will be indexing by vertex... - // but there might be a different number of "vertices" - // between assimp and our output FBX. - // this code is cut-and-pasted from the geometry section above... - // ideally this should not be so. - // --- - // index of original vertex in vertex data vector - std::vector vertex_indices; - // map of vertex value to its index in the data vector - std::map index_by_vertex_value; - int32_t index = 0; - for (size_t vi = 0; vi < m->mNumVertices; ++vi) { - aiVector3D vtx = m->mVertices[vi]; - auto elem = index_by_vertex_value.find(vtx); - if (elem == index_by_vertex_value.end()) { - vertex_indices.push_back(index); - index_by_vertex_value[vtx] = index; - ++index; - } else { - vertex_indices.push_back(int32_t(elem->second)); - } - } + //computed before + std::vector& vertex_indices = vVertexIndice[mi]; // TODO, FIXME: this won't work if anything is not in the bind pose. // for now if such a situation is detected, we throw an exception. @@ -1822,41 +1895,10 @@ void FBXExporter::WriteObjects () inverse_bone_xform.Inverse(); aiMatrix4x4 tr = inverse_bone_xform * mesh_xform; - // this should be the same as the bone's mOffsetMatrix. - // if it's not the same, the skeleton isn't in the bind pose. - const float epsilon = 1e-4f; // some error is to be expected - bool bone_xform_okay = true; - if (b && ! tr.Equal(b->mOffsetMatrix, epsilon)) { - not_in_bind_pose.insert(b); - bone_xform_okay = false; - } + sdnode.AddChild("Transform", tr); - // if we have a bone we should use the mOffsetMatrix, - // otherwise try to just use the calculated transform. - if (b) { - sdnode.AddChild("Transform", b->mOffsetMatrix); - } else { - sdnode.AddChild("Transform", tr); - } - // note: it doesn't matter if we mix these, - // because if they disagree we'll throw an exception later. - // it could be that the skeleton is not in the bone pose - // but all bones are still defined, - // in which case this would use the mOffsetMatrix for everything - // and a correct skeleton would still be output. - // transformlink should be the position of the bone in world space. - // if the bone is in the bind pose (or nonexistent), - // we can just use the matrix we already calculated - if (bone_xform_okay) { - sdnode.AddChild("TransformLink", bone_xform); - // otherwise we can only work it out using the mesh position. - } else { - aiMatrix4x4 trl = b->mOffsetMatrix; - trl.Inverse(); - trl *= mesh_xform; - sdnode.AddChild("TransformLink", trl); - } + sdnode.AddChild("TransformLink", bone_xform); // note: this means we ALWAYS rely on the mesh node transform // being unchanged from the time the skeleton was bound. // there's not really any way around this at the moment. @@ -2441,7 +2483,7 @@ void FBXExporter::WriteModelNodes( void FBXExporter::WriteAnimationCurveNode( StreamWriterLE& outstream, int64_t uid, - std::string name, // "T", "R", or "S" + const std::string& name, // "T", "R", or "S" aiVector3D default_value, std::string property_name, // "Lcl Translation" etc int64_t layer_uid, diff --git a/code/FBX/FBXExporter.h b/code/FBX/FBXExporter.h index 71fb55c57..1ae727eda 100644 --- a/code/FBX/FBXExporter.h +++ b/code/FBX/FBXExporter.h @@ -156,7 +156,7 @@ namespace Assimp void WriteAnimationCurveNode( StreamWriterLE& outstream, int64_t uid, - std::string name, // "T", "R", or "S" + const std::string& name, // "T", "R", or "S" aiVector3D default_value, std::string property_name, // "Lcl Translation" etc int64_t animation_layer_uid, diff --git a/code/FBX/FBXImporter.cpp b/code/FBX/FBXImporter.cpp index ec8bbd2b4..afcc1ddc7 100644 --- a/code/FBX/FBXImporter.cpp +++ b/code/FBX/FBXImporter.cpp @@ -48,26 +48,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "FBXImporter.h" -#include "FBXTokenizer.h" -#include "FBXParser.h" -#include "FBXUtil.h" -#include "FBXDocument.h" #include "FBXConverter.h" +#include "FBXDocument.h" +#include "FBXParser.h" +#include "FBXTokenizer.h" +#include "FBXUtil.h" -#include #include -#include +#include #include +#include namespace Assimp { -template<> -const char* LogFunctions::Prefix() { - static auto prefix = "FBX: "; - return prefix; +template <> +const char *LogFunctions::Prefix() { + static auto prefix = "FBX: "; + return prefix; } -} +} // namespace Assimp using namespace Assimp; using namespace Assimp::Formatter; @@ -76,128 +76,123 @@ using namespace Assimp::FBX; namespace { static const aiImporterDesc desc = { - "Autodesk FBX Importer", - "", - "", - "", - aiImporterFlags_SupportTextFlavour, - 0, - 0, - 0, - 0, - "fbx" + "Autodesk FBX Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "fbx" }; } // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by #Importer -FBXImporter::FBXImporter() -{ +FBXImporter::FBXImporter() { } // ------------------------------------------------------------------------------------------------ // Destructor, private as well -FBXImporter::~FBXImporter() -{ +FBXImporter::~FBXImporter() { } // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. -bool FBXImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const -{ - const std::string& extension = GetExtension(pFile); - if (extension == std::string( desc.mFileExtensions ) ) { - return true; - } +bool FBXImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const { + const std::string &extension = GetExtension(pFile); + if (extension == std::string(desc.mFileExtensions)) { + return true; + } - else if ((!extension.length() || checkSig) && pIOHandler) { - // at least ASCII-FBX files usually have a 'FBX' somewhere in their head - const char* tokens[] = {"fbx"}; - return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1); - } - return false; + else if ((!extension.length() || checkSig) && pIOHandler) { + // at least ASCII-FBX files usually have a 'FBX' somewhere in their head + const char *tokens[] = { "fbx" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1); + } + return false; } // ------------------------------------------------------------------------------------------------ // List all extensions handled by this loader -const aiImporterDesc* FBXImporter::GetInfo () const -{ - return &desc; +const aiImporterDesc *FBXImporter::GetInfo() const { + return &desc; } // ------------------------------------------------------------------------------------------------ // Setup configuration properties for the loader -void FBXImporter::SetupProperties(const Importer* pImp) -{ - settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true); - settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false); - settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true); - settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true); - settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true); - settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true); - settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true); - settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false); - settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true); - settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true); - settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false); - settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true); - settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false); +void FBXImporter::SetupProperties(const Importer *pImp) { + settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true); + settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false); + settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true); + settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true); + settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true); + settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true); + settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true); + settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false); + settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true); + settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true); + settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false); + settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true); + settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false); } // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. -void FBXImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) -{ - std::unique_ptr stream(pIOHandler->Open(pFile,"rb")); - if (!stream) { - ThrowException("Could not open file for reading"); - } +void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr stream(pIOHandler->Open(pFile, "rb")); + if (!stream) { + ThrowException("Could not open file for reading"); + } - // read entire file into memory - no streaming for this, fbx - // files can grow large, but the assimp output data structure - // then becomes very large, too. Assimp doesn't support - // streaming for its output data structures so the net win with - // streaming input data would be very low. - std::vector contents; - contents.resize(stream->FileSize()+1); - stream->Read( &*contents.begin(), 1, contents.size()-1 ); - contents[ contents.size() - 1 ] = 0; - const char* const begin = &*contents.begin(); + // read entire file into memory - no streaming for this, fbx + // files can grow large, but the assimp output data structure + // then becomes very large, too. Assimp doesn't support + // streaming for its output data structures so the net win with + // streaming input data would be very low. + std::vector contents; + contents.resize(stream->FileSize() + 1); + stream->Read(&*contents.begin(), 1, contents.size() - 1); + contents[contents.size() - 1] = 0; + const char *const begin = &*contents.begin(); - // broadphase tokenizing pass in which we identify the core - // syntax elements of FBX (brackets, commas, key:value mappings) - TokenList tokens; - try { + // broadphase tokenizing pass in which we identify the core + // syntax elements of FBX (brackets, commas, key:value mappings) + TokenList tokens; + try { - bool is_binary = false; - if (!strncmp(begin,"Kaydara FBX Binary",18)) { - is_binary = true; - TokenizeBinary(tokens,begin,contents.size()); - } - else { - Tokenize(tokens,begin); - } + bool is_binary = false; + if (!strncmp(begin, "Kaydara FBX Binary", 18)) { + is_binary = true; + TokenizeBinary(tokens, begin, contents.size()); + } else { + Tokenize(tokens, begin); + } - // use this information to construct a very rudimentary - // parse-tree representing the FBX scope structure - Parser parser(tokens, is_binary); + // use this information to construct a very rudimentary + // parse-tree representing the FBX scope structure + Parser parser(tokens, is_binary); - // take the raw parse-tree and convert it to a FBX DOM - Document doc(parser,settings); + // take the raw parse-tree and convert it to a FBX DOM + Document doc(parser, settings); - FbxUnit unit(FbxUnit::cm); - if (settings.convertToMeters) { - unit = FbxUnit::m; - } - // convert the FBX DOM to aiScene - ConvertToAssimpScene(pScene,doc, settings.removeEmptyBones, unit); + // convert the FBX DOM to aiScene + ConvertToAssimpScene(pScene, doc, settings.removeEmptyBones); - std::for_each(tokens.begin(),tokens.end(),Util::delete_fun()); - } - catch(std::exception&) { - std::for_each(tokens.begin(),tokens.end(),Util::delete_fun()); - throw; - } + // size relative to cm + float size_relative_to_cm = doc.GlobalSettings().UnitScaleFactor(); + + // Set FBX file scale is relative to CM must be converted to M for + // assimp universal format (M) + SetFileScale(size_relative_to_cm * 0.01f); + + std::for_each(tokens.begin(), tokens.end(), Util::delete_fun()); + } catch (std::exception &) { + std::for_each(tokens.begin(), tokens.end(), Util::delete_fun()); + throw; + } } #endif // !ASSIMP_BUILD_NO_FBX_IMPORTER diff --git a/code/FBX/FBXMeshGeometry.cpp b/code/FBX/FBXMeshGeometry.cpp index 44a0264ca..1386e2383 100644 --- a/code/FBX/FBXMeshGeometry.cpp +++ b/code/FBX/FBXMeshGeometry.cpp @@ -115,7 +115,6 @@ MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::strin if(tempVerts.empty()) { FBXImporter::LogWarn("encountered mesh with no vertices"); - return; } std::vector tempFaces; @@ -123,7 +122,6 @@ MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::strin if(tempFaces.empty()) { FBXImporter::LogWarn("encountered mesh with no faces"); - return; } m_vertices.reserve(tempFaces.size()); @@ -612,8 +610,11 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector& materials_out, cons const std::string& ReferenceInformationType) { const size_t face_count = m_faces.size(); - ai_assert(face_count); - + if( 0 == face_count ) + { + return; + } + // materials are handled separately. First of all, they are assigned per-face // and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect // has a slightly different meaning for materials. @@ -624,16 +625,14 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector& materials_out, cons if (materials_out.empty()) { FBXImporter::LogError(Formatter::format("expected material index, ignoring")); return; - } - else if (materials_out.size() > 1) { + } else if (materials_out.size() > 1) { FBXImporter::LogWarn(Formatter::format("expected only a single material index, ignoring all except the first one")); materials_out.clear(); } materials_out.resize(m_vertices.size()); std::fill(materials_out.begin(), materials_out.end(), materials_out.at(0)); - } - else if (MappingInformationType == "ByPolygon" && ReferenceInformationType == "IndexToDirect") { + } else if (MappingInformationType == "ByPolygon" && ReferenceInformationType == "IndexToDirect") { materials_out.resize(face_count); if(materials_out.size() != face_count) { @@ -642,18 +641,16 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector& materials_out, cons ); return; } - } - else { + } else { FBXImporter::LogError(Formatter::format("ignoring material assignments, access type not implemented: ") << MappingInformationType << "," << ReferenceInformationType); } } // ------------------------------------------------------------------------------------------------ ShapeGeometry::ShapeGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc) - : Geometry(id, element, name, doc) -{ - const Scope* sc = element.Compound(); - if (!sc) { +: Geometry(id, element, name, doc) { + const Scope *sc = element.Compound(); + if (nullptr == sc) { DOMError("failed to read Geometry object (class: Shape), no data scope found"); } const Element& Indexes = GetRequiredElement(*sc, "Indexes", &element); diff --git a/code/Importer/IFC/IFCCurve.cpp b/code/Importer/IFC/IFCCurve.cpp index b0a1fdaa9..a817b4f9f 100644 --- a/code/Importer/IFC/IFCCurve.cpp +++ b/code/Importer/IFC/IFCCurve.cpp @@ -311,10 +311,9 @@ class TrimmedCurve : public BoundedCurve { public: // -------------------------------------------------- TrimmedCurve(const Schema_2x3::IfcTrimmedCurve& entity, ConversionData& conv) - : BoundedCurve(entity,conv) + : BoundedCurve(entity,conv), + base(std::shared_ptr(Curve::Convert(entity.BasisCurve,conv))) { - base = std::shared_ptr(Curve::Convert(entity.BasisCurve,conv)); - typedef std::shared_ptr Entry; // for some reason, trimmed curves can either specify a parametric value @@ -500,7 +499,7 @@ bool Curve::InRange(IfcFloat u) const { if (IsClosed()) { return true; } - const IfcFloat epsilon = 1e-5; + const IfcFloat epsilon = Math::getEpsilon(); return u - range.first > -epsilon && range.second - u > -epsilon; } #endif diff --git a/code/Importer/IFC/IFCGeometry.cpp b/code/Importer/IFC/IFCGeometry.cpp index 032030112..d1c7aee19 100644 --- a/code/Importer/IFC/IFCGeometry.cpp +++ b/code/Importer/IFC/IFCGeometry.cpp @@ -128,7 +128,7 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m outer_polygon_it = begin + master_bounds; } else { - for(iit = begin; iit != end; iit++) { + for(iit = begin; iit != end; ++iit) { // find the polygon with the largest area and take it as the outer bound. IfcVector3& n = normals[std::distance(begin,iit)]; const IfcFloat area = n.SquareLength(); diff --git a/code/Importer/IFC/IFCOpenings.cpp b/code/Importer/IFC/IFCOpenings.cpp index a24ffb4ca..d6c40b383 100644 --- a/code/Importer/IFC/IFCOpenings.cpp +++ b/code/Importer/IFC/IFCOpenings.cpp @@ -593,7 +593,7 @@ typedef std::vector(); return (std::fabs(bb.second.x - ibb.first.x) < epsilon && bb.first.y <= ibb.second.y && bb.second.y >= ibb.first.y) || (std::fabs(bb.first.x - ibb.second.x) < epsilon && ibb.first.y <= bb.second.y && ibb.second.y >= bb.first.y) || (std::fabs(bb.second.y - ibb.first.y) < epsilon && bb.first.x <= ibb.second.x && bb.second.x >= ibb.first.x) || @@ -681,7 +681,7 @@ bool IntersectingLineSegments(const IfcVector2& n0, const IfcVector2& n1, // ------------------------------------------------------------------------------------------------ void FindAdjacentContours(ContourVector::iterator current, const ContourVector& contours) { - const IfcFloat sqlen_epsilon = static_cast(1e-8); + const IfcFloat sqlen_epsilon = static_cast(Math::getEpsilon()); const BoundingBox& bb = (*current).bb; // What is to be done here is to populate the skip lists for the contour @@ -758,7 +758,7 @@ void FindAdjacentContours(ContourVector::iterator current, const ContourVector& // ------------------------------------------------------------------------------------------------ AI_FORCE_INLINE bool LikelyBorder(const IfcVector2& vdelta) { - const IfcFloat dot_point_epsilon = static_cast(1e-5); + const IfcFloat dot_point_epsilon = static_cast(Math::getEpsilon()); return std::fabs(vdelta.x * vdelta.y) < dot_point_epsilon; } diff --git a/code/Irr/IRRMeshLoader.cpp b/code/Irr/IRRMeshLoader.cpp index f3aed5943..057218464 100644 --- a/code/Irr/IRRMeshLoader.cpp +++ b/code/Irr/IRRMeshLoader.cpp @@ -57,7 +57,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include using namespace Assimp; using namespace irr; diff --git a/code/M3D/M3DExporter.cpp b/code/M3D/M3DExporter.cpp new file mode 100644 index 000000000..b1c7ebdba --- /dev/null +++ b/code/M3D/M3DExporter.cpp @@ -0,0 +1,420 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team +Copyright (c) 2019 bzt + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER + +#define M3D_IMPLEMENTATION +#define M3D_NOIMPORTER +#define M3D_EXPORTER +#define M3D_ASCII +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER +#define M3D_NODUP +#endif + +// Header files, standard library. +#include // shared_ptr +#include +#include + +#include // aiGetVersion +#include +#include +#include +#include // StreamWriterLE +#include // DeadlyExportError +#include // aiTextureType +#include +#include +#include "M3DExporter.h" +#include "M3DMaterials.h" + +// RESOURCES: +// https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md +// https://gitlab.com/bztsrc/model3d/blob/master/docs/a3d_format.md + +/* + * Currently supports static meshes, vertex colors, materials, textures + * + * For animation, it would require the following conversions: + * - aiNode (bones) -> m3d_t.bone (with parent id, position vector and oriantation quaternion) + * - aiMesh.aiBone -> m3d_t.skin (per vertex, with bone id, weight pairs) + * - aiAnimation -> m3d_action (frame with timestamp and list of bone id, position, orientation + * triplets, instead of per bone timestamp + lists) + */ +using namespace Assimp; + +namespace Assimp { + + // --------------------------------------------------------------------- + // Worker function for exporting a scene to binary M3D. + // Prototyped and registered in Exporter.cpp + void ExportSceneM3D ( + const char* pFile, + IOSystem* pIOSystem, + const aiScene* pScene, + const ExportProperties* pProperties + ){ + // initialize the exporter + M3DExporter exporter(pScene, pProperties); + + // perform binary export + exporter.doExport(pFile, pIOSystem, false); + } + + // --------------------------------------------------------------------- + // Worker function for exporting a scene to ASCII A3D. + // Prototyped and registered in Exporter.cpp + void ExportSceneA3D ( + const char* pFile, + IOSystem* pIOSystem, + const aiScene* pScene, + const ExportProperties* pProperties + + ){ + // initialize the exporter + M3DExporter exporter(pScene, pProperties); + + // perform ascii export + exporter.doExport(pFile, pIOSystem, true); + } + +} // end of namespace Assimp + +// ------------------------------------------------------------------------------------------------ +M3DExporter::M3DExporter ( const aiScene* pScene, const ExportProperties* pProperties ) +: mScene(pScene) +, mProperties(pProperties) +, outfile() +, m3d(nullptr) { } + +// ------------------------------------------------------------------------------------------------ +void M3DExporter::doExport ( + const char* pFile, + IOSystem* pIOSystem, + bool toAscii +){ + // TODO: convert mProperties into M3D_EXP_* flags + (void)mProperties; + + // open the indicated file for writing (in binary / ASCII mode) + outfile.reset(pIOSystem->Open(pFile, toAscii ? "wt" : "wb")); + if (!outfile) { + throw DeadlyExportError( "could not open output .m3d file: " + std::string(pFile) ); + } + + // use malloc() here because m3d_free() will call free() + m3d = (m3d_t*)calloc(1, sizeof(m3d_t)); + if(!m3d) { + throw DeadlyExportError( "memory allocation error" ); + } + m3d->name = _m3d_safestr((char*)&mScene->mRootNode->mName.data, 2); + + // Create a model from assimp structures + aiMatrix4x4 m; + NodeWalk(mScene->mRootNode, m); + + // serialize the structures + unsigned int size; + unsigned char *output = m3d_save(m3d, M3D_EXP_FLOAT, + M3D_EXP_EXTRA | (toAscii ? M3D_EXP_ASCII : 0), &size); + m3d_free(m3d); + if(!output || size < 8) { + throw DeadlyExportError( "unable to serialize into Model 3D" ); + } + + // Write out serialized model + outfile->Write(output, size, 1); + + // explicitly release file pointer, + // so we don't have to rely on class destruction. + outfile.reset(); +} + + +// ------------------------------------------------------------------------------------------------ +// helper to add a vertex (private to NodeWalk) +m3dv_t *M3DExporter::AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx) +{ + if(v->x == (M3D_FLOAT)-0.0) v->x = (M3D_FLOAT)0.0; + if(v->y == (M3D_FLOAT)-0.0) v->y = (M3D_FLOAT)0.0; + if(v->z == (M3D_FLOAT)-0.0) v->z = (M3D_FLOAT)0.0; + if(v->w == (M3D_FLOAT)-0.0) v->w = (M3D_FLOAT)0.0; + vrtx = (m3dv_t*)M3D_REALLOC(vrtx, ((*numvrtx) + 1) * sizeof(m3dv_t)); + memcpy(&vrtx[*numvrtx], v, sizeof(m3dv_t)); + *idx = *numvrtx; + (*numvrtx)++; + return vrtx; +} + +// ------------------------------------------------------------------------------------------------ +// helper to add a tmap (private to NodeWalk) +m3dti_t *M3DExporter::AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx) +{ + tmap = (m3dti_t*)M3D_REALLOC(tmap, ((*numtmap) + 1) * sizeof(m3dti_t)); + memcpy(&tmap[*numtmap], ti, sizeof(m3dti_t)); + *idx = *numtmap; + (*numtmap)++; + return tmap; +} + +// ------------------------------------------------------------------------------------------------ +// recursive node walker +void M3DExporter::NodeWalk(const aiNode* pNode, aiMatrix4x4 m) +{ + aiMatrix4x4 nm = m * pNode->mTransformation; + + for(unsigned int i = 0; i < pNode->mNumMeshes; i++) { + const aiMesh *mesh = mScene->mMeshes[pNode->mMeshes[i]]; + unsigned int mi = (M3D_INDEX)-1U; + if(mScene->mMaterials) { + // get the material for this mesh + mi = addMaterial(mScene->mMaterials[mesh->mMaterialIndex]); + } + // iterate through the mesh faces + for(unsigned int j = 0; j < mesh->mNumFaces; j++) { + unsigned int n; + const aiFace* face = &(mesh->mFaces[j]); + // only triangle meshes supported for now + if(face->mNumIndices != 3) { + throw DeadlyExportError( "use aiProcess_Triangulate before export" ); + } + // add triangle to the output + n = m3d->numface++; + m3d->face = (m3df_t*)M3D_REALLOC(m3d->face, + m3d->numface * sizeof(m3df_t)); + if(!m3d->face) { + throw DeadlyExportError( "memory allocation error" ); + } + /* set all index to -1 by default */ + m3d->face[n].vertex[0] = m3d->face[n].vertex[1] = m3d->face[n].vertex[2] = + m3d->face[n].normal[0] = m3d->face[n].normal[1] = m3d->face[n].normal[2] = + m3d->face[n].texcoord[0] = m3d->face[n].texcoord[1] = m3d->face[n].texcoord[2] = -1U; + m3d->face[n].materialid = mi; + for(unsigned int k = 0; k < face->mNumIndices; k++) { + // get the vertex's index + unsigned int l = face->mIndices[k]; + unsigned int idx; + m3dv_t vertex; + m3dti_t ti; + // multiply the position vector by the transformation matrix + aiVector3D v = mesh->mVertices[l]; + v *= nm; + vertex.x = v.x; + vertex.y = v.y; + vertex.z = v.z; + vertex.w = 1.0; + vertex.color = 0; + vertex.skinid = -1U; + // add color if defined + if(mesh->HasVertexColors(0)) + vertex.color = mkColor(&mesh->mColors[0][l]); + // save the vertex to the output + m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, + &vertex, &idx); + m3d->face[n].vertex[k] = (M3D_INDEX)idx; + // do we have texture coordinates? + if(mesh->HasTextureCoords(0)) { + ti.u = mesh->mTextureCoords[0][l].x; + ti.v = mesh->mTextureCoords[0][l].y; + m3d->tmap = AddTmap(m3d->tmap, &m3d->numtmap, &ti, &idx); + m3d->face[n].texcoord[k] = (M3D_INDEX)idx; + } + // do we have normal vectors? + if(mesh->HasNormals()) { + vertex.x = mesh->mNormals[l].x; + vertex.y = mesh->mNormals[l].y; + vertex.z = mesh->mNormals[l].z; + vertex.color = 0; + m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, &vertex, &idx); + m3d->face[n].normal[k] = (M3D_INDEX)idx; + } + } + } + } + // repeat for the children nodes + for (unsigned int i = 0; i < pNode->mNumChildren; i++) { + NodeWalk(pNode->mChildren[i], nm); + } +} + +// ------------------------------------------------------------------------------------------------ +// convert aiColor4D into uint32_t +uint32_t M3DExporter::mkColor(aiColor4D* c) +{ + return ((uint8_t)(c->a*255) << 24L) | + ((uint8_t)(c->b*255) << 16L) | + ((uint8_t)(c->g*255) << 8L) | + ((uint8_t)(c->r*255) << 0L); +} + +// ------------------------------------------------------------------------------------------------ +// add a material to the output +M3D_INDEX M3DExporter::addMaterial(const aiMaterial *mat) +{ + unsigned int mi = -1U; + aiColor4D c; + aiString name; + ai_real f; + char *fn; + + if(mat && mat->Get(AI_MATKEY_NAME, name) == AI_SUCCESS && name.length && + strcmp((char*)&name.data, AI_DEFAULT_MATERIAL_NAME)) { + // check if we have saved a material by this name. This has to be done + // because only the referenced materials should be added to the output + for(unsigned int i = 0; i < m3d->nummaterial; i++) + if(!strcmp((char*)&name.data, m3d->material[i].name)) { + mi = i; + break; + } + // if not found, add the material to the output + if(mi == -1U) { + unsigned int k; + mi = m3d->nummaterial++; + m3d->material = (m3dm_t*)M3D_REALLOC(m3d->material, m3d->nummaterial + * sizeof(m3dm_t)); + if(!m3d->material) { + throw DeadlyExportError( "memory allocation error" ); + } + m3d->material[mi].name = _m3d_safestr((char*)&name.data, 0); + m3d->material[mi].numprop = 0; + m3d->material[mi].prop = NULL; + // iterate through the material property table and see what we got + for(k = 0; k < 15; k++) { + unsigned int j; + if(m3d_propertytypes[k].format == m3dpf_map) + continue; + if(aiProps[k].pKey) { + switch(m3d_propertytypes[k].format) { + case m3dpf_color: + if(mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, c) == AI_SUCCESS) + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, mkColor(&c)); + break; + case m3dpf_float: + if(mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, f) == AI_SUCCESS) + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, + /* not (uint32_t)f, because we don't want to convert + * it, we want to see it as 32 bits of memory */ + *((uint32_t*)&f)); + break; + case m3dpf_uint8: + if(mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, j) == AI_SUCCESS) { + // special conversion for illumination model property + if(m3d_propertytypes[k].id == m3dp_il) { + switch(j) { + case aiShadingMode_NoShading: j = 0; break; + case aiShadingMode_Phong: j = 2; break; + default: j = 1; break; + } + } + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, j); + } + break; + default: + if(mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, j) == AI_SUCCESS) + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, j); + break; + } + } + if(aiTxProps[k].pKey && + mat->GetTexture((aiTextureType)aiTxProps[k].type, + aiTxProps[k].index, &name, NULL, NULL, NULL, + NULL, NULL) == AI_SUCCESS) { + unsigned int i; + for(j = name.length-1; j > 0 && name.data[j]!='.'; j++); + if(j && name.data[j]=='.' && + (name.data[j+1]=='p' || name.data[j+1]=='P') && + (name.data[j+1]=='n' || name.data[j+1]=='N') && + (name.data[j+1]=='g' || name.data[j+1]=='G')) + name.data[j]=0; + // do we have this texture saved already? + fn = _m3d_safestr((char*)&name.data, 0); + for(j = 0, i = -1U; j < m3d->numtexture; j++) + if(!strcmp(fn, m3d->texture[j].name)) { + i = j; + free(fn); + break; + } + if(i == -1U) { + i = m3d->numtexture++; + m3d->texture = (m3dtx_t*)M3D_REALLOC( + m3d->texture, + m3d->numtexture * sizeof(m3dtx_t)); + if(!m3d->texture) { + throw DeadlyExportError( "memory allocation error" ); + } + // we don't need the texture itself, only its name + m3d->texture[i].name = fn; + m3d->texture[i].w = 0; + m3d->texture[i].h = 0; + m3d->texture[i].d = NULL; + } + addProp(&m3d->material[mi], + m3d_propertytypes[k].id + 128, i); + } + } + } + } + return mi; +} + +// ------------------------------------------------------------------------------------------------ +// add a material property to the output +void M3DExporter::addProp(m3dm_t *m, uint8_t type, uint32_t value) +{ + unsigned int i; + i = m->numprop++; + m->prop = (m3dp_t*)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t)); + if(!m->prop) { throw DeadlyExportError( "memory allocation error" ); } + m->prop[i].type = type; + m->prop[i].value.num = value; +} + +#endif // ASSIMP_BUILD_NO_M3D_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/code/M3D/M3DExporter.h b/code/M3D/M3DExporter.h new file mode 100644 index 000000000..58d8d597e --- /dev/null +++ b/code/M3D/M3DExporter.h @@ -0,0 +1,100 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team +Copyright (c) 2019 bzt + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file M3DExporter.h +* @brief Declares the exporter class to write a scene to a Model 3D file +*/ +#ifndef AI_M3DEXPORTER_H_INC +#define AI_M3DEXPORTER_H_INC + +#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER + +#include "m3d.h" + +#include +//#include +#include // StreamWriterLE +#include // DeadlyExportError + +#include // shared_ptr + +struct aiScene; +struct aiNode; +struct aiMaterial; +struct aiFace; + +namespace Assimp +{ + class IOSystem; + class IOStream; + class ExportProperties; + + // --------------------------------------------------------------------- + /** Helper class to export a given scene to an M3D file. */ + // --------------------------------------------------------------------- + class M3DExporter + { + public: + /// Constructor for a specific scene to export + M3DExporter(const aiScene* pScene, const ExportProperties* pProperties); + // call this to do the actual export + void doExport(const char* pFile, IOSystem* pIOSystem, bool toAscii); + + private: + const aiScene* mScene; // the scene to export + const ExportProperties* mProperties; // currently unused + std::shared_ptr outfile; // file to write to + m3d_t *m3d; // model for the C library to convert to + + // helper to do the recursive walking + void NodeWalk(const aiNode* pNode, aiMatrix4x4 m); + m3dv_t *AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx); + m3dti_t *AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx); + uint32_t mkColor(aiColor4D* c); + M3D_INDEX addMaterial(const aiMaterial *mat); + void addProp(m3dm_t *m, uint8_t type, uint32_t value); + }; +} + +#endif // ASSIMP_BUILD_NO_M3D_EXPORTER + +#endif // AI_M3DEXPORTER_H_INC diff --git a/code/M3D/M3DImporter.cpp b/code/M3D/M3DImporter.cpp new file mode 100644 index 000000000..9371e2228 --- /dev/null +++ b/code/M3D/M3DImporter.cpp @@ -0,0 +1,766 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team +Copyright (c) 2019 bzt + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER + +#define M3D_IMPLEMENTATION +#define M3D_ASCII +#define M3D_NONORMALS /* leave the post-processing to Assimp */ +#define M3D_NOWEIGHTS +#define M3D_NOANIMATION + +#include +#include +#include +#include +#include +#include +#include +#include +#include "M3DImporter.h" +#include "M3DMaterials.h" + +// RESOURCES: +// https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md +// https://gitlab.com/bztsrc/model3d/blob/master/docs/a3d_format.md + +/* + Unfortunately aiNode has bone structures and meshes too, yet we can't assign + the mesh to a bone aiNode as a skin may refer to several aiNodes. Therefore + I've decided to import into this structure: + + aiScene->mRootNode + | |->mMeshes (all the meshes) + | \->children (empty if there's no skeleton imported, no meshes) + | \->skeleton root aiNode* + | |->bone aiNode + | | \->subbone aiNode + | |->bone aiNode + | | ... + | \->bone aiNode + \->mMeshes[] + \->aiBone, referencing mesh-less aiNodes from above + + * - normally one, but if a model has several skeleton roots, then all of them + are listed in aiScene->mRootNode->children, but all without meshes +*/ + +static const aiImporterDesc desc = { + "Model 3D Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "m3d a3d" +}; + +// workaround: the SDK expects a C callback, but we want to use Assimp::IOSystem to implement that +extern "C" { + void* m3dimporter_pIOHandler; + + unsigned char *m3dimporter_readfile(char *fn, unsigned int *size) { + ai_assert( nullptr != fn ); + ai_assert( nullptr != size ); + std::string file(fn); + std::unique_ptr pStream( + (reinterpret_cast(m3dimporter_pIOHandler))->Open( file, "rb")); + size_t fileSize = 0; + unsigned char *data = NULL; + // sometimes pStream is nullptr for some reason (should be an empty object returning nothing I guess) + if(pStream) { + fileSize = pStream->FileSize(); + // should be allocated with malloc(), because the library will call free() to deallocate + data = (unsigned char*)malloc(fileSize); + if( !data || !pStream.get() || !fileSize || fileSize != pStream->Read(data,1,fileSize)) { + pStream.reset(); + *size = 0; + // don't throw a deadly exception, it's not fatal if we can't read an external asset + return nullptr; + } + pStream.reset(); + } + *size = (int)fileSize; + return data; + } +} + +namespace Assimp { + +using namespace std; + +// ------------------------------------------------------------------------------------------------ +// Default constructor +M3DImporter::M3DImporter() +: mScene(nullptr) +, m3d(nullptr) { } + +// ------------------------------------------------------------------------------------------------ +// Destructor. +M3DImporter::~M3DImporter() {} + +// ------------------------------------------------------------------------------------------------ +// Returns true, if file is a binary or ASCII Model 3D file. +bool M3DImporter::CanRead(const std::string& pFile, IOSystem* pIOHandler , bool checkSig) const { + const std::string extension = GetExtension(pFile); + + if (extension == "m3d" || extension == "a3d") + return true; + else if (!extension.length() || checkSig) { + if (!pIOHandler) { + return true; + } + /* + * don't use CheckMagicToken because that checks with swapped bytes too, leading to false + * positives. This magic is not uint32_t, but char[4], so memcmp is the best way + + const char* tokens[] = {"3DMO", "3dmo"}; + return CheckMagicToken(pIOHandler,pFile,tokens,2,0,4); + */ + std::unique_ptr pStream (pIOHandler->Open(pFile, "rb")); + unsigned char data[4]; + if(4 != pStream->Read(data,1,4)) { + return false; + } + return !memcmp(data, "3DMO", 4) /* bin */ || !memcmp(data, "3dmo", 4) /* ASCII */; + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc* M3DImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Model 3D import implementation +void M3DImporter::InternReadFile( const std::string &file, aiScene* pScene, IOSystem* pIOHandler) { + // Read file into memory + std::unique_ptr pStream( pIOHandler->Open( file, "rb")); + if( !pStream.get() ) { + throw DeadlyImportError( "Failed to open file " + file + "." ); + } + + // Get the file-size and validate it, throwing an exception when fails + size_t fileSize = pStream->FileSize(); + if( fileSize < 8 ) { + throw DeadlyImportError( "M3D-file " + file + " is too small." ); + } + std::unique_ptr _buffer (new unsigned char[fileSize]); + unsigned char *data( _buffer.get() ); + if(fileSize != pStream->Read(data,1,fileSize)) { + throw DeadlyImportError( "Failed to read the file " + file + "." ); + } + + // Get the path for external assets + std::string folderName( "./" ); + std::string::size_type pos = file.find_last_of( "\\/" ); + if ( pos != std::string::npos ) { + folderName = file.substr( 0, pos ); + if ( !folderName.empty() ) { + pIOHandler->PushDirectory( folderName ); + } + } + // pass this IOHandler to the C callback + m3dimporter_pIOHandler = pIOHandler; + + //DefaultLogger::create("/dev/stderr", Logger::VERBOSE); + ASSIMP_LOG_DEBUG_F("M3D: loading ", file); + + // let the C SDK do the hard work for us + m3d = m3d_load(&data[0], m3dimporter_readfile, free, nullptr); + m3dimporter_pIOHandler = nullptr; + if( !m3d ) { + throw DeadlyImportError( "Unable to parse " + file + " as M3D." ); + } + + // create the root node + pScene->mRootNode = new aiNode; + pScene->mRootNode->mName = aiString(std::string(std::string(m3d->name))); + pScene->mRootNode->mTransformation = aiMatrix4x4(); + pScene->mRootNode->mNumChildren = 0; + mScene = pScene; + + ASSIMP_LOG_DEBUG("M3D: root node " + std::string(m3d->name)); + + // now we just have to fill up the Assimp structures in pScene + importMaterials(); + importTextures(); + importBones(-1U, pScene->mRootNode); + importMeshes(); + importAnimations(); + + // we don't need the SDK's version any more + m3d_free(m3d); + + // Pop directory stack + if ( pIOHandler->StackSize() > 0 ) { + pIOHandler->PopDirectory(); + } +} + +// ------------------------------------------------------------------------------------------------ +// convert materials. properties are converted using a static table in M3DMaterials.h +void M3DImporter::importMaterials() +{ + unsigned int i, j, k, l, n; + m3dm_t *m; + aiString name = aiString(AI_DEFAULT_MATERIAL_NAME); + aiColor4D c; + ai_real f; + + ai_assert(mScene != nullptr); + ai_assert(m3d != nullptr); + + mScene->mNumMaterials = m3d->nummaterial + 1; + mScene->mMaterials = new aiMaterial*[ m3d->nummaterial + 1 ]; + + ASSIMP_LOG_DEBUG_F("M3D: importMaterials ", mScene->mNumMaterials); + + // add a default material as first + aiMaterial* mat = new aiMaterial; + mat->AddProperty( &name, AI_MATKEY_NAME ); + c.a = 1.0; c.b = c.g = c.r = 0.6; + mat->AddProperty( &c, 1, AI_MATKEY_COLOR_DIFFUSE); + mScene->mMaterials[0] = mat; + + for(i = 0; i < m3d->nummaterial; i++) { + m = &m3d->material[i]; + aiMaterial* mat = new aiMaterial; + name.Set(std::string(m->name)); + mat->AddProperty( &name, AI_MATKEY_NAME ); + for(j = 0; j < m->numprop; j++) { + // look up property type + // 0 - 127 scalar values, + // 128 - 255 the same properties but for texture maps + k = 256; + for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) + if(m->prop[j].type == m3d_propertytypes[l].id || + m->prop[j].type == m3d_propertytypes[l].id + 128) { + k = l; + break; + } + // should never happen, but be safe than sorry + if(k == 256) continue; + + // scalar properties + if(m->prop[j].type < 128 && aiProps[k].pKey) { + switch(m3d_propertytypes[k].format) { + case m3dpf_color: + c = mkColor(m->prop[j].value.color); + mat->AddProperty(&c, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); + break; + case m3dpf_float: + f = m->prop[j].value.fnum; + mat->AddProperty(&f, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); + break; + default: + n = m->prop[j].value.num; + if(m->prop[j].type == m3dp_il) { + switch(n) { + case 0: n = aiShadingMode_NoShading; break; + case 2: n = aiShadingMode_Phong; break; + default: n = aiShadingMode_Gouraud; break; + } + } + mat->AddProperty(&n, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); + break; + } + } + // texture map properties + if(m->prop[j].type >= 128 && aiTxProps[k].pKey && + // extra check, should never happen, do we have the refered texture? + m->prop[j].value.textureid < m3d->numtexture && + m3d->texture[m->prop[j].value.textureid].name) { + name.Set(std::string(std::string(m3d->texture[m->prop[j].value.textureid].name) + ".png")); + mat->AddProperty(&name, aiTxProps[k].pKey, aiTxProps[k].type, aiTxProps[k].index); + n = 0; + mat->AddProperty(&n, 1, _AI_MATKEY_UVWSRC_BASE, aiProps[k].type, aiProps[k].index); + } + } + mScene->mMaterials[i + 1] = mat; + } +} + +// ------------------------------------------------------------------------------------------------ +// import textures, this is the simplest of all +void M3DImporter::importTextures() +{ + unsigned int i; + const char *formatHint[] = { "rgba0800", "rgba0808", "rgba8880", "rgba8888" }; + m3dtx_t *t; + + ai_assert(mScene != nullptr); + ai_assert(m3d != nullptr); + + mScene->mNumTextures = m3d->numtexture; + ASSIMP_LOG_DEBUG_F("M3D: importTextures ", mScene->mNumTextures); + + if(!m3d->numtexture) + return; + + mScene->mTextures = new aiTexture*[m3d->numtexture]; + for(i = 0; i < m3d->numtexture; i++) { + unsigned int j, k; + t = &m3d->texture[i]; + if(!t->w || !t->h || !t->f || !t->d) continue; + aiTexture *tx = new aiTexture; + strcpy(tx->achFormatHint, formatHint[t->f - 1]); + tx->mFilename = aiString(std::string(t->name) + ".png"); + tx->mWidth = t->w; + tx->mHeight = t->h; + tx->pcData = new aiTexel[ tx->mWidth*tx->mHeight ]; + for(j = k = 0; j < tx->mWidth*tx->mHeight; j++) { + switch(t->f) { + case 1: tx->pcData[j].g = t->d[k++]; break; + case 2: tx->pcData[j].g = t->d[k++]; tx->pcData[j].a = t->d[k++]; break; + case 3: + tx->pcData[j].r = t->d[k++]; tx->pcData[j].g = t->d[k++]; + tx->pcData[j].b = t->d[k++]; tx->pcData[j].a = 255; + break; + case 4: + tx->pcData[j].r = t->d[k++]; tx->pcData[j].g = t->d[k++]; + tx->pcData[j].b = t->d[k++]; tx->pcData[j].a = t->d[k++]; + break; + } + } + mScene->mTextures[i] = tx; + } +} + +// ------------------------------------------------------------------------------------------------ +// this is tricky. M3D has a global vertex and UV list, and faces are indexing them +// individually. In assimp there're per mesh vertex and UV lists, and they must be +// indexed simultaneously. +void M3DImporter::importMeshes() +{ + unsigned int i, j, k, l, numpoly = 3, lastMat = -2U; + std::vector *meshes = new std::vector(); + std::vector *faces = nullptr; + std::vector *vertices = nullptr; + std::vector *normals = nullptr; + std::vector *texcoords = nullptr; + std::vector *colors = nullptr; + std::vector *vertexids = nullptr; + aiMesh *pMesh = nullptr; + + ai_assert(mScene != nullptr); + ai_assert(m3d != nullptr); + ai_assert(mScene->mRootNode != nullptr); + + ASSIMP_LOG_DEBUG_F("M3D: importMeshes ", m3d->numface); + + for(i = 0; i < m3d->numface; i++) { + // we must switch mesh if material changes + if(lastMat != m3d->face[i].materialid) { + lastMat = m3d->face[i].materialid; + if(pMesh && vertices && vertices->size() && faces && faces->size()) { + populateMesh(pMesh, faces, vertices, normals, texcoords, colors, vertexids); + meshes->push_back(pMesh); + delete faces; + delete vertices; + delete normals; + delete texcoords; + delete colors; + delete vertexids; // this is not stored in pMesh, just to collect bone vertices + } + pMesh = new aiMesh; + pMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + pMesh->mMaterialIndex = lastMat + 1; + faces = new std::vector(); + vertices = new std::vector(); + normals = new std::vector(); + texcoords = new std::vector(); + colors = new std::vector(); + vertexids = new std::vector(); + } + // add a face to temporary vector + aiFace *pFace = new aiFace; + pFace->mNumIndices = numpoly; + pFace->mIndices = new unsigned int[numpoly]; + for(j = 0; j < numpoly; j++) { + aiVector3D pos, uv, norm; + k = vertices->size(); + pFace->mIndices[j] = k; + l = m3d->face[i].vertex[j]; + pos.x = m3d->vertex[l].x; + pos.y = m3d->vertex[l].y; + pos.z = m3d->vertex[l].z; + vertices->push_back(pos); + colors->push_back(mkColor(m3d->vertex[l].color)); + // add a bone to temporary vector + if(m3d->vertex[l].skinid != -1U &&m3d->vertex[l].skinid != -2U && m3d->skin && m3d->bone) { + // this is complicated, because M3D stores a list of bone id / weight pairs per + // vertex but assimp uses lists of local vertex id/weight pairs per local bone list + vertexids->push_back(l); + } + l = m3d->face[i].texcoord[j]; + if(l != -1U) { + uv.x = m3d->tmap[l].u; + uv.y = m3d->tmap[l].v; + uv.z = 0.0; + texcoords->push_back(uv); + } + l = m3d->face[i].normal[j]; + if(l != -1U) { + norm.x = m3d->vertex[l].x; + norm.y = m3d->vertex[l].y; + norm.z = m3d->vertex[l].z; + normals->push_back(norm); + } + } + faces->push_back(*pFace); + delete pFace; + } + // if there's data left in the temporary vectors, flush them + if(pMesh && vertices->size() && faces->size()) { + populateMesh(pMesh, faces, vertices, normals, texcoords, colors, vertexids); + meshes->push_back(pMesh); + } + + // create global mesh list in scene + mScene->mNumMeshes = meshes->size(); + mScene->mMeshes = new aiMesh*[mScene->mNumMeshes]; + std::copy(meshes->begin(), meshes->end(), mScene->mMeshes); + + // create mesh indeces in root node + mScene->mRootNode->mNumMeshes = meshes->size(); + mScene->mRootNode->mMeshes = new unsigned int[meshes->size()]; + for(i = 0; i < meshes->size(); i++) { + mScene->mRootNode->mMeshes[i] = i; + } + + delete meshes; + if(faces) delete faces; + if(vertices) delete vertices; + if(normals) delete normals; + if(texcoords) delete texcoords; + if(colors) delete colors; + if(vertexids) delete vertexids; +} + +// ------------------------------------------------------------------------------------------------ +// a reentrant node parser. Otherwise this is simple +void M3DImporter::importBones(unsigned int parentid, aiNode *pParent) +{ + unsigned int i, n; + + ai_assert(pParent != nullptr); + ai_assert(mScene != nullptr); + ai_assert(m3d != nullptr); + + ASSIMP_LOG_DEBUG_F("M3D: importBones ", m3d->numbone, " parentid ", (int)parentid); + + for(n = 0, i = parentid + 1; i < m3d->numbone; i++) + if(m3d->bone[i].parent == parentid) n++; + pParent->mChildren = new aiNode*[n]; + + for(i = parentid + 1; i < m3d->numbone; i++) { + if(m3d->bone[i].parent == parentid) { + aiNode *pChild = new aiNode; + pChild->mParent = pParent; + pChild->mName = aiString(std::string(m3d->bone[i].name)); + convertPose(&pChild->mTransformation, m3d->bone[i].pos, m3d->bone[i].ori); + pChild->mNumChildren = 0; + pParent->mChildren[pParent->mNumChildren] = pChild; + pParent->mNumChildren++; + importBones(i, pChild); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// this is another headache. M3D stores list of changed bone id/position/orientation triplets and +// a timestamp per frame, but assimp needs timestamp and lists of position, orientation lists per +// bone, so we have to convert between the two conceptually different representation forms +void M3DImporter::importAnimations() +{ + unsigned int i, j, k, l, pos, ori; + double t; + m3da_t *a; + + ai_assert(mScene != nullptr); + ai_assert(m3d != nullptr); + + mScene->mNumAnimations = m3d->numaction; + + ASSIMP_LOG_DEBUG_F("M3D: importAnimations ", mScene->mNumAnimations); + + if(!m3d->numaction || !m3d->numbone) + return; + + mScene->mAnimations = new aiAnimation*[m3d->numaction]; + for(i = 0; i < m3d->numaction; i++) { + a = &m3d->action[i]; + aiAnimation *pAnim = new aiAnimation; + pAnim->mName = aiString(std::string(a->name)); + pAnim->mDuration = ((double)a->durationmsec) / 10; + pAnim->mTicksPerSecond = 100; + // now we know how many bones are referenced in this animation + pAnim->mNumChannels = m3d->numbone; + pAnim->mChannels = new aiNodeAnim*[pAnim->mNumChannels]; + for(l = 0; l < m3d->numbone; l++) { + unsigned int n; + pAnim->mChannels[l] = new aiNodeAnim; + pAnim->mChannels[l]->mNodeName = aiString(std::string(m3d->bone[l].name)); + // now n is the size of positions / orientations arrays + pAnim->mChannels[l]->mNumPositionKeys = pAnim->mChannels[l]->mNumRotationKeys = a->numframe; + pAnim->mChannels[l]->mPositionKeys = new aiVectorKey[a->numframe]; + pAnim->mChannels[l]->mRotationKeys = new aiQuatKey[a->numframe]; + pos = m3d->bone[l].pos; + ori = m3d->bone[l].ori; + for(j = n = 0; j < a->numframe; j++) { + t = ((double)a->frame[j].msec) / 10; + for(k = 0; k < a->frame[j].numtransform; k++) { + if(a->frame[j].transform[k].boneid == l) { + pos = a->frame[j].transform[k].pos; + ori = a->frame[j].transform[k].ori; + } + } + m3dv_t *v = &m3d->vertex[pos]; + m3dv_t *q = &m3d->vertex[ori]; + pAnim->mChannels[l]->mPositionKeys[j].mTime = t; + pAnim->mChannels[l]->mPositionKeys[j].mValue.x = v->x; + pAnim->mChannels[l]->mPositionKeys[j].mValue.y = v->y; + pAnim->mChannels[l]->mPositionKeys[j].mValue.z = v->z; + pAnim->mChannels[l]->mRotationKeys[j].mTime = t; + pAnim->mChannels[l]->mRotationKeys[j].mValue.w = q->w; + pAnim->mChannels[l]->mRotationKeys[j].mValue.x = q->x; + pAnim->mChannels[l]->mRotationKeys[j].mValue.y = q->y; + pAnim->mChannels[l]->mRotationKeys[j].mValue.z = q->z; + }// foreach frame + }// foreach bones + mScene->mAnimations[i] = pAnim; + } +} + +// ------------------------------------------------------------------------------------------------ +// convert uint32_t into aiColor4D +aiColor4D M3DImporter::mkColor(uint32_t c) { + aiColor4D color; + color.a = ((float)((c >> 24)&0xff)) / 255; + color.b = ((float)((c >> 16)&0xff)) / 255; + color.g = ((float)((c >> 8)&0xff)) / 255; + color.r = ((float)((c >> 0)&0xff)) / 255; + return color; +} + +// ------------------------------------------------------------------------------------------------ +// convert a position id and orientation id into a 4 x 4 transformation matrix +void M3DImporter::convertPose(aiMatrix4x4 *m, unsigned int posid, unsigned int orientid) +{ + ai_assert(m != nullptr); + ai_assert(m3d != nullptr); + ai_assert(posid != -1U && posid < m3d->numvertex); + ai_assert(orientid != -1U && orientid < m3d->numvertex); + m3dv_t *p = &m3d->vertex[posid]; + m3dv_t *q = &m3d->vertex[orientid]; + + /* quaternion to matrix. Do NOT use aiQuaternion to aiMatrix3x3, gives bad results */ + if(q->x == 0.0 && q->y == 0.0 && q->z >= 0.7071065 && q->z <= 0.7071075 && q->w == 0.0) { + m->a2 = m->a3 = m->b1 = m->b3 = m->c1 = m->c2 = 0.0; + m->a1 = m->b2 = m->c3 = -1.0; + } else { + m->a1 = 1 - 2 * (q->y * q->y + q->z * q->z); if(m->a1 > -M3D_EPSILON && m->a1 < M3D_EPSILON) m->a1 = 0.0; + m->a2 = 2 * (q->x * q->y - q->z * q->w); if(m->a2 > -M3D_EPSILON && m->a2 < M3D_EPSILON) m->a2 = 0.0; + m->a3 = 2 * (q->x * q->z + q->y * q->w); if(m->a3 > -M3D_EPSILON && m->a3 < M3D_EPSILON) m->a3 = 0.0; + m->b1 = 2 * (q->x * q->y + q->z * q->w); if(m->b1 > -M3D_EPSILON && m->b1 < M3D_EPSILON) m->b1 = 0.0; + m->b2 = 1 - 2 * (q->x * q->x + q->z * q->z); if(m->b2 > -M3D_EPSILON && m->b2 < M3D_EPSILON) m->b2 = 0.0; + m->b3 = 2 * (q->y * q->z - q->x * q->w); if(m->b3 > -M3D_EPSILON && m->b3 < M3D_EPSILON) m->b3 = 0.0; + m->c1 = 2 * (q->x * q->z - q->y * q->w); if(m->c1 > -M3D_EPSILON && m->c1 < M3D_EPSILON) m->c1 = 0.0; + m->c2 = 2 * (q->y * q->z + q->x * q->w); if(m->c2 > -M3D_EPSILON && m->c2 < M3D_EPSILON) m->c2 = 0.0; + m->c3 = 1 - 2 * (q->x * q->x + q->y * q->y); if(m->c3 > -M3D_EPSILON && m->c3 < M3D_EPSILON) m->c3 = 0.0; + } + + /* set translation */ + m->a4 = p->x; m->b4 = p->y; m->c4 = p->z; + + m->d1 = 0; m->d2 = 0; m->d3 = 0; m->d4 = 1; +} + +// ------------------------------------------------------------------------------------------------ +// find a node by name +aiNode *M3DImporter::findNode(aiNode *pNode, aiString name) +{ + unsigned int i; + + ai_assert(pNode != nullptr); + ai_assert(mScene != nullptr); + + if(pNode->mName == name) + return pNode; + for(i = 0; i < pNode->mNumChildren; i++) { + aiNode *pChild = findNode(pNode->mChildren[i], name); + if(pChild) return pChild; + } + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// fills up offsetmatrix in mBones +void M3DImporter::calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m) +{ + ai_assert(pNode != nullptr); + ai_assert(mScene != nullptr); + + if(pNode->mParent) { + calculateOffsetMatrix(pNode->mParent, m); + *m *= pNode->mTransformation; + } else { + *m = pNode->mTransformation; + } +} + +// ------------------------------------------------------------------------------------------------ +// because M3D has a global mesh, global vertex ids and stores materialid on the face, we need +// temporary lists to collect data for an aiMesh, which requires local arrays and local indeces +// this function fills up an aiMesh with those temporary lists +void M3DImporter::populateMesh(aiMesh *pMesh, std::vector *faces, std::vector *vertices, + std::vector *normals, std::vector *texcoords, std::vector *colors, + std::vector *vertexids) { + + ai_assert(pMesh != nullptr); + ai_assert(faces != nullptr); + ai_assert(vertices != nullptr); + ai_assert(normals != nullptr); + ai_assert(texcoords != nullptr); + ai_assert(colors != nullptr); + ai_assert(vertexids != nullptr); + ai_assert(m3d != nullptr); + + ASSIMP_LOG_DEBUG_F("M3D: populateMesh numvertices ", vertices->size(), " numfaces ", faces->size(), + " numnormals ", normals->size(), " numtexcoord ", texcoords->size(), " numbones ", m3d->numbone); + + if(vertices->size() && faces->size()) { + pMesh->mNumFaces = faces->size(); + pMesh->mFaces = new aiFace[pMesh->mNumFaces]; + std::copy(faces->begin(), faces->end(), pMesh->mFaces); + pMesh->mNumVertices = vertices->size(); + pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; + std::copy(vertices->begin(), vertices->end(), pMesh->mVertices); + if(normals->size() == vertices->size()) { + pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + std::copy(normals->begin(), normals->end(), pMesh->mNormals); + } + if(texcoords->size() == vertices->size()) { + pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices]; + std::copy(texcoords->begin(), texcoords->end(), pMesh->mTextureCoords[0]); + pMesh->mNumUVComponents[0] = 2; + } + if(colors->size() == vertices->size()) { + pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices]; + std::copy(colors->begin(), colors->end(), pMesh->mColors[0]); + } + // this is complicated, because M3D stores a list of bone id / weight pairs per + // vertex but assimp uses lists of local vertex id/weight pairs per local bone list + pMesh->mNumBones = m3d->numbone; + /* we need aiBone with mOffsetMatrix for bones without weights as well */ + if(pMesh->mNumBones) { + pMesh->mBones = new aiBone*[pMesh->mNumBones]; + for(unsigned int i = 0; i < m3d->numbone; i++) { + aiNode *pNode; + pMesh->mBones[i] = new aiBone; + pMesh->mBones[i]->mName = aiString(std::string(m3d->bone[i].name)); + pMesh->mBones[i]->mNumWeights = 0; + pNode = findNode(mScene->mRootNode, pMesh->mBones[i]->mName); + if(pNode) { + calculateOffsetMatrix(pNode, &pMesh->mBones[i]->mOffsetMatrix); + pMesh->mBones[i]->mOffsetMatrix.Inverse(); + } else + pMesh->mBones[i]->mOffsetMatrix = aiMatrix4x4(); + } + if(vertexids->size()) { + unsigned int i, j; + // first count how many vertices we have per bone + for(i = 0; i < vertexids->size(); i++) { + unsigned int s = m3d->vertex[vertexids->at(i)].skinid; + if(s != -1U && s!= -2U) { + for(unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) { + aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name)); + for(j = 0; j < pMesh->mNumBones; j++) { + if(pMesh->mBones[j]->mName == name) { + pMesh->mBones[j]->mNumWeights++; + break; + } + } + } + } + } + // allocate mWeights + for(j = 0; j < pMesh->mNumBones; j++) { + aiBone *pBone = pMesh->mBones[j]; + if(pBone->mNumWeights) { + pBone->mWeights = new aiVertexWeight[pBone->mNumWeights]; + pBone->mNumWeights = 0; + } + } + // fill up with data + for(i = 0; i < vertexids->size(); i++) { + unsigned int s = m3d->vertex[vertexids->at(i)].skinid; + if(s != -1U && s!= -2U) { + for(unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) { + aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name)); + for(j = 0; j < pMesh->mNumBones; j++) { + if(pMesh->mBones[j]->mName == name) { + aiBone *pBone = pMesh->mBones[j]; + pBone->mWeights[pBone->mNumWeights].mVertexId = i; + pBone->mWeights[pBone->mNumWeights].mWeight = m3d->skin[s].weight[k]; + pBone->mNumWeights++; + break; + } + } + } // foreach skin + } + } // foreach vertexids + } + } + } +} + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_M3D_IMPORTER diff --git a/code/M3D/M3DImporter.h b/code/M3D/M3DImporter.h new file mode 100644 index 000000000..06cc757b6 --- /dev/null +++ b/code/M3D/M3DImporter.h @@ -0,0 +1,106 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team +Copyright (c) 2019 bzt + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file M3DImporter.h +* @brief Declares the importer class to read a scene from a Model 3D file +*/ +#ifndef AI_M3DIMPORTER_H_INC +#define AI_M3DIMPORTER_H_INC + +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER + +#include "m3d.h" +#include +#include +#include + +struct aiMesh; +struct aiNode; +struct aiMaterial; +struct aiFace; + +namespace Assimp { + +class M3DImporter : public BaseImporter { +public: + /// \brief Default constructor + M3DImporter(); + + /// \brief Destructor + ~M3DImporter(); + +public: + /// \brief Returns whether the class can handle the format of the given file. + /// \remark See BaseImporter::CanRead() for details. + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const; + +private: + aiScene* mScene; // the scene to import to + m3d_t *m3d; // model for the C library to convert from + + //! \brief Appends the supported extension. + const aiImporterDesc* GetInfo () const; + + //! \brief File import implementation. + void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); + + void importMaterials(); + void importTextures(); + void importMeshes(); + void importBones(unsigned int parentid, aiNode *pParent); + void importAnimations(); + + // helper functions + aiColor4D mkColor(uint32_t c); + void convertPose(aiMatrix4x4 *m, unsigned int posid, unsigned int orientid); + aiNode *findNode(aiNode *pNode, aiString name); + void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m); + void populateMesh(aiMesh *pMesh, std::vector *faces, std::vector *verteces, + std::vector *normals, std::vector *texcoords, std::vector *colors, + std::vector *vertexids); +}; + +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_M3D_IMPORTER + +#endif // AI_M3DIMPORTER_H_INC diff --git a/code/M3D/M3DMaterials.h b/code/M3D/M3DMaterials.h new file mode 100644 index 000000000..3d6fe246d --- /dev/null +++ b/code/M3D/M3DMaterials.h @@ -0,0 +1,106 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team +Copyright (c) 2019 bzt + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file M3DMaterials.h +* @brief Declares the Assimp and Model 3D file material type relations +*/ +#ifndef AI_M3DMATERIALS_H_INC +#define AI_M3DMATERIALS_H_INC + +/* + * In the m3d.h header, there's a static array which defines the material + * properties, called m3d_propertytypes. These must have the same size, and + * list the matching Assimp materials for those properties. Used by both the + * M3DImporter and the M3DExporter, so you have to define these relations + * only once. D.R.Y. and K.I.S.S. + */ +typedef struct { + const char *pKey; + unsigned int type; + unsigned int index; +} aiMatProp; + +/* --- Scalar Properties --- !!!!! must match m3d_propertytypes !!!!! */ +static const aiMatProp aiProps[] = { + { AI_MATKEY_COLOR_DIFFUSE }, /* m3dp_Kd */ + { AI_MATKEY_COLOR_AMBIENT }, /* m3dp_Ka */ + { AI_MATKEY_COLOR_SPECULAR }, /* m3dp_Ks */ + { AI_MATKEY_SHININESS }, /* m3dp_Ns */ + { AI_MATKEY_COLOR_EMISSIVE }, /* m3dp_Ke */ + { AI_MATKEY_COLOR_REFLECTIVE }, /* m3dp_Tf */ + { AI_MATKEY_BUMPSCALING }, /* m3dp_Km */ + { AI_MATKEY_OPACITY }, /* m3dp_d */ + { AI_MATKEY_SHADING_MODEL }, /* m3dp_il */ + + { NULL, 0, 0 }, /* m3dp_Pr */ + { AI_MATKEY_REFLECTIVITY }, /* m3dp_Pm */ + { NULL, 0, 0 }, /* m3dp_Ps */ + { AI_MATKEY_REFRACTI }, /* m3dp_Ni */ + { NULL, 0, 0 }, /* m3dp_Nt */ + { NULL, 0, 0 }, + { NULL, 0, 0 }, + { NULL, 0, 0 } +}; + +/* --- Texture Map Properties --- !!!!! must match m3d_propertytypes !!!!! */ +static const aiMatProp aiTxProps[] = { + { AI_MATKEY_TEXTURE_DIFFUSE(0) }, /* m3dp_map_Kd */ + { AI_MATKEY_TEXTURE_AMBIENT(0) }, /* m3dp_map_Ka */ + { AI_MATKEY_TEXTURE_SPECULAR(0) }, /* m3dp_map_Ks */ + { AI_MATKEY_TEXTURE_SHININESS(0) }, /* m3dp_map_Ns */ + { AI_MATKEY_TEXTURE_EMISSIVE(0) }, /* m3dp_map_Ke */ + { NULL, 0, 0 }, /* m3dp_map_Tf */ + { AI_MATKEY_TEXTURE_HEIGHT(0) }, /* m3dp_bump */ + { AI_MATKEY_TEXTURE_OPACITY(0) }, /* m3dp_map_d */ + { AI_MATKEY_TEXTURE_REFLECTION(0) }, /* m3dp_refl */ + + { AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE_ROUGHNESS,0) },/* m3dp_map_Pr */ + { AI_MATKEY_TEXTURE(aiTextureType_METALNESS,0) }, /* m3dp_map_Pm */ + { NULL, 0, 0 }, /* m3dp_map_Ps */ + { AI_MATKEY_TEXTURE(aiTextureType_AMBIENT_OCCLUSION,0) },/* m3dp_map_Ni */ + { NULL, 0, 0 }, /* m3dp_map_Nt */ + { NULL, 0, 0 }, + { NULL, 0, 0 }, + { NULL, 0, 0 } +}; + +#endif // AI_M3DMATERIALS_H_INC diff --git a/code/M3D/m3d.h b/code/M3D/m3d.h new file mode 100644 index 000000000..a73e7a02c --- /dev/null +++ b/code/M3D/m3d.h @@ -0,0 +1,5568 @@ +/* + * m3d.h + * + * Copyright (C) 2019 bzt (bztsrc@gitlab) + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * @brief ANSI C89 / C++11 single header importer / exporter SDK for the Model 3D (.M3D) format + * https://gitlab.com/bztsrc/model3d + * + * PNG decompressor included from (with minor modifications to make it C89 valid): + * stb_image - v2.13 - public domain image loader - http://nothings.org/stb_image.h + * + * @version: 1.0.0 + */ + +#ifndef _M3D_H_ +#define _M3D_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/*** configuration ***/ +#ifndef M3D_MALLOC +# define M3D_MALLOC(sz) malloc(sz) +#endif +#ifndef M3D_REALLOC +# define M3D_REALLOC(p,nsz) realloc(p,nsz) +#endif +#ifndef M3D_FREE +# define M3D_FREE(p) free(p) +#endif +#ifndef M3D_LOG +# define M3D_LOG(x) +#endif +#ifndef M3D_APIVERSION +#define M3D_APIVERSION 0x0100 +#ifndef M3D_DOUBLE +typedef float M3D_FLOAT; +#ifndef M3D_EPSILON +/* carefully choosen for IEEE 754 don't change */ +#define M3D_EPSILON ((M3D_FLOAT)1e-7) +#endif +#else +typedef double M3D_FLOAT; +#ifndef M3D_EPSILON +#define M3D_EPSILON ((M3D_FLOAT)1e-14) +#endif +#endif +#if !defined(M3D_SMALLINDEX) +typedef uint32_t M3D_INDEX; +#define M3D_INDEXMAX 0xfffffffe +#else +typedef uint16_t M3D_INDEX; +#define M3D_INDEXMAX 0xfffe +#endif +#ifndef M3D_NUMBONE +#define M3D_NUMBONE 4 +#endif +#ifndef M3D_BONEMAXLEVEL +#define M3D_BONEMAXLEVEL 8 +#endif +#ifndef _MSC_VER +#define _inline __inline__ +#define _pack __attribute__((packed)) +#define _unused __attribute__((unused)) +#else +#define _inline +#define _pack +#define _unused +#endif +#ifndef __cplusplus +#define _register register +#else +#define _register +#endif + +/*** File format structures ***/ + +/** + * M3D file format structure + * 3DMO m3dchunk_t file header chunk, may followed by compressed data + * HEAD m3dhdr_t model header chunk + * n x m3dchunk_t more chunks follow + * PRVW preview chunk (optional) + * CMAP color map chunk (optional) + * TMAP texture map chunk (optional) + * VRTS vertex data chunk (optional if it's a material library) + * BONE bind-pose skeleton, bone hierarchy chunk (optional) + * n x m3db_t contains propably more, but at least one bone + * n x m3ds_t skin group records + * MTRL* material chunk(s), can be more (optional) + * n x m3dp_t each material contains propapbly more, but at least one property + * the properties are configurable with a static array, see m3d_propertytypes + * n x m3dchunk_t at least one, but maybe more face chunks + * PROC* procedural face, or + * MESH* triangle mesh (vertex index list) or + * SHPE* mathematical shapes like parameterized surfaces + * LBLS* annotation label chunks, can be more (optional) + * ACTN* action chunk(s), animation-pose skeletons, can be more (optional) + * n x m3dfr_t each action contains probably more, but at least one frame + * n x m3dtr_t each frame contains probably more, but at least one transformation + * ASET* inlined asset chunk(s), can be more (optional) + * OMD3 end chunk + * + * Typical chunks for a game engine: 3DMO, HEAD, CMAP, TMAP, VRTS, BONE, MTRL, MESH, ACTN, OMD3 + * Typical chunks for CAD software: 3DMO, HEAD, PRVW, CMAP, TMAP, VRTS, MTRL, SHPE, LBLS, OMD3 + */ +#ifdef _MSC_VER +#pragma pack(push) +#pragma pack(1) +#endif + +typedef struct { + char magic[4]; + uint32_t length; + float scale; /* deliberately not M3D_FLOAT */ + uint32_t types; +} _pack m3dhdr_t; + +typedef struct { + char magic[4]; + uint32_t length; +} _pack m3dchunk_t; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif + +/*** in-memory model structure ***/ + +/* textmap entry */ +typedef struct { + M3D_FLOAT u; + M3D_FLOAT v; +} m3dti_t; +#define m3d_textureindex_t m3dti_t + +/* texture */ +typedef struct { + char *name; /* texture name */ + uint8_t *d; /* pixels data */ + uint16_t w; /* width */ + uint16_t h; /* height */ + uint8_t f; /* format, 1 = grayscale, 2 = grayscale+alpha, 3 = rgb, 4 = rgba */ +} m3dtx_t; +#define m3d_texturedata_t m3dtx_t + +typedef struct { + M3D_INDEX vertexid; + M3D_FLOAT weight; +} m3dw_t; +#define m3d_weight_t m3dw_t + +/* bone entry */ +typedef struct { + M3D_INDEX parent; /* parent bone index */ + char *name; /* name for this bone */ + M3D_INDEX pos; /* vertex index position */ + M3D_INDEX ori; /* vertex index orientation (quaternion) */ + M3D_INDEX numweight; /* number of controlled vertices */ + m3dw_t *weight; /* weights for those vertices */ + M3D_FLOAT mat4[16]; /* transformation matrix */ +} m3db_t; +#define m3d_bone_t m3db_t + +/* skin: bone per vertex entry */ +typedef struct { + M3D_INDEX boneid[M3D_NUMBONE]; + M3D_FLOAT weight[M3D_NUMBONE]; +} m3ds_t; +#define m3d_skin_t m3ds_t + +/* vertex entry */ +typedef struct { + M3D_FLOAT x; /* 3D coordinates and weight */ + M3D_FLOAT y; + M3D_FLOAT z; + M3D_FLOAT w; + uint32_t color; /* default vertex color */ + M3D_INDEX skinid; /* skin index */ +#ifdef M3D_VERTEXTYPE + uint8_t type; +#endif +} m3dv_t; +#define m3d_vertex_t m3dv_t + +/* material property formats */ +enum { + m3dpf_color, + m3dpf_uint8, + m3dpf_uint16, + m3dpf_uint32, + m3dpf_float, + m3dpf_map +}; +typedef struct { + uint8_t format; + uint8_t id; +#ifdef M3D_ASCII +#define M3D_PROPERTYDEF(f,i,n) { (f), (i), (char*)(n) } + char *key; +#else +#define M3D_PROPERTYDEF(f,i,n) { (f), (i) } +#endif +} m3dpd_t; + +/* material property types */ +/* You shouldn't change the first 8 display and first 4 physical property. Assign the rest as you like. */ +enum { + m3dp_Kd = 0, /* scalar display properties */ + m3dp_Ka, + m3dp_Ks, + m3dp_Ns, + m3dp_Ke, + m3dp_Tf, + m3dp_Km, + m3dp_d, + m3dp_il, + + m3dp_Pr = 64, /* scalar physical properties */ + m3dp_Pm, + m3dp_Ps, + m3dp_Ni, + m3dp_Nt, + + m3dp_map_Kd = 128, /* textured display map properties */ + m3dp_map_Ka, + m3dp_map_Ks, + m3dp_map_Ns, + m3dp_map_Ke, + m3dp_map_Tf, + m3dp_map_Km, /* bump map */ + m3dp_map_D, + m3dp_map_il, /* reflection map */ + + m3dp_map_Pr = 192, /* textured physical map properties */ + m3dp_map_Pm, + m3dp_map_Ps, + m3dp_map_Ni, + m3dp_map_Nt +}; +enum { /* aliases */ + m3dp_bump = m3dp_map_Km, + m3dp_refl = m3dp_map_Pm +}; + +/* material property */ +typedef struct { + uint8_t type; /* property type, see "m3dp_*" enumeration */ + union { + uint32_t color; /* if value is a color, m3dpf_color */ + uint32_t num; /* if value is a number, m3dpf_uint8, m3pf_uint16, m3dpf_uint32 */ + float fnum; /* if value is a floating point number, m3dpf_float */ + M3D_INDEX textureid; /* if value is a texture, m3dpf_map */ + } value; +} m3dp_t; +#define m3d_property_t m3dp_t + +/* material entry */ +typedef struct { + char *name; /* name of the material */ + uint8_t numprop; /* number of properties */ + m3dp_t *prop; /* properties array */ +} m3dm_t; +#define m3d_material_t m3dm_t + +/* face entry */ +typedef struct { + M3D_INDEX materialid; /* material index */ + M3D_INDEX vertex[3]; /* 3D points of the triangle in CCW order */ + M3D_INDEX normal[3]; /* normal vectors */ + M3D_INDEX texcoord[3]; /* UV coordinates */ +} m3df_t; +#define m3d_face_t m3df_t + +/* shape command types. must match the row in m3d_commandtypes */ +enum { + /* special commands */ + m3dc_use = 0, /* use material */ + m3dc_inc, /* include another shape */ + m3dc_mesh, /* include part of polygon mesh */ + /* approximations */ + m3dc_div, /* subdivision by constant resolution for both u, v */ + m3dc_sub, /* subdivision by constant, different for u and v */ + m3dc_len, /* spacial subdivision by maxlength */ + m3dc_dist, /* subdivision by maxdistance and maxangle */ + /* modifiers */ + m3dc_degu, /* degree for both u, v */ + m3dc_deg, /* separate degree for u and v */ + m3dc_rangeu, /* range for u */ + m3dc_range, /* range for u and v */ + m3dc_paru, /* u parameters (knots) */ + m3dc_parv, /* v parameters */ + m3dc_trim, /* outer trimming curve */ + m3dc_hole, /* inner trimming curve */ + m3dc_scrv, /* spacial curve */ + m3dc_sp, /* special points */ + /* helper curves */ + m3dc_bez1, /* Bezier 1D */ + m3dc_bsp1, /* B-spline 1D */ + m3dc_bez2, /* bezier 2D */ + m3dc_bsp2, /* B-spline 2D */ + /* surfaces */ + m3dc_bezun, /* Bezier 3D with control, UV, normal */ + m3dc_bezu, /* with control and UV */ + m3dc_bezn, /* with control and normal */ + m3dc_bez, /* control points only */ + m3dc_nurbsun, /* B-spline 3D */ + m3dc_nurbsu, + m3dc_nurbsn, + m3dc_nurbs, + m3dc_conn, /* connect surfaces */ + /* geometrical */ + m3dc_line, + m3dc_polygon, + m3dc_circle, + m3dc_cylinder, + m3dc_shpere, + m3dc_torus, + m3dc_cone, + m3dc_cube +}; + +/* shape command argument types */ +enum { + m3dcp_mi_t = 1, /* material index */ + m3dcp_hi_t, /* shape index */ + m3dcp_fi_t, /* face index */ + m3dcp_ti_t, /* texture map index */ + m3dcp_vi_t, /* vertex index */ + m3dcp_qi_t, /* vertex index for quaternions */ + m3dcp_vc_t, /* coordinate or radius, float scalar */ + m3dcp_i1_t, /* int8 scalar */ + m3dcp_i2_t, /* int16 scalar */ + m3dcp_i4_t, /* int32 scalar */ + m3dcp_va_t /* variadic arguments */ +}; + +#define M3D_CMDMAXARG 8 /* if you increase this, add more arguments to the macro below */ +typedef struct { +#ifdef M3D_ASCII +#define M3D_CMDDEF(t,n,p,a,b,c,d,e,f,g,h) { (char*)(n), (p), { (a), (b), (c), (d), (e), (f), (g), (h) } } + char *key; +#else +#define M3D_CMDDEF(t,n,p,a,b,c,d,e,f,g,h) { (p), { (a), (b), (c), (d), (e), (f), (g), (h) } } +#endif + uint8_t p; + uint8_t a[M3D_CMDMAXARG]; +} m3dcd_t; + +/* shape command */ +typedef struct { + uint16_t type; /* shape type */ + uint32_t *arg; /* arguments array */ +} m3dc_t; +#define m3d_shapecommand_t m3dc_t + +/* shape entry */ +typedef struct { + char *name; /* name of the mathematical shape */ + M3D_INDEX group; /* group this shape belongs to or -1 */ + uint32_t numcmd; /* number of commands */ + m3dc_t *cmd; /* commands array */ +} m3dh_t; +#define m3d_shape_t m3dh_t + +/* label entry */ +typedef struct { + char *name; /* name of the annotation group or NULL */ + char *lang; /* language code or NULL */ + char *text; /* the label text */ + uint32_t color; /* color */ + M3D_INDEX vertexid; /* the vertex the label refers to */ +} m3dl_t; +#define m3d_label_t m3dl_t + +/* frame transformations / working copy skeleton entry */ +typedef struct { + M3D_INDEX boneid; /* selects a node in bone hierarchy */ + M3D_INDEX pos; /* vertex index new position */ + M3D_INDEX ori; /* vertex index new orientation (quaternion) */ +} m3dtr_t; +#define m3d_transform_t m3dtr_t + +/* animation frame entry */ +typedef struct { + uint32_t msec; /* frame's position on the timeline, timestamp */ + M3D_INDEX numtransform; /* number of transformations in this frame */ + m3dtr_t *transform; /* transformations */ +} m3dfr_t; +#define m3d_frame_t m3dfr_t + +/* model action entry */ +typedef struct { + char *name; /* name of the action */ + uint32_t durationmsec; /* duration in millisec (1/1000 sec) */ + M3D_INDEX numframe; /* number of frames in this animation */ + m3dfr_t *frame; /* frames array */ +} m3da_t; +#define m3d_action_t m3da_t + +/* inlined asset */ +typedef struct { + char *name; /* asset name (same pointer as in texture[].name) */ + uint8_t *data; /* compressed asset data */ + uint32_t length; /* compressed data length */ +} m3di_t; +#define m3d_inlinedasset_t m3di_t + +/*** in-memory model structure ***/ +#define M3D_FLG_FREERAW (1<<0) +#define M3D_FLG_FREESTR (1<<1) +#define M3D_FLG_MTLLIB (1<<2) +#define M3D_FLG_GENNORM (1<<3) + +typedef struct { + m3dhdr_t *raw; /* pointer to raw data */ + char flags; /* internal flags */ + char errcode; /* returned error code */ + char vc_s, vi_s, si_s, ci_s, ti_s, bi_s, nb_s, sk_s, fc_s, hi_s,fi_s; /* decoded sizes for types */ + char *name; /* name of the model, like "Utah teapot" */ + char *license; /* usage condition or license, like "MIT", "LGPL" or "BSD-3clause" */ + char *author; /* nickname, email, homepage or github URL etc. */ + char *desc; /* comments, descriptions. May contain '\n' newline character */ + M3D_FLOAT scale; /* the model's bounding cube's size in SI meters */ + M3D_INDEX numcmap; + uint32_t *cmap; /* color map */ + M3D_INDEX numtmap; + m3dti_t *tmap; /* texture map indices */ + M3D_INDEX numtexture; + m3dtx_t *texture; /* uncompressed textures */ + M3D_INDEX numbone; + m3db_t *bone; /* bone hierarchy */ + M3D_INDEX numvertex; + m3dv_t *vertex; /* vertex data */ + M3D_INDEX numskin; + m3ds_t *skin; /* skin data */ + M3D_INDEX nummaterial; + m3dm_t *material; /* material list */ + M3D_INDEX numface; + m3df_t *face; /* model face, polygon (triangle) mesh */ + M3D_INDEX numshape; + m3dh_t *shape; /* model face, shape commands */ + M3D_INDEX numlabel; + m3dl_t *label; /* annotation labels */ + M3D_INDEX numaction; + m3da_t *action; /* action animations */ + M3D_INDEX numinlined; + m3di_t *inlined; /* inlined assets */ + M3D_INDEX numextra; + m3dchunk_t **extra; /* unknown chunks, application / engine specific data probably */ + m3di_t preview; /* preview chunk */ +} m3d_t; + +/*** export parameters ***/ +#define M3D_EXP_INT8 0 +#define M3D_EXP_INT16 1 +#define M3D_EXP_FLOAT 2 +#define M3D_EXP_DOUBLE 3 + +#define M3D_EXP_NOCMAP (1<<0) +#define M3D_EXP_NOMATERIAL (1<<1) +#define M3D_EXP_NOFACE (1<<2) +#define M3D_EXP_NONORMAL (1<<3) +#define M3D_EXP_NOTXTCRD (1<<4) +#define M3D_EXP_FLIPTXTCRD (1<<5) +#define M3D_EXP_NORECALC (1<<6) +#define M3D_EXP_IDOSUCK (1<<7) +#define M3D_EXP_NOBONE (1<<8) +#define M3D_EXP_NOACTION (1<<9) +#define M3D_EXP_INLINE (1<<10) +#define M3D_EXP_EXTRA (1<<11) +#define M3D_EXP_NOZLIB (1<<14) +#define M3D_EXP_ASCII (1<<15) + +/*** error codes ***/ +#define M3D_SUCCESS 0 +#define M3D_ERR_ALLOC -1 +#define M3D_ERR_BADFILE -2 +#define M3D_ERR_UNIMPL -65 +#define M3D_ERR_UNKPROP -66 +#define M3D_ERR_UNKMESH -67 +#define M3D_ERR_UNKIMG -68 +#define M3D_ERR_UNKFRAME -69 +#define M3D_ERR_UNKCMD -70 +#define M3D_ERR_TRUNC -71 +#define M3D_ERR_CMAP -72 +#define M3D_ERR_TMAP -73 +#define M3D_ERR_VRTS -74 +#define M3D_ERR_BONE -75 +#define M3D_ERR_MTRL -76 +#define M3D_ERR_SHPE -77 + +#define M3D_ERR_ISFATAL(x) ((x) < 0 && (x) > -65) + +/* callbacks */ +typedef unsigned char *(*m3dread_t)(char *filename, unsigned int *size); /* read file contents into buffer */ +typedef void (*m3dfree_t)(void *buffer); /* free file contents buffer */ +typedef int (*m3dtxsc_t)(const char *name, const void *script, uint32_t len, m3dtx_t *output); /* interpret texture script */ +typedef int (*m3dprsc_t)(const char *name, const void *script, uint32_t len, m3d_t *model); /* interpret surface script */ +#endif /* ifndef M3D_APIVERSION */ + +/*** C prototypes ***/ +/* import / export */ +m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d_t *mtllib); +unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size); +void m3d_free(m3d_t *model); +/* generate animation pose skeleton */ +m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t *skeleton); +m3db_t *m3d_pose(m3d_t *model, M3D_INDEX actionid, uint32_t msec); + +/* private prototypes used by both importer and exporter */ +char *_m3d_safestr(char *in, int morelines); + +/*** C implementation ***/ +#ifdef M3D_IMPLEMENTATION +#if !defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER) +/* material property definitions */ +static m3dpd_t m3d_propertytypes[] = { + M3D_PROPERTYDEF(m3dpf_color, m3dp_Kd, "Kd"), /* diffuse color */ + M3D_PROPERTYDEF(m3dpf_color, m3dp_Ka, "Ka"), /* ambient color */ + M3D_PROPERTYDEF(m3dpf_color, m3dp_Ks, "Ks"), /* specular color */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Ns, "Ns"), /* specular exponent */ + M3D_PROPERTYDEF(m3dpf_color, m3dp_Ke, "Ke"), /* emissive (emitting light of this color) */ + M3D_PROPERTYDEF(m3dpf_color, m3dp_Tf, "Tf"), /* transmission color */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Km, "Km"), /* bump strength */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_d, "d"), /* dissolve (transparency) */ + M3D_PROPERTYDEF(m3dpf_uint8, m3dp_il, "il"), /* illumination model (informational, ignored by PBR-shaders) */ + + M3D_PROPERTYDEF(m3dpf_float, m3dp_Pr, "Pr"), /* roughness */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Pm, "Pm"), /* metallic, also reflection */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Ps, "Ps"), /* sheen */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Ni, "Ni"), /* index of refraction (optical density) */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Nt, "Nt"), /* thickness of face in millimeter, for printing */ + + /* aliases, note that "map_*" aliases are handled automatically */ + M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Km, "bump"), + M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Pm, "refl") +}; +/* shape command definitions. if more commands start with the same string, the longer must come first */ +static m3dcd_t m3d_commandtypes[] = { + /* technical */ + M3D_CMDDEF(m3dc_use, "use", 1, m3dcp_mi_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_inc, "inc", 3, m3dcp_hi_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vi_t, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_mesh, "mesh", 1, m3dcp_fi_t, m3dcp_fi_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vi_t, 0, 0, 0), + /* approximations */ + M3D_CMDDEF(m3dc_div, "div", 1, m3dcp_vc_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_sub, "sub", 2, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_len, "len", 1, m3dcp_vc_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_dist, "dist", 2, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + /* modifiers */ + M3D_CMDDEF(m3dc_degu, "degu", 1, m3dcp_i1_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_deg, "deg", 2, m3dcp_i1_t, m3dcp_i1_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_rangeu, "rangeu", 1, m3dcp_ti_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_range, "range", 2, m3dcp_ti_t, m3dcp_ti_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_paru, "paru", 2, m3dcp_va_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_parv, "parv", 2, m3dcp_va_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_trim, "trim", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_hole, "hole", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_scrv, "scrv", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_sp, "sp", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + /* helper curves */ + M3D_CMDDEF(m3dc_bez1, "bez1", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bsp1, "bsp1", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bez2, "bez2", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bsp2, "bsp2", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + /* surfaces */ + M3D_CMDDEF(m3dc_bezun, "bezun", 4, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, m3dcp_vi_t, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bezu, "bezu", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bezn, "bezn", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bez, "bez", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_nurbsun, "nurbsun", 4, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, m3dcp_vi_t, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_nurbsu, "nurbsu", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_nurbsn, "nurbsn", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_nurbs, "nurbs", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_conn, "conn", 6, m3dcp_i2_t, m3dcp_ti_t, m3dcp_i2_t, m3dcp_i2_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0), + /* geometrical */ + M3D_CMDDEF(m3dc_line, "line", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_polygon, "polygon", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_circle, "circle", 3, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_cylinder,"cylinder",6, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, 0, 0), + M3D_CMDDEF(m3dc_shpere, "shpere", 2, m3dcp_vi_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_torus, "torus", 4, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_cone, "cone", 3, m3dcp_vi_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_cube, "cube", 3, m3dcp_vi_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0) +}; +#endif + +#include +#include + +#if !defined(M3D_NOIMPORTER) && !defined(STBI_INCLUDE_STB_IMAGE_H) +/* PNG decompressor from + + stb_image - v2.23 - public domain image loader - http://nothings.org/stb_image.h +*/ +static const char *_m3dstbi__g_failure_reason; + +enum +{ + STBI_default = 0, + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +typedef unsigned short _m3dstbi_us; + +typedef uint16_t _m3dstbi__uint16; +typedef int16_t _m3dstbi__int16; +typedef uint32_t _m3dstbi__uint32; +typedef int32_t _m3dstbi__int32; + +typedef struct +{ + _m3dstbi__uint32 img_x, img_y; + int img_n, img_out_n; + + void *io_user_data; + + int read_from_callbacks; + int buflen; + unsigned char buffer_start[128]; + + unsigned char *img_buffer, *img_buffer_end; + unsigned char *img_buffer_original, *img_buffer_original_end; +} _m3dstbi__context; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} _m3dstbi__result_info; + +#define STBI_ASSERT(v) +#define STBI_NOTUSED(v) (void)sizeof(v) +#define STBI__BYTECAST(x) ((unsigned char) ((x) & 255)) +#define STBI_MALLOC(sz) M3D_MALLOC(sz) +#define STBI_REALLOC(p,newsz) M3D_REALLOC(p,newsz) +#define STBI_FREE(p) M3D_FREE(p) +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) + +_inline static unsigned char _m3dstbi__get8(_m3dstbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + return 0; +} + +_inline static int _m3dstbi__at_eof(_m3dstbi__context *s) +{ + return s->img_buffer >= s->img_buffer_end; +} + +static void _m3dstbi__skip(_m3dstbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + s->img_buffer += n; +} + +static int _m3dstbi__getn(_m3dstbi__context *s, unsigned char *buffer, int n) +{ + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int _m3dstbi__get16be(_m3dstbi__context *s) +{ + int z = _m3dstbi__get8(s); + return (z << 8) + _m3dstbi__get8(s); +} + +static _m3dstbi__uint32 _m3dstbi__get32be(_m3dstbi__context *s) +{ + _m3dstbi__uint32 z = _m3dstbi__get16be(s); + return (z << 16) + _m3dstbi__get16be(s); +} + +#define _m3dstbi__err(x,y) _m3dstbi__errstr(y) +static int _m3dstbi__errstr(const char *str) +{ + _m3dstbi__g_failure_reason = str; + return 0; +} + +_inline static void *_m3dstbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +static int _m3dstbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + return a <= 2147483647 - b; +} + +static int _m3dstbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; + return a <= 2147483647/b; +} + +static int _m3dstbi__mad2sizes_valid(int a, int b, int add) +{ + return _m3dstbi__mul2sizes_valid(a, b) && _m3dstbi__addsizes_valid(a*b, add); +} + +static int _m3dstbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return _m3dstbi__mul2sizes_valid(a, b) && _m3dstbi__mul2sizes_valid(a*b, c) && + _m3dstbi__addsizes_valid(a*b*c, add); +} + +static void *_m3dstbi__malloc_mad2(int a, int b, int add) +{ + if (!_m3dstbi__mad2sizes_valid(a, b, add)) return NULL; + return _m3dstbi__malloc(a*b + add); +} + +static void *_m3dstbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!_m3dstbi__mad3sizes_valid(a, b, c, add)) return NULL; + return _m3dstbi__malloc(a*b*c + add); +} + +static unsigned char _m3dstbi__compute_y(int r, int g, int b) +{ + return (unsigned char) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *_m3dstbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) _m3dstbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + _m3dstbi__err("outofmem", "Out of memory"); + return NULL; + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=_m3dstbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=_m3dstbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=_m3dstbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=_m3dstbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +static _m3dstbi__uint16 _m3dstbi__compute_y_16(int r, int g, int b) +{ + return (_m3dstbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static _m3dstbi__uint16 *_m3dstbi__convert_format16(_m3dstbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + _m3dstbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (_m3dstbi__uint16 *) _m3dstbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + _m3dstbi__err("outofmem", "Out of memory"); + return NULL; + } + + for (j=0; j < (int) y; ++j) { + _m3dstbi__uint16 *src = data + j * x * img_n ; + _m3dstbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=_m3dstbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=_m3dstbi__compute_y_16(src[0],src[1],src[2]), dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=_m3dstbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=_m3dstbi__compute_y_16(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +#define STBI__ZFAST_BITS 9 +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +typedef struct +{ + _m3dstbi__uint16 fast[1 << STBI__ZFAST_BITS]; + _m3dstbi__uint16 firstcode[16]; + int maxcode[17]; + _m3dstbi__uint16 firstsymbol[16]; + unsigned char size[288]; + _m3dstbi__uint16 value[288]; +} _m3dstbi__zhuffman; + +_inline static int _m3dstbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +_inline static int _m3dstbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + return _m3dstbi__bitreverse16(v) >> (16-bits); +} + +static int _m3dstbi__zbuild_huffman(_m3dstbi__zhuffman *z, unsigned char *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return _m3dstbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (_m3dstbi__uint16) code; + z->firstsymbol[i] = (_m3dstbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return _m3dstbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + _m3dstbi__uint16 fastv = (_m3dstbi__uint16) ((s << 9) | i); + z->size [c] = (unsigned char ) s; + z->value[c] = (_m3dstbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = _m3dstbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +typedef struct +{ + unsigned char *zbuffer, *zbuffer_end; + int num_bits; + _m3dstbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + _m3dstbi__zhuffman z_length, z_distance; +} _m3dstbi__zbuf; + +_inline static unsigned char _m3dstbi__zget8(_m3dstbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void _m3dstbi__fill_bits(_m3dstbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) _m3dstbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +_inline static unsigned int _m3dstbi__zreceive(_m3dstbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) _m3dstbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int _m3dstbi__zhuffman_decode_slowpath(_m3dstbi__zbuf *a, _m3dstbi__zhuffman *z) +{ + int b,s,k; + k = _m3dstbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +_inline static int _m3dstbi__zhuffman_decode(_m3dstbi__zbuf *a, _m3dstbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) _m3dstbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return _m3dstbi__zhuffman_decode_slowpath(a, z); +} + +static int _m3dstbi__zexpand(_m3dstbi__zbuf *z, char *zout, int n) +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return _m3dstbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return _m3dstbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int _m3dstbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int _m3dstbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int _m3dstbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int _m3dstbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int _m3dstbi__parse_huffman_block(_m3dstbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = _m3dstbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return _m3dstbi__err("bad huffman code","Corrupt PNG"); + if (zout >= a->zout_end) { + if (!_m3dstbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + unsigned char *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = _m3dstbi__zlength_base[z]; + if (_m3dstbi__zlength_extra[z]) len += _m3dstbi__zreceive(a, _m3dstbi__zlength_extra[z]); + z = _m3dstbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return _m3dstbi__err("bad huffman code","Corrupt PNG"); + dist = _m3dstbi__zdist_base[z]; + if (_m3dstbi__zdist_extra[z]) dist += _m3dstbi__zreceive(a, _m3dstbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return _m3dstbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!_m3dstbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (unsigned char *) (zout - dist); + if (dist == 1) { + unsigned char v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int _m3dstbi__compute_huffman_codes(_m3dstbi__zbuf *a) +{ + static unsigned char length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + _m3dstbi__zhuffman z_codelength; + unsigned char lencodes[286+32+137]; + unsigned char codelength_sizes[19]; + int i,n; + + int hlit = _m3dstbi__zreceive(a,5) + 257; + int hdist = _m3dstbi__zreceive(a,5) + 1; + int hclen = _m3dstbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = _m3dstbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (unsigned char) s; + } + if (!_m3dstbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = _m3dstbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (unsigned char) c; + else { + unsigned char fill = 0; + if (c == 16) { + c = _m3dstbi__zreceive(a,2)+3; + if (n == 0) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) + c = _m3dstbi__zreceive(a,3)+3; + else { + STBI_ASSERT(c == 18); + c = _m3dstbi__zreceive(a,7)+11; + } + if (ntot - n < c) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return _m3dstbi__err("bad codelengths","Corrupt PNG"); + if (!_m3dstbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!_m3dstbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +_inline static int _m3dstbi__parse_uncompressed_block(_m3dstbi__zbuf *a) +{ + unsigned char header[4]; + int len,nlen,k; + if (a->num_bits & 7) + _m3dstbi__zreceive(a, a->num_bits & 7); + k = 0; + while (a->num_bits > 0) { + header[k++] = (unsigned char) (a->code_buffer & 255); + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + while (k < 4) + header[k++] = _m3dstbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return _m3dstbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return _m3dstbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!_m3dstbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int _m3dstbi__parse_zlib_header(_m3dstbi__zbuf *a) +{ + int cmf = _m3dstbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = _m3dstbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return _m3dstbi__err("bad zlib header","Corrupt PNG"); + if (flg & 32) return _m3dstbi__err("no preset dict","Corrupt PNG"); + if (cm != 8) return _m3dstbi__err("bad compression","Corrupt PNG"); + return 1; +} + +static unsigned char _m3dstbi__zdefault_length[288], _m3dstbi__zdefault_distance[32]; +static void _m3dstbi__init_zdefaults(void) +{ + int i; + for (i=0; i <= 143; ++i) _m3dstbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) _m3dstbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) _m3dstbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) _m3dstbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) _m3dstbi__zdefault_distance[i] = 5; +} + +static int _m3dstbi__parse_zlib(_m3dstbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!_m3dstbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = _m3dstbi__zreceive(a,1); + type = _m3dstbi__zreceive(a,2); + if (type == 0) { + if (!_m3dstbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + _m3dstbi__init_zdefaults(); + if (!_m3dstbi__zbuild_huffman(&a->z_length , _m3dstbi__zdefault_length , 288)) return 0; + if (!_m3dstbi__zbuild_huffman(&a->z_distance, _m3dstbi__zdefault_distance, 32)) return 0; + } else { + if (!_m3dstbi__compute_huffman_codes(a)) return 0; + } + if (!_m3dstbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int _m3dstbi__do_zlib(_m3dstbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return _m3dstbi__parse_zlib(a, parse_header); +} + +char *_m3dstbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + _m3dstbi__zbuf a; + char *p = (char *) _m3dstbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (unsigned char *) buffer; + a.zbuffer_end = (unsigned char *) buffer + len; + if (_m3dstbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +typedef struct +{ + _m3dstbi__uint32 length; + _m3dstbi__uint32 type; +} _m3dstbi__pngchunk; + +static _m3dstbi__pngchunk _m3dstbi__get_chunk_header(_m3dstbi__context *s) +{ + _m3dstbi__pngchunk c; + c.length = _m3dstbi__get32be(s); + c.type = _m3dstbi__get32be(s); + return c; +} + +_inline static int _m3dstbi__check_png_header(_m3dstbi__context *s) +{ + static unsigned char png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (_m3dstbi__get8(s) != png_sig[i]) return _m3dstbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + _m3dstbi__context *s; + unsigned char *idata, *expanded, *out; + int depth; +} _m3dstbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static unsigned char first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int _m3dstbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static unsigned char _m3dstbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +static int _m3dstbi__create_png_image_raw(_m3dstbi__png *a, unsigned char *raw, _m3dstbi__uint32 raw_len, int out_n, _m3dstbi__uint32 x, _m3dstbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + _m3dstbi__context *s = a->s; + _m3dstbi__uint32 i,j,stride = x*out_n*bytes; + _m3dstbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (unsigned char *) _m3dstbi__malloc_mad3(x, y, output_bytes, 0); + if (!a->out) return _m3dstbi__err("outofmem", "Out of memory"); + + if (!_m3dstbi__mad3sizes_valid(img_n, x, depth, 7)) return _m3dstbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + if (s->img_x == x && s->img_y == y) { + if (raw_len != img_len) return _m3dstbi__err("not enough pixels","Corrupt PNG"); + } else { + if (raw_len < img_len) return _m3dstbi__err("not enough pixels","Corrupt PNG"); + } + + for (j=0; j < y; ++j) { + unsigned char *cur = a->out + stride*j; + unsigned char *prior = cur - stride; + int filter = *raw++; + + if (filter > 4) + return _m3dstbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; + + if (j == 0) filter = first_row_filter[filter]; + + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; + cur[filter_bytes+1] = 255; + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + if (depth == 16) { + cur = a->out + stride*j; + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + if (depth < 8) { + for (j=0; j < y; ++j) { + unsigned char *cur = a->out + stride*j; + unsigned char *in = a->out + stride*j + x*out_n - img_width_bytes; + unsigned char scale = (color == 0) ? _m3dstbi__depth_scale_table[depth] : 1; + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + unsigned char *cur = a->out; + _m3dstbi__uint16 *cur16 = (_m3dstbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int _m3dstbi__create_png_image(_m3dstbi__png *a, unsigned char *image_data, _m3dstbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + unsigned char *final; + int p; + if (!interlaced) + return _m3dstbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + final = (unsigned char *) _m3dstbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + _m3dstbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!_m3dstbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int _m3dstbi__compute_transparency(_m3dstbi__png *z, unsigned char tc[3], int out_n) +{ + _m3dstbi__context *s = z->s; + _m3dstbi__uint32 i, pixel_count = s->img_x * s->img_y; + unsigned char *p = z->out; + + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int _m3dstbi__compute_transparency16(_m3dstbi__png *z, _m3dstbi__uint16 tc[3], int out_n) +{ + _m3dstbi__context *s = z->s; + _m3dstbi__uint32 i, pixel_count = s->img_x * s->img_y; + _m3dstbi__uint16 *p = (_m3dstbi__uint16*) z->out; + + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int _m3dstbi__expand_png_palette(_m3dstbi__png *a, unsigned char *palette, int len, int pal_img_n) +{ + _m3dstbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + unsigned char *p, *temp_out, *orig = a->out; + + p = (unsigned char *) _m3dstbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return _m3dstbi__err("outofmem", "Out of memory"); + + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int _m3dstbi__parse_png_file(_m3dstbi__png *z, int scan, int req_comp) +{ + unsigned char palette[1024], pal_img_n=0; + unsigned char has_trans=0, tc[3]; + _m3dstbi__uint16 tc16[3]; + _m3dstbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0; + _m3dstbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!_m3dstbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + _m3dstbi__pngchunk c = _m3dstbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + _m3dstbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return _m3dstbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return _m3dstbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = _m3dstbi__get32be(s); if (s->img_x > (1 << 24)) return _m3dstbi__err("too large","Very large image (corrupt?)"); + s->img_y = _m3dstbi__get32be(s); if (s->img_y > (1 << 24)) return _m3dstbi__err("too large","Very large image (corrupt?)"); + z->depth = _m3dstbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return _m3dstbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = _m3dstbi__get8(s); if (color > 6) return _m3dstbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return _m3dstbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return _m3dstbi__err("bad ctype","Corrupt PNG"); + comp = _m3dstbi__get8(s); if (comp) return _m3dstbi__err("bad comp method","Corrupt PNG"); + filter= _m3dstbi__get8(s); if (filter) return _m3dstbi__err("bad filter method","Corrupt PNG"); + interlace = _m3dstbi__get8(s); if (interlace>1) return _m3dstbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return _m3dstbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return _m3dstbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return _m3dstbi__err("too large","Corrupt PNG"); + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return _m3dstbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return _m3dstbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = _m3dstbi__get8(s); + palette[i*4+1] = _m3dstbi__get8(s); + palette[i*4+2] = _m3dstbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return _m3dstbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return _m3dstbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return _m3dstbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = _m3dstbi__get8(s); + } else { + if (!(s->img_n & 1)) return _m3dstbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (_m3dstbi__uint32) s->img_n*2) return _m3dstbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (_m3dstbi__uint16)_m3dstbi__get16be(s); + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (unsigned char)(_m3dstbi__get16be(s) & 255) * _m3dstbi__depth_scale_table[z->depth]; + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return _m3dstbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + _m3dstbi__uint32 idata_limit_old = idata_limit; + unsigned char *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (unsigned char *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return _m3dstbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!_m3dstbi__getn(s, z->idata+ioff,c.length)) return _m3dstbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + _m3dstbi__uint32 raw_len, bpl; + if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return _m3dstbi__err("no IDAT","Corrupt PNG"); + bpl = (s->img_x * z->depth + 7) / 8; + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (unsigned char *) _m3dstbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, 1); + if (z->expanded == NULL) return 0; + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!_m3dstbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!_m3dstbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!_m3dstbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (pal_img_n) { + s->img_n = pal_img_n; + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!_m3dstbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + return 1; + } + + default: + if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + return _m3dstbi__err("invalid_chunk", "PNG not supported: unknown PNG chunk type"); + } + _m3dstbi__skip(s, c.length); + break; + } + _m3dstbi__get32be(s); + } +} + +static void *_m3dstbi__do_png(_m3dstbi__png *p, int *x, int *y, int *n, int req_comp, _m3dstbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) { _m3dstbi__err("bad req_comp", "Internal error"); return NULL; } + if (_m3dstbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth < 8) + ri->bits_per_channel = 8; + else + ri->bits_per_channel = p->depth; + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = _m3dstbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = _m3dstbi__convert_format16((_m3dstbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *_m3dstbi__png_load(_m3dstbi__context *s, int *x, int *y, int *comp, int req_comp, _m3dstbi__result_info *ri) +{ + _m3dstbi__png p; + p.s = s; + return _m3dstbi__do_png(&p, x,y,comp,req_comp, ri); +} +#define stbi__context _m3dstbi__context +#define stbi__result_info _m3dstbi__result_info +#define stbi__png_load _m3dstbi__png_load +#define stbi_zlib_decode_malloc_guesssize_headerflag _m3dstbi_zlib_decode_malloc_guesssize_headerflag +#endif + +#if defined(M3D_EXPORTER) && !defined(INCLUDE_STB_IMAGE_WRITE_H) +/* zlib_compressor from + + stb_image_write - v1.13 - public domain - http://nothings.org/stb/stb_image_write.h +*/ +typedef unsigned char _m3dstbiw__uc; +typedef unsigned short _m3dstbiw__us; + +typedef uint16_t _m3dstbiw__uint16; +typedef int16_t _m3dstbiw__int16; +typedef uint32_t _m3dstbiw__uint32; +typedef int32_t _m3dstbiw__int32; + +#define STBIW_MALLOC(s) M3D_MALLOC(s) +#define STBIW_REALLOC(p,ns) M3D_REALLOC(p,ns) +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#define STBIW_FREE M3D_FREE +#define STBIW_MEMMOVE memmove +#define STBIW_UCHAR (uint8_t) +#define STBIW_ASSERT(x) +#define _m3dstbiw___sbraw(a) ((int *) (a) - 2) +#define _m3dstbiw___sbm(a) _m3dstbiw___sbraw(a)[0] +#define _m3dstbiw___sbn(a) _m3dstbiw___sbraw(a)[1] + +#define _m3dstbiw___sbneedgrow(a,n) ((a)==0 || _m3dstbiw___sbn(a)+n >= _m3dstbiw___sbm(a)) +#define _m3dstbiw___sbmaybegrow(a,n) (_m3dstbiw___sbneedgrow(a,(n)) ? _m3dstbiw___sbgrow(a,n) : 0) +#define _m3dstbiw___sbgrow(a,n) _m3dstbiw___sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define _m3dstbiw___sbpush(a, v) (_m3dstbiw___sbmaybegrow(a,1), (a)[_m3dstbiw___sbn(a)++] = (v)) +#define _m3dstbiw___sbcount(a) ((a) ? _m3dstbiw___sbn(a) : 0) +#define _m3dstbiw___sbfree(a) ((a) ? STBIW_FREE(_m3dstbiw___sbraw(a)),0 : 0) + +static void *_m3dstbiw___sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*_m3dstbiw___sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? _m3dstbiw___sbraw(*arr) : 0, *arr ? (_m3dstbiw___sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + _m3dstbiw___sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *_m3dstbiw___zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + _m3dstbiw___sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int _m3dstbiw___zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int _m3dstbiw___zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int _m3dstbiw___zhash(unsigned char *data) +{ + _m3dstbiw__uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define _m3dstbiw___zlib_flush() (out = _m3dstbiw___zlib_flushf(out, &bitbuf, &bitcount)) +#define _m3dstbiw___zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), _m3dstbiw___zlib_flush()) +#define _m3dstbiw___zlib_huffa(b,c) _m3dstbiw___zlib_add(_m3dstbiw___zlib_bitrev(b,c),c) +#define _m3dstbiw___zlib_huff1(n) _m3dstbiw___zlib_huffa(0x30 + (n), 8) +#define _m3dstbiw___zlib_huff2(n) _m3dstbiw___zlib_huffa(0x190 + (n)-144, 9) +#define _m3dstbiw___zlib_huff3(n) _m3dstbiw___zlib_huffa(0 + (n)-256,7) +#define _m3dstbiw___zlib_huff4(n) _m3dstbiw___zlib_huffa(0xc0 + (n)-280,8) +#define _m3dstbiw___zlib_huff(n) ((n) <= 143 ? _m3dstbiw___zlib_huff1(n) : (n) <= 255 ? _m3dstbiw___zlib_huff2(n) : (n) <= 279 ? _m3dstbiw___zlib_huff3(n) : _m3dstbiw___zlib_huff4(n)) +#define _m3dstbiw___zlib_huffb(n) ((n) <= 143 ? _m3dstbiw___zlib_huff1(n) : _m3dstbiw___zlib_huff2(n)) + +#define _m3dstbiw___ZHASH 16384 + +unsigned char * _m3dstbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(_m3dstbiw___ZHASH * sizeof(char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + _m3dstbiw___sbpush(out, 0x78); + _m3dstbiw___sbpush(out, 0x5e); + _m3dstbiw___zlib_add(1,1); + _m3dstbiw___zlib_add(1,2); + + for (i=0; i < _m3dstbiw___ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + int h = _m3dstbiw___zhash(data+i)&(_m3dstbiw___ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = _m3dstbiw___sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { + int d = _m3dstbiw___zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) best=d,bestloc=hlist[j]; + } + } + if (hash_table[h] && _m3dstbiw___sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + _m3dstbiw___sbn(hash_table[h]) = quality; + } + _m3dstbiw___sbpush(hash_table[h],data+i); + + if (bestloc) { + h = _m3dstbiw___zhash(data+i+1)&(_m3dstbiw___ZHASH-1); + hlist = hash_table[h]; + n = _m3dstbiw___sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = _m3dstbiw___zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + _m3dstbiw___zlib_huff(j+257); + if (lengtheb[j]) _m3dstbiw___zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + _m3dstbiw___zlib_add(_m3dstbiw___zlib_bitrev(j,5),5); + if (disteb[j]) _m3dstbiw___zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + _m3dstbiw___zlib_huffb(data[i]); + ++i; + } + } + for (;i < data_len; ++i) + _m3dstbiw___zlib_huffb(data[i]); + _m3dstbiw___zlib_huff(256); + while (bitcount) + _m3dstbiw___zlib_add(0,1); + + for (i=0; i < _m3dstbiw___ZHASH; ++i) + (void) _m3dstbiw___sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + { + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; + s1 %= 65521, s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + _m3dstbiw___sbpush(out, STBIW_UCHAR(s2 >> 8)); + _m3dstbiw___sbpush(out, STBIW_UCHAR(s2)); + _m3dstbiw___sbpush(out, STBIW_UCHAR(s1 >> 8)); + _m3dstbiw___sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = _m3dstbiw___sbn(out); + STBIW_MEMMOVE(_m3dstbiw___sbraw(out), out, *out_len); + return (unsigned char *) _m3dstbiw___sbraw(out); +} +#define stbi_zlib_compress _m3dstbi_zlib_compress +#else +unsigned char * _m3dstbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality); +#endif + +#define M3D_CHUNKMAGIC(m, a,b,c,d) ((m)[0]==(a) && (m)[1]==(b) && (m)[2]==(c) && (m)[3]==(d)) + +#ifdef M3D_ASCII +#include /* get sprintf */ +#include /* sprintf and strtod cares about number locale */ +#endif + +#if !defined(M3D_NOIMPORTER) && defined(M3D_ASCII) +/* helper functions for the ASCII parser */ +static char *_m3d_findarg(char *s) { + while(s && *s && *s != ' ' && *s != '\t' && *s != '\r' && *s != '\n') s++; + while(s && *s && (*s == ' ' || *s == '\t')) s++; + return s; +} +static char *_m3d_findnl(char *s) { + while(s && *s && *s != '\r' && *s != '\n') s++; + if(*s == '\r') s++; + if(*s == '\n') s++; + return s; +} +static char *_m3d_gethex(char *s, uint32_t *ret) +{ + if(*s == '#') s++; + *ret = 0; + for(; *s; s++) { + if(*s >= '0' && *s <= '9') { *ret <<= 4; *ret += (uint32_t)(*s-'0'); } + else if(*s >= 'a' && *s <= 'f') { *ret <<= 4; *ret += (uint32_t)(*s-'a'+10); } + else if(*s >= 'A' && *s <= 'F') { *ret <<= 4; *ret += (uint32_t)(*s-'A'+10); } + else break; + } + return _m3d_findarg(s); +} +static char *_m3d_getint(char *s, uint32_t *ret) +{ + char *e = s; + if(!s || !*s || *s == '\r' || *s == '\n') return s; + for(; *e >= '0' && *e <= '9'; e++); + *ret = atoi(s); + return e; +} +static char *_m3d_getfloat(char *s, M3D_FLOAT *ret) +{ + char *e = s; + if(!s || !*s || *s == '\r' || *s == '\n') return s; + for(; *e == '-' || *e == '+' || *e == '.' || (*e >= '0' && *e <= '9') || *e == 'e' || *e == 'E'; e++); + *ret = (M3D_FLOAT)strtod(s, NULL); + return _m3d_findarg(e); +} +#endif +#if !defined(M3D_NODUP) && (defined(M3D_ASCII) || defined(M3D_EXPORTER)) +/* helper function to create safe strings */ +char *_m3d_safestr(char *in, int morelines) +{ + char *out, *o, *i = in; + int l; + if(!in || !*in) { + out = (char*)M3D_MALLOC(1); + if(!out) return NULL; + out[0] =0; + } else { + for(o = in, l = 0; *o && ((morelines & 1) || (*o != '\r' && *o != '\n')) && l < 256; o++, l++); + out = o = (char*)M3D_MALLOC(l+1); + if(!out) return NULL; + while(*i == ' ' || *i == '\t' || *i == '\r' || (morelines && *i == '\n')) i++; + for(; *i && (morelines || (*i != '\r' && *i != '\n')); i++) { + if(*i == '\r') continue; + if(*i == '\n') { + if(morelines >= 3 && o > out && *(o-1) == '\n') break; + if(i > in && *(i-1) == '\n') continue; + if(morelines & 1) { + if(morelines == 1) *o++ = '\r'; + *o++ = '\n'; + } else + break; + } else + if(*i == ' ' || *i == '\t') { + *o++ = morelines? ' ' : '_'; + } else + *o++ = !morelines && (*i == '/' || *i == '\\') ? '_' : *i; + } + for(; o > out && (*(o-1) == ' ' || *(o-1) == '\t' || *(o-1) == '\r' || *(o-1) == '\n'); o--); + *o = 0; + out = (char*)M3D_REALLOC(out, (uint64_t)o - (uint64_t)out + 1); + } + return out; +} +#endif +#ifndef M3D_NOIMPORTER +/* helper function to load and decode/generate a texture */ +M3D_INDEX _m3d_gettx(m3d_t *model, m3dread_t readfilecb, m3dfree_t freecb, char *fn) +{ + unsigned int i, len = 0; + unsigned char *buff = NULL; + char *fn2; +#ifdef STBI__PNG_TYPE + unsigned int w, h; + stbi__context s; + stbi__result_info ri; +#endif + + /* do we have loaded this texture already? */ + for(i = 0; i < model->numtexture; i++) + if(!strcmp(fn, model->texture[i].name)) return i; + /* see if it's inlined in the model */ + if(model->inlined) { + for(i = 0; i < model->numinlined; i++) + if(!strcmp(fn, model->inlined[i].name)) { + buff = model->inlined[i].data; + len = model->inlined[i].length; + freecb = NULL; + break; + } + } + /* try to load from external source */ + if(!buff && readfilecb) { + i = strlen(fn); + if(i < 5 || fn[i - 4] != '.') { + fn2 = (char*)M3D_MALLOC(i + 5); + if(!fn2) { model->errcode = M3D_ERR_ALLOC; return (M3D_INDEX)-1U; } + memcpy(fn2, fn, i); + memcpy(fn2+i, ".png", 5); + buff = (*readfilecb)(fn2, &len); + M3D_FREE(fn2); + } + if(!buff) + buff = (*readfilecb)(fn, &len); + } + if(!buff) return (M3D_INDEX)-1U; + /* add to textures array */ + i = model->numtexture++; + model->texture = (m3dtx_t*)M3D_REALLOC(model->texture, model->numtexture * sizeof(m3dtx_t)); + if(!model->texture) { + if(freecb) (*freecb)(buff); + model->errcode = M3D_ERR_ALLOC; + return (M3D_INDEX)-1U; + } + model->texture[i].name = fn; + model->texture[i].w = model->texture[i].h = 0; model->texture[i].d = NULL; + if(buff[0] == 0x89 && buff[1] == 'P' && buff[2] == 'N' && buff[3] == 'G') { +#ifdef STBI__PNG_TYPE + s.read_from_callbacks = 0; + s.img_buffer = s.img_buffer_original = (unsigned char *) buff; + s.img_buffer_end = s.img_buffer_original_end = (unsigned char *) buff+len; + /* don't use model->texture[i].w directly, it's a uint16_t */ + w = h = len = 0; + ri.bits_per_channel = 8; + model->texture[i].d = (uint8_t*)stbi__png_load(&s, (int*)&w, (int*)&h, (int*)&len, 0, &ri); + model->texture[i].w = w; + model->texture[i].h = h; + model->texture[i].f = (uint8_t)len; +#endif + } else { +#ifdef M3D_TX_INTERP + if((model->errcode = M3D_TX_INTERP(fn, buff, len, &model->texture[i])) != M3D_SUCCESS) { + M3D_LOG("Unable to generate texture"); + M3D_LOG(fn); + } +#else + M3D_LOG("Unimplemented interpreter"); + M3D_LOG(fn); +#endif + } + if(freecb) (*freecb)(buff); + if(!model->texture[i].d) + model->errcode = M3D_ERR_UNKIMG; + return i; +} + +/* helper function to load and generate a procedural surface */ +void _m3d_getpr(m3d_t *model, _unused m3dread_t readfilecb, _unused m3dfree_t freecb, _unused char *fn) +{ +#ifdef M3D_PR_INTERP + unsigned int i, len = 0; + unsigned char *buff = readfilecb ? (*readfilecb)(fn, &len) : NULL; + + if(!buff && model->inlined) { + for(i = 0; i < model->numinlined; i++) + if(!strcmp(fn, model->inlined[i].name)) { + buff = model->inlined[i].data; + len = model->inlined[i].length; + freecb = NULL; + break; + } + } + if(!buff || !len || (model->errcode = M3D_PR_INTERP(fn, buff, len, model)) != M3D_SUCCESS) { + M3D_LOG("Unable to generate procedural surface"); + M3D_LOG(fn); + model->errcode = M3D_ERR_UNKIMG; + } + if(freecb && buff) (*freecb)(buff); +#else + M3D_LOG("Unimplemented interpreter"); + M3D_LOG(fn); + model->errcode = M3D_ERR_UNIMPL; +#endif +} +/* helpers to read indices from data stream */ +#define M3D_GETSTR(x) do{offs=0;data=_m3d_getidx(data,model->si_s,&offs);x=offs?((char*)model->raw+16+offs):NULL;}while(0) +_inline static unsigned char *_m3d_getidx(unsigned char *data, char type, M3D_INDEX *idx) +{ + switch(type) { + case 1: *idx = data[0] > 253 ? (int8_t)data[0] : data[0]; data++; break; + case 2: *idx = *((uint16_t*)data) > 65533 ? *((int16_t*)data) : *((uint16_t*)data); data += 2; break; + case 4: *idx = *((int32_t*)data); data += 4; break; + } + return data; +} + +#ifndef M3D_NOANIMATION +/* multiply 4 x 4 matrices. Do not use float *r[16] as argument, because some compilers misinterpret that as + * 16 pointers each pointing to a float, but we need a single pointer to 16 floats. */ +void _m3d_mul(M3D_FLOAT *r, M3D_FLOAT *a, M3D_FLOAT *b) +{ + r[ 0] = b[ 0] * a[ 0] + b[ 4] * a[ 1] + b[ 8] * a[ 2] + b[12] * a[ 3]; + r[ 1] = b[ 1] * a[ 0] + b[ 5] * a[ 1] + b[ 9] * a[ 2] + b[13] * a[ 3]; + r[ 2] = b[ 2] * a[ 0] + b[ 6] * a[ 1] + b[10] * a[ 2] + b[14] * a[ 3]; + r[ 3] = b[ 3] * a[ 0] + b[ 7] * a[ 1] + b[11] * a[ 2] + b[15] * a[ 3]; + r[ 4] = b[ 0] * a[ 4] + b[ 4] * a[ 5] + b[ 8] * a[ 6] + b[12] * a[ 7]; + r[ 5] = b[ 1] * a[ 4] + b[ 5] * a[ 5] + b[ 9] * a[ 6] + b[13] * a[ 7]; + r[ 6] = b[ 2] * a[ 4] + b[ 6] * a[ 5] + b[10] * a[ 6] + b[14] * a[ 7]; + r[ 7] = b[ 3] * a[ 4] + b[ 7] * a[ 5] + b[11] * a[ 6] + b[15] * a[ 7]; + r[ 8] = b[ 0] * a[ 8] + b[ 4] * a[ 9] + b[ 8] * a[10] + b[12] * a[11]; + r[ 9] = b[ 1] * a[ 8] + b[ 5] * a[ 9] + b[ 9] * a[10] + b[13] * a[11]; + r[10] = b[ 2] * a[ 8] + b[ 6] * a[ 9] + b[10] * a[10] + b[14] * a[11]; + r[11] = b[ 3] * a[ 8] + b[ 7] * a[ 9] + b[11] * a[10] + b[15] * a[11]; + r[12] = b[ 0] * a[12] + b[ 4] * a[13] + b[ 8] * a[14] + b[12] * a[15]; + r[13] = b[ 1] * a[12] + b[ 5] * a[13] + b[ 9] * a[14] + b[13] * a[15]; + r[14] = b[ 2] * a[12] + b[ 6] * a[13] + b[10] * a[14] + b[14] * a[15]; + r[15] = b[ 3] * a[12] + b[ 7] * a[13] + b[11] * a[14] + b[15] * a[15]; +} +/* calculate 4 x 4 matrix inverse */ +void _m3d_inv(M3D_FLOAT *m) +{ + M3D_FLOAT r[16]; + M3D_FLOAT det = + m[ 0]*m[ 5]*m[10]*m[15] - m[ 0]*m[ 5]*m[11]*m[14] + m[ 0]*m[ 6]*m[11]*m[13] - m[ 0]*m[ 6]*m[ 9]*m[15] + + m[ 0]*m[ 7]*m[ 9]*m[14] - m[ 0]*m[ 7]*m[10]*m[13] - m[ 1]*m[ 6]*m[11]*m[12] + m[ 1]*m[ 6]*m[ 8]*m[15] + - m[ 1]*m[ 7]*m[ 8]*m[14] + m[ 1]*m[ 7]*m[10]*m[12] - m[ 1]*m[ 4]*m[10]*m[15] + m[ 1]*m[ 4]*m[11]*m[14] + + m[ 2]*m[ 7]*m[ 8]*m[13] - m[ 2]*m[ 7]*m[ 9]*m[12] + m[ 2]*m[ 4]*m[ 9]*m[15] - m[ 2]*m[ 4]*m[11]*m[13] + + m[ 2]*m[ 5]*m[11]*m[12] - m[ 2]*m[ 5]*m[ 8]*m[15] - m[ 3]*m[ 4]*m[ 9]*m[14] + m[ 3]*m[ 4]*m[10]*m[13] + - m[ 3]*m[ 5]*m[10]*m[12] + m[ 3]*m[ 5]*m[ 8]*m[14] - m[ 3]*m[ 6]*m[ 8]*m[13] + m[ 3]*m[ 6]*m[ 9]*m[12]; + if(det == (M3D_FLOAT)0.0 || det == (M3D_FLOAT)-0.0) det = (M3D_FLOAT)1.0; else det = (M3D_FLOAT)1.0 / det; + r[ 0] = det *(m[ 5]*(m[10]*m[15] - m[11]*m[14]) + m[ 6]*(m[11]*m[13] - m[ 9]*m[15]) + m[ 7]*(m[ 9]*m[14] - m[10]*m[13])); + r[ 1] = -det*(m[ 1]*(m[10]*m[15] - m[11]*m[14]) + m[ 2]*(m[11]*m[13] - m[ 9]*m[15]) + m[ 3]*(m[ 9]*m[14] - m[10]*m[13])); + r[ 2] = det *(m[ 1]*(m[ 6]*m[15] - m[ 7]*m[14]) + m[ 2]*(m[ 7]*m[13] - m[ 5]*m[15]) + m[ 3]*(m[ 5]*m[14] - m[ 6]*m[13])); + r[ 3] = -det*(m[ 1]*(m[ 6]*m[11] - m[ 7]*m[10]) + m[ 2]*(m[ 7]*m[ 9] - m[ 5]*m[11]) + m[ 3]*(m[ 5]*m[10] - m[ 6]*m[ 9])); + r[ 4] = -det*(m[ 4]*(m[10]*m[15] - m[11]*m[14]) + m[ 6]*(m[11]*m[12] - m[ 8]*m[15]) + m[ 7]*(m[ 8]*m[14] - m[10]*m[12])); + r[ 5] = det *(m[ 0]*(m[10]*m[15] - m[11]*m[14]) + m[ 2]*(m[11]*m[12] - m[ 8]*m[15]) + m[ 3]*(m[ 8]*m[14] - m[10]*m[12])); + r[ 6] = -det*(m[ 0]*(m[ 6]*m[15] - m[ 7]*m[14]) + m[ 2]*(m[ 7]*m[12] - m[ 4]*m[15]) + m[ 3]*(m[ 4]*m[14] - m[ 6]*m[12])); + r[ 7] = det *(m[ 0]*(m[ 6]*m[11] - m[ 7]*m[10]) + m[ 2]*(m[ 7]*m[ 8] - m[ 4]*m[11]) + m[ 3]*(m[ 4]*m[10] - m[ 6]*m[ 8])); + r[ 8] = det *(m[ 4]*(m[ 9]*m[15] - m[11]*m[13]) + m[ 5]*(m[11]*m[12] - m[ 8]*m[15]) + m[ 7]*(m[ 8]*m[13] - m[ 9]*m[12])); + r[ 9] = -det*(m[ 0]*(m[ 9]*m[15] - m[11]*m[13]) + m[ 1]*(m[11]*m[12] - m[ 8]*m[15]) + m[ 3]*(m[ 8]*m[13] - m[ 9]*m[12])); + r[10] = det *(m[ 0]*(m[ 5]*m[15] - m[ 7]*m[13]) + m[ 1]*(m[ 7]*m[12] - m[ 4]*m[15]) + m[ 3]*(m[ 4]*m[13] - m[ 5]*m[12])); + r[11] = -det*(m[ 0]*(m[ 5]*m[11] - m[ 7]*m[ 9]) + m[ 1]*(m[ 7]*m[ 8] - m[ 4]*m[11]) + m[ 3]*(m[ 4]*m[ 9] - m[ 5]*m[ 8])); + r[12] = -det*(m[ 4]*(m[ 9]*m[14] - m[10]*m[13]) + m[ 5]*(m[10]*m[12] - m[ 8]*m[14]) + m[ 6]*(m[ 8]*m[13] - m[ 9]*m[12])); + r[13] = det *(m[ 0]*(m[ 9]*m[14] - m[10]*m[13]) + m[ 1]*(m[10]*m[12] - m[ 8]*m[14]) + m[ 2]*(m[ 8]*m[13] - m[ 9]*m[12])); + r[14] = -det*(m[ 0]*(m[ 5]*m[14] - m[ 6]*m[13]) + m[ 1]*(m[ 6]*m[12] - m[ 4]*m[14]) + m[ 2]*(m[ 4]*m[13] - m[ 5]*m[12])); + r[15] = det *(m[ 0]*(m[ 5]*m[10] - m[ 6]*m[ 9]) + m[ 1]*(m[ 6]*m[ 8] - m[ 4]*m[10]) + m[ 2]*(m[ 4]*m[ 9] - m[ 5]*m[ 8])); + memcpy(m, &r, sizeof(r)); +} +/* compose a coloumn major 4 x 4 matrix from vec3 position and vec4 orientation/rotation quaternion */ +void _m3d_mat(M3D_FLOAT *r, m3dv_t *p, m3dv_t *q) +{ + if(q->x == (M3D_FLOAT)0.0 && q->y == (M3D_FLOAT)0.0 && q->z >=(M3D_FLOAT) 0.7071065 && q->z <= (M3D_FLOAT)0.7071075 && + q->w == (M3D_FLOAT)0.0) { + r[ 1] = r[ 2] = r[ 4] = r[ 6] = r[ 8] = r[ 9] = (M3D_FLOAT)0.0; + r[ 0] = r[ 5] = r[10] = (M3D_FLOAT)-1.0; + } else { + r[ 0] = 1 - 2 * (q->y * q->y + q->z * q->z); if(r[ 0]>-M3D_EPSILON && r[ 0]x * q->y - q->z * q->w); if(r[ 1]>-M3D_EPSILON && r[ 1]x * q->z + q->y * q->w); if(r[ 2]>-M3D_EPSILON && r[ 2]x * q->y + q->z * q->w); if(r[ 4]>-M3D_EPSILON && r[ 4]x * q->x + q->z * q->z); if(r[ 5]>-M3D_EPSILON && r[ 5]y * q->z - q->x * q->w); if(r[ 6]>-M3D_EPSILON && r[ 6]x * q->z - q->y * q->w); if(r[ 8]>-M3D_EPSILON && r[ 8]y * q->z + q->x * q->w); if(r[ 9]>-M3D_EPSILON && r[ 9]x * q->x + q->y * q->y); if(r[10]>-M3D_EPSILON && r[10]x; r[ 7] = p->y; r[11] = p->z; + r[12] = 0; r[13] = 0; r[14] = 0; r[15] = 1; +} +#endif +#if !defined(M3D_NOANIMATION) || !defined(M3D_NONORMALS) +/* fast inverse square root calculation. returns 1/sqrt(x) */ +static M3D_FLOAT _m3d_rsq(M3D_FLOAT x) +{ +#ifdef M3D_DOUBLE + return ((M3D_FLOAT)15.0/(M3D_FLOAT)8.0) + ((M3D_FLOAT)-5.0/(M3D_FLOAT)4.0)*x + ((M3D_FLOAT)3.0/(M3D_FLOAT)8.0)*x*x; +#else + /* John Carmack's */ + float x2 = x * 0.5f; + *((uint32_t*)&x) = (0x5f3759df - (*((uint32_t*)&x) >> 1)); + return x * (1.5f - (x2 * x * x)); +#endif +} +#endif + +/** + * Function to decode a Model 3D into in-memory format + */ +m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d_t *mtllib) +{ + unsigned char *end, *chunk, *buff, weights[8]; + unsigned int i, j, k, l, n, am, len = 0, reclen, offs; + char *name, *lang; + float f; + m3d_t *model; + M3D_INDEX mi; + M3D_FLOAT w; + m3dcd_t *cd; + m3dtx_t *tx; + m3dh_t *h; + m3dm_t *m; + m3da_t *a; + m3di_t *t; +#ifndef M3D_NONORMALS + m3dv_t *norm = NULL, *v0, *v1, *v2, va, vb; +#endif +#ifndef M3D_NOANIMATION + M3D_FLOAT r[16]; +#endif +#if !defined(M3D_NOWEIGHTS) || !defined(M3D_NOANIMATION) + m3db_t *b; +#endif +#ifndef M3D_NOWEIGHTS + m3ds_t *sk; +#endif +#ifdef M3D_ASCII + m3ds_t s; + M3D_INDEX bi[M3D_BONEMAXLEVEL+1], level; + const char *ol; + char *ptr, *pe, *fn; +#endif + + if(!data || (!M3D_CHUNKMAGIC(data, '3','D','M','O') +#ifdef M3D_ASCII + && !M3D_CHUNKMAGIC(data, '3','d','m','o') +#endif + )) return NULL; + model = (m3d_t*)M3D_MALLOC(sizeof(m3d_t)); + if(!model) { + M3D_LOG("Out of memory"); + return NULL; + } + memset(model, 0, sizeof(m3d_t)); + + if(mtllib) { + model->nummaterial = mtllib->nummaterial; + model->material = mtllib->material; + model->numtexture = mtllib->numtexture; + model->texture = mtllib->texture; + model->flags |= M3D_FLG_MTLLIB; + } +#ifdef M3D_ASCII + /* ASCII variant? */ + if(M3D_CHUNKMAGIC(data, '3','d','m','o')) { + model->errcode = M3D_ERR_BADFILE; + model->flags |= M3D_FLG_FREESTR; + model->raw = (m3dhdr_t*)data; + ptr = (char*)data; + ol = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + /* parse header. Don't use sscanf, that's incredibly slow */ + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + pe = _m3d_findnl(ptr); + model->scale = (float)strtod(ptr, NULL); ptr = pe; + if(model->scale <= (M3D_FLOAT)0.0) model->scale = (M3D_FLOAT)1.0; + model->name = _m3d_safestr(ptr, 2); ptr = _m3d_findnl(ptr); + if(!*ptr) goto asciiend; + model->license = _m3d_safestr(ptr, 2); ptr = _m3d_findnl(ptr); + if(!*ptr) goto asciiend; + model->author = _m3d_safestr(ptr, 2); ptr = _m3d_findnl(ptr); + if(!*ptr) goto asciiend; + if(*ptr != '\r' && *ptr != '\n') + model->desc = _m3d_safestr(ptr, 3); + while(*ptr) { + while(*ptr && *ptr!='\n') ptr++; + ptr++; if(*ptr=='\r') ptr++; + if(*ptr == '\n') break; + } + + /* the main chunk reader loop */ + while(*ptr) { + while(*ptr && (*ptr == '\r' || *ptr == '\n')) ptr++; + if(!*ptr || (ptr[0]=='E' && ptr[1]=='n' && ptr[2]=='d')) break; + /* make sure there's at least one data row */ + pe = ptr; ptr = _m3d_findnl(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + /* Preview chunk */ + if(!memcmp(pe, "Preview", 7)) { + if(readfilecb) { + pe = _m3d_safestr(ptr, 0); + if(!pe || !*pe) goto asciiend; + model->preview.data = (*readfilecb)(pe, &model->preview.length); + M3D_FREE(pe); + } + while(*ptr && *ptr != '\r' && *ptr != '\n') + ptr = _m3d_findnl(ptr); + } else + /* texture map chunk */ + if(!memcmp(pe, "Textmap", 7)) { + if(model->tmap) { M3D_LOG("More texture map chunks, should be unique"); goto asciiend; } + while(*ptr && *ptr != '\r' && *ptr != '\n') { + i = model->numtmap++; + model->tmap = (m3dti_t*)M3D_REALLOC(model->tmap, model->numtmap * sizeof(m3dti_t)); + if(!model->tmap) goto memerr; + ptr = _m3d_getfloat(ptr, &model->tmap[i].u); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + _m3d_getfloat(ptr, &model->tmap[i].v); + ptr = _m3d_findnl(ptr); + } + } else + /* vertex chunk */ + if(!memcmp(pe, "Vertex", 6)) { + if(model->vertex) { M3D_LOG("More vertex chunks, should be unique"); goto asciiend; } + while(*ptr && *ptr != '\r' && *ptr != '\n') { + i = model->numvertex++; + model->vertex = (m3dv_t*)M3D_REALLOC(model->vertex, model->numvertex * sizeof(m3dv_t)); + if(!model->vertex) goto memerr; + memset(&model->vertex[i], 0, sizeof(m3dv_t)); + model->vertex[i].skinid = (M3D_INDEX)-1U; + model->vertex[i].color = 0; + model->vertex[i].w = (M3D_FLOAT)1.0; + ptr = _m3d_getfloat(ptr, &model->vertex[i].x); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + ptr = _m3d_getfloat(ptr, &model->vertex[i].y); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + ptr = _m3d_getfloat(ptr, &model->vertex[i].z); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + ptr = _m3d_getfloat(ptr, &model->vertex[i].w); + if(!*ptr) goto asciiend; + if(*ptr == '#') { + ptr = _m3d_gethex(ptr, &model->vertex[i].color); + if(!*ptr) goto asciiend; + } + /* parse skin */ + memset(&s, 0, sizeof(m3ds_t)); + for(j = 0, w = (M3D_FLOAT)0.0; j < M3D_NUMBONE && *ptr && *ptr != '\r' && *ptr != '\n'; j++) { + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + ptr = _m3d_getint(ptr, &k); + s.boneid[j] = (M3D_INDEX)k; + if(*ptr == ':') { + ptr++; + ptr = _m3d_getfloat(ptr, &s.weight[j]); + w += s.weight[j]; + } else if(!j) + s.weight[j] = (M3D_FLOAT)1.0; + if(!*ptr) goto asciiend; + } + if(s.boneid[0] != (M3D_INDEX)-1U && s.weight[0] > (M3D_FLOAT)0.0) { + if(w != (M3D_FLOAT)1.0 && w != (M3D_FLOAT)0.0) + for(j = 0; j < M3D_NUMBONE && s.weight[j] > (M3D_FLOAT)0.0; j++) + s.weight[j] /= w; + k = -1U; + if(model->skin) { + for(j = 0; j < model->numskin; j++) + if(!memcmp(&model->skin[j], &s, sizeof(m3ds_t))) { k = j; break; } + } + if(k == -1U) { + k = model->numskin++; + model->skin = (m3ds_t*)M3D_REALLOC(model->skin, model->numskin * sizeof(m3ds_t)); + memcpy(&model->skin[k], &s, sizeof(m3ds_t)); + } + model->vertex[i].skinid = (M3D_INDEX)k; + } + ptr = _m3d_findnl(ptr); + } + } else + /* Skeleton, bone hierarchy */ + if(!memcmp(pe, "Bones", 5)) { + if(model->bone) { M3D_LOG("More bones chunks, should be unique"); goto asciiend; } + bi[0] = (M3D_INDEX)-1U; + while(*ptr && *ptr != '\r' && *ptr != '\n') { + i = model->numbone++; + model->bone = (m3db_t*)M3D_REALLOC(model->bone, model->numbone * sizeof(m3db_t)); + if(!model->bone) goto memerr; + for(level = 0; *ptr == '/'; ptr++, level++); + if(level > M3D_BONEMAXLEVEL || !*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + bi[level+1] = i; + model->bone[i].numweight = 0; + model->bone[i].weight = NULL; + model->bone[i].parent = bi[level]; + ptr = _m3d_getint(ptr, &k); + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + model->bone[i].pos = (M3D_INDEX)k; + ptr = _m3d_getint(ptr, &k); + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + model->bone[i].ori = (M3D_INDEX)k; + model->vertex[k].skinid = (M3D_INDEX)-2U; + pe = _m3d_safestr(ptr, 0); + if(!pe || !*pe) goto asciiend; + model->bone[i].name = pe; + ptr = _m3d_findnl(ptr); + } + } else + /* material chunk */ + if(!memcmp(pe, "Material", 8)) { + pe = _m3d_findarg(pe); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + pe = _m3d_safestr(pe, 0); + if(!pe || !*pe) goto asciiend; + for(i = 0; i < model->nummaterial; i++) + if(!strcmp(pe, model->material[i].name)) { + M3D_LOG("Multiple definitions for material"); + M3D_LOG(pe); + M3D_FREE(pe); + pe = NULL; + while(*ptr && *ptr != '\r' && *ptr != '\n') ptr = _m3d_findnl(ptr); + break; + } + if(!pe) continue; + i = model->nummaterial++; + if(model->flags & M3D_FLG_MTLLIB) { + m = model->material; + model->material = (m3dm_t*)M3D_MALLOC(model->nummaterial * sizeof(m3dm_t)); + if(!model->material) goto memerr; + memcpy(model->material, m, (model->nummaterial - 1) * sizeof(m3dm_t)); + if(model->texture) { + tx = model->texture; + model->texture = (m3dtx_t*)M3D_MALLOC(model->numtexture * sizeof(m3dtx_t)); + if(!model->texture) goto memerr; + memcpy(model->texture, tx, model->numtexture * sizeof(m3dm_t)); + } + model->flags &= ~M3D_FLG_MTLLIB; + } else { + model->material = (m3dm_t*)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); + if(!model->material) goto memerr; + } + m = &model->material[i]; + m->name = pe; + m->numprop = 0; + m->prop = NULL; + while(*ptr && *ptr != '\r' && *ptr != '\n') { + k = n = 256; + if(*ptr == 'm' && *(ptr+1) == 'a' && *(ptr+2) == 'p' && *(ptr+3) == '_') { + k = m3dpf_map; + ptr += 4; + } + for(j = 0; j < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); j++) + if(!memcmp(ptr, m3d_propertytypes[j].key, strlen(m3d_propertytypes[j].key))) { + n = m3d_propertytypes[j].id; + if(k != m3dpf_map) k = m3d_propertytypes[j].format; + break; + } + if(n != 256 && k != 256) { + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + j = m->numprop++; + m->prop = (m3dp_t*)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t)); + if(!m->prop) goto memerr; + m->prop[j].type = n + (k == m3dpf_map && n < 128 ? 128 : 0); + switch(k) { + case m3dpf_color: ptr = _m3d_gethex(ptr, &m->prop[j].value.color); break; + case m3dpf_uint8: + case m3dpf_uint16: + case m3dpf_uint32: ptr = _m3d_getint(ptr, &m->prop[j].value.num); break; + case m3dpf_float: ptr = _m3d_getfloat(ptr, &m->prop[j].value.fnum); break; + case m3dpf_map: + pe = _m3d_safestr(ptr, 0); + if(!pe || !*pe) goto asciiend; + m->prop[j].value.textureid = _m3d_gettx(model, readfilecb, freecb, pe); + if(model->errcode == M3D_ERR_ALLOC) { M3D_FREE(pe); goto memerr; } + if(m->prop[j].value.textureid == (M3D_INDEX)-1U) { + M3D_LOG("Texture not found"); + M3D_LOG(pe); + m->numprop--; + } + M3D_FREE(pe); + break; + } + } else { + M3D_LOG("Unknown material property in"); + M3D_LOG(m->name); + model->errcode = M3D_ERR_UNKPROP; + } + ptr = _m3d_findnl(ptr); + } + if(!m->numprop) model->nummaterial--; + } else + /* procedural */ + if(!memcmp(pe, "Procedural", 10)) { + pe = _m3d_safestr(ptr, 0); + _m3d_getpr(model, readfilecb, freecb, pe); + M3D_FREE(pe); + while(*ptr && *ptr != '\r' && *ptr != '\n') ptr = _m3d_findnl(ptr); + } else + /* mesh */ + if(!memcmp(pe, "Mesh", 4)) { + mi = (M3D_INDEX)-1U; + while(*ptr && *ptr != '\r' && *ptr != '\n') { + if(*ptr == 'u') { + ptr = _m3d_findarg(ptr); + if(!*ptr) goto asciiend; + mi = (M3D_INDEX)-1U; + if(*ptr != '\r' && *ptr != '\n') { + pe = _m3d_safestr(ptr, 0); + if(!pe || !*pe) goto asciiend; + for(j = 0; j < model->nummaterial; j++) + if(!strcmp(pe, model->material[j].name)) { mi = (M3D_INDEX)j; break; } + if(mi == (M3D_INDEX)-1U && !(model->flags & M3D_FLG_MTLLIB)) { + mi = model->nummaterial++; + model->material = (m3dm_t*)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); + if(!model->material) goto memerr; + model->material[mi].name = pe; + model->material[mi].numprop = 1; + model->material[mi].prop = NULL; + } else + M3D_FREE(pe); + } + } else { + i = model->numface++; + model->face = (m3df_t*)M3D_REALLOC(model->face, model->numface * sizeof(m3df_t)); + if(!model->face) goto memerr; + memset(&model->face[i], 255, sizeof(m3df_t)); /* set all index to -1 by default */ + model->face[i].materialid = mi; + /* hardcoded triangles. */ + for(j = 0; j < 3; j++) { + /* vertex */ + ptr = _m3d_getint(ptr, &k); + model->face[i].vertex[j] = (M3D_INDEX)k; + if(!*ptr) goto asciiend; + if(*ptr == '/') { + ptr++; + if(*ptr != '/') { + /* texcoord */ + ptr = _m3d_getint(ptr, &k); + model->face[i].texcoord[j] = (M3D_INDEX)k; + if(!*ptr) goto asciiend; + } + if(*ptr == '/') { + ptr++; + /* normal */ + ptr = _m3d_getint(ptr, &k); + model->face[i].normal[j] = (M3D_INDEX)k; + if(!*ptr) goto asciiend; + } + } + ptr = _m3d_findarg(ptr); + } + } + ptr = _m3d_findnl(ptr); + } + } else + /* mathematical shape */ + if(!memcmp(pe, "Shape", 5)) { + pe = _m3d_findarg(pe); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + pe = _m3d_safestr(pe, 0); + if(!pe || !*pe) goto asciiend; + i = model->numshape++; + model->shape = (m3dh_t*)M3D_REALLOC(model->shape, model->numshape * sizeof(m3ds_t)); + if(!model->shape) goto memerr; + h = &model->shape[i]; + h->name = pe; + h->group = (M3D_INDEX)-1U; + h->numcmd = 0; + h->cmd = NULL; + while(*ptr && *ptr != '\r' && *ptr != '\n') { + if(!memcmp(ptr, "group", 5)) { + ptr = _m3d_findarg(ptr); + ptr = _m3d_getint(ptr, &h->group); + ptr = _m3d_findnl(ptr); + if(h->group != (M3D_INDEX)-1U && h->group >= model->numbone) { + M3D_LOG("Unknown bone id as shape group in shape"); + M3D_LOG(pe); + h->group = (M3D_INDEX)-1U; + model->errcode = M3D_ERR_SHPE; + } + continue; + } + for(cd = NULL, k = 0; k < (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])); k++) { + j = strlen(m3d_commandtypes[k].key); + if(!memcmp(ptr, m3d_commandtypes[k].key, j) && (ptr[j] == ' ' || ptr[j] == '\r' || ptr[j] == '\n')) + { cd = &m3d_commandtypes[k]; break; } + } + if(cd) { + j = h->numcmd++; + h->cmd = (m3dc_t*)M3D_REALLOC(h->cmd, h->numcmd * sizeof(m3dc_t)); + if(!h->cmd) goto memerr; + h->cmd[j].type = k; + h->cmd[j].arg = (uint32_t*)M3D_MALLOC(cd->p * sizeof(uint32_t)); + if(!h->cmd[j].arg) goto memerr; + memset(h->cmd[j].arg, 0, cd->p * sizeof(uint32_t)); + for(k = n = 0, l = cd->p; k < l; k++) { + ptr = _m3d_findarg(ptr); + if(!*ptr) goto asciiend; + if(*ptr == '[') { + ptr = _m3d_findarg(ptr + 1); + if(!*ptr) goto asciiend; + } + if(*ptr == ']' || *ptr == '\r' || *ptr == '\n') break; + switch(cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + mi = (M3D_INDEX)-1U; + if(*ptr != '\r' && *ptr != '\n') { + pe = _m3d_safestr(ptr, 0); + if(!pe || !*pe) goto asciiend; + for(n = 0; n < model->nummaterial; n++) + if(!strcmp(pe, model->material[n].name)) { mi = (M3D_INDEX)n; break; } + if(mi == (M3D_INDEX)-1U && !(model->flags & M3D_FLG_MTLLIB)) { + mi = model->nummaterial++; + model->material = (m3dm_t*)M3D_REALLOC(model->material, + model->nummaterial * sizeof(m3dm_t)); + if(!model->material) goto memerr; + model->material[mi].name = pe; + model->material[mi].numprop = 1; + model->material[mi].prop = NULL; + } else + M3D_FREE(pe); + } + h->cmd[j].arg[k] = mi; + break; + case m3dcp_vc_t: + _m3d_getfloat(ptr, &w); + h->cmd[j].arg[k] = *((uint32_t*)&w); + break; + case m3dcp_va_t: + ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); + n = k + 1; l += (h->cmd[j].arg[k] - 1) * (cd->p - k - 1); + h->cmd[j].arg = (uint32_t*)M3D_REALLOC(h->cmd[j].arg, l * sizeof(uint32_t)); + if(!h->cmd[j].arg) goto memerr; + memset(&h->cmd[j].arg[k + 1], 0, (l - k - 1) * sizeof(uint32_t)); + break; + case m3dcp_qi_t: + ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); + model->vertex[h->cmd[i].arg[k]].skinid = (M3D_INDEX)-2U; + break; + default: + ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); + break; + } + } + } else { + M3D_LOG("Unknown shape command in"); + M3D_LOG(h->name); + model->errcode = M3D_ERR_UNKCMD; + } + ptr = _m3d_findnl(ptr); + } + if(!h->numcmd) model->numshape--; + } else + /* annotation labels */ + if(!memcmp(pe, "Labels", 6)) { + pe = _m3d_findarg(pe); + if(!*pe) goto asciiend; + if(*pe == '\r' || *pe == '\n') pe = NULL; + else pe = _m3d_safestr(pe, 0); + k = 0; fn = NULL; + while(*ptr && *ptr != '\r' && *ptr != '\n') { + if(*ptr == 'c') { + ptr = _m3d_findarg(ptr); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + ptr = _m3d_gethex(ptr, &k); + } else + if(*ptr == 'l') { + ptr = _m3d_findarg(ptr); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + fn = _m3d_safestr(ptr, 2); + } else { + i = model->numlabel++; + model->label = (m3dl_t*)M3D_REALLOC(model->label, model->numlabel * sizeof(m3dl_t)); + if(!model->label) goto memerr; + model->label[i].name = pe; + model->label[i].lang = fn; + model->label[i].color = k; + ptr = _m3d_getint(ptr, &j); + model->label[i].vertexid = (M3D_INDEX)j; + ptr = _m3d_findarg(ptr); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + model->label[i].text = _m3d_safestr(ptr, 2); + } + ptr = _m3d_findnl(ptr); + } + } else + /* action */ + if(!memcmp(pe, "Action", 6)) { + pe = _m3d_findarg(pe); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + pe = _m3d_getint(pe, &k); + pe = _m3d_findarg(pe); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + pe = _m3d_safestr(pe, 0); + if(!pe || !*pe) goto asciiend; + i = model->numaction++; + model->action = (m3da_t*)M3D_REALLOC(model->action, model->numaction * sizeof(m3da_t)); + if(!model->action) goto memerr; + a = &model->action[i]; + a->name = pe; + a->durationmsec = k; + /* skip the first frame marker as there's always at least one frame */ + a->numframe = 1; + a->frame = (m3dfr_t*)M3D_MALLOC(sizeof(m3dfr_t)); + if(!a->frame) goto memerr; + a->frame[0].msec = 0; + a->frame[0].numtransform = 0; + a->frame[0].transform = NULL; + i = 0; + if(*ptr == 'f') + ptr = _m3d_findnl(ptr); + while(*ptr && *ptr != '\r' && *ptr != '\n') { + if(*ptr == 'f') { + i = a->numframe++; + a->frame = (m3dfr_t*)M3D_REALLOC(a->frame, a->numframe * sizeof(m3dfr_t)); + if(!a->frame) goto memerr; + ptr = _m3d_findarg(ptr); + ptr = _m3d_getint(ptr, &a->frame[i].msec); + a->frame[i].numtransform = 0; + a->frame[i].transform = NULL; + } else { + j = a->frame[i].numtransform++; + a->frame[i].transform = (m3dtr_t*)M3D_REALLOC(a->frame[i].transform, + a->frame[i].numtransform * sizeof(m3dtr_t)); + if(!a->frame[i].transform) goto memerr; + ptr = _m3d_getint(ptr, &k); + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + a->frame[i].transform[j].boneid = (M3D_INDEX)k; + ptr = _m3d_getint(ptr, &k); + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + a->frame[i].transform[j].pos = (M3D_INDEX)k; + ptr = _m3d_getint(ptr, &k); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + a->frame[i].transform[j].ori = (M3D_INDEX)k; + model->vertex[k].skinid = (M3D_INDEX)-2U; + } + ptr = _m3d_findnl(ptr); + } + } else + /* inlined assets chunk */ + if(!memcmp(pe, "Assets", 6)) { + while(*ptr && *ptr != '\r' && *ptr != '\n') { + if(readfilecb) { + pe = _m3d_safestr(ptr, 2); + if(!pe || !*pe) goto asciiend; + i = model->numinlined++; + model->inlined = (m3di_t*)M3D_REALLOC(model->inlined, model->numinlined * sizeof(m3di_t)); + if(!model->inlined) goto memerr; + t = &model->inlined[i]; + model->inlined[i].data = (*readfilecb)(pe, &model->inlined[i].length); + if(model->inlined[i].data) { + fn = strrchr(pe, '.'); + if(fn && (fn[1] == 'p' || fn[1] == 'P') && (fn[2] == 'n' || fn[2] == 'N') && + (fn[3] == 'g' || fn[3] == 'G')) *fn = 0; + fn = strrchr(pe, '/'); + if(!fn) fn = strrchr(pe, '\\'); + if(!fn) fn = pe; else fn++; + model->inlined[i].name = _m3d_safestr(fn, 0); + } else + model->numinlined--; + M3D_FREE(pe); + } + ptr = _m3d_findnl(ptr); + } + } else + /* extra chunks */ + if(!memcmp(pe, "Extra", 5)) { + pe = _m3d_findarg(pe); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + buff = (unsigned char*)_m3d_findnl(ptr); + k = ((uint32_t)((uint64_t)buff - (uint64_t)ptr) / 3) + 1; + i = model->numextra++; + model->extra = (m3dchunk_t**)M3D_REALLOC(model->extra, model->numextra * sizeof(m3dchunk_t*)); + if(!model->extra) goto memerr; + model->extra[i] = (m3dchunk_t*)M3D_MALLOC(k + sizeof(m3dchunk_t)); + if(!model->extra[i]) goto memerr; + memcpy(&model->extra[i]->magic, pe, 4); + model->extra[i]->length = sizeof(m3dchunk_t); + pe = (char*)model->extra[i] + sizeof(m3dchunk_t); + while(*ptr && *ptr != '\r' && *ptr != '\n') { + ptr = _m3d_gethex(ptr, &k); + *pe++ = (uint8_t)k; + model->extra[i]->length++; + } + } else + goto asciiend; + } + model->errcode = M3D_SUCCESS; +asciiend: + setlocale(LC_NUMERIC, ol); + goto postprocess; + } + /* Binary variant */ +#endif + if(!M3D_CHUNKMAGIC(data + 8, 'H','E','A','D')) { + buff = (unsigned char *)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)data+8, ((m3dchunk_t*)data)->length-8, + 4096, (int*)&len, 1); + if(!buff || !len || !M3D_CHUNKMAGIC(buff, 'H','E','A','D')) { + if(buff) M3D_FREE(buff); + M3D_FREE(model); + return NULL; + } + buff = (unsigned char*)M3D_REALLOC(buff, len); + model->flags |= M3D_FLG_FREERAW; /* mark that we have to free the raw buffer */ + data = buff; + } else { + len = ((m3dhdr_t*)data)->length; + data += 8; + } + model->raw = (m3dhdr_t*)data; + end = data + len; + + /* parse header */ + data += sizeof(m3dhdr_t); + M3D_LOG(data); + model->name = (char*)data; + for(; data < end && *data; data++) {}; data++; + model->license = (char*)data; + for(; data < end && *data; data++) {}; data++; + model->author = (char*)data; + for(; data < end && *data; data++) {}; data++; + model->desc = (char*)data; + chunk = (unsigned char*)model->raw + model->raw->length; + model->scale = (M3D_FLOAT)model->raw->scale; + if(model->scale <= (M3D_FLOAT)0.0) model->scale = (M3D_FLOAT)1.0; + model->vc_s = 1 << ((model->raw->types >> 0) & 3); /* vertex coordinate size */ + model->vi_s = 1 << ((model->raw->types >> 2) & 3); /* vertex index size */ + model->si_s = 1 << ((model->raw->types >> 4) & 3); /* string offset size */ + model->ci_s = 1 << ((model->raw->types >> 6) & 3); /* color index size */ + model->ti_s = 1 << ((model->raw->types >> 8) & 3); /* tmap index size */ + model->bi_s = 1 << ((model->raw->types >>10) & 3); /* bone index size */ + model->nb_s = 1 << ((model->raw->types >>12) & 3); /* number of bones per vertex */ + model->sk_s = 1 << ((model->raw->types >>14) & 3); /* skin index size */ + model->fc_s = 1 << ((model->raw->types >>16) & 3); /* frame counter size */ + model->hi_s = 1 << ((model->raw->types >>18) & 3); /* shape index size */ + model->fi_s = 1 << ((model->raw->types >>20) & 3); /* face index size */ + if(model->ci_s == 8) model->ci_s = 0; /* optional indices */ + if(model->ti_s == 8) model->ti_s = 0; + if(model->bi_s == 8) model->bi_s = 0; + if(model->sk_s == 8) model->sk_s = 0; + if(model->fc_s == 8) model->fc_s = 0; + if(model->hi_s == 8) model->hi_s = 0; + if(model->fi_s == 8) model->fi_s = 0; + + /* variable limit checks */ + if(sizeof(M3D_FLOAT) == 4 && model->vc_s > 4) { + M3D_LOG("Double precision coordinates not supported, truncating to float..."); + model->errcode = M3D_ERR_TRUNC; + } + if(sizeof(M3D_INDEX) == 2 && (model->vi_s > 2 || model->si_s > 2 || model->ci_s > 2 || model->ti_s > 2 || + model->bi_s > 2 || model->sk_s > 2 || model->fc_s > 2 || model->hi_s > 2 || model->fi_s > 2)) { + M3D_LOG("32 bit indices not supported, unable to load model"); + M3D_FREE(model); + return NULL; + } + if(model->vi_s > 4 || model->si_s > 4) { + M3D_LOG("Invalid index size, unable to load model"); + M3D_FREE(model); + return NULL; + } + if(model->nb_s > M3D_NUMBONE) { + M3D_LOG("Model has more bones per vertex than what importer configured to support"); + model->errcode = M3D_ERR_TRUNC; + } + + /* look for inlined assets in advance, material and procedural chunks may need them */ + buff = chunk; + while(buff < end && !M3D_CHUNKMAGIC(buff, 'O','M','D','3')) { + data = buff; + len = ((m3dchunk_t*)data)->length; + if(len < sizeof(m3dchunk_t)) { + M3D_LOG("Invalid chunk size"); + break; + } + buff += len; + len -= sizeof(m3dchunk_t) + model->si_s; + + /* inlined assets */ + if(M3D_CHUNKMAGIC(data, 'A','S','E','T') && len > 0) { + M3D_LOG("Inlined asset"); + i = model->numinlined++; + model->inlined = (m3di_t*)M3D_REALLOC(model->inlined, model->numinlined * sizeof(m3di_t)); + if(!model->inlined) { +memerr: M3D_LOG("Out of memory"); + model->errcode = M3D_ERR_ALLOC; + return model; + } + data += sizeof(m3dchunk_t); + t = &model->inlined[i]; + M3D_GETSTR(t->name); + M3D_LOG(t->name); + t->data = (uint8_t*)data; + t->length = len; + } + } + + /* parse chunks */ + while(chunk < end && !M3D_CHUNKMAGIC(chunk, 'O','M','D','3')) { + data = chunk; + len = ((m3dchunk_t*)chunk)->length; + if(len < sizeof(m3dchunk_t)) { + M3D_LOG("Invalid chunk size"); + break; + } + chunk += len; + len -= sizeof(m3dchunk_t); + + /* preview chunk */ + if(M3D_CHUNKMAGIC(data, 'P','R','V','W') && len > 0) { + model->preview.length = len; + model->preview.data = data + sizeof(m3dchunk_t); + } else + /* color map */ + if(M3D_CHUNKMAGIC(data, 'C','M','A','P')) { + M3D_LOG("Color map"); + if(model->cmap) { M3D_LOG("More color map chunks, should be unique"); model->errcode = M3D_ERR_CMAP; continue; } + if(!model->ci_s) { M3D_LOG("Color map chunk, shouldn't be any"); model->errcode = M3D_ERR_CMAP; continue; } + model->numcmap = len / sizeof(uint32_t); + model->cmap = (uint32_t*)(data + sizeof(m3dchunk_t)); + } else + /* texture map */ + if(M3D_CHUNKMAGIC(data, 'T','M','A','P')) { + M3D_LOG("Texture map"); + if(model->tmap) { M3D_LOG("More texture map chunks, should be unique"); model->errcode = M3D_ERR_TMAP; continue; } + if(!model->ti_s) { M3D_LOG("Texture map chunk, shouldn't be any"); model->errcode = M3D_ERR_TMAP; continue; } + reclen = model->vc_s + model->vc_s; + model->numtmap = len / reclen; + model->tmap = (m3dti_t*)M3D_MALLOC(model->numtmap * sizeof(m3dti_t)); + if(!model->tmap) goto memerr; + for(i = 0, data += sizeof(m3dchunk_t); data < chunk; i++) { + switch(model->vc_s) { + case 1: + model->tmap[i].u = (M3D_FLOAT)(data[0]) / 255; + model->tmap[i].v = (M3D_FLOAT)(data[1]) / 255; + break; + case 2: + model->tmap[i].u = (M3D_FLOAT)(*((int16_t*)(data+0))) / 65535; + model->tmap[i].v = (M3D_FLOAT)(*((int16_t*)(data+2))) / 65535; + break; + case 4: + model->tmap[i].u = (M3D_FLOAT)(*((float*)(data+0))); + model->tmap[i].v = (M3D_FLOAT)(*((float*)(data+4))); + break; + case 8: + model->tmap[i].u = (M3D_FLOAT)(*((double*)(data+0))); + model->tmap[i].v = (M3D_FLOAT)(*((double*)(data+8))); + break; + } + data += reclen; + } + } else + /* vertex list */ + if(M3D_CHUNKMAGIC(data, 'V','R','T','S')) { + M3D_LOG("Vertex list"); + if(model->vertex) { M3D_LOG("More vertex chunks, should be unique"); model->errcode = M3D_ERR_VRTS; continue; } + if(model->ci_s && model->ci_s < 4 && !model->cmap) model->errcode = M3D_ERR_CMAP; + reclen = model->ci_s + model->sk_s + 4 * model->vc_s; + model->numvertex = len / reclen; + model->vertex = (m3dv_t*)M3D_MALLOC(model->numvertex * sizeof(m3dv_t)); + if(!model->vertex) goto memerr; + memset(model->vertex, 0, model->numvertex * sizeof(m3dv_t)); + for(i = 0, data += sizeof(m3dchunk_t); data < chunk && i < model->numvertex; i++) { + switch(model->vc_s) { + case 1: + model->vertex[i].x = (M3D_FLOAT)((int8_t)data[0]) / 127; + model->vertex[i].y = (M3D_FLOAT)((int8_t)data[1]) / 127; + model->vertex[i].z = (M3D_FLOAT)((int8_t)data[2]) / 127; + model->vertex[i].w = (M3D_FLOAT)((int8_t)data[3]) / 127; + data += 4; + break; + case 2: + model->vertex[i].x = (M3D_FLOAT)(*((int16_t*)(data+0))) / 32767; + model->vertex[i].y = (M3D_FLOAT)(*((int16_t*)(data+2))) / 32767; + model->vertex[i].z = (M3D_FLOAT)(*((int16_t*)(data+4))) / 32767; + model->vertex[i].w = (M3D_FLOAT)(*((int16_t*)(data+6))) / 32767; + data += 8; + break; + case 4: + model->vertex[i].x = (M3D_FLOAT)(*((float*)(data+0))); + model->vertex[i].y = (M3D_FLOAT)(*((float*)(data+4))); + model->vertex[i].z = (M3D_FLOAT)(*((float*)(data+8))); + model->vertex[i].w = (M3D_FLOAT)(*((float*)(data+12))); + data += 16; + break; + case 8: + model->vertex[i].x = (M3D_FLOAT)(*((double*)(data+0))); + model->vertex[i].y = (M3D_FLOAT)(*((double*)(data+8))); + model->vertex[i].z = (M3D_FLOAT)(*((double*)(data+16))); + model->vertex[i].w = (M3D_FLOAT)(*((double*)(data+24))); + data += 32; + break; + } + switch(model->ci_s) { + case 1: model->vertex[i].color = model->cmap ? model->cmap[data[0]] : 0; data++; break; + case 2: model->vertex[i].color = model->cmap ? model->cmap[*((uint16_t*)data)] : 0; data += 2; break; + case 4: model->vertex[i].color = *((uint32_t*)data); data += 4; break; + /* case 8: break; */ + } + model->vertex[i].skinid = (M3D_INDEX)-1U; + data = _m3d_getidx(data, model->sk_s, &model->vertex[i].skinid); + } + } else + /* skeleton: bone hierarchy and skin */ + if(M3D_CHUNKMAGIC(data, 'B','O','N','E')) { + M3D_LOG("Skeleton"); + if(model->bone) { M3D_LOG("More bone chunks, should be unique"); model->errcode = M3D_ERR_BONE; continue; } + if(!model->bi_s) { M3D_LOG("Bone chunk, shouldn't be any"); model->errcode=M3D_ERR_BONE; continue; } + if(!model->vertex) { M3D_LOG("No vertex chunk before bones"); model->errcode = M3D_ERR_VRTS; break; } + data += sizeof(m3dchunk_t); + model->numbone = 0; + data = _m3d_getidx(data, model->bi_s, &model->numbone); + if(model->numbone) { + model->bone = (m3db_t*)M3D_MALLOC(model->numbone * sizeof(m3db_t)); + if(!model->bone) goto memerr; + } + model->numskin = 0; + data = _m3d_getidx(data, model->sk_s, &model->numskin); + /* read bone hierarchy */ + for(i = 0; i < model->numbone; i++) { + data = _m3d_getidx(data, model->bi_s, &model->bone[i].parent); + M3D_GETSTR(model->bone[i].name); + data = _m3d_getidx(data, model->vi_s, &model->bone[i].pos); + data = _m3d_getidx(data, model->vi_s, &model->bone[i].ori); + model->bone[i].numweight = 0; + model->bone[i].weight = NULL; + } + /* read skin definitions */ + if(model->numskin) { + model->skin = (m3ds_t*)M3D_MALLOC(model->numskin * sizeof(m3ds_t)); + if(!model->skin) goto memerr; + for(i = 0; data < chunk && i < model->numskin; i++) { + for(j = 0; j < M3D_NUMBONE; j++) { + model->skin[i].boneid[j] = (M3D_INDEX)-1U; + model->skin[i].weight[j] = (M3D_FLOAT)0.0; + } + memset(&weights, 0, sizeof(weights)); + if(model->nb_s == 1) weights[0] = 255; + else { + memcpy(&weights, data, model->nb_s); + data += model->nb_s; + } + for(j = 0, w = (M3D_FLOAT)0.0; j < (unsigned int)model->nb_s; j++) { + if(weights[j]) { + if(j >= M3D_NUMBONE) + data += model->bi_s; + else { + model->skin[i].weight[j] = (M3D_FLOAT)(weights[j]) / 255; + w += model->skin[i].weight[j]; + data = _m3d_getidx(data, model->bi_s, &model->skin[i].boneid[j]); + } + } + } + /* this can occur if model has more bones than what the importer is configured to handle */ + if(w != (M3D_FLOAT)1.0 && w != (M3D_FLOAT)0.0) { + for(j = 0; j < M3D_NUMBONE; j++) + model->skin[i].weight[j] /= w; + } + } + } + } else + /* material */ + if(M3D_CHUNKMAGIC(data, 'M','T','R','L')) { + data += sizeof(m3dchunk_t); + M3D_GETSTR(name); + M3D_LOG("Material"); + M3D_LOG(name); + if(model->ci_s < 4 && !model->numcmap) model->errcode = M3D_ERR_CMAP; + for(i = 0; i < model->nummaterial; i++) + if(!strcmp(name, model->material[i].name)) { + model->errcode = M3D_ERR_MTRL; + M3D_LOG("Multiple definitions for material"); + M3D_LOG(name); + name = NULL; + break; + } + if(name) { + i = model->nummaterial++; + if(model->flags & M3D_FLG_MTLLIB) { + m = model->material; + model->material = (m3dm_t*)M3D_MALLOC(model->nummaterial * sizeof(m3dm_t)); + if(!model->material) goto memerr; + memcpy(model->material, m, (model->nummaterial - 1) * sizeof(m3dm_t)); + if(model->texture) { + tx = model->texture; + model->texture = (m3dtx_t*)M3D_MALLOC(model->numtexture * sizeof(m3dtx_t)); + if(!model->texture) goto memerr; + memcpy(model->texture, tx, model->numtexture * sizeof(m3dm_t)); + } + model->flags &= ~M3D_FLG_MTLLIB; + } else { + model->material = (m3dm_t*)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); + if(!model->material) goto memerr; + } + m = &model->material[i]; + m->numprop = 0; + m->name = name; + m->prop = (m3dp_t*)M3D_MALLOC((len / 2) * sizeof(m3dp_t)); + if(!m->prop) goto memerr; + while(data < chunk) { + i = m->numprop++; + m->prop[i].type = *data++; + m->prop[i].value.num = 0; + if(m->prop[i].type >= 128) + k = m3dpf_map; + else { + for(k = 256, j = 0; j < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); j++) + if(m->prop[i].type == m3d_propertytypes[j].id) { k = m3d_propertytypes[j].format; break; } + } + switch(k) { + case m3dpf_color: + switch(model->ci_s) { + case 1: m->prop[i].value.color = model->cmap ? model->cmap[data[0]] : 0; data++; break; + case 2: m->prop[i].value.color = model->cmap ? model->cmap[*((uint16_t*)data)] : 0; data += 2; break; + case 4: m->prop[i].value.color = *((uint32_t*)data); data += 4; break; + } + break; + + case m3dpf_uint8: m->prop[i].value.num = *data++; break; + case m3dpf_uint16:m->prop[i].value.num = *((uint16_t*)data); data += 2; break; + case m3dpf_uint32:m->prop[i].value.num = *((uint32_t*)data); data += 4; break; + case m3dpf_float: m->prop[i].value.fnum = *((float*)data); data += 4; break; + + case m3dpf_map: + M3D_GETSTR(name); + m->prop[i].value.textureid = _m3d_gettx(model, readfilecb, freecb, name); + if(model->errcode == M3D_ERR_ALLOC) goto memerr; + if(m->prop[i].value.textureid == (M3D_INDEX)-1U) { + M3D_LOG("Texture not found"); + M3D_LOG(m->name); + m->numprop--; + } + break; + + default: + M3D_LOG("Unknown material property in"); + M3D_LOG(m->name); + model->errcode = M3D_ERR_UNKPROP; + data = chunk; + break; + } + } + m->prop = (m3dp_t*)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t)); + if(!m->prop) goto memerr; + } + } else + /* face */ + if(M3D_CHUNKMAGIC(data, 'P','R','O','C')) { + /* procedural surface */ + M3D_GETSTR(name); + M3D_LOG("Procedural surface"); + M3D_LOG(name); + _m3d_getpr(model, readfilecb, freecb, name); + } else + if(M3D_CHUNKMAGIC(data, 'M','E','S','H')) { + M3D_LOG("Mesh data"); + /* mesh */ + data += sizeof(m3dchunk_t); + mi = (M3D_INDEX)-1U; + am = model->numface; + while(data < chunk) { + k = *data++; + n = k >> 4; + k &= 15; + if(!n) { + /* use material */ + mi = (M3D_INDEX)-1U; + M3D_GETSTR(name); + if(name) { + for(j = 0; j < model->nummaterial; j++) + if(!strcmp(name, model->material[j].name)) { + mi = (M3D_INDEX)j; + break; + } + if(mi == (M3D_INDEX)-1U) model->errcode = M3D_ERR_MTRL; + } + continue; + } + if(n != 3) { M3D_LOG("Only triangle mesh supported for now"); model->errcode = M3D_ERR_UNKMESH; return model; } + i = model->numface++; + if(model->numface > am) { + am = model->numface + 4095; + model->face = (m3df_t*)M3D_REALLOC(model->face, am * sizeof(m3df_t)); + if(!model->face) goto memerr; + } + memset(&model->face[i], 255, sizeof(m3df_t)); /* set all index to -1 by default */ + model->face[i].materialid = mi; + for(j = 0; j < n; j++) { + /* vertex */ + data = _m3d_getidx(data, model->vi_s, &model->face[i].vertex[j]); + /* texcoord */ + if(k & 1) + data = _m3d_getidx(data, model->ti_s, &model->face[i].texcoord[j]); + /* normal */ + if(k & 2) + data = _m3d_getidx(data, model->vi_s, &model->face[i].normal[j]); + } + } + model->face = (m3df_t*)M3D_REALLOC(model->face, model->numface * sizeof(m3df_t)); + } else + if(M3D_CHUNKMAGIC(data, 'S','H','P','E')) { + /* mathematical shape */ + data += sizeof(m3dchunk_t); + M3D_GETSTR(name); + M3D_LOG("Mathematical Shape"); + M3D_LOG(name); + i = model->numshape++; + model->shape = (m3dh_t*)M3D_REALLOC(model->shape, model->numshape * sizeof(m3dh_t)); + if(!model->shape) goto memerr; + h = &model->shape[i]; + h->numcmd = 0; + h->cmd = NULL; + h->name = name; + h->group = (M3D_INDEX)-1U; + data = _m3d_getidx(data, model->bi_s, &h->group); + if(h->group != (M3D_INDEX)-1U && h->group >= model->numbone) { + M3D_LOG("Unknown bone id as shape group in shape"); + M3D_LOG(name); + h->group = (M3D_INDEX)-1U; + model->errcode = M3D_ERR_SHPE; + } + while(data < chunk) { + i = h->numcmd++; + h->cmd = (m3dc_t*)M3D_REALLOC(h->cmd, h->numcmd * sizeof(m3dc_t)); + if(!h->cmd) goto memerr; + h->cmd[i].type = *data++; + if(h->cmd[i].type & 0x80) { + h->cmd[i].type &= 0x7F; + h->cmd[i].type |= (*data++ << 7); + } + if(h->cmd[i].type >= (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0]))) { + M3D_LOG("Unknown shape command in"); + M3D_LOG(h->name); + model->errcode = M3D_ERR_UNKCMD; + break; + } + cd = &m3d_commandtypes[h->cmd[i].type]; + h->cmd[i].arg = (uint32_t*)M3D_MALLOC(cd->p * sizeof(uint32_t)); + if(!h->cmd[i].arg) goto memerr; + memset(h->cmd[i].arg, 0, cd->p * sizeof(uint32_t)); + for(k = n = 0, l = cd->p; k < l; k++) + switch(cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + h->cmd[i].arg[k] = -1U; + M3D_GETSTR(name); + if(name) { + for(n = 0; n < model->nummaterial; n++) + if(!strcmp(name, model->material[n].name)) { + h->cmd[i].arg[k] = n; + break; + } + if(h->cmd[i].arg[k] == -1U) model->errcode = M3D_ERR_MTRL; + } + break; + case m3dcp_vc_t: + f = 0.0f; + switch(model->vc_s) { + case 1: f = (float)((int8_t)data[0]) / 127; break; + case 2: f = (float)(*((int16_t*)(data+0))) / 32767; break; + case 4: f = (float)(*((float*)(data+0))); break; + case 8: f = (float)(*((double*)(data+0))); break; + } + h->cmd[i].arg[k] = *((uint32_t*)&f); + data += model->vc_s; + break; + case m3dcp_hi_t: data = _m3d_getidx(data, model->hi_s, &h->cmd[i].arg[k]); break; + case m3dcp_fi_t: data = _m3d_getidx(data, model->fi_s, &h->cmd[i].arg[k]); break; + case m3dcp_ti_t: data = _m3d_getidx(data, model->ti_s, &h->cmd[i].arg[k]); break; + case m3dcp_qi_t: + case m3dcp_vi_t: data = _m3d_getidx(data, model->vi_s, &h->cmd[i].arg[k]); break; + case m3dcp_i1_t: data = _m3d_getidx(data, 1, &h->cmd[i].arg[k]); break; + case m3dcp_i2_t: data = _m3d_getidx(data, 2, &h->cmd[i].arg[k]); break; + case m3dcp_i4_t: data = _m3d_getidx(data, 4, &h->cmd[i].arg[k]); break; + case m3dcp_va_t: data = _m3d_getidx(data, 4, &h->cmd[i].arg[k]); + n = k + 1; l += (h->cmd[i].arg[k] - 1) * (cd->p - k - 1); + h->cmd[i].arg = (uint32_t*)M3D_REALLOC(h->cmd[i].arg, l * sizeof(uint32_t)); + if(!h->cmd[i].arg) goto memerr; + memset(&h->cmd[i].arg[k + 1], 0, (l - k - 1) * sizeof(uint32_t)); + break; + } + } + } else + /* annotation label list */ + if(M3D_CHUNKMAGIC(data, 'L','B','L','S')) { + data += sizeof(m3dchunk_t); + M3D_GETSTR(name); + M3D_GETSTR(lang); + M3D_LOG("Label list"); + if(name) { M3D_LOG(name); } + if(lang) { M3D_LOG(lang); } + if(model->ci_s && model->ci_s < 4 && !model->cmap) model->errcode = M3D_ERR_CMAP; + k = 0; + switch(model->ci_s) { + case 1: k = model->cmap ? model->cmap[data[0]] : 0; data++; break; + case 2: k = model->cmap ? model->cmap[*((uint16_t*)data)] : 0; data += 2; break; + case 4: k = *((uint32_t*)data); data += 4; break; + /* case 8: break; */ + } + reclen = model->vi_s + model->si_s; + i = model->numlabel; model->numlabel += len / reclen; + model->label = (m3dl_t*)M3D_REALLOC(model->label, model->numlabel * sizeof(m3dl_t)); + if(!model->label) goto memerr; + memset(&model->label[i], 0, (model->numlabel - i) * sizeof(m3dl_t)); + for(; data < chunk && i < model->numlabel; i++) { + model->label[i].name = name; + model->label[i].lang = lang; + model->label[i].color = k; + data = _m3d_getidx(data, model->vi_s, &model->label[i].vertexid); + M3D_GETSTR(model->label[i].text); + } + } else + /* action */ + if(M3D_CHUNKMAGIC(data, 'A','C','T','N')) { + M3D_LOG("Action"); + i = model->numaction++; + model->action = (m3da_t*)M3D_REALLOC(model->action, model->numaction * sizeof(m3da_t)); + if(!model->action) goto memerr; + a = &model->action[i]; + data += sizeof(m3dchunk_t); + M3D_GETSTR(a->name); + M3D_LOG(a->name); + a->numframe = *((uint16_t*)data); data += 2; + if(a->numframe < 1) { + model->numaction--; + } else { + a->durationmsec = *((uint32_t*)data); data += 4; + a->frame = (m3dfr_t*)M3D_MALLOC(a->numframe * sizeof(m3dfr_t)); + if(!a->frame) goto memerr; + for(i = 0; data < chunk && i < a->numframe; i++) { + a->frame[i].msec = *((uint32_t*)data); data += 4; + a->frame[i].numtransform = 0; a->frame[i].transform = NULL; + data = _m3d_getidx(data, model->fc_s, &a->frame[i].numtransform); + if(a->frame[i].numtransform > 0) { + a->frame[i].transform = (m3dtr_t*)M3D_MALLOC(a->frame[i].numtransform * sizeof(m3dtr_t)); + for(j = 0; j < a->frame[i].numtransform; j++) { + data = _m3d_getidx(data, model->bi_s, &a->frame[i].transform[j].boneid); + data = _m3d_getidx(data, model->vi_s, &a->frame[i].transform[j].pos); + data = _m3d_getidx(data, model->vi_s, &a->frame[i].transform[j].ori); + } + } + } + } + } else { + i = model->numextra++; + model->extra = (m3dchunk_t**)M3D_REALLOC(model->extra, model->numextra * sizeof(m3dchunk_t*)); + if(!model->extra) goto memerr; + model->extra[i] = (m3dchunk_t*)data; + } + } + /* calculate normals, normalize skin weights, create bone/vertex cross-references and calculate transform matrices */ +#ifdef M3D_ASCII +postprocess: +#endif + if(model) { + M3D_LOG("Post-process"); +#ifndef M3D_NONORMALS + if(model->numface && model->face) { + /* if they are missing, calculate triangle normals into a temporary buffer */ + for(i = 0, n = model->numvertex; i < model->numface; i++) + if(model->face[i].normal[0] == -1U) { + v0 = &model->vertex[model->face[i].vertex[0]]; + v1 = &model->vertex[model->face[i].vertex[1]]; + v2 = &model->vertex[model->face[i].vertex[2]]; + va.x = v1->x - v0->x; va.y = v1->y - v0->y; va.z = v1->z - v0->z; + vb.x = v2->x - v0->x; vb.y = v2->y - v0->y; vb.z = v2->z - v0->z; + if(!norm) { + norm = (m3dv_t*)M3D_MALLOC(model->numface * sizeof(m3dv_t)); + if(!norm) goto memerr; + } + v0 = &norm[i]; + v0->x = (va.y * vb.z) - (va.z * vb.y); + v0->y = (va.z * vb.x) - (va.x * vb.z); + v0->z = (va.x * vb.y) - (va.y * vb.x); + w = _m3d_rsq((v0->x * v0->x) + (v0->y * v0->y) + (v0->z * v0->z)); + v0->x *= w; v0->y *= w; v0->z *= w; + model->face[i].normal[0] = model->face[i].vertex[0] + n; + model->face[i].normal[1] = model->face[i].vertex[1] + n; + model->face[i].normal[2] = model->face[i].vertex[2] + n; + } + /* this is the fast way, we don't care if a normal is repeated in model->vertex */ + if(norm) { + M3D_LOG("Generating normals"); + model->flags |= M3D_FLG_GENNORM; + model->numvertex <<= 1; + model->vertex = (m3dv_t*)M3D_REALLOC(model->vertex, model->numvertex * sizeof(m3dv_t)); + if(!model->vertex) goto memerr; + memset(&model->vertex[n], 0, n * sizeof(m3dv_t)); + for(i = 0; i < model->numface; i++) + for(j = 0; j < 3; j++) { + v0 = &model->vertex[model->face[i].vertex[j] + n]; + v0->x += norm[i].x; + v0->y += norm[i].y; + v0->z += norm[i].z; + } + /* for each vertex, take the average of the temporary normals and use that */ + for(i = 0, v0 = &model->vertex[n]; i < n; i++, v0++) { + w = _m3d_rsq((v0->x * v0->x) + (v0->y * v0->y) + (v0->z * v0->z)); + v0->x *= w; v0->y *= w; v0->z *= w; + v0->skinid = -1U; + } + M3D_FREE(norm); + } + } +#endif + if(model->numbone && model->bone && model->numskin && model->skin && model->numvertex && model->vertex) { +#ifndef M3D_NOWEIGHTS + M3D_LOG("Generating weight cross-reference"); + for(i = 0; i < model->numvertex; i++) { + if(model->vertex[i].skinid < model->numskin) { + sk = &model->skin[model->vertex[i].skinid]; + w = (M3D_FLOAT)0.0; + for(j = 0; j < M3D_NUMBONE && sk->boneid[j] != (M3D_INDEX)-1U && sk->weight[j] > (M3D_FLOAT)0.0; j++) + w += sk->weight[j]; + for(j = 0; j < M3D_NUMBONE && sk->boneid[j] != (M3D_INDEX)-1U && sk->weight[j] > (M3D_FLOAT)0.0; j++) { + sk->weight[j] /= w; + b = &model->bone[sk->boneid[j]]; + k = b->numweight++; + b->weight = (m3dw_t*)M3D_REALLOC(b->weight, b->numweight * sizeof(m3da_t)); + if(!b->weight) goto memerr; + b->weight[k].vertexid = i; + b->weight[k].weight = sk->weight[j]; + } + } + } +#endif +#ifndef M3D_NOANIMATION + M3D_LOG("Calculating bone transformation matrices"); + for(i = 0; i < model->numbone; i++) { + b = &model->bone[i]; + if(model->bone[i].parent == (M3D_INDEX)-1U) { + _m3d_mat((M3D_FLOAT*)&b->mat4, &model->vertex[b->pos], &model->vertex[b->ori]); + } else { + _m3d_mat((M3D_FLOAT*)&r, &model->vertex[b->pos], &model->vertex[b->ori]); + _m3d_mul((M3D_FLOAT*)&b->mat4, (M3D_FLOAT*)&model->bone[b->parent].mat4, (M3D_FLOAT*)&r); + } + } + for(i = 0; i < model->numbone; i++) + _m3d_inv((M3D_FLOAT*)&model->bone[i].mat4); +#endif + } + } + return model; +} + +/** + * Calculates skeletons for animation frames, returns a working copy (should be freed after use) + */ +m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t *skeleton) +{ + unsigned int i; + M3D_INDEX s = frameid; + m3dfr_t *fr; + + if(!model || !model->numbone || !model->bone || (actionid != (M3D_INDEX)-1U && (!model->action || + actionid >= model->numaction || frameid >= model->action[actionid].numframe))) { + model->errcode = M3D_ERR_UNKFRAME; + return skeleton; + } + model->errcode = M3D_SUCCESS; + if(!skeleton) { + skeleton = (m3dtr_t*)M3D_MALLOC(model->numbone * sizeof(m3dtr_t)); + if(!skeleton) { + model->errcode = M3D_ERR_ALLOC; + return NULL; + } + goto gen; + } + if(actionid == (M3D_INDEX)-1U || !frameid) { +gen: s = 0; + for(i = 0; i < model->numbone; i++) { + skeleton[i].boneid = i; + skeleton[i].pos = model->bone[i].pos; + skeleton[i].ori = model->bone[i].ori; + } + } + if(actionid < model->numaction && (frameid || !model->action[actionid].frame[0].msec)) { + for(; s <= frameid; s++) { + fr = &model->action[actionid].frame[s]; + for(i = 0; i < fr->numtransform; i++) { + skeleton[fr->transform[i].boneid].pos = fr->transform[i].pos; + skeleton[fr->transform[i].boneid].ori = fr->transform[i].ori; + } + } + } + return skeleton; +} + +#ifndef M3D_NOANIMATION +/** + * Returns interpolated animation-pose, a working copy (should be freed after use) + */ +m3db_t *m3d_pose(m3d_t *model, M3D_INDEX actionid, uint32_t msec) +{ + unsigned int i, j, l; + M3D_FLOAT r[16], t, c, d, s; + m3db_t *ret; + m3dv_t *v, *p, *f; + m3dtr_t *tmp; + m3dfr_t *fr; + + if(!model || !model->numbone || !model->bone) { + model->errcode = M3D_ERR_UNKFRAME; + return NULL; + } + ret = (m3db_t*)M3D_MALLOC(model->numbone * sizeof(m3db_t)); + if(!ret) { + model->errcode = M3D_ERR_ALLOC; + return NULL; + } + memcpy(ret, model->bone, model->numbone * sizeof(m3db_t)); + for(i = 0; i < model->numbone; i++) + _m3d_inv((M3D_FLOAT*)&ret[i].mat4); + if(!model->action || actionid >= model->numaction) { + model->errcode = M3D_ERR_UNKFRAME; + return ret; + } + msec %= model->action[actionid].durationmsec; + model->errcode = M3D_SUCCESS; + fr = &model->action[actionid].frame[0]; + for(j = l = 0; j < model->action[actionid].numframe && model->action[actionid].frame[j].msec <= msec; j++) { + fr = &model->action[actionid].frame[j]; + l = fr->msec; + for(i = 0; i < fr->numtransform; i++) { + ret[fr->transform[i].boneid].pos = fr->transform[i].pos; + ret[fr->transform[i].boneid].ori = fr->transform[i].ori; + } + } + if(l != msec) { + model->vertex = (m3dv_t*)M3D_REALLOC(model->vertex, (model->numvertex + 2 * model->numbone) * sizeof(m3dv_t)); + if(!model->vertex) { + free(ret); + model->errcode = M3D_ERR_ALLOC; + return NULL; + } + tmp = (m3dtr_t*)M3D_MALLOC(model->numbone * sizeof(m3dtr_t)); + if(tmp) { + for(i = 0; i < model->numbone; i++) { + tmp[i].pos = ret[i].pos; + tmp[i].ori = ret[i].ori; + } + fr = &model->action[actionid].frame[j % model->action[actionid].numframe]; + t = l >= fr->msec ? (M3D_FLOAT)1.0 : (M3D_FLOAT)(msec - l) / (M3D_FLOAT)(fr->msec - l); + for(i = 0; i < fr->numtransform; i++) { + tmp[fr->transform[i].boneid].pos = fr->transform[i].pos; + tmp[fr->transform[i].boneid].ori = fr->transform[i].ori; + } + for(i = 0, j = model->numvertex; i < model->numbone; i++) { + /* interpolation of position */ + if(ret[i].pos != tmp[i].pos) { + p = &model->vertex[ret[i].pos]; + f = &model->vertex[tmp[i].pos]; + v = &model->vertex[j]; + v->x = p->x + t * (f->x - p->x); + v->y = p->y + t * (f->y - p->y); + v->z = p->z + t * (f->z - p->z); + ret[i].pos = j++; + } + /* interpolation of orientation */ + if(ret[i].ori != tmp[i].ori) { + p = &model->vertex[ret[i].ori]; + f = &model->vertex[tmp[i].ori]; + v = &model->vertex[j]; + d = p->w * f->w + p->x * f->x + p->y * f->y + p->z * f->z; + if(d < 0) { d = -d; s = (M3D_FLOAT)-1.0; } else s = (M3D_FLOAT)1.0; +#if 0 + /* don't use SLERP, requires two more variables, libm linkage and it is slow (but nice) */ + a = (M3D_FLOAT)1.0 - t; b = t; + if(d < (M3D_FLOAT)0.999999) { c = acosf(d); b = 1 / sinf(c); a = sinf(a * c) * b; b *= sinf(t * c) * s; } + v->x = p->x * a + f->x * b; + v->y = p->y * a + f->y * b; + v->z = p->z * a + f->z * b; + v->w = p->w * a + f->w * b; +#else + /* approximated NLERP, original approximation by Arseny Kapoulkine, heavily optimized by me */ + c = t - (M3D_FLOAT)0.5; t += t * c * (t - (M3D_FLOAT)1.0) * (((M3D_FLOAT)1.0904 + d * ((M3D_FLOAT)-3.2452 + + d * ((M3D_FLOAT)3.55645 - d * (M3D_FLOAT)1.43519))) * c * c + ((M3D_FLOAT)0.848013 + d * + ((M3D_FLOAT)-1.06021 + d * (M3D_FLOAT)0.215638))); + v->x = p->x + t * (s * f->x - p->x); + v->y = p->y + t * (s * f->y - p->y); + v->z = p->z + t * (s * f->z - p->z); + v->w = p->w + t * (s * f->w - p->w); + d = _m3d_rsq(v->w * v->w + v->x * v->x + v->y * v->y + v->z * v->z); + v->x *= d; v->y *= d; v->z *= d; v->w *= d; +#endif + ret[i].ori = j++; + } + } + M3D_FREE(tmp); + } + } + for(i = 0; i < model->numbone; i++) { + if(ret[i].parent == (M3D_INDEX)-1U) { + _m3d_mat((M3D_FLOAT*)&ret[i].mat4, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]); + } else { + _m3d_mat((M3D_FLOAT*)&r, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]); + _m3d_mul((M3D_FLOAT*)&ret[i].mat4, (M3D_FLOAT*)&ret[ret[i].parent].mat4, (M3D_FLOAT*)&r); + } + } + return ret; +} + +#endif /* M3D_NOANIMATION */ + +#endif /* M3D_IMPLEMENTATION */ + +#if !defined(M3D_NODUP) && (!defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER)) +/** + * Free the in-memory model + */ +void m3d_free(m3d_t *model) +{ + unsigned int i, j; + + if(!model) return; +#ifdef M3D_ASCII + /* if model imported from ASCII, we have to free all strings as well */ + if(model->flags & M3D_FLG_FREESTR) { + if(model->name) M3D_FREE(model->name); + if(model->license) M3D_FREE(model->license); + if(model->author) M3D_FREE(model->author); + if(model->desc) M3D_FREE(model->desc); + if(model->bone) + for(i = 0; i < model->numbone; i++) + if(model->bone[i].name) + M3D_FREE(model->bone[i].name); + if(model->shape) + for(i = 0; i < model->numshape; i++) + if(model->shape[i].name) + M3D_FREE(model->shape[i].name); + if(model->material) + for(i = 0; i < model->nummaterial; i++) + if(model->material[i].name) + M3D_FREE(model->material[i].name); + if(model->action) + for(i = 0; i < model->numaction; i++) + if(model->action[i].name) + M3D_FREE(model->action[i].name); + if(model->texture) + for(i = 0; i < model->numtexture; i++) + if(model->texture[i].name) + M3D_FREE(model->texture[i].name); + if(model->inlined) + for(i = 0; i < model->numinlined; i++) { + if(model->inlined[i].name) + M3D_FREE(model->inlined[i].name); + if(model->inlined[i].data) + M3D_FREE(model->inlined[i].data); + } + if(model->extra) + for(i = 0; i < model->numextra; i++) + if(model->extra[i]) + M3D_FREE(model->extra[i]); + if(model->label) + for(i = 0; i < model->numlabel; i++) { + if(model->label[i].name) { + for(j = i + 1; j < model->numlabel; j++) + if(model->label[j].name == model->label[i].name) + model->label[j].name = NULL; + M3D_FREE(model->label[i].name); + } + if(model->label[i].lang) { + for(j = i + 1; j < model->numlabel; j++) + if(model->label[j].lang == model->label[i].lang) + model->label[j].lang = NULL; + M3D_FREE(model->label[i].lang); + } + if(model->label[i].text) + M3D_FREE(model->label[i].text); + } + if(model->preview.data) + M3D_FREE(model->preview.data); + } +#endif + if(model->flags & M3D_FLG_FREERAW) M3D_FREE(model->raw); + + if(model->tmap) M3D_FREE(model->tmap); + if(model->bone) { + for(i = 0; i < model->numbone; i++) + if(model->bone[i].weight) + M3D_FREE(model->bone[i].weight); + M3D_FREE(model->bone); + } + if(model->skin) M3D_FREE(model->skin); + if(model->vertex) M3D_FREE(model->vertex); + if(model->face) M3D_FREE(model->face); + if(model->shape) { + for(i = 0; i < model->numshape; i++) { + if(model->shape[i].cmd) { + for(j = 0; j < model->shape[i].numcmd; j++) + if(model->shape[i].cmd[j].arg) M3D_FREE(model->shape[i].cmd[j].arg); + M3D_FREE(model->shape[i].cmd); + } + } + M3D_FREE(model->shape); + } + if(model->material && !(model->flags & M3D_FLG_MTLLIB)) { + for(i = 0; i < model->nummaterial; i++) + if(model->material[i].prop) M3D_FREE(model->material[i].prop); + M3D_FREE(model->material); + } + if(model->texture) { + for(i = 0; i < model->numtexture; i++) + if(model->texture[i].d) M3D_FREE(model->texture[i].d); + M3D_FREE(model->texture); + } + if(model->action) { + for(i = 0; i < model->numaction; i++) { + if(model->action[i].frame) { + for(j = 0; j < model->action[i].numframe; j++) + if(model->action[i].frame[j].transform) M3D_FREE(model->action[i].frame[j].transform); + M3D_FREE(model->action[i].frame); + } + } + M3D_FREE(model->action); + } + if(model->label) M3D_FREE(model->label); + if(model->inlined) M3D_FREE(model->inlined); + if(model->extra) M3D_FREE(model->extra); + free(model); +} +#endif + +#ifdef M3D_EXPORTER +typedef struct { + char *str; + uint32_t offs; +} m3dstr_t; + +typedef struct { + m3dti_t data; + M3D_INDEX oldidx; + M3D_INDEX newidx; +} m3dtisave_t; + +typedef struct { + m3dv_t data; + M3D_INDEX oldidx; + M3D_INDEX newidx; + unsigned char norm; +} m3dvsave_t; + +typedef struct { + m3ds_t data; + M3D_INDEX oldidx; + M3D_INDEX newidx; +} m3dssave_t; + +typedef struct { + m3df_t data; + int group; + uint8_t opacity; +} m3dfsave_t; + +/* create unique list of strings */ +static m3dstr_t *_m3d_addstr(m3dstr_t *str, uint32_t *numstr, char *s) +{ + uint32_t i; + if(!s || !*s) return str; + if(str) { + for(i = 0; i < *numstr; i++) + if(str[i].str == s || !strcmp(str[i].str, s)) return str; + } + str = (m3dstr_t*)M3D_REALLOC(str, ((*numstr) + 1) * sizeof(m3dstr_t)); + str[*numstr].str = s; + str[*numstr].offs = 0; + (*numstr)++; + return str; +} + +/* add strings to header */ +m3dhdr_t *_m3d_addhdr(m3dhdr_t *h, m3dstr_t *s) +{ + int i; + char *safe = _m3d_safestr(s->str, 0); + i = strlen(safe); + h = (m3dhdr_t*)M3D_REALLOC(h, h->length + i+1); + if(!h) { M3D_FREE(safe); return NULL; } + memcpy((uint8_t*)h + h->length, safe, i+1); + s->offs = h->length - 16; + h->length += i+1; + M3D_FREE(safe); + return h; +} + +/* return offset of string */ +static uint32_t _m3d_stridx(m3dstr_t *str, uint32_t numstr, char *s) +{ + uint32_t i; + char *safe; + if(!s || !*s) return 0; + if(str) { + safe = _m3d_safestr(s, 0); + if(!safe) return 0; + if(!*safe) { + free(safe); + return 0; + } + for(i = 0; i < numstr; i++) + if(!strcmp(str[i].str, s)) { + free(safe); + return str[i].offs; + } + free(safe); + } + return 0; +} + +/* compare to faces by their material */ +static int _m3d_facecmp(const void *a, const void *b) { + const m3dfsave_t *A = (const m3dfsave_t*)a, *B = (const m3dfsave_t*)b; + return A->group != B->group ? A->group - B->group : (A->opacity != B->opacity ? (int)B->opacity - (int)A->opacity : + (int)A->data.materialid - (int)B->data.materialid); +} +/* compare face groups */ +static int _m3d_grpcmp(const void *a, const void *b) { return *((uint32_t*)a) - *((uint32_t*)b); } +/* compare UVs */ +static int _m3d_ticmp(const void *a, const void *b) { return memcmp(a, b, sizeof(m3dti_t)); } +/* compare skin groups */ +static int _m3d_skincmp(const void *a, const void *b) { return memcmp(a, b, sizeof(m3ds_t)); } +/* compare vertices */ +static int _m3d_vrtxcmp(const void *a, const void *b) { + int c = memcmp(a, b, 3 * sizeof(M3D_FLOAT)); + if(c) return c; + c = ((m3dvsave_t*)a)->norm - ((m3dvsave_t*)b)->norm; + if(c) return c; + return memcmp(a, b, sizeof(m3dv_t)); +} +/* compare labels */ +static _inline int _m3d_strcmp(char *a, char *b) +{ + if(a == NULL && b != NULL) return -1; + if(a != NULL && b == NULL) return 1; + if(a == NULL && b == NULL) return 0; + return strcmp(a, b); +} +static int _m3d_lblcmp(const void *a, const void *b) { + const m3dl_t *A = (const m3dl_t*)a, *B = (const m3dl_t*)b; + int c = _m3d_strcmp(A->lang, B->lang); + if(!c) c = _m3d_strcmp(A->name, B->name); + if(!c) c = _m3d_strcmp(A->text, B->text); + return c; +} +/* compare two colors by HSV value */ +_inline static int _m3d_cmapcmp(const void *a, const void *b) +{ + uint8_t *A = (uint8_t*)a, *B = (uint8_t*)b; + _register int m, vA, vB; + /* get HSV value for A */ + m = A[2] < A[1]? A[2] : A[1]; if(A[0] < m) m = A[0]; + vA = A[2] > A[1]? A[2] : A[1]; if(A[0] > vA) vA = A[0]; + /* get HSV value for B */ + m = B[2] < B[1]? B[2] : B[1]; if(B[0] < m) m = B[0]; + vB = B[2] > B[1]? B[2] : B[1]; if(B[0] > vB) vB = B[0]; + return vA - vB; +} + +/* create sorted list of colors */ +static uint32_t *_m3d_addcmap(uint32_t *cmap, uint32_t *numcmap, uint32_t color) +{ + uint32_t i; + if(cmap) { + for(i = 0; i < *numcmap; i++) + if(cmap[i] == color) return cmap; + } + cmap = (uint32_t*)M3D_REALLOC(cmap, ((*numcmap) + 1) * sizeof(uint32_t)); + for(i = 0; i < *numcmap && _m3d_cmapcmp(&color, &cmap[i]) > 0; i++); + if(i < *numcmap) memmove(&cmap[i+1], &cmap[i], ((*numcmap) - i)*sizeof(uint32_t)); + cmap[i] = color; + (*numcmap)++; + return cmap; +} + +/* look up a color and return its index */ +static uint32_t _m3d_cmapidx(uint32_t *cmap, uint32_t numcmap, uint32_t color) +{ + uint32_t i; + if(numcmap >= 65536) + return color; + for(i = 0; i < numcmap; i++) + if(cmap[i] == color) return i; + return 0; +} + +/* add index to output */ +static unsigned char *_m3d_addidx(unsigned char *out, char type, uint32_t idx) { + switch(type) { + case 1: *out++ = (uint8_t)(idx); break; + case 2: *((uint16_t*)out) = (uint16_t)(idx); out += 2; break; + case 4: *((uint32_t*)out) = (uint32_t)(idx); out += 4; break; + /* case 0: case 8: break; */ + } + return out; +} + +/* round a vertex position */ +static void _m3d_round(int quality, m3dv_t *src, m3dv_t *dst) +{ + _register int t; + /* copy additional attributes */ + if(src != dst) memcpy(dst, src, sizeof(m3dv_t)); + /* round according to quality */ + switch(quality) { + case M3D_EXP_INT8: + t = src->x * 127 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->x = (M3D_FLOAT)t / 127; + t = src->y * 127 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->y = (M3D_FLOAT)t / 127; + t = src->z * 127 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->z = (M3D_FLOAT)t / 127; + t = src->w * 127 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->w = (M3D_FLOAT)t / 127; + break; + case M3D_EXP_INT16: + t = src->x * 32767 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->x = (M3D_FLOAT)t / 32767; + t = src->y * 32767 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->y = (M3D_FLOAT)t / 32767; + t = src->z * 32767 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->z = (M3D_FLOAT)t / 32767; + t = src->w * 32767 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->w = (M3D_FLOAT)t / 32767; + break; + } + if(dst->x == (M3D_FLOAT)-0.0) dst->x = (M3D_FLOAT)0.0; + if(dst->y == (M3D_FLOAT)-0.0) dst->y = (M3D_FLOAT)0.0; + if(dst->z == (M3D_FLOAT)-0.0) dst->z = (M3D_FLOAT)0.0; + if(dst->w == (M3D_FLOAT)-0.0) dst->w = (M3D_FLOAT)0.0; +} + +#ifdef M3D_ASCII +/* add a bone to ascii output */ +static char *_m3d_prtbone(char *ptr, m3db_t *bone, M3D_INDEX numbone, M3D_INDEX parent, uint32_t level, M3D_INDEX *vrtxidx) +{ + uint32_t i, j; + char *sn; + + if(level > M3D_BONEMAXLEVEL || !bone) return ptr; + for(i = 0; i < numbone; i++) { + if(bone[i].parent == parent) { + for(j = 0; j < level; j++) *ptr++ = '/'; + sn = _m3d_safestr(bone[i].name, 0); + ptr += sprintf(ptr, "%d %d %s\r\n", vrtxidx[bone[i].pos], vrtxidx[bone[i].ori], sn); + M3D_FREE(sn); + ptr = _m3d_prtbone(ptr, bone, numbone, i, level + 1, vrtxidx); + } + } + return ptr; +} +#endif + +/** + * Function to encode an in-memory model into on storage Model 3D format + */ +unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size) +{ +#ifdef M3D_ASCII + const char *ol; + char *ptr; +#endif + char vc_s, vi_s, si_s, ci_s, ti_s, bi_s, nb_s, sk_s, fc_s, hi_s, fi_s; + char *sn = NULL, *sl = NULL, *sa = NULL, *sd = NULL; + unsigned char *out = NULL, *z = NULL, weights[M3D_NUMBONE], *norm = NULL; + unsigned int i, j, k, l, n, len, chunklen, *length; + M3D_FLOAT scale = (M3D_FLOAT)0.0, min_x, max_x, min_y, max_y, min_z, max_z; + M3D_INDEX last, *vrtxidx = NULL, *mtrlidx = NULL, *tmapidx = NULL, *skinidx = NULL; + uint32_t idx, numcmap = 0, *cmap = NULL, numvrtx = 0, maxvrtx = 0, numtmap = 0, maxtmap = 0, numproc = 0; + uint32_t numskin = 0, maxskin = 0, numstr = 0, maxt = 0, maxbone = 0, numgrp = 0, maxgrp = 0, *grpidx = NULL; + uint8_t *opa; + m3dcd_t *cd; + m3dc_t *cmd; + m3dstr_t *str = NULL; + m3dvsave_t *vrtx = NULL, vertex; + m3dtisave_t *tmap = NULL, tcoord; + m3dssave_t *skin = NULL, sk; + m3dfsave_t *face = NULL; + m3dhdr_t *h = NULL; + m3dm_t *m; + m3da_t *a; + + if(!model) { + if(size) *size = 0; + return NULL; + } + model->errcode = M3D_SUCCESS; +#ifdef M3D_ASCII + if(flags & M3D_EXP_ASCII) quality = M3D_EXP_DOUBLE; +#endif + vrtxidx = (M3D_INDEX*)M3D_MALLOC(model->numvertex * sizeof(M3D_INDEX)); + if(!vrtxidx) goto memerr; + memset(vrtxidx, 255, model->numvertex * sizeof(M3D_INDEX)); + if(model->numvertex && !(flags & M3D_EXP_NONORMAL)){ + norm = (unsigned char*)M3D_MALLOC(model->numvertex * sizeof(unsigned char)); + if(!norm) goto memerr; + memset(norm, 0, model->numvertex * sizeof(unsigned char)); + } + if(model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { + mtrlidx = (M3D_INDEX*)M3D_MALLOC(model->nummaterial * sizeof(M3D_INDEX)); + if(!mtrlidx) goto memerr; + memset(mtrlidx, 255, model->nummaterial * sizeof(M3D_INDEX)); + opa = (uint8_t*)M3D_MALLOC(model->nummaterial * 2 * sizeof(M3D_INDEX)); + if(!opa) goto memerr; + memset(opa, 255, model->nummaterial * 2 * sizeof(M3D_INDEX)); + } + if(model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) { + tmapidx = (M3D_INDEX*)M3D_MALLOC(model->numtmap * sizeof(M3D_INDEX)); + if(!tmapidx) goto memerr; + memset(tmapidx, 255, model->numtmap * sizeof(M3D_INDEX)); + } + /** collect array elements that are actually referenced **/ + if(!(flags & M3D_EXP_NOFACE)) { + /* face */ + if(model->numface && model->face) { + M3D_LOG("Processing mesh face"); + face = (m3dfsave_t*)M3D_MALLOC(model->numface * sizeof(m3dfsave_t)); + if(!face) goto memerr; + for(i = 0; i < model->numface; i++) { + memcpy(&face[i].data, &model->face[i], sizeof(m3df_t)); + face[i].group = 0; + face[i].opacity = 255; + if(!(flags & M3D_EXP_NOMATERIAL) && model->face[i].materialid < model->nummaterial) { + if(model->material[model->face[i].materialid].numprop) { + mtrlidx[model->face[i].materialid] = 0; + if(opa[model->face[i].materialid * 2]) { + m = &model->material[model->face[i].materialid]; + for(j = 0; j < m->numprop; j++) + if(m->prop[j].type == m3dp_Kd) { + opa[model->face[i].materialid * 2 + 1] = ((uint8_t*)&m->prop[j].value.color)[3]; + break; + } + for(j = 0; j < m->numprop; j++) + if(m->prop[j].type == m3dp_d) { + opa[model->face[i].materialid * 2 + 1] = (uint8_t)(m->prop[j].value.fnum * 255); + break; + } + opa[model->face[i].materialid * 2] = 0; + } + face[i].opacity = opa[model->face[i].materialid * 2 + 1]; + } else + face[i].data.materialid = (M3D_INDEX)-1U; + } + for(j = 0; j < 3; j++) { + k = model->face[i].vertex[j]; + if(k < model->numvertex) + vrtxidx[k] = 0; + if(!(flags & M3D_EXP_NOCMAP)) { + cmap = _m3d_addcmap(cmap, &numcmap, model->vertex[k].color); + if(!cmap) goto memerr; + } + k = model->face[i].normal[j]; + if(k < model->numvertex && !(flags & M3D_EXP_NONORMAL)) { + vrtxidx[k] = 0; + norm[k] = 1; + } + k = model->face[i].texcoord[j]; + if(k < model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) + tmapidx[k] = 0; + } + /* convert from CW to CCW */ + if(flags & M3D_EXP_IDOSUCK) { + j = face[i].data.vertex[1]; + face[i].data.vertex[1] = face[i].data.vertex[2]; + face[i].data.vertex[2] = face[i].data.vertex[1]; + j = face[i].data.normal[1]; + face[i].data.normal[1] = face[i].data.normal[2]; + face[i].data.normal[2] = face[i].data.normal[1]; + j = face[i].data.texcoord[1]; + face[i].data.texcoord[1] = face[i].data.texcoord[2]; + face[i].data.texcoord[2] = face[i].data.texcoord[1]; + } + } + } + if(model->numshape && model->shape) { + M3D_LOG("Processing shape face"); + for(i = 0; i < model->numshape; i++) { + if(!model->shape[i].numcmd) continue; + str = _m3d_addstr(str, &numstr, model->shape[i].name); + if(!str) goto memerr; + for(j = 0; j < model->shape[i].numcmd; j++) { + cmd = &model->shape[i].cmd[j]; + if(cmd->type >= (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])) || !cmd->arg) + continue; + if(cmd->type == m3dc_mesh) { + if(numgrp + 2 < maxgrp) { + maxgrp += 1024; + grpidx = (uint32_t*)realloc(grpidx, maxgrp * sizeof(uint32_t)); + if(!grpidx) goto memerr; + if(!numgrp) { + grpidx[0] = 0; + grpidx[1] = model->numface; + numgrp += 2; + } + } + grpidx[numgrp + 0] = cmd->arg[0]; + grpidx[numgrp + 1] = cmd->arg[0] + cmd->arg[1]; + numgrp += 2; + } + cd = &m3d_commandtypes[cmd->type]; + for(k = n = 0, l = cd->p; k < l; k++) + switch(cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + if(!(flags & M3D_EXP_NOMATERIAL) && cmd->arg[k] < model->nummaterial) + mtrlidx[cmd->arg[k]] = 0; + break; + case m3dcp_ti_t: + if(!(flags & M3D_EXP_NOTXTCRD) && cmd->arg[k] < model->numtmap) + tmapidx[cmd->arg[k]] = 0; + break; + case m3dcp_qi_t: + case m3dcp_vi_t: + if(cmd->arg[k] < model->numvertex) + vrtxidx[cmd->arg[k]] = 0; + break; + case m3dcp_va_t: + n = k + 1; l += (cmd->arg[k] - 1) * (cd->p - k - 1); + break; + } + } + } + } + if(model->numface && face) { + if(numgrp && grpidx) { + qsort(grpidx, numgrp, sizeof(uint32_t), _m3d_grpcmp); + for(i = j = 0; i < model->numface && j < numgrp; i++) { + while(j < numgrp && grpidx[j] < i) j++; + face[i].group = j; + } + } + qsort(face, model->numface, sizeof(m3dfsave_t), _m3d_facecmp); + } + if(grpidx) { M3D_FREE(grpidx); grpidx = NULL; } + if(model->numlabel && model->label) { + M3D_LOG("Processing annotation labels"); + for(i = 0; i < model->numlabel; i++) { + str = _m3d_addstr(str, &numstr, model->label[i].name); + str = _m3d_addstr(str, &numstr, model->label[i].lang); + str = _m3d_addstr(str, &numstr, model->label[i].text); + if(!(flags & M3D_EXP_NOCMAP)) { + cmap = _m3d_addcmap(cmap, &numcmap, model->label[i].color); + if(!cmap) goto memerr; + } + if(model->label[i].vertexid < model->numvertex) + vrtxidx[model->label[i].vertexid] = 0; + } + qsort(model->label, model->numlabel, sizeof(m3dl_t), _m3d_lblcmp); + } + } else if(!(flags & M3D_EXP_NOMATERIAL)) { + /* without a face, simply add all materials, because it can be an mtllib */ + for(i = 0; i < model->nummaterial; i++) + mtrlidx[i] = i; + } + /* bind-pose skeleton */ + if(model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) { + M3D_LOG("Processing bones"); + for(i = 0; i < model->numbone; i++) { + str = _m3d_addstr(str, &numstr, model->bone[i].name); + if(!str) goto memerr; + k = model->bone[i].pos; + if(k < model->numvertex) + vrtxidx[k] = 0; + k = model->bone[i].ori; + if(k < model->numvertex) + vrtxidx[k] = 0; + } + } + /* actions, animated skeleton poses */ + if(model->numaction && model->action && !(flags & M3D_EXP_NOACTION)) { + M3D_LOG("Processing action list"); + for(j = 0; j < model->numaction; j++) { + a = &model->action[j]; + str = _m3d_addstr(str, &numstr, a->name); + if(!str) goto memerr; + if(a->numframe > 65535) a->numframe = 65535; + for(i = 0; i < a->numframe; i++) { + for(l = 0; l < a->frame[i].numtransform; l++) { + k = a->frame[i].transform[l].pos; + if(k < model->numvertex) + vrtxidx[k] = 0; + k = a->frame[i].transform[l].ori; + if(k < model->numvertex) + vrtxidx[k] = 0; + } + if(l > maxt) maxt = l; + } + } + } + /* add colors to color map and texture names to string table */ + if(!(flags & M3D_EXP_NOMATERIAL)) { + M3D_LOG("Processing materials"); + for(i = k = 0; i < model->nummaterial; i++) { + if(mtrlidx[i] == (M3D_INDEX)-1U || !model->material[i].numprop) continue; + mtrlidx[i] = k++; + m = &model->material[i]; + str = _m3d_addstr(str, &numstr, m->name); + if(!str) goto memerr; + if(m->prop) + for(j = 0; j < m->numprop; j++) { + if(!(flags & M3D_EXP_NOCMAP) && m->prop[j].type < 128) { + for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) { + if(m->prop[j].type == m3d_propertytypes[l].id && m3d_propertytypes[l].format == m3dpf_color) { + ((uint8_t*)&m->prop[j].value.color)[3] = opa[i * 2 + 1]; + cmap = _m3d_addcmap(cmap, &numcmap, m->prop[j].value.color); + if(!cmap) goto memerr; + break; + } + } + } + if(m->prop[j].type >= 128 && m->prop[j].value.textureid < model->numtexture && + model->texture[m->prop[j].value.textureid].name) { + str = _m3d_addstr(str, &numstr, model->texture[m->prop[j].value.textureid].name); + if(!str) goto memerr; + } + } + } + } + /* if there's only one black color, don't store it */ + if(numcmap == 1 && cmap && !cmap[0]) numcmap = 0; + + /** compress lists **/ + if(model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) { + M3D_LOG("Compressing tmap"); + tmap = (m3dtisave_t*)M3D_MALLOC(model->numtmap * sizeof(m3dtisave_t)); + if(!tmap) goto memerr; + for(i = 0; i < model->numtmap; i++) { + if(tmapidx[i] == (M3D_INDEX)-1U) continue; + switch(quality) { + case M3D_EXP_INT8: + l = model->tmap[i].u * 255; tcoord.data.u = (M3D_FLOAT)l / 255; + l = model->tmap[i].v * 255; tcoord.data.v = (M3D_FLOAT)l / 255; + break; + case M3D_EXP_INT16: + l = model->tmap[i].u * 65535; tcoord.data.u = (M3D_FLOAT)l / 65535; + l = model->tmap[i].v * 65535; tcoord.data.v = (M3D_FLOAT)l / 65535; + break; + default: + tcoord.data.u = model->tmap[i].u; + tcoord.data.v = model->tmap[i].v; + break; + } + if(flags & M3D_EXP_FLIPTXTCRD) + tcoord.data.v = (M3D_FLOAT)1.0 - tcoord.data.v; + tcoord.oldidx = i; + memcpy(&tmap[numtmap++], &tcoord, sizeof(m3dtisave_t)); + } + if(numtmap) { + qsort(tmap, numtmap, sizeof(m3dtisave_t), _m3d_ticmp); + memcpy(&tcoord.data, &tmap[0], sizeof(m3dti_t)); + for(i = 0; i < numtmap; i++) { + if(memcmp(&tcoord.data, &tmap[i].data, sizeof(m3dti_t))) { + memcpy(&tcoord.data, &tmap[i].data, sizeof(m3dti_t)); + maxtmap++; + } + tmap[i].newidx = maxtmap; + tmapidx[tmap[i].oldidx] = maxtmap; + } + maxtmap++; + } + } + if(model->numskin && model->skin && !(flags & M3D_EXP_NOBONE)) { + M3D_LOG("Compressing skin"); + skinidx = (M3D_INDEX*)M3D_MALLOC(model->numskin * sizeof(M3D_INDEX)); + if(!skinidx) goto memerr; + skin = (m3dssave_t*)M3D_MALLOC(model->numskin * sizeof(m3dssave_t)); + if(!skin) goto memerr; + memset(skinidx, 255, model->numskin * sizeof(M3D_INDEX)); + for(i = 0; i < model->numvertex; i++) { + if(vrtxidx[i] != (M3D_INDEX)-1U && model->vertex[i].skinid < model->numskin) + skinidx[model->vertex[i].skinid] = 0; + } + for(i = 0; i < model->numskin; i++) { + if(skinidx[i] == (M3D_INDEX)-1U) continue; + memset(&sk, 0, sizeof(m3dssave_t)); + for(j = 0, min_x = (M3D_FLOAT)0.0; j < M3D_NUMBONE && model->skin[i].boneid[j] != (M3D_INDEX)-1U && + model->skin[i].weight[j] > (M3D_FLOAT)0.0; j++) { + sk.data.boneid[j] = model->skin[i].boneid[j]; + sk.data.weight[j] = model->skin[i].weight[j]; + min_x += sk.data.weight[j]; + } + if(j > maxbone) maxbone = j; + if(min_x != (M3D_FLOAT)1.0 && min_x != (M3D_FLOAT)0.0) + for(j = 0; j < M3D_NUMBONE && sk.data.weight[j] > (M3D_FLOAT)0.0; j++) + sk.data.weight[j] /= min_x; + sk.oldidx = i; + memcpy(&skin[numskin++], &sk, sizeof(m3dssave_t)); + } + if(numskin) { + qsort(skin, numskin, sizeof(m3dssave_t), _m3d_skincmp); + memcpy(&sk.data, &skin[0].data, sizeof(m3ds_t)); + for(i = 0; i < numskin; i++) { + if(memcmp(&sk.data, &skin[i].data, sizeof(m3ds_t))) { + memcpy(&sk.data, &skin[i].data, sizeof(m3ds_t)); + maxskin++; + } + skin[i].newidx = maxskin; + skinidx[skin[i].oldidx] = maxskin; + } + maxskin++; + } + } + + M3D_LOG("Compressing vertex list"); + min_x = min_y = min_z = (M3D_FLOAT)1e10; + max_x = max_y = max_z = (M3D_FLOAT)-1e10; + if(vrtxidx) { + vrtx = (m3dvsave_t*)M3D_MALLOC(model->numvertex * sizeof(m3dvsave_t)); + if(!vrtx) goto memerr; + for(i = numvrtx = 0; i < model->numvertex; i++) { + if(vrtxidx[i] == (M3D_INDEX)-1U) continue; + _m3d_round(quality, &model->vertex[i], &vertex.data); + vertex.norm = norm ? norm[i] : 0; + if(vertex.data.skinid != (M3D_INDEX)-2U && !vertex.norm) { + vertex.data.skinid = vertex.data.skinid != (M3D_INDEX)-1U && skinidx ? skinidx[vertex.data.skinid] : (M3D_INDEX)-1U; + if(vertex.data.x > max_x) max_x = vertex.data.x; + if(vertex.data.x < min_x) min_x = vertex.data.x; + if(vertex.data.y > max_y) max_y = vertex.data.y; + if(vertex.data.y < min_y) min_y = vertex.data.y; + if(vertex.data.z > max_z) max_z = vertex.data.z; + if(vertex.data.z < min_z) min_z = vertex.data.z; + } +#ifdef M3D_VERTEXTYPE + vertex.data.type = 0; +#endif + vertex.oldidx = i; + memcpy(&vrtx[numvrtx++], &vertex, sizeof(m3dvsave_t)); + } + if(numvrtx) { + qsort(vrtx, numvrtx, sizeof(m3dvsave_t), _m3d_vrtxcmp); + memcpy(&vertex.data, &vrtx[0].data, sizeof(m3dv_t)); + for(i = 0; i < numvrtx; i++) { + if(memcmp(&vertex.data, &vrtx[i].data, vrtx[i].norm ? 3 * sizeof(M3D_FLOAT) : sizeof(m3dv_t))) { + memcpy(&vertex.data, &vrtx[i].data, sizeof(m3dv_t)); + maxvrtx++; + } + vrtx[i].newidx = maxvrtx; + vrtxidx[vrtx[i].oldidx] = maxvrtx; + } + maxvrtx++; + } + } + if(skinidx) { M3D_FREE(skinidx); skinidx = NULL; } + if(norm) { M3D_FREE(norm); norm = NULL; } + + /* normalize to bounding cube */ + if(numvrtx && !(flags & M3D_EXP_NORECALC)) { + M3D_LOG("Normalizing coordinates"); + if(min_x < (M3D_FLOAT)0.0) min_x = -min_x; + if(max_x < (M3D_FLOAT)0.0) max_x = -max_x; + if(min_y < (M3D_FLOAT)0.0) min_y = -min_y; + if(max_y < (M3D_FLOAT)0.0) max_y = -max_y; + if(min_z < (M3D_FLOAT)0.0) min_z = -min_z; + if(max_z < (M3D_FLOAT)0.0) max_z = -max_z; + scale = min_x; + if(max_x > scale) scale = max_x; + if(min_y > scale) scale = min_y; + if(max_y > scale) scale = max_y; + if(min_z > scale) scale = min_z; + if(max_z > scale) scale = max_z; + if(scale == (M3D_FLOAT)0.0) scale = (M3D_FLOAT)1.0; + if(scale != (M3D_FLOAT)1.0) { + for(i = 0; i < numvrtx; i++) { + if(vrtx[i].data.skinid == (M3D_INDEX)-2U) continue; + vrtx[i].data.x /= scale; + vrtx[i].data.y /= scale; + vrtx[i].data.z /= scale; + } + } + } + if(model->scale > (M3D_FLOAT)0.0) scale = model->scale; + if(scale <= (M3D_FLOAT)0.0) scale = (M3D_FLOAT)1.0; + + /* meta info */ + sn = _m3d_safestr(model->name && *model->name ? model->name : (char*)"(noname)", 2); + sl = _m3d_safestr(model->license ? model->license : (char*)"MIT", 2); + sa = _m3d_safestr(model->author ? model->author : getenv("LOGNAME"), 2); + if(!sn || !sl || !sa) { +memerr: if(vrtxidx) M3D_FREE(vrtxidx); + if(mtrlidx) M3D_FREE(mtrlidx); + if(tmapidx) M3D_FREE(tmapidx); + if(skinidx) M3D_FREE(skinidx); + if(grpidx) M3D_FREE(grpidx); + if(norm) M3D_FREE(norm); + if(face) M3D_FREE(face); + if(cmap) M3D_FREE(cmap); + if(tmap) M3D_FREE(tmap); + if(skin) M3D_FREE(skin); + if(str) M3D_FREE(str); + if(vrtx) M3D_FREE(vrtx); + if(sn) M3D_FREE(sn); + if(sl) M3D_FREE(sl); + if(sa) M3D_FREE(sa); + if(sd) M3D_FREE(sd); + if(out) M3D_FREE(out); + if(h) M3D_FREE(h); + M3D_LOG("Out of memory"); + model->errcode = M3D_ERR_ALLOC; + return NULL; + } + + M3D_LOG("Serializing model"); +#ifdef M3D_ASCII + if(flags & M3D_EXP_ASCII) { + /* use CRLF to make model creators on Win happy... */ + sd = _m3d_safestr(model->desc, 1); + if(!sd) goto memerr; + ol = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + /* header */ + len = 64 + strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd); + out = (unsigned char*)M3D_MALLOC(len); + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr = (char*)out; + ptr += sprintf(ptr, "3dmodel %g\r\n%s\r\n%s\r\n%s\r\n%s\r\n\r\n", scale, + sn, sl, sa, sd); + M3D_FREE(sl); M3D_FREE(sa); M3D_FREE(sd); + sl = sa = sd = NULL; + /* preview chunk */ + if(model->preview.data && model->preview.length) { + sl = _m3d_safestr(sn, 0); + if(sl) { + ptr -= (uint64_t)out; len = (uint64_t)ptr + 20; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Preview\r\n%s.png\r\n\r\n", sl); + M3D_FREE(sl); sl = NULL; + } + } + M3D_FREE(sn); sn = NULL; + /* texture map */ + if(numtmap && tmap && !(flags & M3D_EXP_NOTXTCRD) && !(flags & M3D_EXP_NOFACE)) { + ptr -= (uint64_t)out; len = (uint64_t)ptr + maxtmap * 32 + 12; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Textmap\r\n"); + last = (M3D_INDEX)-1U; + for(i = 0; i < numtmap; i++) { + if(tmap[i].newidx == last) continue; + last = tmap[i].newidx; + ptr += sprintf(ptr, "%g %g\r\n", tmap[i].data.u, tmap[i].data.v); + } + ptr += sprintf(ptr, "\r\n"); + } + /* vertex chunk */ + if(numvrtx && vrtx && !(flags & M3D_EXP_NOFACE)) { + ptr -= (uint64_t)out; len = (uint64_t)ptr + maxvrtx * 128 + 10; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Vertex\r\n"); + last = (M3D_INDEX)-1U; + for(i = 0; i < numvrtx; i++) { + if(vrtx[i].newidx == last) continue; + last = vrtx[i].newidx; + ptr += sprintf(ptr, "%g %g %g %g", vrtx[i].data.x, vrtx[i].data.y, vrtx[i].data.z, vrtx[i].data.w); + if(!(flags & M3D_EXP_NOCMAP) && vrtx[i].data.color) + ptr += sprintf(ptr, " #%08x", vrtx[i].data.color); + if(!(flags & M3D_EXP_NOBONE) && model->numbone && maxskin && vrtx[i].data.skinid < M3D_INDEXMAX) { + if(skin[vrtx[i].data.skinid].data.weight[0] == (M3D_FLOAT)1.0) + ptr += sprintf(ptr, " %d", skin[vrtx[i].data.skinid].data.boneid[0]); + else + for(j = 0; j < M3D_NUMBONE && skin[vrtx[i].data.skinid].data.boneid[j] != (M3D_INDEX)-1U && + skin[vrtx[i].data.skinid].data.weight[j] > (M3D_FLOAT)0.0; j++) + ptr += sprintf(ptr, " %d:%g", skin[vrtx[i].data.skinid].data.boneid[j], + skin[vrtx[i].data.skinid].data.weight[j]); + } + ptr += sprintf(ptr, "\r\n"); + } + ptr += sprintf(ptr, "\r\n"); + } + /* bones chunk */ + if(model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) { + ptr -= (uint64_t)out; len = (uint64_t)ptr + 9; + for(i = 0; i < model->numbone; i++) { + len += strlen(model->bone[i].name) + 128; + } + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Bones\r\n"); + ptr = _m3d_prtbone(ptr, model->bone, model->numbone, (M3D_INDEX)-1U, 0, vrtxidx); + ptr += sprintf(ptr, "\r\n"); + } + /* materials */ + if(model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { + for(j = 0; j < model->nummaterial; j++) { + if(mtrlidx[j] == (M3D_INDEX)-1U || !model->material[j].numprop || !model->material[j].prop) continue; + m = &model->material[j]; + sn = _m3d_safestr(m->name, 0); + if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(sn) + 12; + for(i = 0; i < m->numprop; i++) { + if(m->prop[i].type < 128) + len += 32; + else if(m->prop[i].value.textureid < model->numtexture && model->texture[m->prop[i].value.textureid].name) + len += strlen(model->texture[m->prop[i].value.textureid].name) + 16; + } + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Material %s\r\n", sn); + M3D_FREE(sn); sn = NULL; + for(i = 0; i < m->numprop; i++) { + k = 256; + if(m->prop[i].type >= 128) { + for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) + if(m->prop[i].type == m3d_propertytypes[l].id) { + sn = m3d_propertytypes[l].key; + break; + } + if(!sn) + for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) + if(m->prop[i].type - 128 == m3d_propertytypes[l].id) { + sn = m3d_propertytypes[l].key; + break; + } + k = sn ? m3dpf_map : 256; + } else { + for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) + if(m->prop[i].type == m3d_propertytypes[l].id) { + sn = m3d_propertytypes[l].key; + k = m3d_propertytypes[l].format; + break; + } + } + switch(k) { + case m3dpf_color: ptr += sprintf(ptr, "%s #%08x\r\n", sn, m->prop[i].value.color); break; + case m3dpf_uint8: + case m3dpf_uint16: + case m3dpf_uint32: ptr += sprintf(ptr, "%s %d\r\n", sn, m->prop[i].value.num); break; + case m3dpf_float: ptr += sprintf(ptr, "%s %g\r\n", sn, m->prop[i].value.fnum); break; + case m3dpf_map: + if(m->prop[i].value.textureid < model->numtexture && + model->texture[m->prop[i].value.textureid].name) { + sl = _m3d_safestr(model->texture[m->prop[i].value.textureid].name, 0); + if(!sl) { setlocale(LC_NUMERIC, ol); goto memerr; } + if(*sl) + ptr += sprintf(ptr, "map_%s %s\r\n", sn, sl); + M3D_FREE(sn); M3D_FREE(sl); sl = NULL; + } + break; + } + sn = NULL; + } + ptr += sprintf(ptr, "\r\n"); + } + } + /* procedural face */ + if(model->numinlined && model->inlined && !(flags & M3D_EXP_NOFACE)) { + /* all inlined assets which are not textures should be procedural surfaces */ + for(j = 0; j < model->numinlined; j++) { + if(!model->inlined[j].name || !*model->inlined[j].name || !model->inlined[j].length || !model->inlined[j].data || + (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G')) + continue; + for(i = k = 0; i < model->numtexture; i++) { + if(!strcmp(model->inlined[j].name, model->texture[i].name)) { k = 1; break; } + } + if(k) continue; + sn = _m3d_safestr(model->inlined[j].name, 0); + if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(sn) + 18; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Procedural\r\n%s\r\n\r\n", sn); + M3D_FREE(sn); sn = NULL; + } + } + /* mesh face */ + if(model->numface && face && !(flags & M3D_EXP_NOFACE)) { + ptr -= (uint64_t)out; len = (uint64_t)ptr + model->numface * 128 + 6; + last = (M3D_INDEX)-1U; + if(!(flags & M3D_EXP_NOMATERIAL)) + for(i = 0; i < model->numface; i++) { + j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : (M3D_INDEX)-1U; + if(j != last) { + last = j; + if(last < model->nummaterial) + len += strlen(model->material[last].name); + len += 6; + } + } + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Mesh\r\n"); + last = (M3D_INDEX)-1U; + for(i = 0; i < model->numface; i++) { + j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : (M3D_INDEX)-1U; + if(!(flags & M3D_EXP_NOMATERIAL) && j != last) { + last = j; + if(last < model->nummaterial) { + sn = _m3d_safestr(model->material[last].name, 0); + if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "use %s\r\n", sn); + M3D_FREE(sn); sn = NULL; + } else + ptr += sprintf(ptr, "use\r\n"); + } + /* hardcoded triangles. Should be repeated as many times as the number of edges in polygon */ + for(j = 0; j < 3; j++) { + ptr += sprintf(ptr, "%s%d", j?" ":"", vrtxidx[face[i].data.vertex[j]]); + k = -1U; + if(!(flags & M3D_EXP_NOTXTCRD) && (face[i].data.texcoord[j] != (M3D_INDEX)-1U) && + (tmapidx[face[i].data.texcoord[j]] != (M3D_INDEX)-1U)) { + k = tmapidx[face[i].data.texcoord[j]]; + ptr += sprintf(ptr, "/%d", k); + } + if(!(flags & M3D_EXP_NONORMAL) && (face[i].data.normal[j] != (M3D_INDEX)-1U)) + ptr += sprintf(ptr, "%s/%d", k == -1U? "/" : "", vrtxidx[face[i].data.normal[j]]); + } + ptr += sprintf(ptr, "\r\n"); + } + ptr += sprintf(ptr, "\r\n"); + } + /* mathematical shapes face */ + if(model->numshape && model->numshape && !(flags & M3D_EXP_NOFACE)) { + for(j = 0; j < model->numshape; j++) { + sn = _m3d_safestr(model->shape[j].name, 0); + if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(sn) + 33; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Shape %s\r\n", sn); + M3D_FREE(sn); sn = NULL; + if(model->shape[j].group != (M3D_INDEX)-1U && !(flags & M3D_EXP_NOBONE)) + ptr += sprintf(ptr, "group %d\r\n", model->shape[j].group); + for(i = 0; i < model->shape[j].numcmd; i++) { + cmd = &model->shape[j].cmd[i]; + if(cmd->type >= (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])) || !cmd->arg) + continue; + cd = &m3d_commandtypes[cmd->type]; + ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(cd->key) + 3; + for(k = 0; k < cd->p; k++) + switch(cd->a[k]) { + case m3dcp_mi_t: if(cmd->arg[k] != -1U) { len += strlen(model->material[cmd->arg[k]].name) + 1; } break; + case m3dcp_va_t: len += cmd->arg[k] * (cd->p - k - 1) * 16; k = cd->p; break; + default: len += 16; break; + } + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "%s", cd->key); + for(k = n = 0, l = cd->p; k < l; k++) { + switch(cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + if(cmd->arg[k] != -1U) { + sn = _m3d_safestr(model->material[cmd->arg[k]].name, 0); + if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, " %s", sn); + M3D_FREE(sn); sn = NULL; + } + break; + case m3dcp_vc_t: ptr += sprintf(ptr, " %g", *((float*)&cmd->arg[k])); break; + case m3dcp_va_t: ptr += sprintf(ptr, " %d[", cmd->arg[k]); + n = k + 1; l += (cmd->arg[k] - 1) * (cd->p - k - 1); + break; + default: ptr += sprintf(ptr, " %d", cmd->arg[k]); break; + } + } + ptr += sprintf(ptr, "%s\r\n", l > cd->p ? " ]" : ""); + } + ptr += sprintf(ptr, "\r\n"); + } + } + /* annotation labels */ + if(model->numlabel && model->label && !(flags & M3D_EXP_NOFACE)) { + for(i = 0, j = 3, length = NULL; i < model->numlabel; i++) { + if(model->label[i].name) j += strlen(model->label[i].name); + if(model->label[i].lang) j += strlen(model->label[i].lang); + if(model->label[i].text) j += strlen(model->label[i].text); + j += 40; + } + ptr -= (uint64_t)out; len = (uint64_t)ptr + j; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + for(i = 0; i < model->numlabel; i++) { + if(!i || _m3d_strcmp(sl, model->label[i].lang) || _m3d_strcmp(sn, model->label[i].name)) { + sl = model->label[i].lang; + sn = model->label[i].name; + sd = _m3d_safestr(sn, 0); + if(!sd) { setlocale(LC_NUMERIC, ol); sn = sl = NULL; goto memerr; } + if(i) ptr += sprintf(ptr, "\r\n"); + ptr += sprintf(ptr, "Labels %s\r\n", sd); + M3D_FREE(sd); sd = NULL; + if(model->label[i].color) + ptr += sprintf(ptr, "color #0x%08x\r\n", model->label[i].color); + if(sl && *sl) { + sd = _m3d_safestr(sl, 0); + if(!sd) { setlocale(LC_NUMERIC, ol); sn = sl = NULL; goto memerr; } + ptr += sprintf(ptr, "lang %s\r\n", sd); + M3D_FREE(sd); sd = NULL; + } + } + sd = _m3d_safestr(model->label[i].text, 2); + if(!sd) { setlocale(LC_NUMERIC, ol); sn = sl = NULL; goto memerr; } + ptr += sprintf(ptr, "%d %s\r\n", model->label[i].vertexid, sd); + M3D_FREE(sd); sd = NULL; + } + ptr += sprintf(ptr, "\r\n"); + sn = sl = NULL; + } + /* actions */ + if(model->numaction && model->action && !(flags & M3D_EXP_NOACTION)) { + for(j = 0; j < model->numaction; j++) { + a = &model->action[j]; + sn = _m3d_safestr(a->name, 0); + if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(sn) + 48; + for(i = 0; i < a->numframe; i++) + len += a->frame[i].numtransform * 128 + 8; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Action %d %s\r\n", a->durationmsec, sn); + M3D_FREE(sn); sn = NULL; + for(i = 0; i < a->numframe; i++) { + ptr += sprintf(ptr, "frame %d\r\n", a->frame[i].msec); + for(k = 0; k < a->frame[i].numtransform; k++) { + ptr += sprintf(ptr, "%d %d %d\r\n", a->frame[i].transform[k].boneid, + vrtxidx[a->frame[i].transform[k].pos], vrtxidx[a->frame[i].transform[k].ori]); + } + } + ptr += sprintf(ptr, "\r\n"); + } + } + /* inlined assets */ + if(model->numinlined && model->inlined) { + for(i = j = 0; i < model->numinlined; i++) + if(model->inlined[i].name) + j += strlen(model->inlined[i].name) + 6; + if(j > 0) { + ptr -= (uint64_t)out; len = (uint64_t)ptr + j + 16; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Assets\r\n"); + for(i = 0; i < model->numinlined; i++) + if(model->inlined[i].name) + ptr += sprintf(ptr, "%s%s\r\n", model->inlined[i].name, strrchr(model->inlined[i].name, '.') ? "" : ".png"); + ptr += sprintf(ptr, "\r\n"); + } + } + /* extra info */ + if(model->numextra && (flags & M3D_EXP_EXTRA)) { + for(i = 0; i < model->numextra; i++) { + if(model->extra[i]->length < 9) continue; + ptr -= (uint64_t)out; len = (uint64_t)ptr + 17 + model->extra[i]->length * 3; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Extra %c%c%c%c\r\n", + model->extra[i]->magic[0] > ' ' ? model->extra[i]->magic[0] : '_', + model->extra[i]->magic[1] > ' ' ? model->extra[i]->magic[1] : '_', + model->extra[i]->magic[2] > ' ' ? model->extra[i]->magic[2] : '_', + model->extra[i]->magic[3] > ' ' ? model->extra[i]->magic[3] : '_'); + for(j = 0; j < model->extra[i]->length; j++) + ptr += sprintf(ptr, "%02x ", *((unsigned char *)model->extra + sizeof(m3dchunk_t) + j)); + ptr--; + ptr += sprintf(ptr, "\r\n\r\n"); + } + } + setlocale(LC_NUMERIC, ol); + len = (uint64_t)ptr - (uint64_t)out; + out = (unsigned char*)M3D_REALLOC(out, len + 1); + if(!out) goto memerr; + out[len] = 0; + } else +#endif + { + /* stricly only use LF (newline) in binary */ + sd = _m3d_safestr(model->desc, 3); + if(!sd) goto memerr; + /* header */ + h = (m3dhdr_t*)M3D_MALLOC(sizeof(m3dhdr_t) + strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd) + 4); + if(!h) goto memerr; + memcpy((uint8_t*)h, "HEAD", 4); + h->length = sizeof(m3dhdr_t); + h->scale = scale; + i = strlen(sn); memcpy((uint8_t*)h + h->length, sn, i+1); h->length += i+1; M3D_FREE(sn); + i = strlen(sl); memcpy((uint8_t*)h + h->length, sl, i+1); h->length += i+1; M3D_FREE(sl); + i = strlen(sa); memcpy((uint8_t*)h + h->length, sa, i+1); h->length += i+1; M3D_FREE(sa); + i = strlen(sd); memcpy((uint8_t*)h + h->length, sd, i+1); h->length += i+1; M3D_FREE(sd); + sn = sl = sa = sd = NULL; + if(model->inlined) + for(i = 0; i < model->numinlined; i++) { + if(model->inlined[i].name && *model->inlined[i].name && model->inlined[i].length > 0) { + str = _m3d_addstr(str, &numstr, model->inlined[i].name); + if(!str) goto memerr; + } + } + if(str) + for(i = 0; i < numstr; i++) { + h = _m3d_addhdr(h, &str[i]); + if(!h) goto memerr; + } + vc_s = quality == M3D_EXP_INT8? 1 : (quality == M3D_EXP_INT16? 2 : (quality == M3D_EXP_DOUBLE? 8 : 4)); + vi_s = maxvrtx < 254 ? 1 : (maxvrtx < 65534 ? 2 : 4); + si_s = h->length - 16 < 254 ? 1 : (h->length - 16 < 65534 ? 2 : 4); + ci_s = !numcmap || !cmap ? 0 : (numcmap < 254 ? 1 : (numcmap < 65534 ? 2 : 4)); + ti_s = !maxtmap || !tmap ? 0 : (maxtmap < 254 ? 1 : (maxtmap < 65534 ? 2 : 4)); + bi_s = !model->numbone || !model->bone || (flags & M3D_EXP_NOBONE)? 0 : (model->numbone < 254 ? 1 : + (model->numbone < 65534 ? 2 : 4)); + nb_s = maxbone < 2 ? 1 : (maxbone == 2 ? 2 : (maxbone <= 4 ? 4 : 8)); + sk_s = !bi_s || !maxskin || !skin ? 0 : (maxskin < 254 ? 1 : (maxskin < 65534 ? 2 : 4)); + fc_s = maxt < 254 ? 1 : (maxt < 65534 ? 2 : 4); + hi_s = !model->numshape || !model->shape || (flags & M3D_EXP_NOFACE)? 0 : (model->numshape < 254 ? 1 : + (model->numshape < 65534 ? 2 : 4)); + fi_s = !model->numface || !model->face || (flags & M3D_EXP_NOFACE)? 0 : (model->numface < 254 ? 1 : + (model->numface < 65534 ? 2 : 4)); + h->types = (vc_s == 8 ? (3<<0) : (vc_s == 2 ? (1<<0) : (vc_s == 1 ? (0<<0) : (2<<0)))) | + (vi_s == 2 ? (1<<2) : (vi_s == 1 ? (0<<2) : (2<<2))) | + (si_s == 2 ? (1<<4) : (si_s == 1 ? (0<<4) : (2<<4))) | + (ci_s == 2 ? (1<<6) : (ci_s == 1 ? (0<<6) : (ci_s == 4 ? (2<<6) : (3<<6)))) | + (ti_s == 2 ? (1<<8) : (ti_s == 1 ? (0<<8) : (ti_s == 4 ? (2<<8) : (3<<8)))) | + (bi_s == 2 ? (1<<10): (bi_s == 1 ? (0<<10): (bi_s == 4 ? (2<<10) : (3<<10)))) | + (nb_s == 2 ? (1<<12): (nb_s == 1 ? (0<<12): (2<<12))) | + (sk_s == 2 ? (1<<14): (sk_s == 1 ? (0<<14): (sk_s == 4 ? (2<<14) : (3<<14)))) | + (fc_s == 2 ? (1<<16): (fc_s == 1 ? (0<<16): (2<<16))) | + (hi_s == 2 ? (1<<18): (hi_s == 1 ? (0<<18): (hi_s == 4 ? (2<<18) : (3<<18)))) | + (fi_s == 2 ? (1<<20): (fi_s == 1 ? (0<<20): (fi_s == 4 ? (2<<20) : (3<<20)))); + len = h->length; + /* preview image chunk, must be the first if exists */ + if(model->preview.data && model->preview.length) { + chunklen = 8 + model->preview.length; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "PRVW", 4); + *((uint32_t*)((uint8_t*)h + len + 4)) = chunklen; + memcpy((uint8_t*)h + len + 8, model->preview.data, model->preview.length); + len += chunklen; + } + /* color map */ + if(numcmap && cmap && ci_s < 4 && !(flags & M3D_EXP_NOCMAP)) { + chunklen = 8 + numcmap * sizeof(uint32_t); + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "CMAP", 4); + *((uint32_t*)((uint8_t*)h + len + 4)) = chunklen; + memcpy((uint8_t*)h + len + 8, cmap, chunklen - 8); + len += chunklen; + } else numcmap = 0; + /* texture map */ + if(numtmap && tmap && !(flags & M3D_EXP_NOTXTCRD) && !(flags & M3D_EXP_NOFACE)) { + chunklen = 8 + maxtmap * vc_s * 2; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "TMAP", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + last = (M3D_INDEX)-1U; + for(i = 0; i < numtmap; i++) { + if(tmap[i].newidx == last) continue; + last = tmap[i].newidx; + switch(vc_s) { + case 1: *out++ = (uint8_t)(tmap[i].data.u * 255); *out++ = (uint8_t)(tmap[i].data.v * 255); break; + case 2: + *((uint16_t*)out) = (uint16_t)(tmap[i].data.u * 65535); out += 2; + *((uint16_t*)out) = (uint16_t)(tmap[i].data.v * 65535); out += 2; + break; + case 4: *((float*)out) = tmap[i].data.u; out += 4; *((float*)out) = tmap[i].data.v; out += 4; break; + case 8: *((double*)out) = tmap[i].data.u; out += 8; *((double*)out) = tmap[i].data.v; out += 8; break; + } + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + out = NULL; + len += *length; + } + /* vertex */ + if(numvrtx && vrtx) { + chunklen = 8 + maxvrtx * (ci_s + sk_s + 4 * vc_s); + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "VRTS", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + last = (M3D_INDEX)-1U; + for(i = 0; i < numvrtx; i++) { + if(vrtx[i].newidx == last) continue; + last = vrtx[i].newidx; + switch(vc_s) { + case 1: + *out++ = (int8_t)(vrtx[i].data.x * 127); + *out++ = (int8_t)(vrtx[i].data.y * 127); + *out++ = (int8_t)(vrtx[i].data.z * 127); + *out++ = (int8_t)(vrtx[i].data.w * 127); + break; + case 2: + *((int16_t*)out) = (int16_t)(vrtx[i].data.x * 32767); out += 2; + *((int16_t*)out) = (int16_t)(vrtx[i].data.y * 32767); out += 2; + *((int16_t*)out) = (int16_t)(vrtx[i].data.z * 32767); out += 2; + *((int16_t*)out) = (int16_t)(vrtx[i].data.w * 32767); out += 2; + break; + case 4: + *((float*)out) = vrtx[i].data.x; out += 4; + *((float*)out) = vrtx[i].data.y; out += 4; + *((float*)out) = vrtx[i].data.z; out += 4; + *((float*)out) = vrtx[i].data.w; out += 4; + break; + case 8: + *((double*)out) = vrtx[i].data.x; out += 8; + *((double*)out) = vrtx[i].data.y; out += 8; + *((double*)out) = vrtx[i].data.z; out += 8; + *((double*)out) = vrtx[i].data.w; out += 8; + break; + } + idx = _m3d_cmapidx(cmap, numcmap, vrtx[i].data.color); + switch(ci_s) { + case 1: *out++ = (uint8_t)(idx); break; + case 2: *((uint16_t*)out) = (uint16_t)(idx); out += 2; break; + case 4: *((uint32_t*)out) = vrtx[i].data.color; out += 4; break; + } + out = _m3d_addidx(out, sk_s, vrtx[i].data.skinid); + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + out = NULL; + len += *length; + } + /* bones chunk */ + if(model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) { + i = 8 + bi_s + sk_s + model->numbone * (bi_s + si_s + 2*vi_s); + chunklen = i + numskin * nb_s * (bi_s + 1); + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "BONE", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, bi_s, model->numbone); + out = _m3d_addidx(out, sk_s, maxskin); + for(i = 0; i < model->numbone; i++) { + out = _m3d_addidx(out, bi_s, model->bone[i].parent); + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->bone[i].name)); + out = _m3d_addidx(out, vi_s, vrtxidx[model->bone[i].pos]); + out = _m3d_addidx(out, vi_s, vrtxidx[model->bone[i].ori]); + } + if(numskin && skin && sk_s) { + last = (M3D_INDEX)-1U; + for(i = 0; i < numskin; i++) { + if(skin[i].newidx == last) continue; + last = skin[i].newidx; + memset(&weights, 0, nb_s); + for(j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != (M3D_INDEX)-1U && + skin[i].data.weight[j] > (M3D_FLOAT)0.0; j++) + weights[j] = (uint8_t)(skin[i].data.weight[j] * 255); + switch(nb_s) { + case 1: weights[0] = 255; break; + case 2: *((uint16_t*)out) = *((uint16_t*)&weights[0]); out += 2; break; + case 4: *((uint32_t*)out) = *((uint32_t*)&weights[0]); out += 4; break; + case 8: *((uint64_t*)out) = *((uint64_t*)&weights[0]); out += 8; break; + } + for(j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != (M3D_INDEX)-1U && weights[j]; j++) { + out = _m3d_addidx(out, bi_s, skin[i].data.boneid[j]); + *length += bi_s; + } + } + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + out = NULL; + len += *length; + } + /* materials */ + if(model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { + for(j = 0; j < model->nummaterial; j++) { + if(mtrlidx[j] == (M3D_INDEX)-1U || !model->material[j].numprop || !model->material[j].prop) continue; + m = &model->material[j]; + chunklen = 12 + si_s + m->numprop * 5; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "MTRL", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, m->name)); + for(i = 0; i < m->numprop; i++) { + if(m->prop[i].type >= 128) { + if(m->prop[i].value.textureid >= model->numtexture || + !model->texture[m->prop[i].value.textureid].name) continue; + k = m3dpf_map; + } else { + for(k = 256, l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) + if(m->prop[i].type == m3d_propertytypes[l].id) { k = m3d_propertytypes[l].format; break; } + } + if(k == 256) continue; + *out++ = m->prop[i].type; + switch(k) { + case m3dpf_color: + if(!(flags & M3D_EXP_NOCMAP)) { + idx = _m3d_cmapidx(cmap, numcmap, m->prop[i].value.color); + switch(ci_s) { + case 1: *out++ = (uint8_t)(idx); break; + case 2: *((uint16_t*)out) = (uint16_t)(idx); out += 2; break; + case 4: *((uint32_t*)out) = (uint32_t)(m->prop[i].value.color); out += 4; break; + } + } else out--; + break; + case m3dpf_uint8: *out++ = m->prop[i].value.num; break; + case m3dpf_uint16: *((uint16_t*)out) = m->prop[i].value.num; out += 2; break; + case m3dpf_uint32: *((uint32_t*)out) = m->prop[i].value.num; out += 4; break; + case m3dpf_float: *((float*)out) = m->prop[i].value.fnum; out += 4; break; + + case m3dpf_map: + idx = _m3d_stridx(str, numstr, model->texture[m->prop[i].value.textureid].name); + out = _m3d_addidx(out, si_s, idx); + break; + } + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + len += *length; + out = NULL; + } + } + /* procedural face */ + if(model->numinlined && model->inlined && !(flags & M3D_EXP_NOFACE)) { + /* all inlined assets which are not textures should be procedural surfaces */ + for(j = 0; j < model->numinlined; j++) { + if(!model->inlined[j].name || !model->inlined[j].name[0] || model->inlined[j].length < 4 || + !model->inlined[j].data || (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && + model->inlined[j].data[3] == 'G')) + continue; + for(i = k = 0; i < model->numtexture; i++) { + if(!strcmp(model->inlined[j].name, model->texture[i].name)) { k = 1; break; } + } + if(k) continue; + numproc++; + chunklen = 8 + si_s; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "PROC", 4); + *((uint32_t*)((uint8_t*)h + len + 4)) = chunklen; + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->inlined[j].name)); + out = NULL; + len += chunklen; + } + } + /* mesh face */ + if(model->numface && face && !(flags & M3D_EXP_NOFACE)) { + chunklen = 8 + si_s + model->numface * (6 * vi_s + 3 * ti_s + si_s + 1); + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "MESH", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + last = (M3D_INDEX)-1U; + for(i = 0; i < model->numface; i++) { + if(!(flags & M3D_EXP_NOMATERIAL) && face[i].data.materialid != last) { + last = face[i].data.materialid; + idx = last < model->nummaterial ? _m3d_stridx(str, numstr, model->material[last].name) : 0; + *out++ = 0; + out = _m3d_addidx(out, si_s, idx); + } + /* hardcoded triangles. */ + k = (3 << 4) | + (((flags & M3D_EXP_NOTXTCRD) || !ti_s || face[i].data.texcoord[0] == (M3D_INDEX)-1U || + face[i].data.texcoord[1] == (M3D_INDEX)-1U || face[i].data.texcoord[2] == (M3D_INDEX)-1U) ? 0 : 1) | + (((flags & M3D_EXP_NONORMAL) || face[i].data.normal[0] == (M3D_INDEX)-1U || + face[i].data.normal[1] == (M3D_INDEX)-1U || face[i].data.normal[2] == (M3D_INDEX)-1U) ? 0 : 2); + *out++ = k; + for(j = 0; j < 3; j++) { + out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.vertex[j]]); + if(k & 1) + out = _m3d_addidx(out, ti_s, tmapidx[face[i].data.texcoord[j]]); + if(k & 2) + out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.normal[j]]); + } + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + len += *length; + out = NULL; + } + /* mathematical shapes face */ + if(model->numshape && model->shape && !(flags & M3D_EXP_NOFACE)) { + for(j = 0; j < model->numshape; j++) { + chunklen = 12 + si_s + model->shape[j].numcmd * (M3D_CMDMAXARG + 1) * 4; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "SHPE", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->shape[j].name)); + out = _m3d_addidx(out, bi_s, model->shape[j].group); + for(i = 0; i < model->shape[j].numcmd; i++) { + cmd = &model->shape[j].cmd[i]; + if(cmd->type >= (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])) || !cmd->arg) + continue; + cd = &m3d_commandtypes[cmd->type]; + *out++ = (cmd->type & 0x7F) | (cmd->type > 127 ? 0x80 : 0); + if(cmd->type > 127) *out++ = (cmd->type >> 7) & 0xff; + for(k = n = 0, l = cd->p; k < l; k++) { + switch(cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + out = _m3d_addidx(out, si_s, cmd->arg[k] < model->nummaterial ? + _m3d_stridx(str, numstr, model->material[cmd->arg[k]].name) : 0); + break; + case m3dcp_vc_t: + min_x = *((float*)&cmd->arg[k]); + switch(vc_s) { + case 1: *out++ = (int8_t)(min_x * 127); break; + case 2: *((int16_t*)out) = (int16_t)(min_x * 32767); out += 2; break; + case 4: *((float*)out) = min_x; out += 4; break; + case 8: *((double*)out) = min_x; out += 8; break; + } + break; + case m3dcp_hi_t: out = _m3d_addidx(out, hi_s, cmd->arg[k]); break; + case m3dcp_fi_t: out = _m3d_addidx(out, fi_s, cmd->arg[k]); break; + case m3dcp_ti_t: out = _m3d_addidx(out, ti_s, cmd->arg[k]); break; + case m3dcp_qi_t: + case m3dcp_vi_t: out = _m3d_addidx(out, vi_s, cmd->arg[k]); break; + case m3dcp_i1_t: out = _m3d_addidx(out, 1, cmd->arg[k]); break; + case m3dcp_i2_t: out = _m3d_addidx(out, 2, cmd->arg[k]); break; + case m3dcp_i4_t: out = _m3d_addidx(out, 4, cmd->arg[k]); break; + case m3dcp_va_t: out = _m3d_addidx(out, 4, cmd->arg[k]); + n = k + 1; l += (cmd->arg[k] - 1) * (cd->p - k - 1); + break; + } + } + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + len += *length; + out = NULL; + } + } + /* annotation labels */ + if(model->numlabel && model->label) { + for(i = 0, length = NULL; i < model->numlabel; i++) { + if(!i || _m3d_strcmp(sl, model->label[i].lang) || _m3d_strcmp(sn, model->label[i].name)) { + sl = model->label[i].lang; + sn = model->label[i].name; + if(length) { + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + len += *length; + } + chunklen = 8 + 2 * si_s + ci_s + model->numlabel * (vi_s + si_s); + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) { sn = NULL; sl = NULL; goto memerr; } + memcpy((uint8_t*)h + len, "LBLS", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].name)); + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].lang)); + idx = _m3d_cmapidx(cmap, numcmap, model->label[i].color); + switch(ci_s) { + case 1: *out++ = (uint8_t)(idx); break; + case 2: *((uint16_t*)out) = (uint16_t)(idx); out += 2; break; + case 4: *((uint32_t*)out) = model->label[i].color; out += 4; break; + } + } + out = _m3d_addidx(out, vi_s, vrtxidx[model->label[i].vertexid]); + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].text)); + } + if(length) { + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + len += *length; + } + out = NULL; + sn = sl = NULL; + } + /* actions */ + if(model->numaction && model->action && model->numbone && model->bone && !(flags & M3D_EXP_NOACTION)) { + for(j = 0; j < model->numaction; j++) { + a = &model->action[j]; + chunklen = 14 + si_s + a->numframe * (4 + fc_s + maxt * (bi_s + 2 * vi_s)); + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "ACTN", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, a->name)); + *((uint16_t*)out) = (uint16_t)(a->numframe); out += 2; + *((uint32_t*)out) = (uint32_t)(a->durationmsec); out += 4; + for(i = 0; i < a->numframe; i++) { + *((uint32_t*)out) = (uint32_t)(a->frame[i].msec); out += 4; + out = _m3d_addidx(out, fc_s, a->frame[i].numtransform); + for(k = 0; k < a->frame[i].numtransform; k++) { + out = _m3d_addidx(out, bi_s, a->frame[i].transform[k].boneid); + out = _m3d_addidx(out, vi_s, vrtxidx[a->frame[i].transform[k].pos]); + out = _m3d_addidx(out, vi_s, vrtxidx[a->frame[i].transform[k].ori]); + } + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + len += *length; + out = NULL; + } + } + /* inlined assets */ + if(model->numinlined && model->inlined && (numproc || (flags & M3D_EXP_INLINE))) { + for(j = 0; j < model->numinlined; j++) { + if(!model->inlined[j].name || !model->inlined[j].name[0] || model->inlined[j].length<4 || !model->inlined[j].data) + continue; + if(!(flags & M3D_EXP_INLINE)) { + if(model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G') + continue; + for(i = k = 0; i < model->numtexture; i++) { + if(!strcmp(model->inlined[j].name, model->texture[i].name)) { k = 1; break; } + } + if(k) continue; + } + chunklen = 8 + si_s + model->inlined[j].length; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "ASET", 4); + *((uint32_t*)((uint8_t*)h + len + 4)) = chunklen; + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->inlined[j].name)); + memcpy(out, model->inlined[j].data, model->inlined[j].length); + out = NULL; + len += chunklen; + } + } + /* extra chunks */ + if(model->numextra && model->extra && (flags & M3D_EXP_EXTRA)) { + for(j = 0; j < model->numextra; j++) { + if(!model->extra[j] || model->extra[j]->length < 8) + continue; + chunklen = model->extra[j]->length; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, model->extra[j], chunklen); + len += chunklen; + } + } + /* add end chunk */ + h = (m3dhdr_t*)M3D_REALLOC(h, len + 4); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "OMD3", 4); + len += 4; + /* zlib compress */ + if(!(flags & M3D_EXP_NOZLIB)) { + M3D_LOG("Deflating chunks"); + z = stbi_zlib_compress((unsigned char *)h, len, (int*)&l, 9); + if(z && l > 0 && l < len) { len = l; M3D_FREE(h); h = (m3dhdr_t*)z; } + } + /* add file header at the begining */ + len += 8; + out = (unsigned char*)M3D_MALLOC(len); + if(!out) goto memerr; + memcpy(out, "3DMO", 4); + *((uint32_t*)(out + 4)) = len; + memcpy(out + 8, h, len - 8); + } + if(size) *size = out ? len : 0; + if(vrtxidx) M3D_FREE(vrtxidx); + if(mtrlidx) M3D_FREE(mtrlidx); + if(tmapidx) M3D_FREE(tmapidx); + if(skinidx) M3D_FREE(skinidx); + if(norm) M3D_FREE(norm); + if(face) M3D_FREE(face); + if(cmap) M3D_FREE(cmap); + if(tmap) M3D_FREE(tmap); + if(skin) M3D_FREE(skin); + if(str) M3D_FREE(str); + if(vrtx) M3D_FREE(vrtx); + if(h) M3D_FREE(h); + return out; +} +#endif + +#endif + +#ifdef __cplusplus +} +#ifdef M3D_CPPWRAPPER +#include +#include +#include + +/*** C++ wrapper class ***/ +namespace M3D { +#ifdef M3D_IMPLEMENTATION + + class Model { + public: + m3d_t *model; + + public: + Model() { + this->model = (m3d_t*)malloc(sizeof(m3d_t)); memset(this->model, 0, sizeof(m3d_t)); + } + Model(_unused const std::string &data, _unused m3dread_t ReadFileCB, + _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) { +#ifndef M3D_NOIMPORTER + this->model = m3d_load((unsigned char *)data.data(), ReadFileCB, FreeCB, mtllib.model); +#else + Model(); +#endif + } + Model(_unused const std::vector data, _unused m3dread_t ReadFileCB, + _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) { +#ifndef M3D_NOIMPORTER + this->model = m3d_load((unsigned char *)&data[0], ReadFileCB, FreeCB, mtllib.model); +#else + Model(); +#endif + } + Model(_unused const unsigned char *data, _unused m3dread_t ReadFileCB, + _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) { +#ifndef M3D_NOIMPORTER + this->model = m3d_load((unsigned char*)data, ReadFileCB, FreeCB, mtllib.model); +#else + Model(); +#endif + } + ~Model() { m3d_free(this->model); } + + public: + m3d_t *getCStruct() { return this->model; } + std::string getName() { return std::string(this->model->name); } + void setName(std::string name) { this->model->name = (char*)name.c_str(); } + std::string getLicense() { return std::string(this->model->license); } + void setLicense(std::string license) { this->model->license = (char*)license.c_str(); } + std::string getAuthor() { return std::string(this->model->author); } + void setAuthor(std::string author) { this->model->author = (char*)author.c_str(); } + std::string getDescription() { return std::string(this->model->desc); } + void setDescription(std::string desc) { this->model->desc = (char*)desc.c_str(); } + float getScale() { return this->model->scale; } + void setScale(float scale) { this->model->scale = scale; } + std::vector getPreview() { return this->model->preview.data ? + std::vector(this->model->preview.data, this->model->preview.data + this->model->preview.length) : + std::vector(); } + std::vector getColorMap() { return this->model->cmap ? std::vector(this->model->cmap, + this->model->cmap + this->model->numcmap) : std::vector(); } + std::vector getTextureMap() { return this->model->tmap ? std::vector(this->model->tmap, + this->model->tmap + this->model->numtmap) : std::vector(); } + std::vector getTextures() { return this->model->texture ? std::vector(this->model->texture, + this->model->texture + this->model->numtexture) : std::vector(); } + std::string getTextureName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numtexture ? + std::string(this->model->texture[idx].name) : nullptr; } + std::vector getBones() { return this->model->bone ? std::vector(this->model->bone, this->model->bone + + this->model->numbone) : std::vector(); } + std::string getBoneName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numbone ? + std::string(this->model->bone[idx].name) : nullptr; } + std::vector getMaterials() { return this->model->material ? std::vector(this->model->material, + this->model->material + this->model->nummaterial) : std::vector(); } + std::string getMaterialName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->nummaterial ? + std::string(this->model->material[idx].name) : nullptr; } + int getMaterialPropertyInt(int idx, int type) { + if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 0 || type >= 127 || + !this->model->material[idx].prop) return -1; + for (int i = 0; i < this->model->material[idx].numprop; i++) { + if (this->model->material[idx].prop[i].type == type) + return this->model->material[idx].prop[i].value.num; + } + return -1; + } + uint32_t getMaterialPropertyColor(int idx, int type) { return this->getMaterialPropertyInt(idx, type); } + float getMaterialPropertyFloat(int idx, int type) { + if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 0 || type >= 127 || + !this->model->material[idx].prop) return -1.0f; + for (int i = 0; i < this->model->material[idx].numprop; i++) { + if (this->model->material[idx].prop[i].type == type) + return this->model->material[idx].prop[i].value.fnum; + } + return -1.0f; + } + m3dtx_t* getMaterialPropertyMap(int idx, int type) { + if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 128 || type > 255 || + !this->model->material[idx].prop) return nullptr; + for (int i = 0; i < this->model->material[idx].numprop; i++) { + if (this->model->material[idx].prop[i].type == type) + return this->model->material[idx].prop[i].value.textureid < this->model->numtexture ? + &this->model->texture[this->model->material[idx].prop[i].value.textureid] : nullptr; + } + return nullptr; + } + std::vector getVertices() { return this->model->vertex ? std::vector(this->model->vertex, + this->model->vertex + this->model->numvertex) : std::vector(); } + std::vector getFace() { return this->model->face ? std::vector(this->model->face, this->model->face + + this->model->numface) : std::vector(); } + std::vector getShape() { return this->model->shape ? std::vector(this->model->shape, + this->model->shape + this->model->numshape) : std::vector(); } + std::string getShapeName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape && + this->model->shape[idx].name && this->model->shape[idx].name[0] ? + std::string(this->model->shape[idx].name) : nullptr; } + unsigned int getShapeGroup(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape ? + this->model->shape[idx].group : 0xFFFFFFFF; } + std::vector getShapeCommands(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape && + this->model->shape[idx].cmd ? std::vector(this->model->shape[idx].cmd, this->model->shape[idx].cmd + + this->model->shape[idx].numcmd) : std::vector(); } + std::vector getAnnotationLabels() { return this->model->label ? std::vector(this->model->label, + this->model->label + this->model->numlabel) : std::vector(); } + std::vector getSkin() { return this->model->skin ? std::vector(this->model->skin, this->model->skin + + this->model->numskin) : std::vector(); } + std::vector getActions() { return this->model->action ? std::vector(this->model->action, + this->model->action + this->model->numaction) : std::vector(); } + std::string getActionName(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? + std::string(this->model->action[aidx].name) : nullptr; } + unsigned int getActionDuration(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? + this->model->action[aidx].durationmsec : 0; } + std::vector getActionFrames(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? + std::vector(this->model->action[aidx].frame, this->model->action[aidx].frame + + this->model->action[aidx].numframe) : std::vector(); } + unsigned int getActionFrameTimestamp(int aidx, int fidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction? + (fidx >= 0 && (unsigned int)fidx < this->model->action[aidx].numframe ? + this->model->action[aidx].frame[fidx].msec : 0) : 0; } + std::vector getActionFrameTransforms(int aidx, int fidx) { + return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? ( + fidx >= 0 && (unsigned int)fidx < this->model->action[aidx].numframe ? + std::vector(this->model->action[aidx].frame[fidx].transform, + this->model->action[aidx].frame[fidx].transform + this->model->action[aidx].frame[fidx].numtransform) : + std::vector()) : std::vector(); } + std::vector getActionFrame(int aidx, int fidx, std::vector skeleton) { + m3dtr_t *pose = m3d_frame(this->model, (unsigned int)aidx, (unsigned int)fidx, + skeleton.size() ? &skeleton[0] : nullptr); + return std::vector(pose, pose + this->model->numbone); } + std::vector getActionPose(int aidx, unsigned int msec) { + m3db_t *pose = m3d_pose(this->model, (unsigned int)aidx, (unsigned int)msec); + return std::vector(pose, pose + this->model->numbone); } + std::vector getInlinedAssets() { return this->model->inlined ? std::vector(this->model->inlined, + this->model->inlined + this->model->numinlined) : std::vector(); } + std::vector> getExtras() { return this->model->extra ? + std::vector>(this->model->extra, + this->model->extra + this->model->numextra) : std::vector>(); } + std::vector Save(_unused int quality, _unused int flags) { +#ifdef M3D_EXPORTER + unsigned int size; + unsigned char *ptr = m3d_save(this->model, quality, flags, &size); + return ptr && size ? std::vector(ptr, ptr + size) : std::vector(); +#else + return std::vector(); +#endif + } + }; + +#else + class Model { + public: + m3d_t *model; + + public: + Model(const std::string &data, m3dread_t ReadFileCB, m3dfree_t FreeCB); + Model(const std::vector data, m3dread_t ReadFileCB, m3dfree_t FreeCB); + Model(const unsigned char *data, m3dread_t ReadFileCB, m3dfree_t FreeCB); + Model(); + ~Model(); + + public: + m3d_t *getCStruct(); + std::string getName(); + void setName(std::string name); + std::string getLicense(); + void setLicense(std::string license); + std::string getAuthor(); + void setAuthor(std::string author); + std::string getDescription(); + void setDescription(std::string desc); + float getScale(); + void setScale(float scale); + std::vector getPreview(); + std::vector getColorMap(); + std::vector getTextureMap(); + std::vector getTextures(); + std::string getTextureName(int idx); + std::vector getBones(); + std::string getBoneName(int idx); + std::vector getMaterials(); + std::string getMaterialName(int idx); + int getMaterialPropertyInt(int idx, int type); + uint32_t getMaterialPropertyColor(int idx, int type); + float getMaterialPropertyFloat(int idx, int type); + m3dtx_t* getMaterialPropertyMap(int idx, int type); + std::vector getVertices(); + std::vector getFace(); + std::vector getShape(); + std::string getShapeName(int idx); + unsigned int getShapeGroup(int idx); + std::vector getShapeCommands(int idx); + std::vector getAnnotationLabels(); + std::vector getSkin(); + std::vector getActions(); + std::string getActionName(int aidx); + unsigned int getActionDuration(int aidx); + std::vector getActionFrames(int aidx); + unsigned int getActionFrameTimestamp(int aidx, int fidx); + std::vector getActionFrameTransforms(int aidx, int fidx); + std::vector getActionFrame(int aidx, int fidx, std::vector skeleton); + std::vector getActionPose(int aidx, unsigned int msec); + std::vector getInlinedAssets(); + std::vector> getExtras(); + std::vector Save(int quality, int flags); + }; + +#endif /* impl */ +} +#endif + +#endif /* __cplusplus */ + +#endif diff --git a/code/MD2/MD2Loader.cpp b/code/MD2/MD2Loader.cpp index a26f70533..7023c0fa3 100644 --- a/code/MD2/MD2Loader.cpp +++ b/code/MD2/MD2Loader.cpp @@ -344,7 +344,7 @@ void MD2Importer::InternReadFile( const std::string& pFile, if (pcSkins->name[0]) { aiString szString; - const size_t iLen = ::strlen(pcSkins->name); + const ai_uint32 iLen = (ai_uint32) ::strlen(pcSkins->name); ::memcpy(szString.data,pcSkins->name,iLen); szString.data[iLen] = '\0'; szString.length = iLen; diff --git a/code/MD5/MD5Loader.cpp b/code/MD5/MD5Loader.cpp index 38c44b515..a4aed8d70 100644 --- a/code/MD5/MD5Loader.cpp +++ b/code/MD5/MD5Loader.cpp @@ -53,6 +53,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "MD5Loader.h" #include #include +#include #include #include #include @@ -64,7 +65,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; // Minimum weight value. Weights inside [-n ... n] are ignored -#define AI_MD5_WEIGHT_EPSILON 1e-5f +#define AI_MD5_WEIGHT_EPSILON Math::getEpsilon() static const aiImporterDesc desc = { diff --git a/code/MD5/MD5Parser.cpp b/code/MD5/MD5Parser.cpp index b955e9cbc..37490212f 100644 --- a/code/MD5/MD5Parser.cpp +++ b/code/MD5/MD5Parser.cpp @@ -235,7 +235,7 @@ bool MD5Parser::ParseSection(Section& out) const char* szStart = ++sz; \ while('\"'!=*sz)++sz; \ const char* szEnd = (sz++); \ - out.length = (size_t)(szEnd - szStart); \ + out.length = (ai_uint32) (szEnd - szStart); \ ::memcpy(out.data,szStart,out.length); \ out.data[out.length] = '\0'; // ------------------------------------------------------------------------------------------------ diff --git a/code/MDL/MDLLoader.cpp b/code/MDL/MDLLoader.cpp index 9ca36c079..d71057d55 100644 --- a/code/MDL/MDLLoader.cpp +++ b/code/MDL/MDLLoader.cpp @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -54,7 +52,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "MDL/MDLDefaultColorMap.h" #include "MD2/MD2FileData.h" -#include #include #include #include @@ -94,23 +91,24 @@ static const aiImporterDesc desc = { // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer MDLImporter::MDLImporter() - : configFrameID(), - mBuffer(), - iGSFileVersion(), - pIOHandler(), - pScene(), - iFileSize() -{} +: configFrameID() +, mBuffer() +, iGSFileVersion() +, pIOHandler() +, pScene() +, iFileSize() { + // empty +} // ------------------------------------------------------------------------------------------------ // Destructor, private as well -MDLImporter::~MDLImporter() -{} +MDLImporter::~MDLImporter() { + // empty +} // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. -bool MDLImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const -{ +bool MDLImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const { const std::string extension = GetExtension(pFile); // if check for extension is not enough, check for the magic tokens @@ -404,23 +402,15 @@ void MDLImporter::InternReadFile_Quake1() { // now get a pointer to the first frame in the file BE_NCONST MDL::Frame* pcFrames = (BE_NCONST MDL::Frame*)szCurrent; - BE_NCONST MDL::SimpleFrame* pcFirstFrame; + MDL::SimpleFrame* pcFirstFrame; if (0 == pcFrames->type) { // get address of single frame - pcFirstFrame = &pcFrames->frame; + pcFirstFrame =( MDL::SimpleFrame*) &pcFrames->frame; } else { // get the first frame in the group - -#if 1 - // FIXME: the cast is wrong and cause a warning on clang 5.0 - // disable this code for now, fix it later - ai_assert(false && "Bad pointer cast"); - pcFirstFrame = nullptr; // Workaround: msvc++ C4703 error -#else - BE_NCONST MDL::GroupFrame* pcFrames2 = (BE_NCONST MDL::GroupFrame*)pcFrames; - pcFirstFrame = (BE_NCONST MDL::SimpleFrame*)(&pcFrames2->time + pcFrames->type); -#endif + BE_NCONST MDL::GroupFrame* pcFrames2 = (BE_NCONST MDL::GroupFrame*) pcFrames; + pcFirstFrame = &(pcFrames2->frames[0]); } BE_NCONST MDL::Vertex* pcVertices = (BE_NCONST MDL::Vertex*) ((pcFirstFrame->name) + sizeof(pcFirstFrame->name)); VALIDATE_FILE_SIZE((const unsigned char*)(pcVertices + pcHeader->num_verts)); diff --git a/code/MDL/MDLLoader.h b/code/MDL/MDLLoader.h index bbe945529..c5ebac024 100644 --- a/code/MDL/MDLLoader.h +++ b/code/MDL/MDLLoader.h @@ -89,16 +89,12 @@ public: MDLImporter(); ~MDLImporter(); - -public: - // ------------------------------------------------------------------- /** Returns whether the class can handle the format of the given file. * See BaseImporter::CanRead() for details. */ bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const; - // ------------------------------------------------------------------- /** Called prior to ReadFile(). * The function is a request to the importer to update its configuration @@ -107,8 +103,6 @@ public: void SetupProperties(const Importer* pImp); protected: - - // ------------------------------------------------------------------- /** Return importer meta information. * See #BaseImporter::GetInfo for the details @@ -122,8 +116,6 @@ protected: void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); -protected: - // ------------------------------------------------------------------- /** Import a quake 1 MDL file (IDPO) */ @@ -154,7 +146,6 @@ protected: void SizeCheck(const void* szPos); void SizeCheck(const void* szPos, const char* szFile, unsigned int iLine); - // ------------------------------------------------------------------- /** Validate the header data structure of a game studio MDL7 file * \param pcHeader Input header to be validated @@ -167,7 +158,6 @@ protected: */ void ValidateHeader_Quake1(const MDL::Header* pcHeader); - // ------------------------------------------------------------------- /** Try to load a palette from the current directory (colormap.lmp) * If it is not found the default palette of Quake1 is returned @@ -179,9 +169,8 @@ protected: */ void FreePalette(const unsigned char* pszColorMap); - // ------------------------------------------------------------------- - /** Load a paletized texture from the file and convert it to 32bpp + /** Load a palletized texture from the file and convert it to 32bpp */ void CreateTextureARGB8_3DGS_MDL3(const unsigned char* szData); @@ -195,7 +184,6 @@ protected: unsigned int iType, unsigned int* piSkip); - // ------------------------------------------------------------------- /** Used to load textures from MDL5 * \param szData Input data @@ -206,7 +194,6 @@ protected: unsigned int iType, unsigned int* piSkip); - // ------------------------------------------------------------------- /** Checks whether a texture can be replaced with a single color * This is useful for all file formats before MDL7 (all those @@ -218,14 +205,12 @@ protected: */ aiColor4D ReplaceTextureWithColor(const aiTexture* pcTexture); - // ------------------------------------------------------------------- /** Converts the absolute texture coordinates in MDL5 files to * relative in a range between 0 and 1 */ void CalculateUVCoordinates_MDL5(); - // ------------------------------------------------------------------- /** Read an UV coordinate from the file. If the file format is not * MDL5, the function calculates relative texture coordinates @@ -245,7 +230,6 @@ protected: */ void SetupMaterialProperties_3DGS_MDL5_Quake1( ); - // ------------------------------------------------------------------- /** Parse a skin lump in a MDL7/HMP7 file with all of its features * variant 1: Current cursor position is the beginning of the skin header diff --git a/code/Material/MaterialSystem.cpp b/code/Material/MaterialSystem.cpp index d0b39093b..fabd9415a 100644 --- a/code/Material/MaterialSystem.cpp +++ b/code/Material/MaterialSystem.cpp @@ -51,7 +51,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include using namespace Assimp; @@ -472,12 +471,12 @@ aiReturn aiMaterial::AddBinaryProperty (const void* pInput, aiPropertyTypeInfo pType ) { - ai_assert( pInput != NULL ); - ai_assert( pKey != NULL ); + ai_assert( pInput != nullptr ); + ai_assert(pKey != nullptr ); ai_assert( 0 != pSizeInBytes ); if ( 0 == pSizeInBytes ) { - + return AI_FAILURE; } // first search the list whether there is already an entry with this key @@ -545,23 +544,7 @@ aiReturn aiMaterial::AddProperty (const aiString* pInput, unsigned int type, unsigned int index) { - // We don't want to add the whole buffer .. write a 32 bit length - // prefix followed by the zero-terminated UTF8 string. - // (HACK) I don't want to break the ABI now, but we definitely - // ought to change aiString::mLength to uint32_t one day. - if (sizeof(size_t) == 8) { - aiString copy = *pInput; - uint32_t* s = reinterpret_cast(©.length); - s[1] = static_cast(pInput->length); - - return AddBinaryProperty(s+1, - static_cast(pInput->length+1+4), - pKey, - type, - index, - aiPTI_String); - } - ai_assert(sizeof(size_t)==4); + ai_assert(sizeof(ai_uint32)==4); return AddBinaryProperty(pInput, static_cast(pInput->length+1+4), pKey, diff --git a/code/Obj/ObjFileImporter.cpp b/code/Obj/ObjFileImporter.cpp index 549956474..26cc6d1f9 100644 --- a/code/Obj/ObjFileImporter.cpp +++ b/code/Obj/ObjFileImporter.cpp @@ -79,10 +79,7 @@ using namespace std; ObjFileImporter::ObjFileImporter() : m_Buffer() , m_pRootObject( nullptr ) -, m_strAbsPath( "" ) { - DefaultIOSystem io; - m_strAbsPath = io.getOsSeparator(); -} +, m_strAbsPath( std::string(1, DefaultIOSystem().getOsSeparator()) ) {} // ------------------------------------------------------------------------------------------------ // Destructor. diff --git a/code/Obj/ObjFileParser.cpp b/code/Obj/ObjFileParser.cpp index d1603c6f0..699aafe6a 100644 --- a/code/Obj/ObjFileParser.cpp +++ b/code/Obj/ObjFileParser.cpp @@ -118,7 +118,7 @@ void ObjFileParser::parseFile( IOStreamBuffer &streamBuffer ) { size_t lastFilePos( 0 ); std::vector buffer; - while ( streamBuffer.getNextDataLine( buffer, '\0' ) ) { + while ( streamBuffer.getNextDataLine( buffer, '\\' ) ) { m_DataIt = buffer.begin(); m_DataItEnd = buffer.end(); @@ -244,8 +244,8 @@ void ObjFileParser::copyNextWord(char *pBuffer, size_t length) { size_t index = 0; m_DataIt = getNextWord(m_DataIt, m_DataItEnd); if ( *m_DataIt == '\\' ) { - m_DataIt++; - m_DataIt++; + ++m_DataIt; + ++m_DataIt; m_DataIt = getNextWord( m_DataIt, m_DataItEnd ); } while( m_DataIt != m_DataItEnd && !IsSpaceOrNewLine( *m_DataIt ) ) { diff --git a/code/Ply/PlyLoader.cpp b/code/Ply/PlyLoader.cpp index 3924305cb..ca1ec22f8 100644 --- a/code/Ply/PlyLoader.cpp +++ b/code/Ply/PlyLoader.cpp @@ -49,7 +49,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // internal headers #include "PlyLoader.h" #include -#include #include #include #include diff --git a/code/PostProcessing/ArmaturePopulate.cpp b/code/PostProcessing/ArmaturePopulate.cpp new file mode 100644 index 000000000..75daeb6b5 --- /dev/null +++ b/code/PostProcessing/ArmaturePopulate.cpp @@ -0,0 +1,268 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#include "ArmaturePopulate.h" + +#include +#include +#include +#include +#include + +namespace Assimp { + +/// The default class constructor. +ArmaturePopulate::ArmaturePopulate() : BaseProcess() +{} + +/// The class destructor. +ArmaturePopulate::~ArmaturePopulate() +{} + +bool ArmaturePopulate::IsActive(unsigned int pFlags) const { + return (pFlags & aiProcess_PopulateArmatureData) != 0; +} + +void ArmaturePopulate::SetupProperties(const Importer *pImp) { + // do nothing +} + +void ArmaturePopulate::Execute(aiScene *out) { + + // Now convert all bone positions to the correct mOffsetMatrix + std::vector bones; + std::vector nodes; + std::map bone_stack; + BuildBoneList(out->mRootNode, out->mRootNode, out, bones); + BuildNodeList(out->mRootNode, nodes); + + BuildBoneStack(out->mRootNode, out->mRootNode, out, bones, bone_stack, nodes); + + ASSIMP_LOG_DEBUG_F("Bone stack size: ", bone_stack.size()); + + for (std::pair kvp : bone_stack) { + aiBone *bone = kvp.first; + aiNode *bone_node = kvp.second; + ASSIMP_LOG_DEBUG_F("active node lookup: ", bone->mName.C_Str()); + // lcl transform grab - done in generate_nodes :) + + // bone->mOffsetMatrix = bone_node->mTransformation; + aiNode *armature = GetArmatureRoot(bone_node, bones); + + ai_assert(armature); + + // set up bone armature id + bone->mArmature = armature; + + // set this bone node to be referenced properly + ai_assert(bone_node); + bone->mNode = bone_node; + } +} + + +/* Reprocess all nodes to calculate bone transforms properly based on the REAL + * mOffsetMatrix not the local. */ +/* Before this would use mesh transforms which is wrong for bone transforms */ +/* Before this would work for simple character skeletons but not complex meshes + * with multiple origins */ +/* Source: sketch fab log cutter fbx */ +void ArmaturePopulate::BuildBoneList(aiNode *current_node, + const aiNode *root_node, + const aiScene *scene, + std::vector &bones) { + ai_assert(scene); + for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) { + aiNode *child = current_node->mChildren[nodeId]; + ai_assert(child); + + // check for bones + for (unsigned int meshId = 0; meshId < child->mNumMeshes; ++meshId) { + ai_assert(child->mMeshes); + unsigned int mesh_index = child->mMeshes[meshId]; + aiMesh *mesh = scene->mMeshes[mesh_index]; + ai_assert(mesh); + + for (unsigned int boneId = 0; boneId < mesh->mNumBones; ++boneId) { + aiBone *bone = mesh->mBones[boneId]; + ai_assert(bone); + + // duplicate meshes exist with the same bones sometimes :) + // so this must be detected + if (std::find(bones.begin(), bones.end(), bone) == bones.end()) { + // add the element once + bones.push_back(bone); + } + } + + // find mesh and get bones + // then do recursive lookup for bones in root node hierarchy + } + + BuildBoneList(child, root_node, scene, bones); + } +} + +/* Prepare flat node list which can be used for non recursive lookups later */ +void ArmaturePopulate::BuildNodeList(const aiNode *current_node, + std::vector &nodes) { + ai_assert(current_node); + + for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) { + aiNode *child = current_node->mChildren[nodeId]; + ai_assert(child); + + nodes.push_back(child); + + BuildNodeList(child, nodes); + } +} + +/* A bone stack allows us to have multiple armatures, with the same bone names + * A bone stack allows us also to retrieve bones true transform even with + * duplicate names :) + */ +void ArmaturePopulate::BuildBoneStack(aiNode *current_node, + const aiNode *root_node, + const aiScene *scene, + const std::vector &bones, + std::map &bone_stack, + std::vector &node_stack) { + ai_assert(scene); + ai_assert(root_node); + ai_assert(!node_stack.empty()); + + for (aiBone *bone : bones) { + ai_assert(bone); + aiNode *node = GetNodeFromStack(bone->mName, node_stack); + if (node == nullptr) { + node_stack.clear(); + BuildNodeList(root_node, node_stack); + ASSIMP_LOG_DEBUG_F("Resetting bone stack: nullptr element ", bone->mName.C_Str()); + + node = GetNodeFromStack(bone->mName, node_stack); + + if (!node) { + ASSIMP_LOG_ERROR("serious import issue node for bone was not detected"); + continue; + } + } + + ASSIMP_LOG_DEBUG_F("Successfully added bone[", bone->mName.C_Str(), "] to stack and bone node is: ", node->mName.C_Str()); + + bone_stack.insert(std::pair(bone, node)); + } +} + + +/* Returns the armature root node */ +/* This is required to be detected for a bone initially, it will recurse up + * until it cannot find another bone and return the node No known failure + * points. (yet) + */ +aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node, + std::vector &bone_list) { + while (bone_node) { + if (!IsBoneNode(bone_node->mName, bone_list)) { + ASSIMP_LOG_DEBUG_F("GetArmatureRoot() Found valid armature: ", bone_node->mName.C_Str()); + return bone_node; + } + + bone_node = bone_node->mParent; + } + + ASSIMP_LOG_ERROR("GetArmatureRoot() can't find armature!"); + + return nullptr; +} + + + +/* Simple IsBoneNode check if this could be a bone */ +bool ArmaturePopulate::IsBoneNode(const aiString &bone_name, + std::vector &bones) { + for (aiBone *bone : bones) { + if (bone->mName == bone_name) { + return true; + } + } + + return false; +} + +/* Pop this node by name from the stack if found */ +/* Used in multiple armature situations with duplicate node / bone names */ +/* Known flaw: cannot have nodes with bone names, will be fixed in later release + */ +/* (serious to be fixed) Known flaw: nodes which have more than one bone could + * be prematurely dropped from stack */ +aiNode *ArmaturePopulate::GetNodeFromStack(const aiString &node_name, + std::vector &nodes) { + std::vector::iterator iter; + aiNode *found = nullptr; + for (iter = nodes.begin(); iter < nodes.end(); ++iter) { + aiNode *element = *iter; + ai_assert(element); + // node valid and node name matches + if (element->mName == node_name) { + found = element; + break; + } + } + + if (found != nullptr) { + ASSIMP_LOG_INFO_F("Removed node from stack: ", found->mName.C_Str()); + // now pop the element from the node list + nodes.erase(iter); + + return found; + } + + // unique names can cause this problem + ASSIMP_LOG_ERROR("[Serious] GetNodeFromStack() can't find node from stack!"); + + return nullptr; +} + + + + +} // Namespace Assimp diff --git a/code/PostProcessing/ArmaturePopulate.h b/code/PostProcessing/ArmaturePopulate.h new file mode 100644 index 000000000..aa1ad7c80 --- /dev/null +++ b/code/PostProcessing/ArmaturePopulate.h @@ -0,0 +1,112 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#ifndef ARMATURE_POPULATE_H_ +#define ARMATURE_POPULATE_H_ + +#include "Common/BaseProcess.h" +#include +#include +#include + + +struct aiNode; +struct aiBone; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** Armature Populate: This is a post process designed + * To save you time when importing models into your game engines + * This was originally designed only for fbx but will work with other formats + * it is intended to auto populate aiBone data with armature and the aiNode + * This is very useful when dealing with skinned meshes + * or when dealing with many different skeletons + * It's off by default but recommend that you try it and use it + * It should reduce down any glue code you have in your + * importers + * You can contact RevoluPowered + * For more info about this +*/ +class ASSIMP_API ArmaturePopulate : public BaseProcess { +public: + /// The default class constructor. + ArmaturePopulate(); + + /// The class destructor. + virtual ~ArmaturePopulate(); + + /// Overwritten, @see BaseProcess + virtual bool IsActive( unsigned int pFlags ) const; + + /// Overwritten, @see BaseProcess + virtual void SetupProperties( const Importer* pImp ); + + /// Overwritten, @see BaseProcess + virtual void Execute( aiScene* pScene ); + + static aiNode *GetArmatureRoot(aiNode *bone_node, + std::vector &bone_list); + + static bool IsBoneNode(const aiString &bone_name, + std::vector &bones); + + static aiNode *GetNodeFromStack(const aiString &node_name, + std::vector &nodes); + + static void BuildNodeList(const aiNode *current_node, + std::vector &nodes); + + static void BuildBoneList(aiNode *current_node, const aiNode *root_node, + const aiScene *scene, + std::vector &bones); + + static void BuildBoneStack(aiNode *current_node, const aiNode *root_node, + const aiScene *scene, + const std::vector &bones, + std::map &bone_stack, + std::vector &node_stack); +}; + +} // Namespace Assimp + + +#endif // SCALE_PROCESS_H_ \ No newline at end of file diff --git a/code/PostProcessing/CalcTangentsProcess.cpp b/code/PostProcessing/CalcTangentsProcess.cpp index b30f39c27..a3f7dd255 100644 --- a/code/PostProcessing/CalcTangentsProcess.cpp +++ b/code/PostProcessing/CalcTangentsProcess.cpp @@ -212,7 +212,7 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) // project tangent and bitangent into the plane formed by the vertex' normal aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]); aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]); - localTangent.Normalize(); localBitangent.Normalize(); + localTangent.NormalizeSafe(); localBitangent.NormalizeSafe(); // reconstruct tangent/bitangent according to normal and bitangent/tangent when it's infinite or NaN. bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z); @@ -220,10 +220,10 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) if (invalid_tangent != invalid_bitangent) { if (invalid_tangent) { localTangent = meshNorm[p] ^ localBitangent; - localTangent.Normalize(); + localTangent.NormalizeSafe(); } else { localBitangent = localTangent ^ meshNorm[p]; - localBitangent.Normalize(); + localBitangent.NormalizeSafe(); } } diff --git a/code/PostProcessing/ComputeUVMappingProcess.cpp b/code/PostProcessing/ComputeUVMappingProcess.cpp index bb571a551..df4d44337 100644 --- a/code/PostProcessing/ComputeUVMappingProcess.cpp +++ b/code/PostProcessing/ComputeUVMappingProcess.cpp @@ -354,12 +354,12 @@ void ComputeUVMappingProcess::ComputePlaneMapping(aiMesh* mesh,const aiVector3D& } else if (axis * base_axis_z >= angle_epsilon) { FindMeshCenter(mesh, center, min, max); - diffu = max.y - min.y; - diffv = max.z - min.z; + diffu = max.x - min.x; + diffv = max.y - min.y; for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt) { const aiVector3D& pos = mesh->mVertices[pnt]; - out[pnt].Set((pos.y - min.y) / diffu,(pos.x - min.x) / diffv,0.0); + out[pnt].Set((pos.x - min.x) / diffu,(pos.y - min.y) / diffv,0.0); } } // slower code path in case the mapping axis is not one of the coordinate system axes diff --git a/code/PostProcessing/FindInvalidDataProcess.cpp b/code/PostProcessing/FindInvalidDataProcess.cpp index 433f04244..016884c6e 100644 --- a/code/PostProcessing/FindInvalidDataProcess.cpp +++ b/code/PostProcessing/FindInvalidDataProcess.cpp @@ -52,7 +52,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "FindInvalidDataProcess.h" #include "ProcessHelper.h" -#include #include #include diff --git a/code/PostProcessing/JoinVerticesProcess.cpp b/code/PostProcessing/JoinVerticesProcess.cpp index 914ec05b4..f121fc60d 100644 --- a/code/PostProcessing/JoinVerticesProcess.cpp +++ b/code/PostProcessing/JoinVerticesProcess.cpp @@ -431,31 +431,6 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) bone->mWeights = new aiVertexWeight[bone->mNumWeights]; memcpy( bone->mWeights, &newWeights[0], bone->mNumWeights * sizeof( aiVertexWeight)); } - else { - - /* NOTE: - * - * In the algorithm above we're assuming that there are no vertices - * with a different bone weight setup at the same position. That wouldn't - * make sense, but it is not absolutely impossible. SkeletonMeshBuilder - * for example generates such input data if two skeleton points - * share the same position. Again this doesn't make sense but is - * reality for some model formats (MD5 for example uses these special - * nodes as attachment tags for its weapons). - * - * Then it is possible that a bone has no weights anymore .... as a quick - * workaround, we're just removing these bones. If they're animated, - * model geometry might be modified but at least there's no risk of a crash. - */ - delete bone; - --pMesh->mNumBones; - for (unsigned int n = a; n < pMesh->mNumBones; ++n) { - pMesh->mBones[n] = pMesh->mBones[n+1]; - } - - --a; - ASSIMP_LOG_WARN("Removing bone -> no weights remaining"); - } } return pMesh->mNumVertices; } diff --git a/code/PostProcessing/MakeVerboseFormat.cpp b/code/PostProcessing/MakeVerboseFormat.cpp index 50ff5ed93..41f50a5ba 100644 --- a/code/PostProcessing/MakeVerboseFormat.cpp +++ b/code/PostProcessing/MakeVerboseFormat.cpp @@ -224,3 +224,32 @@ bool MakeVerboseFormatProcess::MakeVerboseFormat(aiMesh* pcMesh) } return (pcMesh->mNumVertices != iOldNumVertices); } + + +// ------------------------------------------------------------------------------------------------ +bool IsMeshInVerboseFormat(const aiMesh* mesh) { + // avoid slow vector specialization + std::vector seen(mesh->mNumVertices,0); + for(unsigned int i = 0; i < mesh->mNumFaces; ++i) { + const aiFace& f = mesh->mFaces[i]; + for(unsigned int j = 0; j < f.mNumIndices; ++j) { + if(++seen[f.mIndices[j]] == 2) { + // found a duplicate index + return false; + } + } + } + + return true; +} + +// ------------------------------------------------------------------------------------------------ +bool MakeVerboseFormatProcess::IsVerboseFormat(const aiScene* pScene) { + for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + if(!IsMeshInVerboseFormat(pScene->mMeshes[i])) { + return false; + } + } + + return true; +} diff --git a/code/PostProcessing/MakeVerboseFormat.h b/code/PostProcessing/MakeVerboseFormat.h index 1adf8e2f6..8565d5933 100644 --- a/code/PostProcessing/MakeVerboseFormat.h +++ b/code/PostProcessing/MakeVerboseFormat.h @@ -94,6 +94,13 @@ public: * @param pScene The imported data to work at. */ void Execute( aiScene* pScene); +public: + + // ------------------------------------------------------------------- + /** Checks whether the scene is already in verbose format. + * @param pScene The data to check. + * @return true if the scene is already in verbose format. */ + static bool IsVerboseFormat(const aiScene* pScene); private: diff --git a/code/PostProcessing/ScaleProcess.cpp b/code/PostProcessing/ScaleProcess.cpp index 6d458c4b1..ac770c41f 100644 --- a/code/PostProcessing/ScaleProcess.cpp +++ b/code/PostProcessing/ScaleProcess.cpp @@ -39,19 +39,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -#ifndef ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS - #include "ScaleProcess.h" #include #include +#include namespace Assimp { ScaleProcess::ScaleProcess() : BaseProcess() , mScale( AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT ) { - // empty } ScaleProcess::~ScaleProcess() { @@ -71,10 +69,26 @@ bool ScaleProcess::IsActive( unsigned int pFlags ) const { } void ScaleProcess::SetupProperties( const Importer* pImp ) { - mScale = pImp->GetPropertyFloat( AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY, 0 ); + // User scaling + mScale = pImp->GetPropertyFloat( AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY, 1.0f ); + + // File scaling * Application Scaling + float importerScale = pImp->GetPropertyFloat( AI_CONFIG_APP_SCALE_KEY, 1.0f ); + + // apply scale to the scale + // helps prevent bugs with backward compatibility for anyone using normal scaling. + mScale *= importerScale; } void ScaleProcess::Execute( aiScene* pScene ) { + if(mScale == 1.0f) { + return; // nothing to scale + } + + ai_assert( mScale != 0 ); + ai_assert( nullptr != pScene ); + ai_assert( nullptr != pScene->mRootNode ); + if ( nullptr == pScene ) { return; } @@ -82,22 +96,113 @@ void ScaleProcess::Execute( aiScene* pScene ) { if ( nullptr == pScene->mRootNode ) { return; } + + // Process animations and update position transform to new unit system + for( unsigned int animationID = 0; animationID < pScene->mNumAnimations; animationID++ ) + { + aiAnimation* animation = pScene->mAnimations[animationID]; + + for( unsigned int animationChannel = 0; animationChannel < animation->mNumChannels; animationChannel++) + { + aiNodeAnim* anim = animation->mChannels[animationChannel]; + + for( unsigned int posKey = 0; posKey < anim->mNumPositionKeys; posKey++) + { + aiVectorKey& vectorKey = anim->mPositionKeys[posKey]; + vectorKey.mValue *= mScale; + } + } + } + + for( unsigned int meshID = 0; meshID < pScene->mNumMeshes; meshID++) + { + aiMesh *mesh = pScene->mMeshes[meshID]; + + // Reconstruct mesh vertexes to the new unit system + for( unsigned int vertexID = 0; vertexID < mesh->mNumVertices; vertexID++) + { + aiVector3D& vertex = mesh->mVertices[vertexID]; + vertex *= mScale; + } + + + // bone placement / scaling + for( unsigned int boneID = 0; boneID < mesh->mNumBones; boneID++) + { + // Reconstruct matrix by transform rather than by scale + // This prevent scale values being changed which can + // be meaningful in some cases + // like when you want the modeller to see 1:1 compatibility. + aiBone* bone = mesh->mBones[boneID]; + + aiVector3D pos, scale; + aiQuaternion rotation; + + bone->mOffsetMatrix.Decompose( scale, rotation, pos); + + aiMatrix4x4 translation; + aiMatrix4x4::Translation( pos * mScale, translation ); + + aiMatrix4x4 scaling; + aiMatrix4x4::Scaling( aiVector3D(scale), scaling ); + + aiMatrix4x4 RotMatrix = aiMatrix4x4 (rotation.GetMatrix()); + + bone->mOffsetMatrix = translation * RotMatrix * scaling; + } + + + // animation mesh processing + // convert by position rather than scale. + for( unsigned int animMeshID = 0; animMeshID < mesh->mNumAnimMeshes; animMeshID++) + { + aiAnimMesh * animMesh = mesh->mAnimMeshes[animMeshID]; + + for( unsigned int vertexID = 0; vertexID < animMesh->mNumVertices; vertexID++) + { + aiVector3D& vertex = animMesh->mVertices[vertexID]; + vertex *= mScale; + } + } + } traverseNodes( pScene->mRootNode ); } -void ScaleProcess::traverseNodes( aiNode *node ) { +void ScaleProcess::traverseNodes( aiNode *node, unsigned int nested_node_id ) { applyScaling( node ); + + for( size_t i = 0; i < node->mNumChildren; i++) + { + // recurse into the tree until we are done! + traverseNodes( node->mChildren[i], nested_node_id+1 ); + } } void ScaleProcess::applyScaling( aiNode *currentNode ) { if ( nullptr != currentNode ) { - currentNode->mTransformation.a1 = currentNode->mTransformation.a1 * mScale; - currentNode->mTransformation.b2 = currentNode->mTransformation.b2 * mScale; - currentNode->mTransformation.c3 = currentNode->mTransformation.c3 * mScale; + // Reconstruct matrix by transform rather than by scale + // This prevent scale values being changed which can + // be meaningful in some cases + // like when you want the modeller to + // see 1:1 compatibility. + + aiVector3D pos, scale; + aiQuaternion rotation; + currentNode->mTransformation.Decompose( scale, rotation, pos); + + aiMatrix4x4 translation; + aiMatrix4x4::Translation( pos * mScale, translation ); + + aiMatrix4x4 scaling; + + // note: we do not use mScale here, this is on purpose. + aiMatrix4x4::Scaling( scale, scaling ); + + aiMatrix4x4 RotMatrix = aiMatrix4x4 (rotation.GetMatrix()); + + currentNode->mTransformation = translation * RotMatrix * scaling; } } } // Namespace Assimp - -#endif // !! ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS diff --git a/code/PostProcessing/ScaleProcess.h b/code/PostProcessing/ScaleProcess.h index 256737875..468a21673 100644 --- a/code/PostProcessing/ScaleProcess.h +++ b/code/PostProcessing/ScaleProcess.h @@ -39,7 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -#pragma once +#ifndef SCALE_PROCESS_H_ +#define SCALE_PROCESS_H_ #include "Common/BaseProcess.h" @@ -53,6 +54,11 @@ namespace Assimp { // --------------------------------------------------------------------------- /** ScaleProcess: Class to rescale the whole model. + * Now rescales animations, bones, and blend shapes properly. + * Please note this will not write to 'scale' transform it will rewrite mesh + * and matrixes so that your scale values + * from your model package are preserved, so this is completely intentional + * bugs should be reported as soon as they are found. */ class ASSIMP_API ScaleProcess : public BaseProcess { public: @@ -78,7 +84,7 @@ public: virtual void Execute( aiScene* pScene ); private: - void traverseNodes( aiNode *currentNode ); + void traverseNodes( aiNode *currentNode, unsigned int nested_node_id = 0 ); void applyScaling( aiNode *currentNode ); private: @@ -86,3 +92,6 @@ private: }; } // Namespace Assimp + + +#endif // SCALE_PROCESS_H_ \ No newline at end of file diff --git a/code/PostProcessing/ValidateDataStructure.cpp b/code/PostProcessing/ValidateDataStructure.cpp index 712fd6943..1dc217663 100644 --- a/code/PostProcessing/ValidateDataStructure.cpp +++ b/code/PostProcessing/ValidateDataStructure.cpp @@ -538,13 +538,17 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation) { Validate(&pAnimation->mName); - // validate all materials - if (pAnimation->mNumChannels) + // validate all animations + if (pAnimation->mNumChannels || pAnimation->mNumMorphMeshChannels) { - if (!pAnimation->mChannels) { + if (!pAnimation->mChannels && pAnimation->mNumChannels) { ReportError("aiAnimation::mChannels is NULL (aiAnimation::mNumChannels is %i)", pAnimation->mNumChannels); } + if (!pAnimation->mMorphMeshChannels && pAnimation->mNumMorphMeshChannels) { + ReportError("aiAnimation::mMorphMeshChannels is NULL (aiAnimation::mNumMorphMeshChannels is %i)", + pAnimation->mNumMorphMeshChannels); + } for (unsigned int i = 0; i < pAnimation->mNumChannels;++i) { if (!pAnimation->mChannels[i]) @@ -554,6 +558,15 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation) } Validate(pAnimation, pAnimation->mChannels[i]); } + for (unsigned int i = 0; i < pAnimation->mNumMorphMeshChannels;++i) + { + if (!pAnimation->mMorphMeshChannels[i]) + { + ReportError("aiAnimation::mMorphMeshChannels[%i] is NULL (aiAnimation::mNumMorphMeshChannels is %i)", + i, pAnimation->mNumMorphMeshChannels); + } + Validate(pAnimation, pAnimation->mMorphMeshChannels[i]); + } } else { ReportError("aiAnimation::mNumChannels is 0. At least one node animation channel must be there."); @@ -590,15 +603,18 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial, ReportError("%s #%i is set, but there are only %i %s textures", szType,iIndex,iNumIndices,szType); } - if (!iNumIndices)return; + if (!iNumIndices) { + return; + } std::vector mappings(iNumIndices); // Now check whether all UV indices are valid ... bool bNoSpecified = true; - for (unsigned int i = 0; i < pMaterial->mNumProperties;++i) - { + for (unsigned int i = 0; i < pMaterial->mNumProperties;++i) { aiMaterialProperty* prop = pMaterial->mProperties[i]; - if (prop->mSemantic != type)continue; + if (prop->mSemantic != type) { + continue; + } if ((int)prop->mIndex >= iNumIndices) { @@ -621,7 +637,7 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial, ReportError("Material property %s%i is expected to be 5 floats large (size is %i)", prop->mKey.data,prop->mIndex, prop->mDataLength); } - mappings[prop->mIndex] = *((aiTextureMapping*)prop->mData); + //mappings[prop->mIndex] = ((aiUVTransform*)prop->mData); } else if (!::strcmp(prop->mKey.data,"$tex.uvwsrc")) { if (aiPTI_Integer != prop->mType || sizeof(int) > prop->mDataLength) @@ -903,6 +919,48 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation, } } +void ValidateDSProcess::Validate( const aiAnimation* pAnimation, + const aiMeshMorphAnim* pMeshMorphAnim) +{ + Validate(&pMeshMorphAnim->mName); + + if (!pMeshMorphAnim->mNumKeys) { + ReportError("Empty mesh morph animation channel"); + } + + // otherwise check whether one of the keys exceeds the total duration of the animation + if (pMeshMorphAnim->mNumKeys) + { + if (!pMeshMorphAnim->mKeys) + { + ReportError("aiMeshMorphAnim::mKeys is NULL (aiMeshMorphAnim::mNumKeys is %i)", + pMeshMorphAnim->mNumKeys); + } + double dLast = -10e10; + for (unsigned int i = 0; i < pMeshMorphAnim->mNumKeys;++i) + { + // ScenePreprocessor will compute the duration if still the default value + // (Aramis) Add small epsilon, comparison tended to fail if max_time == duration, + // seems to be due the compilers register usage/width. + if (pAnimation->mDuration > 0. && pMeshMorphAnim->mKeys[i].mTime > pAnimation->mDuration+0.001) + { + ReportError("aiMeshMorphAnim::mKeys[%i].mTime (%.5f) is larger " + "than aiAnimation::mDuration (which is %.5f)",i, + (float)pMeshMorphAnim->mKeys[i].mTime, + (float)pAnimation->mDuration); + } + if (i && pMeshMorphAnim->mKeys[i].mTime <= dLast) + { + ReportWarning("aiMeshMorphAnim::mKeys[%i].mTime (%.5f) is smaller " + "than aiMeshMorphAnim::mKeys[%i] (which is %.5f)",i, + (float)pMeshMorphAnim->mKeys[i].mTime, + i-1, (float)dLast); + } + dLast = pMeshMorphAnim->mKeys[i].mTime; + } + } +} + // ------------------------------------------------------------------------------------------------ void ValidateDSProcess::Validate( const aiNode* pNode) { @@ -958,7 +1016,7 @@ void ValidateDSProcess::Validate( const aiString* pString) { if (pString->length > MAXLEN) { - ReportError("aiString::length is too large (%lu, maximum is %lu)", + ReportError("aiString::length is too large (%u, maximum is %lu)", pString->length,MAXLEN); } const char* sz = pString->data; diff --git a/code/PostProcessing/ValidateDataStructure.h b/code/PostProcessing/ValidateDataStructure.h index 0b891ef41..7b309c925 100644 --- a/code/PostProcessing/ValidateDataStructure.h +++ b/code/PostProcessing/ValidateDataStructure.h @@ -55,6 +55,7 @@ struct aiBone; struct aiMesh; struct aiAnimation; struct aiNodeAnim; +struct aiMeshMorphAnim; struct aiTexture; struct aiMaterial; struct aiNode; @@ -150,6 +151,13 @@ protected: void Validate( const aiAnimation* pAnimation, const aiNodeAnim* pBoneAnim); + /** Validates a mesh morph animation channel. + * @param pAnimation Input animation. + * @param pMeshMorphAnim Mesh morph animation channel. + * */ + void Validate( const aiAnimation* pAnimation, + const aiMeshMorphAnim* pMeshMorphAnim); + // ------------------------------------------------------------------- /** Validates a node and all of its subnodes * @param Node Input node*/ diff --git a/code/Q3BSP/Q3BSPFileImporter.cpp b/code/Q3BSP/Q3BSPFileImporter.cpp index 6a3b95bb8..4add00a07 100644 --- a/code/Q3BSP/Q3BSPFileImporter.cpp +++ b/code/Q3BSP/Q3BSPFileImporter.cpp @@ -43,7 +43,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_Q3BSP_IMPORTER #include "Q3BSPFileImporter.h" -#include "Q3BSPZipArchive.h" #include "Q3BSPFileParser.h" #include "Q3BSPFileData.h" @@ -60,6 +59,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include #include #include @@ -181,7 +181,7 @@ const aiImporterDesc* Q3BSPFileImporter::GetInfo () const { // ------------------------------------------------------------------------------------------------ // Import method. void Q3BSPFileImporter::InternReadFile(const std::string &rFile, aiScene* scene, IOSystem* ioHandler) { - Q3BSPZipArchive Archive( ioHandler, rFile ); + ZipArchiveIOSystem Archive( ioHandler, rFile ); if ( !Archive.isOpen() ) { throw DeadlyImportError( "Failed to open file " + rFile + "." ); } @@ -223,10 +223,10 @@ void Q3BSPFileImporter::separateMapName( const std::string &importName, std::str // ------------------------------------------------------------------------------------------------ // Returns the first map in the map archive. -bool Q3BSPFileImporter::findFirstMapInArchive( Q3BSPZipArchive &bspArchive, std::string &mapName ) { +bool Q3BSPFileImporter::findFirstMapInArchive(ZipArchiveIOSystem &bspArchive, std::string &mapName ) { mapName = ""; std::vector fileList; - bspArchive.getFileList( fileList ); + bspArchive.getFileListExtension( fileList, "bsp" ); if (fileList.empty()) { return false; } @@ -249,7 +249,7 @@ bool Q3BSPFileImporter::findFirstMapInArchive( Q3BSPZipArchive &bspArchive, std: // ------------------------------------------------------------------------------------------------ // Creates the assimp specific data. void Q3BSPFileImporter::CreateDataFromImport( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, - Q3BSPZipArchive *pArchive ) { + ZipArchiveIOSystem *pArchive ) { if (nullptr == pModel || nullptr == pScene) { return; } @@ -418,7 +418,7 @@ void Q3BSPFileImporter::createTriangleTopology( const Q3BSP::Q3BSPModel *pModel, // ------------------------------------------------------------------------------------------------ // Creates all referenced materials. void Q3BSPFileImporter::createMaterials( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, - Q3BSPZipArchive *pArchive ) { + ZipArchiveIOSystem *pArchive ) { if ( m_MaterialLookupMap.empty() ) { return; } @@ -564,7 +564,7 @@ aiFace *Q3BSPFileImporter::getNextFace( aiMesh *mesh, unsigned int &faceIdx ) { // ------------------------------------------------------------------------------------------------ // Imports a texture file. bool Q3BSPFileImporter::importTextureFromArchive( const Q3BSP::Q3BSPModel *model, - Q3BSP::Q3BSPZipArchive *archive, aiScene*, + ZipArchiveIOSystem *archive, aiScene*, aiMaterial *pMatHelper, int textureId ) { if (nullptr == archive || nullptr == pMatHelper ) { return false; @@ -669,7 +669,7 @@ bool Q3BSPFileImporter::importLightmap( const Q3BSP::Q3BSPModel *pModel, aiScene // ------------------------------------------------------------------------------------------------ // Will search for a supported extension. -bool Q3BSPFileImporter::expandFile( Q3BSP::Q3BSPZipArchive *pArchive, const std::string &rFilename, +bool Q3BSPFileImporter::expandFile(ZipArchiveIOSystem *pArchive, const std::string &rFilename, const std::vector &rExtList, std::string &rFile, std::string &rExt ) { diff --git a/code/Q3BSP/Q3BSPFileImporter.h b/code/Q3BSP/Q3BSPFileImporter.h index 5af9fb7bc..ee21fa48e 100644 --- a/code/Q3BSP/Q3BSPFileImporter.h +++ b/code/Q3BSP/Q3BSPFileImporter.h @@ -54,9 +54,9 @@ struct aiMaterial; struct aiTexture; namespace Assimp { + class ZipArchiveIOSystem; namespace Q3BSP { - class Q3BSPZipArchive; struct Q3BSPModel; struct sQ3BSPFace; } @@ -85,24 +85,24 @@ protected: const aiImporterDesc* GetInfo () const; void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); void separateMapName( const std::string &rImportName, std::string &rArchiveName, std::string &rMapName ); - bool findFirstMapInArchive( Q3BSP::Q3BSPZipArchive &rArchive, std::string &rMapName ); - void CreateDataFromImport( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, Q3BSP::Q3BSPZipArchive *pArchive ); + bool findFirstMapInArchive(ZipArchiveIOSystem &rArchive, std::string &rMapName ); + void CreateDataFromImport( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, ZipArchiveIOSystem *pArchive ); void CreateNodes( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, aiNode *pParent ); aiNode *CreateTopology( const Q3BSP::Q3BSPModel *pModel, unsigned int materialIdx, std::vector &rArray, aiMesh **pMesh ); void createTriangleTopology( const Q3BSP::Q3BSPModel *pModel, Q3BSP::sQ3BSPFace *pQ3BSPFace, aiMesh* pMesh, unsigned int &rFaceIdx, unsigned int &rVertIdx ); - void createMaterials( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, Q3BSP::Q3BSPZipArchive *pArchive ); + void createMaterials( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, ZipArchiveIOSystem *pArchive ); size_t countData( const std::vector &rArray ) const; size_t countFaces( const std::vector &rArray ) const; size_t countTriangles( const std::vector &rArray ) const; void createMaterialMap( const Q3BSP::Q3BSPModel *pModel); aiFace *getNextFace( aiMesh *pMesh, unsigned int &rFaceIdx ); - bool importTextureFromArchive( const Q3BSP::Q3BSPModel *pModel, Q3BSP::Q3BSPZipArchive *pArchive, aiScene* pScene, + bool importTextureFromArchive( const Q3BSP::Q3BSPModel *pModel, ZipArchiveIOSystem *pArchive, aiScene* pScene, aiMaterial *pMatHelper, int textureId ); bool importLightmap( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, aiMaterial *pMatHelper, int lightmapId ); bool importEntities( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene ); - bool expandFile( Q3BSP::Q3BSPZipArchive *pArchive, const std::string &rFilename, const std::vector &rExtList, + bool expandFile(ZipArchiveIOSystem *pArchive, const std::string &rFilename, const std::vector &rExtList, std::string &rFile, std::string &rExt ); private: diff --git a/code/Q3BSP/Q3BSPFileParser.cpp b/code/Q3BSP/Q3BSPFileParser.cpp index 5bac5ae76..bed2efe53 100644 --- a/code/Q3BSP/Q3BSPFileParser.cpp +++ b/code/Q3BSP/Q3BSPFileParser.cpp @@ -45,9 +45,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Q3BSPFileParser.h" #include "Q3BSPFileData.h" -#include "Q3BSPZipArchive.h" #include #include +#include #include namespace Assimp { @@ -55,7 +55,7 @@ namespace Assimp { using namespace Q3BSP; // ------------------------------------------------------------------------------------------------ -Q3BSPFileParser::Q3BSPFileParser( const std::string &mapName, Q3BSPZipArchive *pZipArchive ) : +Q3BSPFileParser::Q3BSPFileParser( const std::string &mapName, ZipArchiveIOSystem *pZipArchive ) : m_sOffset( 0 ), m_Data(), m_pModel(nullptr), @@ -101,6 +101,7 @@ bool Q3BSPFileParser::readData( const std::string &rMapName ) { const size_t readSize = pMapFile->Read( &m_Data[0], sizeof( char ), size ); if ( readSize != size ) { m_Data.clear(); + m_pZipArchive->Close(pMapFile); return false; } m_pZipArchive->Close( pMapFile ); diff --git a/code/Q3BSP/Q3BSPFileParser.h b/code/Q3BSP/Q3BSPFileParser.h index 143e42b15..fd73f5e10 100644 --- a/code/Q3BSP/Q3BSPFileParser.h +++ b/code/Q3BSP/Q3BSPFileParser.h @@ -48,13 +48,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { + class ZipArchiveIOSystem; + namespace Q3BSP { - -class Q3BSPZipArchive; -struct Q3BSPModel; -class ZipFile; - + struct Q3BSPModel; + class ZipFile; } // ------------------------------------------------------------------- @@ -62,7 +61,7 @@ class ZipFile; class Q3BSPFileParser { public: - Q3BSPFileParser( const std::string &rMapName, Q3BSP::Q3BSPZipArchive *pZipArchive ); + Q3BSPFileParser( const std::string &rMapName, ZipArchiveIOSystem *pZipArchive ); ~Q3BSPFileParser(); Q3BSP::Q3BSPModel *getModel() const; @@ -83,7 +82,7 @@ private: size_t m_sOffset; std::vector m_Data; Q3BSP::Q3BSPModel *m_pModel; - Q3BSP::Q3BSPZipArchive *m_pZipArchive; + ZipArchiveIOSystem *m_pZipArchive; }; } // Namespace Assimp diff --git a/code/Q3BSP/Q3BSPZipArchive.cpp b/code/Q3BSP/Q3BSPZipArchive.cpp deleted file mode 100644 index 931f2b905..000000000 --- a/code/Q3BSP/Q3BSPZipArchive.cpp +++ /dev/null @@ -1,325 +0,0 @@ -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -#ifndef ASSIMP_BUILD_NO_Q3BSP_IMPORTER - -#include "Q3BSPZipArchive.h" -#include -#include -#include - -namespace Assimp { -namespace Q3BSP { - -voidpf IOSystem2Unzip::open(voidpf opaque, const char* filename, int mode) { - IOSystem* io_system = (IOSystem*) opaque; - - const char* mode_fopen = NULL; - if((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) { - mode_fopen = "rb"; - } else { - if(mode & ZLIB_FILEFUNC_MODE_EXISTING) { - mode_fopen = "r+b"; - } else { - if(mode & ZLIB_FILEFUNC_MODE_CREATE) { - mode_fopen = "wb"; - } - } - } - - return (voidpf) io_system->Open(filename, mode_fopen); -} - -uLong IOSystem2Unzip::read(voidpf /*opaque*/, voidpf stream, void* buf, uLong size) { - IOStream* io_stream = (IOStream*) stream; - - return static_cast(io_stream->Read(buf, 1, size)); -} - -uLong IOSystem2Unzip::write(voidpf /*opaque*/, voidpf stream, const void* buf, uLong size) { - IOStream* io_stream = (IOStream*) stream; - - return static_cast(io_stream->Write(buf, 1, size)); -} - -long IOSystem2Unzip::tell(voidpf /*opaque*/, voidpf stream) { - IOStream* io_stream = (IOStream*) stream; - - return static_cast(io_stream->Tell()); -} - -long IOSystem2Unzip::seek(voidpf /*opaque*/, voidpf stream, uLong offset, int origin) { - IOStream* io_stream = (IOStream*) stream; - - aiOrigin assimp_origin; - switch (origin) { - default: - case ZLIB_FILEFUNC_SEEK_CUR: - assimp_origin = aiOrigin_CUR; - break; - case ZLIB_FILEFUNC_SEEK_END: - assimp_origin = aiOrigin_END; - break; - case ZLIB_FILEFUNC_SEEK_SET: - assimp_origin = aiOrigin_SET; - break; - } - - return (io_stream->Seek(offset, assimp_origin) == aiReturn_SUCCESS ? 0 : -1); -} - -int IOSystem2Unzip::close(voidpf opaque, voidpf stream) { - IOSystem* io_system = (IOSystem*) opaque; - IOStream* io_stream = (IOStream*) stream; - - io_system->Close(io_stream); - - return 0; -} - -int IOSystem2Unzip::testerror(voidpf /*opaque*/, voidpf /*stream*/) { - return 0; -} - -zlib_filefunc_def IOSystem2Unzip::get(IOSystem* pIOHandler) { - zlib_filefunc_def mapping; - -#ifdef ASSIMP_USE_HUNTER - mapping.zopen_file = (open_file_func)open; - mapping.zread_file = (read_file_func)read; - mapping.zwrite_file = (write_file_func)write; - mapping.ztell_file = (tell_file_func)tell; - mapping.zseek_file = (seek_file_func)seek; - mapping.zclose_file = (close_file_func)close; - mapping.zerror_file = (error_file_func)testerror; -#else - mapping.zopen_file = open; - mapping.zread_file = read; - mapping.zwrite_file = write; - mapping.ztell_file = tell; - mapping.zseek_file = seek; - mapping.zclose_file = close; - mapping.zerror_file = testerror; -#endif - mapping.opaque = (voidpf) pIOHandler; - - return mapping; -} - -ZipFile::ZipFile(size_t size) : m_Size(size) { - ai_assert(m_Size != 0); - - m_Buffer = malloc(m_Size); -} - -ZipFile::~ZipFile() { - free(m_Buffer); - m_Buffer = NULL; -} - -size_t ZipFile::Read(void* pvBuffer, size_t pSize, size_t pCount) { - const size_t size = pSize * pCount; - assert(size <= m_Size); - - std::memcpy(pvBuffer, m_Buffer, size); - - return size; -} - -size_t ZipFile::Write(const void* /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/) { - return 0; -} - -size_t ZipFile::FileSize() const { - return m_Size; -} - -aiReturn ZipFile::Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/) { - return aiReturn_FAILURE; -} - -size_t ZipFile::Tell() const { - return 0; -} - -void ZipFile::Flush() { - // empty -} - -// ------------------------------------------------------------------------------------------------ -// Constructor. -Q3BSPZipArchive::Q3BSPZipArchive(IOSystem* pIOHandler, const std::string& rFile) : m_ZipFileHandle(NULL), m_ArchiveMap() { - if (! rFile.empty()) { - zlib_filefunc_def mapping = IOSystem2Unzip::get(pIOHandler); - - m_ZipFileHandle = unzOpen2(rFile.c_str(), &mapping); - - if(m_ZipFileHandle != nullptr) { - mapArchive(); - } - } -} - -// ------------------------------------------------------------------------------------------------ -// Destructor. -Q3BSPZipArchive::~Q3BSPZipArchive() { - for(auto &file : m_ArchiveMap) { - delete file.second; - } - m_ArchiveMap.clear(); - - if(m_ZipFileHandle != nullptr) { - unzClose(m_ZipFileHandle); - m_ZipFileHandle = nullptr; - } -} - -// ------------------------------------------------------------------------------------------------ -// Returns true, if the archive is already open. -bool Q3BSPZipArchive::isOpen() const { - return (m_ZipFileHandle != nullptr); -} - -// ------------------------------------------------------------------------------------------------ -// Returns true, if the filename is part of the archive. -bool Q3BSPZipArchive::Exists(const char* pFile) const { - bool exist = false; - if (pFile != nullptr) { - std::string rFile(pFile); - std::map::const_iterator it = m_ArchiveMap.find(rFile); - - if(it != m_ArchiveMap.end()) { - exist = true; - } - } - - return exist; -} - -// ------------------------------------------------------------------------------------------------ -// Returns the separator delimiter. -char Q3BSPZipArchive::getOsSeparator() const { -#ifndef _WIN32 - return '/'; -#else - return '\\'; -#endif -} - -// ------------------------------------------------------------------------------------------------ -// Opens a file, which is part of the archive. -IOStream *Q3BSPZipArchive::Open(const char* pFile, const char* /*pMode*/) { - ai_assert(pFile != nullptr); - - IOStream* result = nullptr; - - std::map::iterator it = m_ArchiveMap.find(pFile); - - if(it != m_ArchiveMap.end()) { - result = (IOStream*) it->second; - } - - return result; -} - -// ------------------------------------------------------------------------------------------------ -// Close a filestream. -void Q3BSPZipArchive::Close(IOStream *pFile) { - (void)(pFile); - ai_assert(pFile != nullptr); - - // We don't do anything in case the file would be opened again in the future -} -// ------------------------------------------------------------------------------------------------ -// Returns the file-list of the archive. -void Q3BSPZipArchive::getFileList(std::vector &rFileList) { - rFileList.clear(); - - for(auto &file : m_ArchiveMap) { - rFileList.push_back(file.first); - } -} - -// ------------------------------------------------------------------------------------------------ -// Maps the archive content. -bool Q3BSPZipArchive::mapArchive() { - bool success = false; - - if(m_ZipFileHandle != nullptr) { - if(m_ArchiveMap.empty()) { - // At first ensure file is already open - if(unzGoToFirstFile(m_ZipFileHandle) == UNZ_OK) { - // Loop over all files - do { - char filename[FileNameSize]; - unz_file_info fileInfo; - - if(unzGetCurrentFileInfo(m_ZipFileHandle, &fileInfo, filename, FileNameSize, NULL, 0, NULL, 0) == UNZ_OK) { - // The file has EXACTLY the size of uncompressed_size. In C - // you need to mark the last character with '\0', so add - // another character - if(fileInfo.uncompressed_size != 0 && unzOpenCurrentFile(m_ZipFileHandle) == UNZ_OK) { - std::pair::iterator, bool> result = m_ArchiveMap.insert(std::make_pair(filename, new ZipFile(fileInfo.uncompressed_size))); - - if(unzReadCurrentFile(m_ZipFileHandle, result.first->second->m_Buffer, fileInfo.uncompressed_size) == (long int) fileInfo.uncompressed_size) { - if(unzCloseCurrentFile(m_ZipFileHandle) == UNZ_OK) { - // Nothing to do anymore... - } - } - } - } - } while(unzGoToNextFile(m_ZipFileHandle) != UNZ_END_OF_LIST_OF_FILE); - } - } - - success = true; - } - - return success; -} - -// ------------------------------------------------------------------------------------------------ - -} // Namespace Q3BSP -} // Namespace Assimp - -#endif // ASSIMP_BUILD_NO_Q3BSP_IMPORTER diff --git a/code/Q3BSP/Q3BSPZipArchive.h b/code/Q3BSP/Q3BSPZipArchive.h deleted file mode 100644 index db68925a8..000000000 --- a/code/Q3BSP/Q3BSPZipArchive.h +++ /dev/null @@ -1,135 +0,0 @@ -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2019, assimp team - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ -#ifndef AI_Q3BSP_ZIPARCHIVE_H_INC -#define AI_Q3BSP_ZIPARCHIVE_H_INC - -#ifdef ASSIMP_USE_HUNTER -# include -#else -# include -#endif -#include -#include -#include -#include -#include - -namespace Assimp { -namespace Q3BSP { - -// ------------------------------------------------------------------------------------------------ -/// \class IOSystem2Unzip -/// \ingroup Assimp::Q3BSP -/// -/// \brief -// ------------------------------------------------------------------------------------------------ -class IOSystem2Unzip { -public: - static voidpf open(voidpf opaque, const char* filename, int mode); - static uLong read(voidpf opaque, voidpf stream, void* buf, uLong size); - static uLong write(voidpf opaque, voidpf stream, const void* buf, uLong size); - static long tell(voidpf opaque, voidpf stream); - static long seek(voidpf opaque, voidpf stream, uLong offset, int origin); - static int close(voidpf opaque, voidpf stream); - static int testerror(voidpf opaque, voidpf stream); - static zlib_filefunc_def get(IOSystem* pIOHandler); -}; - -// ------------------------------------------------------------------------------------------------ -/// \class ZipFile -/// \ingroup Assimp::Q3BSP -/// -/// \brief -// ------------------------------------------------------------------------------------------------ -class ZipFile : public IOStream { - friend class Q3BSPZipArchive; - -public: - explicit ZipFile(size_t size); - ~ZipFile(); - size_t Read(void* pvBuffer, size_t pSize, size_t pCount ); - size_t Write(const void* /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/); - size_t FileSize() const; - aiReturn Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/); - size_t Tell() const; - void Flush(); - -private: - void* m_Buffer; - size_t m_Size; -}; - -// ------------------------------------------------------------------------------------------------ -/// \class Q3BSPZipArchive -/// \ingroup Assimp::Q3BSP -/// -/// \brief IMplements a zip archive like the WinZip archives. Will be also used to import data -/// from a P3K archive ( Quake level format ). -// ------------------------------------------------------------------------------------------------ -class Q3BSPZipArchive : public Assimp::IOSystem { -public: - static const unsigned int FileNameSize = 256; - -public: - Q3BSPZipArchive(IOSystem* pIOHandler, const std::string & rFile); - ~Q3BSPZipArchive(); - bool Exists(const char* pFile) const; - char getOsSeparator() const; - IOStream* Open(const char* pFile, const char* pMode = "rb"); - void Close(IOStream* pFile); - bool isOpen() const; - void getFileList(std::vector &rFileList); - -private: - bool mapArchive(); - -private: - unzFile m_ZipFileHandle; - std::map m_ArchiveMap; -}; - -// ------------------------------------------------------------------------------------------------ - -} // Namespace Q3BSP -} // Namespace Assimp - -#endif // AI_Q3BSP_ZIPARCHIVE_H_INC diff --git a/code/STL/STLLoader.cpp b/code/STL/STLLoader.cpp index c7144e444..199a84a44 100644 --- a/code/STL/STLLoader.cpp +++ b/code/STL/STLLoader.cpp @@ -225,7 +225,7 @@ void STLImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS } pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_DIFFUSE); pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_SPECULAR); - clrDiffuse = aiColor4D( ai_real(1.0), ai_real(1.0), ai_real(1.0), ai_real(1.0)); + clrDiffuse = aiColor4D( ai_real(0.05), ai_real(0.05), ai_real(0.05), ai_real(1.0)); pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_AMBIENT); pScene->mNumMaterials = 1; diff --git a/code/Unreal/UnrealLoader.h b/code/Unreal/UnrealLoader.h index f846eb43a..678aaa76b 100644 --- a/code/Unreal/UnrealLoader.h +++ b/code/Unreal/UnrealLoader.h @@ -127,7 +127,8 @@ inline void CompressVertex(const aiVector3D& v, uint32_t& out) n.X = (int32_t)v.x; n.Y = (int32_t)v.y; n.Z = (int32_t)v.z; - out = t; + ::memcpy( &out, &t, sizeof(int32_t)); + //out = t; } // UNREAL vertex decompression diff --git a/code/X/XFileParser.cpp b/code/X/XFileParser.cpp index 08d3c88da..8fcf87cf6 100644 --- a/code/X/XFileParser.cpp +++ b/code/X/XFileParser.cpp @@ -596,11 +596,11 @@ void XFileParser::ParseDataObjectMeshNormals( Mesh* pMesh) // do not crah when no face definitions are there if (numFaces > 0) { // normal face creation - pMesh->mNormFaces.resize( pMesh->mNormFaces.size() + numFaces ); + pMesh->mNormFaces.resize( numFaces ); for( unsigned int a = 0; a < numFaces; ++a ) { unsigned int numIndices = ReadInt(); - pMesh->mNormFaces.push_back( Face() ); - Face& face = pMesh->mNormFaces.back(); + pMesh->mNormFaces[a] = Face(); + Face& face = pMesh->mNormFaces[a]; for( unsigned int b = 0; b < numIndices; ++b ) { face.mIndices.push_back( ReadInt()); } diff --git a/code/X3D/X3DExporter.cpp b/code/X3D/X3DExporter.cpp index 9839e6ca6..e5eeb0886 100644 --- a/code/X3D/X3DExporter.cpp +++ b/code/X3D/X3DExporter.cpp @@ -68,7 +68,7 @@ aiMatrix4x4 out_matr; } // multiplicate all matrices in reverse order - for(std::list::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); rit++) out_matr = out_matr * (*rit); + for(std::list::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); ++rit) out_matr = out_matr * (*rit); return out_matr; } diff --git a/code/X3D/X3DImporter.cpp b/code/X3D/X3DImporter.cpp index 1f4d3f916..367d84fcf 100644 --- a/code/X3D/X3DImporter.cpp +++ b/code/X3D/X3DImporter.cpp @@ -80,7 +80,13 @@ const aiImporterDesc X3DImporter::Description = { //const std::regex X3DImporter::pattern_nws(R"([^, \t\r\n]+)"); //const std::regex X3DImporter::pattern_true(R"(^\s*(?:true|1)\s*$)", std::regex::icase); -struct WordIterator: public std::iterator { +struct WordIterator { + using iterator_category = std::input_iterator_tag; + using value_type = const char*; + using difference_type = ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + static const char *whitespace; const char *start_, *end_; WordIterator(const char *start, const char *end): start_(start), end_(end) { @@ -130,8 +136,8 @@ X3DImporter::~X3DImporter() { void X3DImporter::Clear() { NodeElement_Cur = nullptr; // Delete all elements - if(NodeElement_List.size()) { - for ( std::list::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); it++ ) { + if(!NodeElement_List.empty()) { + for ( std::list::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); ++it ) { delete *it; } NodeElement_List.clear(); @@ -145,7 +151,7 @@ void X3DImporter::Clear() { bool X3DImporter::FindNodeElement_FromRoot(const std::string& pID, const CX3DImporter_NodeElement::EType pType, CX3DImporter_NodeElement** pElement) { - for(std::list::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); it++) + for(std::list::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); ++it) { if(((*it)->Type == pType) && ((*it)->ID == pID)) { @@ -176,7 +182,7 @@ bool X3DImporter::FindNodeElement_FromNode(CX3DImporter_NodeElement* pStartNode, }// if((pStartNode->Type() == pType) && (pStartNode->ID() == pID)) // Check childs of pStartNode. - for(std::list::iterator ch_it = pStartNode->Child.begin(); ch_it != pStartNode->Child.end(); ch_it++) + for(std::list::iterator ch_it = pStartNode->Child.begin(); ch_it != pStartNode->Child.end(); ++ch_it) { found = FindNodeElement_FromNode(*ch_it, pID, pType, pElement); if ( found ) @@ -608,10 +614,10 @@ void X3DImporter::XML_ReadNode_GetAttrVal_AsArrCol3f(const int pAttrIdx, std::ve XML_ReadNode_GetAttrVal_AsListCol3f(pAttrIdx, tlist);// read as list // and copy to array - if(tlist.size() > 0) + if(!tlist.empty()) { pValue.reserve(tlist.size()); - for(std::list::iterator it = tlist.begin(); it != tlist.end(); it++) pValue.push_back(*it); + for(std::list::iterator it = tlist.begin(); it != tlist.end(); ++it) pValue.push_back(*it); } } @@ -641,10 +647,10 @@ void X3DImporter::XML_ReadNode_GetAttrVal_AsArrCol4f(const int pAttrIdx, std::ve XML_ReadNode_GetAttrVal_AsListCol4f(pAttrIdx, tlist);// read as list // and copy to array - if(tlist.size() > 0) + if(!tlist.empty()) { pValue.reserve(tlist.size()); - for ( std::list::iterator it = tlist.begin(); it != tlist.end(); it++ ) + for ( std::list::iterator it = tlist.begin(); it != tlist.end(); ++it ) { pValue.push_back( *it ); } @@ -678,10 +684,10 @@ void X3DImporter::XML_ReadNode_GetAttrVal_AsArrVec2f(const int pAttrIdx, std::ve XML_ReadNode_GetAttrVal_AsListVec2f(pAttrIdx, tlist);// read as list // and copy to array - if(tlist.size() > 0) + if(!tlist.empty()) { pValue.reserve(tlist.size()); - for ( std::list::iterator it = tlist.begin(); it != tlist.end(); it++ ) + for ( std::list::iterator it = tlist.begin(); it != tlist.end(); ++it ) { pValue.push_back( *it ); } @@ -716,10 +722,10 @@ void X3DImporter::XML_ReadNode_GetAttrVal_AsArrVec3f(const int pAttrIdx, std::ve XML_ReadNode_GetAttrVal_AsListVec3f(pAttrIdx, tlist);// read as list // and copy to array - if(tlist.size() > 0) + if(!tlist.empty()) { pValue.reserve(tlist.size()); - for ( std::list::iterator it = tlist.begin(); it != tlist.end(); it++ ) + for ( std::list::iterator it = tlist.begin(); it != tlist.end(); ++it ) { pValue.push_back( *it ); } @@ -817,7 +823,7 @@ void X3DImporter::GeometryHelper_Extend_PointToLine(const std::list& std::list::const_iterator pit = pPoint.begin(); std::list::const_iterator pit_last = pPoint.end(); - pit_last--; + --pit_last; if ( pPoint.size() < 2 ) { @@ -831,7 +837,7 @@ void X3DImporter::GeometryHelper_Extend_PointToLine(const std::list& { pLine.push_back(*pit);// second point of previous line pLine.push_back(*pit);// first point of next line - pit++; + ++pit; } // add last point of last line pLine.push_back(*pit); @@ -849,7 +855,7 @@ void X3DImporter::GeometryHelper_Extend_PolylineIdxToLineIdx(const std::list::const_iterator plit_next; - plit_next = plit, plit_next++; + plit_next = plit, ++plit_next; pLineCoordIdx.push_back(*plit);// second point of previous line. pLineCoordIdx.push_back(-1);// delimiter if((*plit_next == (-1)) || (plit_next == pPolylineCoordIdx.end())) break;// current polyline is finished @@ -904,7 +910,7 @@ void X3DImporter::GeometryHelper_CoordIdxStr2FacesArr(const std::vector pFaces.reserve(f_data.size() / 3); inds.reserve(4); //PrintVectorSet("build. ci", pCoordIdx); - for(std::vector::iterator it = f_data.begin(); it != f_data.end(); it++) + for(std::vector::iterator it = f_data.begin(); it != f_data.end(); ++it) { // when face is got count how many indices in it. if(*it == (-1)) @@ -951,7 +957,7 @@ void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::list tcol; // create RGBA array from RGB. - for(std::list::const_iterator it = pColors.begin(); it != pColors.end(); it++) tcol.push_back(aiColor4D((*it).r, (*it).g, (*it).b, 1)); + for(std::list::const_iterator it = pColors.begin(); it != pColors.end(); ++it) tcol.push_back(aiColor4D((*it).r, (*it).g, (*it).b, 1)); // call existing function for adding RGBA colors MeshGeometry_AddColor(pMesh, tcol, pColorPerVertex); @@ -991,7 +997,7 @@ void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::list tcol; // create RGBA array from RGB. - for ( std::list::const_iterator it = pColors.begin(); it != pColors.end(); it++ ) + for ( std::list::const_iterator it = pColors.begin(); it != pColors.end(); ++it ) { tcol.push_back( aiColor4D( ( *it ).r, ( *it ).g, ( *it ).b, 1 ) ); } @@ -1025,7 +1031,7 @@ void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::vector::const_iterator it = pColors.begin(); it != pColors.end(); it++ ) + for ( std::list::const_iterator it = pColors.begin(); it != pColors.end(); ++it ) { col_arr_copy.push_back( *it ); } @@ -1042,7 +1048,7 @@ void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::vector::const_iterator colidx_it = pColorIdx.begin(), coordidx_it = pCoordIdx.begin(); colidx_it != pColorIdx.end(); colidx_it++, coordidx_it++) + for(std::vector::const_iterator colidx_it = pColorIdx.begin(), coordidx_it = pCoordIdx.begin(); colidx_it != pColorIdx.end(); ++colidx_it, ++coordidx_it) { if ( *colidx_it == ( -1 ) ) { @@ -1115,7 +1121,7 @@ void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::vector::const_iterator it = col_tgt_arr.begin(); it != col_tgt_arr.end(); it++) col_tgt_list.push_back(*it); + for(std::vector::const_iterator it = col_tgt_arr.begin(); it != col_tgt_arr.end(); ++it) col_tgt_list.push_back(*it); // add prepared colors list to mesh. MeshGeometry_AddColor(pMesh, col_tgt_list, pColorPerVertex); } @@ -1128,7 +1134,7 @@ void X3DImporter::MeshGeometry_AddNormal(aiMesh& pMesh, const std::vector::const_iterator it = pNormals.begin(); it != pNormals.end(); it++ ) + for ( std::list::const_iterator it = pNormals.begin(); it != pNormals.end(); ++it ) { norm_arr_copy.push_back( *it ); } @@ -1141,7 +1147,7 @@ void X3DImporter::MeshGeometry_AddNormal(aiMesh& pMesh, const std::vector::const_iterator it = pNormalIdx.begin(); it != pNormalIdx.end(); it++) + for(std::vector::const_iterator it = pNormalIdx.begin(); it != pNormalIdx.end(); ++it) { if(*it != (-1)) tind.push_back(*it); } @@ -1221,7 +1227,7 @@ void X3DImporter::MeshGeometry_AddNormal(aiMesh& pMesh, const std::list::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); it++) + for(std::list::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); ++it) { texcoord_arr_copy.push_back(aiVector3D((*it).x, (*it).y, 0)); } @@ -1285,7 +1291,7 @@ void X3DImporter::MeshGeometry_AddTexCoord(aiMesh& pMesh, const std::list::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); it++ ) + for ( std::list::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); ++it ) { tc_arr_copy.push_back( aiVector3D( ( *it ).x, ( *it ).y, 0 ) ); } @@ -1693,7 +1699,7 @@ void X3DImporter::InternReadFile(const std::string& pFile, aiScene* pScene, IOSy // create nodes tree Postprocess_BuildNode(*NodeElement_Cur, *pScene->mRootNode, mesh_list, mat_list, light_list); // copy needed data to scene - if(mesh_list.size() > 0) + if(!mesh_list.empty()) { std::list::const_iterator it = mesh_list.begin(); @@ -1702,7 +1708,7 @@ void X3DImporter::InternReadFile(const std::string& pFile, aiScene* pScene, IOSy for(size_t i = 0; i < pScene->mNumMeshes; i++) pScene->mMeshes[i] = *it++; } - if(mat_list.size() > 0) + if(!mat_list.empty()) { std::list::const_iterator it = mat_list.begin(); @@ -1711,7 +1717,7 @@ void X3DImporter::InternReadFile(const std::string& pFile, aiScene* pScene, IOSy for(size_t i = 0; i < pScene->mNumMaterials; i++) pScene->mMaterials[i] = *it++; } - if(light_list.size() > 0) + if(!light_list.empty()) { std::list::const_iterator it = light_list.begin(); diff --git a/code/X3D/X3DImporter_Geometry2D.cpp b/code/X3D/X3DImporter_Geometry2D.cpp index b29c80b04..350fd6c40 100644 --- a/code/X3D/X3DImporter_Geometry2D.cpp +++ b/code/X3D/X3DImporter_Geometry2D.cpp @@ -356,7 +356,7 @@ void X3DImporter::ParseNode_Geometry2D_Polyline2D() std::list tlist; // convert vec2 to vec3 - for(std::list::iterator it2 = lineSegments.begin(); it2 != lineSegments.end(); it2++) tlist.push_back(aiVector3D(it2->x, it2->y, 0)); + for(std::list::iterator it2 = lineSegments.begin(); it2 != lineSegments.end(); ++it2) tlist.push_back(aiVector3D(it2->x, it2->y, 0)); // convert point set to line set GeometryHelper_Extend_PointToLine(tlist, ((CX3DImporter_NodeElement_Geometry2D*)ne)->Vertices); @@ -399,7 +399,7 @@ void X3DImporter::ParseNode_Geometry2D_Polypoint2D() if(!def.empty()) ne->ID = def; // convert vec2 to vec3 - for(std::list::iterator it2 = point.begin(); it2 != point.end(); it2++) + for(std::list::iterator it2 = point.begin(); it2 != point.end(); ++it2) { ((CX3DImporter_NodeElement_Geometry2D*)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0)); } @@ -500,7 +500,7 @@ void X3DImporter::ParseNode_Geometry2D_TriangleSet2D() if(!def.empty()) ne->ID = def; // convert vec2 to vec3 - for(std::list::iterator it2 = vertices.begin(); it2 != vertices.end(); it2++) + for(std::list::iterator it2 = vertices.begin(); it2 != vertices.end(); ++it2) { ((CX3DImporter_NodeElement_Geometry2D*)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0)); } diff --git a/code/X3D/X3DImporter_Geometry3D.cpp b/code/X3D/X3DImporter_Geometry3D.cpp index 2eecc079d..e12cbd3ab 100644 --- a/code/X3D/X3DImporter_Geometry3D.cpp +++ b/code/X3D/X3DImporter_Geometry3D.cpp @@ -153,11 +153,11 @@ void X3DImporter::ParseNode_Geometry3D_Cone() { StandardShapes::MakeCircle(bottomRadius, tess, tvec); height = -(height / 2); - for(std::vector::iterator it = tvec.begin(); it != tvec.end(); it++) it->y = height;// y - because circle made in oXZ. + for(std::vector::iterator it = tvec.begin(); it != tvec.end(); ++it) it->y = height;// y - because circle made in oXZ. } // copy data from temp array - for(std::vector::iterator it = tvec.begin(); it != tvec.end(); it++) ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices.push_back(*it); + for(std::vector::iterator it = tvec.begin(); it != tvec.end(); ++it) ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices.push_back(*it); ((CX3DImporter_NodeElement_Geometry3D*)ne)->Solid = solid; ((CX3DImporter_NodeElement_Geometry3D*)ne)->NumIndices = 3; @@ -226,11 +226,11 @@ void X3DImporter::ParseNode_Geometry3D_Cylinder() // copy data from temp arrays std::list& vlist = ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices;// just short alias. - for(std::vector::iterator it = tside.begin(); it != tside.end(); it++) vlist.push_back(*it); + for(std::vector::iterator it = tside.begin(); it != tside.end(); ++it) vlist.push_back(*it); if(top) { - for(std::vector::iterator it = tcir.begin(); it != tcir.end(); it++) + for(std::vector::iterator it = tcir.begin(); it != tcir.end(); ++it) { (*it).y = height;// y - because circle made in oXZ. vlist.push_back(*it); @@ -239,7 +239,7 @@ void X3DImporter::ParseNode_Geometry3D_Cylinder() if(bottom) { - for(std::vector::iterator it = tcir.begin(); it != tcir.end(); it++) + for(std::vector::iterator it = tcir.begin(); it != tcir.end(); ++it) { (*it).y = -height;// y - because circle made in oXZ. vlist.push_back(*it); @@ -336,7 +336,7 @@ void X3DImporter::ParseNode_Geometry3D_ElevationGrid() aiVector3D tvec(xSpacing * xi, *he_it, zSpacing * zi); grid_alias.Vertices.push_back(tvec); - he_it++; + ++he_it; } } }// END: create grid vertices list @@ -977,7 +977,7 @@ void X3DImporter::ParseNode_Geometry3D_Sphere() StandardShapes::MakeSphere(tess, tlist); // copy data from temp array and apply scale - for(std::vector::iterator it = tlist.begin(); it != tlist.end(); it++) + for(std::vector::iterator it = tlist.begin(); it != tlist.end(); ++it) { ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices.push_back(*it * radius); } diff --git a/code/X3D/X3DImporter_Networking.cpp b/code/X3D/X3DImporter_Networking.cpp index 89010ae08..a7a200675 100644 --- a/code/X3D/X3DImporter_Networking.cpp +++ b/code/X3D/X3DImporter_Networking.cpp @@ -93,7 +93,7 @@ void X3DImporter::ParseNode_Networking_Inline() // at this place new group mode created and made current, so we can name it. if(!def.empty()) NodeElement_Cur->ID = def; - if(load && (url.size() > 0)) + if(load && !url.empty()) { std::string full_path = mpIOHandler->CurrentDirectory() + url.front(); diff --git a/code/X3D/X3DImporter_Postprocess.cpp b/code/X3D/X3DImporter_Postprocess.cpp index e7686b41e..539563fcf 100644 --- a/code/X3D/X3DImporter_Postprocess.cpp +++ b/code/X3D/X3DImporter_Postprocess.cpp @@ -81,7 +81,7 @@ aiMatrix4x4 X3DImporter::PostprocessHelper_Matrix_GlobalToCurrent() const } // multiplicate all matrices in reverse order - for(std::list::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); rit++) out_matr = out_matr * (*rit); + for(std::list::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); ++rit) out_matr = out_matr * (*rit); return out_matr; } @@ -89,7 +89,7 @@ aiMatrix4x4 X3DImporter::PostprocessHelper_Matrix_GlobalToCurrent() const void X3DImporter::PostprocessHelper_CollectMetadata(const CX3DImporter_NodeElement& pNodeElement, std::list& pList) const { // walk through childs and find for metadata. - for(std::list::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); el_it++) + for(std::list::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); ++el_it) { if(((*el_it)->Type == CX3DImporter_NodeElement::ENET_MetaBoolean) || ((*el_it)->Type == CX3DImporter_NodeElement::ENET_MetaDouble) || ((*el_it)->Type == CX3DImporter_NodeElement::ENET_MetaFloat) || ((*el_it)->Type == CX3DImporter_NodeElement::ENET_MetaInteger) || @@ -194,7 +194,7 @@ void X3DImporter::Postprocess_BuildMaterial(const CX3DImporter_NodeElement& pNod aiMaterial& taimat = **pMaterial;// creating alias for convenience. // at this point pNodeElement point to node. Walk through childs and add all stored data. - for(std::list::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); el_it++) + for(std::list::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); ++el_it) { if((*el_it)->Type == CX3DImporter_NodeElement::ENET_Material) { @@ -255,7 +255,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle std::vector tarr; tarr.reserve(tnemesh.Vertices.size()); - for(std::list::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); it++) tarr.push_back(*it); + for(std::list::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); ++it) tarr.push_back(*it); *pMesh = StandardShapes::MakeMesh(tarr, static_cast(tnemesh.NumIndices));// create mesh from vertices using Assimp help. return;// mesh is build, nothing to do anymore. @@ -273,7 +273,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle std::vector tarr; tarr.reserve(tnemesh.Vertices.size()); - for(std::list::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); it++) tarr.push_back(*it); + for(std::list::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); ++it) tarr.push_back(*it); *pMesh = StandardShapes::MakeMesh(tarr, static_cast(tnemesh.NumIndices));// create mesh from vertices using Assimp help. @@ -289,7 +289,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle // at first create mesh from existing vertices. *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIdx, tnemesh.Vertices); // copy additional information from children - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color*)*ch_it)->Value, tnemesh.ColorPerVertex); @@ -301,7 +301,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle MeshGeometry_AddTexCoord(**pMesh, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value); else throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of ElevationGrid: " + to_string((*ch_it)->Type) + "."); - }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) return;// mesh is build, nothing to do anymore. }// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_ElevationGrid) @@ -313,7 +313,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle CX3DImporter_NodeElement_IndexedSet& tnemesh = *((CX3DImporter_NodeElement_IndexedSet*)&pNodeElement);// create alias for convenience // at first search for node and create mesh. - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { @@ -322,7 +322,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle } // copy additional information from children - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_Color*)*ch_it)->Value, tnemesh.ColorPerVertex); @@ -338,7 +338,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value); else throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedFaceSet: " + to_string((*ch_it)->Type) + "."); - }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) return;// mesh is build, nothing to do anymore. }// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedFaceSet) @@ -348,7 +348,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle CX3DImporter_NodeElement_IndexedSet& tnemesh = *((CX3DImporter_NodeElement_IndexedSet*)&pNodeElement);// create alias for convenience // at first search for node and create mesh. - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { @@ -357,7 +357,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle } // copy additional information from children - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { ai_assert(*pMesh); if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) @@ -369,7 +369,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle {} // skip because already read when mesh created. else throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedLineSet: " + to_string((*ch_it)->Type) + "."); - }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) return;// mesh is build, nothing to do anymore. }// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedLineSet) @@ -381,7 +381,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle CX3DImporter_NodeElement_IndexedSet& tnemesh = *((CX3DImporter_NodeElement_IndexedSet*)&pNodeElement);// create alias for convenience // at first search for node and create mesh. - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { @@ -390,7 +390,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle } // copy additional information from children - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { ai_assert(*pMesh); if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) @@ -408,7 +408,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle else throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedTriangleSet or IndexedTriangleFanSet, or \ IndexedTriangleStripSet: " + to_string((*ch_it)->Type) + "."); - }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) return;// mesh is build, nothing to do anymore. }// if((pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleFanSet) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleStripSet)) @@ -430,7 +430,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience // at first search for node and create mesh. - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { @@ -438,7 +438,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle vec_copy.reserve(((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.size()); for(std::list::const_iterator it = ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.begin(); - it != ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.end(); it++) + it != ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.end(); ++it) { vec_copy.push_back(*it); } @@ -448,7 +448,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle } // copy additional information from children - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { ai_assert(*pMesh); if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) @@ -459,7 +459,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle {} // skip because already read when mesh created. else throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of PointSet: " + to_string((*ch_it)->Type) + "."); - }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) return;// mesh is build, nothing to do anymore. }// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_PointSet) @@ -469,7 +469,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience // at first search for node and create mesh. - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { @@ -478,7 +478,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle } // copy additional information from children - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { ai_assert(*pMesh); if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) @@ -489,7 +489,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle {} // skip because already read when mesh created. else throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of LineSet: " + to_string((*ch_it)->Type) + "."); - }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) return;// mesh is build, nothing to do anymore. }// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_LineSet) @@ -499,7 +499,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience // at first search for node and create mesh. - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { @@ -508,7 +508,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle } // copy additional information from children - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { if ( nullptr == *pMesh ) { break; @@ -526,7 +526,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value); else throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeFanSet: " + to_string((*ch_it)->Type) + "."); - }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) return;// mesh is build, nothing to do anymore. }// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleFanSet) @@ -536,7 +536,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience // at first search for node and create mesh. - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { @@ -544,7 +544,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle vec_copy.reserve(((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.size()); for(std::list::const_iterator it = ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.begin(); - it != ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.end(); it++) + it != ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.end(); ++it) { vec_copy.push_back(*it); } @@ -554,7 +554,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle } // copy additional information from children - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { ai_assert(*pMesh); if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) @@ -570,7 +570,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value); else throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeSet: " + to_string((*ch_it)->Type) + "."); - }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) return;// mesh is build, nothing to do anymore. }// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleSet) @@ -580,7 +580,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience // at first search for node and create mesh. - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { @@ -589,7 +589,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle } // copy additional information from children - for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { ai_assert(*pMesh); if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) @@ -605,7 +605,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value); else throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TriangleStripSet: " + to_string((*ch_it)->Type) + "."); - }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++) + }// for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) return;// mesh is build, nothing to do anymore. }// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleStripSet) @@ -639,16 +639,16 @@ void X3DImporter::Postprocess_BuildNode(const CX3DImporter_NodeElement& pNodeEle } else { - for(size_t i = 0; i < (size_t)tne_group.Choice; i++) chit_begin++;// forward iterator to chosen node. + for(size_t i = 0; i < (size_t)tne_group.Choice; i++) ++chit_begin;// forward iterator to chosen node. chit_end = chit_begin; - chit_end++;// point end iterator to next element after chosen node. + ++chit_end;// point end iterator to next element after chosen node. } }// if(tne_group.UseChoice) }// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_Group) // Reserve memory for fast access and check children. - for(std::list::const_iterator it = chit_begin; it != chit_end; it++) + for(std::list::const_iterator it = chit_begin; it != chit_end; ++it) {// in this loop we do not read metadata because it's already read at begin. if((*it)->Type == CX3DImporter_NodeElement::ENET_Group) { @@ -677,7 +677,7 @@ void X3DImporter::Postprocess_BuildNode(const CX3DImporter_NodeElement& pNodeEle }// for(std::list::const_iterator it = chit_begin; it != chit_end; it++) // copy data about children and meshes to aiNode. - if(SceneNode_Child.size() > 0) + if(!SceneNode_Child.empty()) { std::list::const_iterator it = SceneNode_Child.begin(); @@ -686,7 +686,7 @@ void X3DImporter::Postprocess_BuildNode(const CX3DImporter_NodeElement& pNodeEle for(size_t i = 0; i < pSceneNode.mNumChildren; i++) pSceneNode.mChildren[i] = *it++; } - if(SceneNode_Mesh.size() > 0) + if(!SceneNode_Mesh.empty()) { std::list::const_iterator it = SceneNode_Mesh.begin(); @@ -706,7 +706,7 @@ void X3DImporter::Postprocess_BuildShape(const CX3DImporter_NodeElement_Shape& p CX3DImporter_NodeElement::EType mesh_type = CX3DImporter_NodeElement::ENET_Invalid; unsigned int mat_ind = 0; - for(std::list::const_iterator it = pShapeNodeElement.Child.begin(); it != pShapeNodeElement.Child.end(); it++) + for(std::list::const_iterator it = pShapeNodeElement.Child.begin(); it != pShapeNodeElement.Child.end(); ++it) { if(PostprocessHelper_ElementIsMesh((*it)->Type)) { @@ -779,7 +779,7 @@ void X3DImporter::Postprocess_CollectMetadata(const CX3DImporter_NodeElement& pN // copy collected metadata to output node. pSceneNode.mMetaData = aiMetadata::Alloc( static_cast(meta_list.size()) ); meta_idx = 0; - for(std::list::const_iterator it = meta_list.begin(); it != meta_list.end(); it++, meta_idx++) + for(std::list::const_iterator it = meta_list.begin(); it != meta_list.end(); ++it, ++meta_idx) { CX3DImporter_NodeElement_Meta* cur_meta = (CX3DImporter_NodeElement_Meta*)*it; diff --git a/code/X3D/X3DImporter_Rendering.cpp b/code/X3D/X3DImporter_Rendering.cpp index d0c58030d..6e95c9441 100644 --- a/code/X3D/X3DImporter_Rendering.cpp +++ b/code/X3D/X3DImporter_Rendering.cpp @@ -295,7 +295,7 @@ void X3DImporter::ParseNode_Rendering_IndexedTriangleFanSet() ne_alias.CoordIndex.clear(); int counter = 0; int32_t idx[3]; - for(std::vector::const_iterator idx_it = index.begin(); idx_it != index.end(); idx_it++) + for(std::vector::const_iterator idx_it = index.begin(); idx_it != index.end(); ++idx_it) { idx[2] = *idx_it; if (idx[2] < 0) @@ -413,7 +413,7 @@ void X3DImporter::ParseNode_Rendering_IndexedTriangleSet() ne_alias.CoordIndex.clear(); int counter = 0; int32_t idx[3]; - for(std::vector::const_iterator idx_it = index.begin(); idx_it != index.end(); idx_it++) + for(std::vector::const_iterator idx_it = index.begin(); idx_it != index.end(); ++idx_it) { idx[counter++] = *idx_it; if (counter > 2) @@ -519,7 +519,7 @@ void X3DImporter::ParseNode_Rendering_IndexedTriangleStripSet() ne_alias.CoordIndex.clear(); int counter = 0; int32_t idx[3]; - for(std::vector::const_iterator idx_it = index.begin(); idx_it != index.end(); idx_it++) + for(std::vector::const_iterator idx_it = index.begin(); idx_it != index.end(); ++idx_it) { idx[2] = *idx_it; if (idx[2] < 0) @@ -617,7 +617,7 @@ void X3DImporter::ParseNode_Rendering_LineSet() size_t coord_num = 0; ne_alias.CoordIndex.clear(); - for(std::vector::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); vc_it++) + for(std::vector::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); ++vc_it) { if(*vc_it < 2) throw DeadlyImportError("LineSet. vertexCount shall be greater than or equal to two."); @@ -765,7 +765,7 @@ void X3DImporter::ParseNode_Rendering_TriangleFanSet() // assign indices for first triangle coord_num_first = 0; coord_num_prev = 1; - for(std::vector::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); vc_it++) + for(std::vector::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); ++vc_it) { if(*vc_it < 3) throw DeadlyImportError("TriangleFanSet. fanCount shall be greater than or equal to three."); @@ -956,7 +956,7 @@ void X3DImporter::ParseNode_Rendering_TriangleStripSet() ne_alias.CoordIndex.clear(); coord_num_sb = 0; - for(std::vector::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); vc_it++) + for(std::vector::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); ++vc_it) { if(*vc_it < 3) throw DeadlyImportError("TriangleStripSet. stripCount shall be greater than or equal to three."); diff --git a/code/X3D/X3DImporter_Texturing.cpp b/code/X3D/X3DImporter_Texturing.cpp index b4e42025a..2eaf3e6bc 100644 --- a/code/X3D/X3DImporter_Texturing.cpp +++ b/code/X3D/X3DImporter_Texturing.cpp @@ -89,7 +89,7 @@ void X3DImporter::ParseNode_Texturing_ImageTexture() ((CX3DImporter_NodeElement_ImageTexture*)ne)->RepeatS = repeatS; ((CX3DImporter_NodeElement_ImageTexture*)ne)->RepeatT = repeatT; // Attribute "url" can contain list of strings. But we need only one - first. - if(url.size() > 0) + if(!url.empty()) ((CX3DImporter_NodeElement_ImageTexture*)ne)->URL = url.front(); else ((CX3DImporter_NodeElement_ImageTexture*)ne)->URL = ""; diff --git a/code/glTF/glTFAsset.h b/code/glTF/glTFAsset.h index 359917b95..ddc7f086e 100644 --- a/code/glTF/glTFAsset.h +++ b/code/glTF/glTFAsset.h @@ -92,38 +92,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # endif #endif +#include "glTF/glTFCommon.h" + namespace glTF { -#ifdef ASSIMP_API - using Assimp::IOStream; - using Assimp::IOSystem; - using std::shared_ptr; -#else - using std::shared_ptr; - - typedef std::runtime_error DeadlyImportError; - typedef std::runtime_error DeadlyExportError; - - enum aiOrigin { aiOrigin_SET = 0, aiOrigin_CUR = 1, aiOrigin_END = 2 }; - class IOSystem; - class IOStream - { - FILE* f; - public: - IOStream(FILE* file) : f(file) {} - ~IOStream() { fclose(f); f = 0; } - - size_t Read(void* b, size_t sz, size_t n) { return fread(b, sz, n, f); } - size_t Write(const void* b, size_t sz, size_t n) { return fwrite(b, sz, n, f); } - int Seek(size_t off, aiOrigin orig) { return fseek(f, off, int(orig)); } - size_t Tell() const { return ftell(f); } - - size_t FileSize() { - long p = Tell(), len = (Seek(0, aiOrigin_END), Tell()); - return size_t((Seek(p, aiOrigin_SET), len)); - } - }; -#endif + using glTFCommon::shared_ptr; + using glTFCommon::IOSystem; + using glTFCommon::IOStream; using rapidjson::Value; using rapidjson::Document; @@ -136,37 +111,9 @@ namespace glTF struct Light; struct Skin; - - // Vec/matrix types, as raw float arrays - typedef float (vec3)[3]; - typedef float (vec4)[4]; - typedef float (mat4)[16]; - - - namespace Util - { - void EncodeBase64(const uint8_t* in, size_t inLength, std::string& out); - - size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out); - - inline size_t DecodeBase64(const char* in, uint8_t*& out) - { - return DecodeBase64(in, strlen(in), out); - } - - struct DataURI - { - const char* mediaType; - const char* charset; - bool base64; - const char* data; - size_t dataLength; - }; - - //! Check if a uri is a data URI - inline bool ParseDataURI(const char* uri, size_t uriLen, DataURI& out); - } - + using glTFCommon::vec3; + using glTFCommon::vec4; + using glTFCommon::mat4; //! Magic number for GLB files #define AI_GLB_MAGIC_NUMBER "glTF" diff --git a/code/glTF/glTFAsset.inl b/code/glTF/glTFAsset.inl index 7b7acd705..25cf1873c 100644 --- a/code/glTF/glTFAsset.inl +++ b/code/glTF/glTFAsset.inl @@ -4,7 +4,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -52,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endif using namespace Assimp; +using namespace glTFCommon; namespace glTF { @@ -301,7 +301,7 @@ inline void Buffer::Read(Value& obj, Asset& r) const char* uri = it->GetString(); - Util::DataURI dataURI; + glTFCommon::Util::DataURI dataURI; if (ParseDataURI(uri, it->GetStringLength(), dataURI)) { if (dataURI.base64) { uint8_t* data = 0; @@ -654,12 +654,12 @@ inline void Image::Read(Value& obj, Asset& r) if (Value* uri = FindString(obj, "uri")) { const char* uristr = uri->GetString(); - Util::DataURI dataURI; + glTFCommon::Util::DataURI dataURI; if (ParseDataURI(uristr, uri->GetStringLength(), dataURI)) { mimeType = dataURI.mediaType; if (dataURI.base64) { uint8_t *ptr = nullptr; - mDataLength = Util::DecodeBase64(dataURI.data, dataURI.dataLength, ptr); + mDataLength = glTFCommon::Util::DecodeBase64(dataURI.data, dataURI.dataLength, ptr); mData.reset(ptr); } } @@ -1180,8 +1180,12 @@ inline void Light::SetDefaults() falloffExponent = 0.f; } -inline void Node::Read(Value& obj, Asset& r) -{ +inline +void Node::Read(Value& obj, Asset& r) { + if (name.empty()) { + name = id; + } + if (Value* children = FindArray(obj, "children")) { this->children.reserve(children->Size()); for (unsigned int i = 0; i < children->Size(); ++i) { @@ -1423,9 +1427,6 @@ inline void Asset::ReadExtensionsUsed(Document& doc) } } - #define CHECK_EXT(EXT) \ - if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true; - CHECK_EXT(KHR_binary_glTF); CHECK_EXT(KHR_materials_common); @@ -1474,190 +1475,4 @@ inline std::string Asset::FindUniqueID(const std::string& str, const char* suffi return id; } -namespace Util { - - inline - bool ParseDataURI(const char* const_uri, size_t uriLen, DataURI& out) { - if ( NULL == const_uri ) { - return false; - } - - if (const_uri[0] != 0x10) { // we already parsed this uri? - if (strncmp(const_uri, "data:", 5) != 0) // not a data uri? - return false; - } - - // set defaults - out.mediaType = "text/plain"; - out.charset = "US-ASCII"; - out.base64 = false; - - char* uri = const_cast(const_uri); - if (uri[0] != 0x10) { - uri[0] = 0x10; - uri[1] = uri[2] = uri[3] = uri[4] = 0; - - size_t i = 5, j; - if (uri[i] != ';' && uri[i] != ',') { // has media type? - uri[1] = char(i); - for (; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) { - // nothing to do! - } - } - while (uri[i] == ';' && i < uriLen) { - uri[i++] = '\0'; - for (j = i; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) { - // nothing to do! - } - - if ( strncmp( uri + j, "charset=", 8 ) == 0 ) { - uri[2] = char(j + 8); - } else if ( strncmp( uri + j, "base64", 6 ) == 0 ) { - uri[3] = char(j); - } - } - if (i < uriLen) { - uri[i++] = '\0'; - uri[4] = char(i); - } else { - uri[1] = uri[2] = uri[3] = 0; - uri[4] = 5; - } - } - - if ( uri[ 1 ] != 0 ) { - out.mediaType = uri + uri[ 1 ]; - } - if ( uri[ 2 ] != 0 ) { - out.charset = uri + uri[ 2 ]; - } - if ( uri[ 3 ] != 0 ) { - out.base64 = true; - } - out.data = uri + uri[4]; - out.dataLength = (uri + uriLen) - out.data; - - return true; - } - - template - struct DATA - { - static const uint8_t tableDecodeBase64[128]; - }; - - template - const uint8_t DATA::tableDecodeBase64[128] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 64, 0, 0, - 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, - 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0 - }; - - inline char EncodeCharBase64(uint8_t b) - { - return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[size_t(b)]; - } - - inline uint8_t DecodeCharBase64(char c) - { - return DATA::tableDecodeBase64[size_t(c)]; // TODO faster with lookup table or ifs? - /*if (c >= 'A' && c <= 'Z') return c - 'A'; - if (c >= 'a' && c <= 'z') return c - 'a' + 26; - if (c >= '0' && c <= '9') return c - '0' + 52; - if (c == '+') return 62; - if (c == '/') return 63; - return 64; // '-' */ - } - - inline size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out) - { - ai_assert(inLength % 4 == 0); - - if (inLength < 4) { - out = 0; - return 0; - } - - int nEquals = int(in[inLength - 1] == '=') + - int(in[inLength - 2] == '='); - - size_t outLength = (inLength * 3) / 4 - nEquals; - out = new uint8_t[outLength]; - memset(out, 0, outLength); - - size_t i, j = 0; - - for (i = 0; i + 4 < inLength; i += 4) { - uint8_t b0 = DecodeCharBase64(in[i]); - uint8_t b1 = DecodeCharBase64(in[i + 1]); - uint8_t b2 = DecodeCharBase64(in[i + 2]); - uint8_t b3 = DecodeCharBase64(in[i + 3]); - - out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); - out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); - out[j++] = (uint8_t)((b2 << 6) | b3); - } - - { - uint8_t b0 = DecodeCharBase64(in[i]); - uint8_t b1 = DecodeCharBase64(in[i + 1]); - uint8_t b2 = DecodeCharBase64(in[i + 2]); - uint8_t b3 = DecodeCharBase64(in[i + 3]); - - out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); - if (b2 < 64) out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); - if (b3 < 64) out[j++] = (uint8_t)((b2 << 6) | b3); - } - - return outLength; - } - - - - inline void EncodeBase64( - const uint8_t* in, size_t inLength, - std::string& out) - { - size_t outLength = ((inLength + 2) / 3) * 4; - - size_t j = out.size(); - out.resize(j + outLength); - - for (size_t i = 0; i < inLength; i += 3) { - uint8_t b = (in[i] & 0xFC) >> 2; - out[j++] = EncodeCharBase64(b); - - b = (in[i] & 0x03) << 4; - if (i + 1 < inLength) { - b |= (in[i + 1] & 0xF0) >> 4; - out[j++] = EncodeCharBase64(b); - - b = (in[i + 1] & 0x0F) << 2; - if (i + 2 < inLength) { - b |= (in[i + 2] & 0xC0) >> 6; - out[j++] = EncodeCharBase64(b); - - b = in[i + 2] & 0x3F; - out[j++] = EncodeCharBase64(b); - } - else { - out[j++] = EncodeCharBase64(b); - out[j++] = '='; - } - } - else { - out[j++] = EncodeCharBase64(b); - out[j++] = '='; - out[j++] = '='; - } - } - } - -} - } // ns glTF diff --git a/code/glTF/glTFAssetWriter.inl b/code/glTF/glTFAssetWriter.inl index 20afb24e7..1bbb8fd8c 100644 --- a/code/glTF/glTFAssetWriter.inl +++ b/code/glTF/glTFAssetWriter.inl @@ -55,7 +55,8 @@ namespace glTF { namespace { template - inline Value& MakeValue(Value& val, float(&r)[N], MemoryPoolAllocator<>& al) { + inline + Value& MakeValue(Value& val, float(&r)[N], MemoryPoolAllocator<>& al) { val.SetArray(); val.Reserve(N, al); for (decltype(N) i = 0; i < N; ++i) { @@ -64,7 +65,8 @@ namespace glTF { return val; } - inline Value& MakeValue(Value& val, const std::vector & r, MemoryPoolAllocator<>& al) { + inline + Value& MakeValue(Value& val, const std::vector & r, MemoryPoolAllocator<>& al) { val.SetArray(); val.Reserve(static_cast(r.size()), al); for (unsigned int i = 0; i < r.size(); ++i) { @@ -213,7 +215,7 @@ namespace glTF { else if (img.HasData()) { uri = "data:" + (img.mimeType.empty() ? "application/octet-stream" : img.mimeType); uri += ";base64,"; - Util::EncodeBase64(img.GetData(), img.GetDataLength(), uri); + glTFCommon::Util::EncodeBase64(img.GetData(), img.GetDataLength(), uri); } else { uri = img.uri; diff --git a/code/glTF/glTFCommon.cpp b/code/glTF/glTFCommon.cpp new file mode 100644 index 000000000..cd03224e4 --- /dev/null +++ b/code/glTF/glTFCommon.cpp @@ -0,0 +1,193 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#include "glTF/glTFCommon.h" + +namespace glTFCommon { + +using namespace glTFCommon::Util; + +namespace Util { + +size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out) { + ai_assert(inLength % 4 == 0); + + if (inLength < 4) { + out = 0; + return 0; + } + + int nEquals = int(in[inLength - 1] == '=') + + int(in[inLength - 2] == '='); + + size_t outLength = (inLength * 3) / 4 - nEquals; + out = new uint8_t[outLength]; + memset(out, 0, outLength); + + size_t i, j = 0; + + for (i = 0; i + 4 < inLength; i += 4) { + uint8_t b0 = DecodeCharBase64(in[i]); + uint8_t b1 = DecodeCharBase64(in[i + 1]); + uint8_t b2 = DecodeCharBase64(in[i + 2]); + uint8_t b3 = DecodeCharBase64(in[i + 3]); + + out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); + out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); + out[j++] = (uint8_t)((b2 << 6) | b3); + } + + { + uint8_t b0 = DecodeCharBase64(in[i]); + uint8_t b1 = DecodeCharBase64(in[i + 1]); + uint8_t b2 = DecodeCharBase64(in[i + 2]); + uint8_t b3 = DecodeCharBase64(in[i + 3]); + + out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); + if (b2 < 64) out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); + if (b3 < 64) out[j++] = (uint8_t)((b2 << 6) | b3); + } + + return outLength; +} + +void EncodeBase64(const uint8_t* in, size_t inLength, std::string& out) { + size_t outLength = ((inLength + 2) / 3) * 4; + + size_t j = out.size(); + out.resize(j + outLength); + + for (size_t i = 0; i < inLength; i += 3) { + uint8_t b = (in[i] & 0xFC) >> 2; + out[j++] = EncodeCharBase64(b); + + b = (in[i] & 0x03) << 4; + if (i + 1 < inLength) { + b |= (in[i + 1] & 0xF0) >> 4; + out[j++] = EncodeCharBase64(b); + + b = (in[i + 1] & 0x0F) << 2; + if (i + 2 < inLength) { + b |= (in[i + 2] & 0xC0) >> 6; + out[j++] = EncodeCharBase64(b); + + b = in[i + 2] & 0x3F; + out[j++] = EncodeCharBase64(b); + } + else { + out[j++] = EncodeCharBase64(b); + out[j++] = '='; + } + } + else { + out[j++] = EncodeCharBase64(b); + out[j++] = '='; + out[j++] = '='; + } + } +} + +bool ParseDataURI(const char* const_uri, size_t uriLen, DataURI& out) { + if (nullptr == const_uri) { + return false; + } + + if (const_uri[0] != 0x10) { // we already parsed this uri? + if (strncmp(const_uri, "data:", 5) != 0) // not a data uri? + return false; + } + + // set defaults + out.mediaType = "text/plain"; + out.charset = "US-ASCII"; + out.base64 = false; + + char* uri = const_cast(const_uri); + if (uri[0] != 0x10) { + uri[0] = 0x10; + uri[1] = uri[2] = uri[3] = uri[4] = 0; + + size_t i = 5, j; + if (uri[i] != ';' && uri[i] != ',') { // has media type? + uri[1] = char(i); + for (; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) { + // nothing to do! + } + } + while (uri[i] == ';' && i < uriLen) { + uri[i++] = '\0'; + for (j = i; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) { + // nothing to do! + } + + if (strncmp(uri + j, "charset=", 8) == 0) { + uri[2] = char(j + 8); + } + else if (strncmp(uri + j, "base64", 6) == 0) { + uri[3] = char(j); + } + } + if (i < uriLen) { + uri[i++] = '\0'; + uri[4] = char(i); + } + else { + uri[1] = uri[2] = uri[3] = 0; + uri[4] = 5; + } + } + + if (uri[1] != 0) { + out.mediaType = uri + uri[1]; + } + if (uri[2] != 0) { + out.charset = uri + uri[2]; + } + if (uri[3] != 0) { + out.base64 = true; + } + out.data = uri + uri[4]; + out.dataLength = (uri + uriLen) - out.data; + + return true; +} + +} +} diff --git a/code/glTF/glTFCommon.h b/code/glTF/glTFCommon.h new file mode 100644 index 000000000..b2e28d580 --- /dev/null +++ b/code/glTF/glTFCommon.h @@ -0,0 +1,245 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#ifndef AI_GLFTCOMMON_H_INC +#define AI_GLFTCOMMON_H_INC + +#ifndef ASSIMP_BUILD_NO_GLTF_IMPORTER + +#include + +#include +#include +#include +#include +#include +#include + +#define RAPIDJSON_HAS_STDSTRING 1 +#include +#include +#include + +#ifdef ASSIMP_API +# include +# include +# include +#else +# include +# define AI_SWAP4(p) +# define ai_assert +#endif + + +#if _MSC_VER > 1500 || (defined __GNUC___) +# define ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +# else +# define gltf_unordered_map map +#endif + +#ifdef ASSIMP_GLTF_USE_UNORDERED_MULTIMAP +# include +# if _MSC_VER > 1600 +# define gltf_unordered_map unordered_map +# else +# define gltf_unordered_map tr1::unordered_map +# endif +#endif + +namespace glTFCommon { + +#ifdef ASSIMP_API + using Assimp::IOStream; + using Assimp::IOSystem; + using std::shared_ptr; +#else + using std::shared_ptr; + + typedef std::runtime_error DeadlyImportError; + typedef std::runtime_error DeadlyExportError; + + enum aiOrigin { + aiOrigin_SET = 0, + aiOrigin_CUR = 1, + aiOrigin_END = 2 + }; + + class IOSystem; + + class IOStream { + public: + IOStream(FILE* file) : f(file) {} + ~IOStream() { fclose(f); f = 0; } + + size_t Read(void* b, size_t sz, size_t n) { return fread(b, sz, n, f); } + size_t Write(const void* b, size_t sz, size_t n) { return fwrite(b, sz, n, f); } + int Seek(size_t off, aiOrigin orig) { return fseek(f, off, int(orig)); } + size_t Tell() const { return ftell(f); } + + size_t FileSize() { + long p = Tell(), len = (Seek(0, aiOrigin_END), Tell()); + return size_t((Seek(p, aiOrigin_SET), len)); + } + + private: + FILE* f; + }; +#endif + + // Vec/matrix types, as raw float arrays + typedef float(vec3)[3]; + typedef float(vec4)[4]; + typedef float(mat4)[16]; + + inline + void CopyValue(const glTFCommon::vec3& v, aiColor4D& out) { + out.r = v[0]; + out.g = v[1]; + out.b = v[2]; + out.a = 1.0; + } + + inline + void CopyValue(const glTFCommon::vec4& v, aiColor4D& out) { + out.r = v[0]; + out.g = v[1]; + out.b = v[2]; + out.a = v[3]; + } + + inline + void CopyValue(const glTFCommon::vec4& v, aiColor3D& out) { + out.r = v[0]; + out.g = v[1]; + out.b = v[2]; + } + + inline + void CopyValue(const glTFCommon::vec3& v, aiColor3D& out) { + out.r = v[0]; + out.g = v[1]; + out.b = v[2]; + } + + inline + void CopyValue(const glTFCommon::vec3& v, aiVector3D& out) { + out.x = v[0]; + out.y = v[1]; + out.z = v[2]; + } + + inline + void CopyValue(const glTFCommon::vec4& v, aiQuaternion& out) { + out.x = v[0]; + out.y = v[1]; + out.z = v[2]; + out.w = v[3]; + } + + inline + void CopyValue(const glTFCommon::mat4& v, aiMatrix4x4& o) { + o.a1 = v[0]; o.b1 = v[1]; o.c1 = v[2]; o.d1 = v[3]; + o.a2 = v[4]; o.b2 = v[5]; o.c2 = v[6]; o.d2 = v[7]; + o.a3 = v[8]; o.b3 = v[9]; o.c3 = v[10]; o.d3 = v[11]; + o.a4 = v[12]; o.b4 = v[13]; o.c4 = v[14]; o.d4 = v[15]; + } + + namespace Util { + + void EncodeBase64(const uint8_t* in, size_t inLength, std::string& out); + + size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out); + + inline + size_t DecodeBase64(const char* in, uint8_t*& out) { + return DecodeBase64(in, strlen(in), out); + } + + struct DataURI { + const char* mediaType; + const char* charset; + bool base64; + const char* data; + size_t dataLength; + }; + + //! Check if a uri is a data URI + bool ParseDataURI(const char* const_uri, size_t uriLen, DataURI& out); + + template + struct DATA { + static const uint8_t tableDecodeBase64[128]; + }; + + template + const uint8_t DATA::tableDecodeBase64[128] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 64, 0, 0, + 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, + 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0 + }; + + inline + char EncodeCharBase64(uint8_t b) { + return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[size_t(b)]; + } + + inline + uint8_t DecodeCharBase64(char c) { + return DATA::tableDecodeBase64[size_t(c)]; // TODO faster with lookup table or ifs? + } + + size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out); + + void EncodeBase64(const uint8_t* in, size_t inLength, std::string& out); + } // namespace Util + +#define CHECK_EXT(EXT) \ + if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true; + +} + +#endif // ASSIMP_BUILD_NO_GLTF_IMPORTER + +#endif // AI_GLFTCOMMON_H_INC diff --git a/code/glTF/glTFExporter.cpp b/code/glTF/glTFExporter.cpp index 3bd944bb6..034f91f3b 100644 --- a/code/glTF/glTFExporter.cpp +++ b/code/glTF/glTFExporter.cpp @@ -242,7 +242,10 @@ inline Ref ExportData(Asset& a, std::string& meshName, Ref& bu namespace { void GetMatScalar(const aiMaterial* mat, float& val, const char* propName, int type, int idx) { - ai_assert(mat->Get(propName, type, idx, val) == AI_SUCCESS); + ai_assert( nullptr != mat ); + if ( nullptr != mat ) { + mat->Get(propName, type, idx, val); + } } } diff --git a/code/glTF/glTFImporter.cpp b/code/glTF/glTFImporter.cpp index 146d7453e..9ec13ea69 100644 --- a/code/glTF/glTFImporter.cpp +++ b/code/glTF/glTFImporter.cpp @@ -82,7 +82,7 @@ glTFImporter::glTFImporter() : BaseImporter() , meshOffsets() , embeddedTexIdxs() -, mScene( NULL ) { +, mScene( nullptr ) { // empty } @@ -90,17 +90,16 @@ glTFImporter::~glTFImporter() { // empty } -const aiImporterDesc* glTFImporter::GetInfo() const -{ +const aiImporterDesc* glTFImporter::GetInfo() const { return &desc; } -bool glTFImporter::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool /* checkSig */) const -{ +bool glTFImporter::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool /* checkSig */) const { const std::string &extension = GetExtension(pFile); - if (extension != "gltf" && extension != "glb") + if (extension != "gltf" && extension != "glb") { return false; + } if (pIOHandler) { glTF::Asset asset(pIOHandler); @@ -116,44 +115,9 @@ bool glTFImporter::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool return false; } - - -//static void CopyValue(const glTF::vec3& v, aiColor3D& out) -//{ -// out.r = v[0]; out.g = v[1]; out.b = v[2]; -//} - -static void CopyValue(const glTF::vec4& v, aiColor4D& out) -{ - out.r = v[0]; out.g = v[1]; out.b = v[2]; out.a = v[3]; -} - -static void CopyValue(const glTF::vec4& v, aiColor3D& out) -{ - out.r = v[0]; out.g = v[1]; out.b = v[2]; -} - -static void CopyValue(const glTF::vec3& v, aiVector3D& out) -{ - out.x = v[0]; out.y = v[1]; out.z = v[2]; -} - -static void CopyValue(const glTF::vec4& v, aiQuaternion& out) -{ - out.x = v[0]; out.y = v[1]; out.z = v[2]; out.w = v[3]; -} - -static void CopyValue(const glTF::mat4& v, aiMatrix4x4& o) -{ - o.a1 = v[ 0]; o.b1 = v[ 1]; o.c1 = v[ 2]; o.d1 = v[ 3]; - o.a2 = v[ 4]; o.b2 = v[ 5]; o.c2 = v[ 6]; o.d2 = v[ 7]; - o.a3 = v[ 8]; o.b3 = v[ 9]; o.c3 = v[10]; o.d3 = v[11]; - o.a4 = v[12]; o.b4 = v[13]; o.c4 = v[14]; o.d4 = v[15]; -} - -inline void SetMaterialColorProperty(std::vector& embeddedTexIdxs, Asset& /*r*/, glTF::TexProperty prop, aiMaterial* mat, - aiTextureType texType, const char* pKey, unsigned int type, unsigned int idx) -{ +inline +void SetMaterialColorProperty(std::vector& embeddedTexIdxs, Asset& /*r*/, glTF::TexProperty prop, aiMaterial* mat, + aiTextureType texType, const char* pKey, unsigned int type, unsigned int idx) { if (prop.texture) { if (prop.texture->source) { aiString uri(prop.texture->source->uri); @@ -167,16 +131,14 @@ inline void SetMaterialColorProperty(std::vector& embeddedTexIdxs, Asset& / mat->AddProperty(&uri, _AI_MATKEY_TEXTURE_BASE, texType, 0); } - } - else { + } else { aiColor4D col; CopyValue(prop.color, col); mat->AddProperty(&col, 1, pKey, type, idx); } } -void glTFImporter::ImportMaterials(glTF::Asset& r) -{ +void glTFImporter::ImportMaterials(glTF::Asset& r) { mScene->mNumMaterials = unsigned(r.materials.Size()); mScene->mMaterials = new aiMaterial*[mScene->mNumMaterials]; @@ -208,6 +170,8 @@ void glTFImporter::ImportMaterials(glTF::Asset& r) if (mScene->mNumMaterials == 0) { mScene->mNumMaterials = 1; + // Delete the array of length zero created above. + delete[] mScene->mMaterials; mScene->mMaterials = new aiMaterial*[1]; mScene->mMaterials[0] = new aiMaterial(); } @@ -304,7 +268,7 @@ void glTFImporter::ImportMeshes(glTF::Asset& r) aim->mName = mesh.id; if (mesh.primitives.size() > 1) { - size_t& len = aim->mName.length; + ai_uint32& len = aim->mName.length; aim->mName.data[len] = '-'; len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p); } @@ -368,6 +332,10 @@ void glTFImporter::ImportMeshes(glTF::Asset& r) case PrimitiveMode_LINES: { nFaces = count / 2; + if (nFaces * 2 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); + count = nFaces * 2; + } faces = new aiFace[nFaces]; for (unsigned int i = 0; i < count; i += 2) { SetFace(faces[i / 2], data.GetUInt(i), data.GetUInt(i + 1)); @@ -391,6 +359,10 @@ void glTFImporter::ImportMeshes(glTF::Asset& r) case PrimitiveMode_TRIANGLES: { nFaces = count / 3; + if (nFaces * 3 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); + count = nFaces * 3; + } faces = new aiFace[nFaces]; for (unsigned int i = 0; i < count; i += 3) { SetFace(faces[i / 3], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); @@ -433,6 +405,10 @@ void glTFImporter::ImportMeshes(glTF::Asset& r) case PrimitiveMode_LINES: { nFaces = count / 2; + if (nFaces * 2 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); + count = nFaces * 2; + } faces = new aiFace[nFaces]; for (unsigned int i = 0; i < count; i += 2) { SetFace(faces[i / 2], i, i + 1); @@ -456,6 +432,10 @@ void glTFImporter::ImportMeshes(glTF::Asset& r) case PrimitiveMode_TRIANGLES: { nFaces = count / 3; + if (nFaces * 3 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); + count = nFaces * 3; + } faces = new aiFace[nFaces]; for (unsigned int i = 0; i < count; i += 3) { SetFace(faces[i / 3], i, i + 1, i + 2); @@ -499,27 +479,31 @@ void glTFImporter::ImportMeshes(glTF::Asset& r) CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes); } -void glTFImporter::ImportCameras(glTF::Asset& r) -{ - if (!r.cameras.Size()) return; +void glTFImporter::ImportCameras(glTF::Asset& r) { + if (!r.cameras.Size()) { + return; + } mScene->mNumCameras = r.cameras.Size(); mScene->mCameras = new aiCamera*[r.cameras.Size()]; - for (size_t i = 0; i < r.cameras.Size(); ++i) { Camera& cam = r.cameras[i]; aiCamera* aicam = mScene->mCameras[i] = new aiCamera(); if (cam.type == Camera::Perspective) { - aicam->mAspect = cam.perspective.aspectRatio; - aicam->mHorizontalFOV = cam.perspective.yfov * aicam->mAspect; + aicam->mHorizontalFOV = cam.perspective.yfov * ((aicam->mAspect == 0.f) ? 1.f : aicam->mAspect); aicam->mClipPlaneFar = cam.perspective.zfar; aicam->mClipPlaneNear = cam.perspective.znear; - } - else { - // assimp does not support orthographic cameras + } else { + aicam->mClipPlaneFar = cam.ortographic.zfar; + aicam->mClipPlaneNear = cam.ortographic.znear; + aicam->mHorizontalFOV = 0.0; + aicam->mAspect = 1.0f; + if (0.f != cam.ortographic.ymag) { + aicam->mAspect = cam.ortographic.xmag / cam.ortographic.ymag; + } } } } diff --git a/code/glTF2/glTF2Asset.h b/code/glTF2/glTF2Asset.h index 70f92df5b..831a763cd 100644 --- a/code/glTF2/glTF2Asset.h +++ b/code/glTF2/glTF2Asset.h @@ -46,6 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * glTF Extensions Support: * KHR_materials_pbrSpecularGlossiness full * KHR_materials_unlit full + * KHR_lights_punctual full */ #ifndef GLTF2ASSET_H_INC #define GLTF2ASSET_H_INC @@ -94,38 +95,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#include "glTF/glTFCommon.h" + namespace glTF2 { -#ifdef ASSIMP_API - using Assimp::IOStream; - using Assimp::IOSystem; - using std::shared_ptr; -#else - using std::shared_ptr; - - typedef std::runtime_error DeadlyImportError; - typedef std::runtime_error DeadlyExportError; - - enum aiOrigin { aiOrigin_SET = 0, aiOrigin_CUR = 1, aiOrigin_END = 2 }; - class IOSystem; - class IOStream - { - FILE* f; - public: - IOStream(FILE* file) : f(file) {} - ~IOStream() { fclose(f); f = 0; } - - size_t Read(void* b, size_t sz, size_t n) { return fread(b, sz, n, f); } - size_t Write(const void* b, size_t sz, size_t n) { return fwrite(b, sz, n, f); } - int Seek(size_t off, aiOrigin orig) { return fseek(f, off, int(orig)); } - size_t Tell() const { return ftell(f); } - - size_t FileSize() { - long p = Tell(), len = (Seek(0, aiOrigin_END), Tell()); - return size_t((Seek(p, aiOrigin_SET), len)); - } - }; -#endif + using glTFCommon::shared_ptr; + using glTFCommon::IOSystem; + using glTFCommon::IOStream; using rapidjson::Value; using rapidjson::Document; @@ -137,35 +113,9 @@ namespace glTF2 struct Texture; struct Skin; - // Vec/matrix types, as raw float arrays - typedef float (vec3)[3]; - typedef float (vec4)[4]; - typedef float (mat4)[16]; - - namespace Util - { - void EncodeBase64(const uint8_t* in, size_t inLength, std::string& out); - - size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out); - - inline size_t DecodeBase64(const char* in, uint8_t*& out) - { - return DecodeBase64(in, strlen(in), out); - } - - struct DataURI - { - const char* mediaType; - const char* charset; - bool base64; - const char* data; - size_t dataLength; - }; - - //! Check if a uri is a data URI - inline bool ParseDataURI(const char* uri, size_t uriLen, DataURI& out); - } - + using glTFCommon::vec3; + using glTFCommon::vec4; + using glTFCommon::mat4; //! Magic number for GLB files #define AI_GLB_MAGIC_NUMBER "glTF" @@ -551,7 +501,7 @@ namespace glTF2 /// but in real life you'll get: /// "accessor_0" : { byteOffset: 0, byteLength: 4}, "accessor_1" : { byteOffset: 2, byteLength: 4} /// Yes, accessor of next mesh has offset and length which mean: current mesh data is decoded, all other data is encoded. - /// And when before you start to read data of current mesh (with encoded data ofcourse) you must decode region of "bufferView", after read finished + /// And when before you start to read data of current mesh (with encoded data of course) you must decode region of "bufferView", after read finished /// delete encoding mark. And after that you can repeat process: decode data of mesh, read, delete decoded data. /// /// Remark. Encoding all data at once is good in world with computers which do not has RAM limitation. So, you must use step by step encoding in @@ -668,6 +618,28 @@ namespace glTF2 void Read(Value& obj, Asset& r); }; + //! A light (from KHR_lights_punctual extension) + struct Light : public Object + { + enum Type + { + Directional, + Point, + Spot + }; + + Type type; + + vec3 color; + float intensity; + Nullable range; + + float innerConeAngle; + float outerConeAngle; + + Light() {} + void Read(Value& obj, Asset& r); + }; //! Image data used to create a texture. struct Image : public Object @@ -713,6 +685,13 @@ namespace glTF2 Ref texture; unsigned int index; unsigned int texCoord = 0; + + bool textureTransformSupported = false; + struct TextureTransformExt { + float offset[2]; + float rotation; + float scale[2]; + } TextureTransformExt_t; }; struct NormalTextureInfo : TextureInfo @@ -819,6 +798,7 @@ namespace glTF2 Nullable scale; Ref camera; + Ref light; std::vector< Ref > skeletons; //!< The ID of skeleton nodes. Each of which is the root of a node hierarchy. Ref skin; //!< The ID of the skin referenced by this node. @@ -1050,7 +1030,8 @@ namespace glTF2 { bool KHR_materials_pbrSpecularGlossiness; bool KHR_materials_unlit; - + bool KHR_lights_punctual; + bool KHR_texture_transform; } extensionsUsed; AssetMetadata asset; @@ -1063,6 +1044,7 @@ namespace glTF2 LazyDict buffers; LazyDict bufferViews; LazyDict cameras; + LazyDict lights; LazyDict images; LazyDict materials; LazyDict meshes; @@ -1083,6 +1065,7 @@ namespace glTF2 , buffers (*this, "buffers") , bufferViews (*this, "bufferViews") , cameras (*this, "cameras") + , lights (*this, "lights", "KHR_lights_punctual") , images (*this, "images") , materials (*this, "materials") , meshes (*this, "meshes") diff --git a/code/glTF2/glTF2Asset.inl b/code/glTF2/glTF2Asset.inl index b51975c77..4259022e9 100644 --- a/code/glTF2/glTF2Asset.inl +++ b/code/glTF2/glTF2Asset.inl @@ -282,9 +282,7 @@ Ref LazyDict::Retrieve(unsigned int i) template Ref LazyDict::Get(unsigned int i) { - return Ref(mObjs, i); - } template @@ -361,11 +359,11 @@ inline void Buffer::Read(Value& obj, Asset& r) const char* uri = it->GetString(); - Util::DataURI dataURI; + glTFCommon::Util::DataURI dataURI; if (ParseDataURI(uri, it->GetStringLength(), dataURI)) { if (dataURI.base64) { uint8_t* data = 0; - this->byteLength = Util::DecodeBase64(dataURI.data, dataURI.dataLength, data); + this->byteLength = glTFCommon::Util::DecodeBase64(dataURI.data, dataURI.dataLength, data); this->mData.reset(data, std::default_delete()); if (statedLength > 0 && this->byteLength != statedLength) { @@ -717,12 +715,12 @@ inline void Image::Read(Value& obj, Asset& r) if (Value* uri = FindString(obj, "uri")) { const char* uristr = uri->GetString(); - Util::DataURI dataURI; + glTFCommon::Util::DataURI dataURI; if (ParseDataURI(uristr, uri->GetStringLength(), dataURI)) { mimeType = dataURI.mediaType; if (dataURI.base64) { uint8_t *ptr = nullptr; - mDataLength = Util::DecodeBase64(dataURI.data, dataURI.dataLength, ptr); + mDataLength = glTFCommon::Util::DecodeBase64(dataURI.data, dataURI.dataLength, ptr); mData.reset(ptr); } } @@ -802,8 +800,34 @@ inline void Texture::Read(Value& obj, Asset& r) } namespace { - inline void SetTextureProperties(Asset& r, Value* prop, TextureInfo& out) - { + inline void SetTextureProperties(Asset& r, Value* prop, TextureInfo& out) { + if (r.extensionsUsed.KHR_texture_transform) { + if (Value *extensions = FindObject(*prop, "extensions")) { + out.textureTransformSupported = true; + if (Value *pKHR_texture_transform = FindObject(*extensions, "KHR_texture_transform")) { + if (Value *array = FindArray(*pKHR_texture_transform, "offset")) { + out.TextureTransformExt_t.offset[0] = (*array)[0].GetFloat(); + out.TextureTransformExt_t.offset[1] = (*array)[1].GetFloat(); + } else { + out.TextureTransformExt_t.offset[0] = 0; + out.TextureTransformExt_t.offset[1] = 0; + } + + if (!ReadMember(*pKHR_texture_transform, "rotation", out.TextureTransformExt_t.rotation)) { + out.TextureTransformExt_t.rotation = 0; + } + + if (Value *array = FindArray(*pKHR_texture_transform, "scale")) { + out.TextureTransformExt_t.scale[0] = (*array)[0].GetFloat(); + out.TextureTransformExt_t.scale[1] = (*array)[1].GetFloat(); + } else { + out.TextureTransformExt_t.scale[0] = 1; + out.TextureTransformExt_t.scale[1] = 1; + } + } + } + } + if (Value* index = FindUInt(*prop, "index")) { out.texture = r.textures.Retrieve(index->GetUint()); } @@ -879,6 +903,9 @@ inline void Material::Read(Value& material, Asset& r) } } + if (r.extensionsUsed.KHR_texture_transform) { + } + unlit = nullptr != FindObject(*extensions, "KHR_materials_unlit"); } } @@ -1067,8 +1094,44 @@ inline void Camera::Read(Value& obj, Asset& /*r*/) } } -inline void Node::Read(Value& obj, Asset& r) +inline void Light::Read(Value& obj, Asset& /*r*/) { +#ifndef M_PI + const float M_PI = 3.14159265358979323846f; +#endif + + std::string type_string; + ReadMember(obj, "type", type_string); + if (type_string == "directional") + type = Light::Directional; + else if (type_string == "point") + type = Light::Point; + else + type = Light::Spot; + + name = MemberOrDefault(obj, "name", ""); + + SetVector(color, vec3{ 1.0f, 1.0f, 1.0f }); + ReadMember(obj, "color", color); + + intensity = MemberOrDefault(obj, "intensity", 1.0f); + + ReadMember(obj, "range", range); + + if (type == Light::Spot) + { + Value* spot = FindObject(obj, "spot"); + if (!spot) throw DeadlyImportError("GLTF: Light missing its spot parameters"); + innerConeAngle = MemberOrDefault(*spot, "innerConeAngle", 0.0f); + outerConeAngle = MemberOrDefault(*spot, "outerConeAngle", M_PI / 4.0f); + } +} + +inline +void Node::Read(Value& obj, Asset& r) { + if (name.empty()) { + name = id; + } if (Value* children = FindArray(obj, "children")) { this->children.reserve(children->Size()); @@ -1110,6 +1173,19 @@ inline void Node::Read(Value& obj, Asset& r) if (this->camera) this->camera->id = this->id; } + + if (Value* extensions = FindObject(obj, "extensions")) { + if (r.extensionsUsed.KHR_lights_punctual) { + + if (Value* ext = FindObject(*extensions, "KHR_lights_punctual")) { + if (Value* light = FindUInt(*ext, "light")) { + this->light = r.lights.Retrieve(light->GetUint()); + if (this->light) + this->light->id = this->id; + } + } + } + } } inline void Scene::Read(Value& obj, Asset& r) @@ -1416,11 +1492,10 @@ inline void Asset::ReadExtensionsUsed(Document& doc) } } - #define CHECK_EXT(EXT) \ - if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true; - CHECK_EXT(KHR_materials_pbrSpecularGlossiness); CHECK_EXT(KHR_materials_unlit); + CHECK_EXT(KHR_lights_punctual); + CHECK_EXT(KHR_texture_transform); #undef CHECK_EXT } @@ -1468,190 +1543,4 @@ inline std::string Asset::FindUniqueID(const std::string& str, const char* suffi return id; } -namespace Util { - - inline - bool ParseDataURI(const char* const_uri, size_t uriLen, DataURI& out) { - if ( NULL == const_uri ) { - return false; - } - - if (const_uri[0] != 0x10) { // we already parsed this uri? - if (strncmp(const_uri, "data:", 5) != 0) // not a data uri? - return false; - } - - // set defaults - out.mediaType = "text/plain"; - out.charset = "US-ASCII"; - out.base64 = false; - - char* uri = const_cast(const_uri); - if (uri[0] != 0x10) { - uri[0] = 0x10; - uri[1] = uri[2] = uri[3] = uri[4] = 0; - - size_t i = 5, j; - if (uri[i] != ';' && uri[i] != ',') { // has media type? - uri[1] = char(i); - for (; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) { - // nothing to do! - } - } - while (uri[i] == ';' && i < uriLen) { - uri[i++] = '\0'; - for (j = i; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) { - // nothing to do! - } - - if ( strncmp( uri + j, "charset=", 8 ) == 0 ) { - uri[2] = char(j + 8); - } else if ( strncmp( uri + j, "base64", 6 ) == 0 ) { - uri[3] = char(j); - } - } - if (i < uriLen) { - uri[i++] = '\0'; - uri[4] = char(i); - } else { - uri[1] = uri[2] = uri[3] = 0; - uri[4] = 5; - } - } - - if ( uri[ 1 ] != 0 ) { - out.mediaType = uri + uri[ 1 ]; - } - if ( uri[ 2 ] != 0 ) { - out.charset = uri + uri[ 2 ]; - } - if ( uri[ 3 ] != 0 ) { - out.base64 = true; - } - out.data = uri + uri[4]; - out.dataLength = (uri + uriLen) - out.data; - - return true; - } - - template - struct DATA - { - static const uint8_t tableDecodeBase64[128]; - }; - - template - const uint8_t DATA::tableDecodeBase64[128] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 64, 0, 0, - 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, - 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0 - }; - - inline char EncodeCharBase64(uint8_t b) - { - return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[size_t(b)]; - } - - inline uint8_t DecodeCharBase64(char c) - { - return DATA::tableDecodeBase64[size_t(c)]; // TODO faster with lookup table or ifs? - /*if (c >= 'A' && c <= 'Z') return c - 'A'; - if (c >= 'a' && c <= 'z') return c - 'a' + 26; - if (c >= '0' && c <= '9') return c - '0' + 52; - if (c == '+') return 62; - if (c == '/') return 63; - return 64; // '-' */ - } - - inline size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out) - { - ai_assert(inLength % 4 == 0); - - if (inLength < 4) { - out = 0; - return 0; - } - - int nEquals = int(in[inLength - 1] == '=') + - int(in[inLength - 2] == '='); - - size_t outLength = (inLength * 3) / 4 - nEquals; - out = new uint8_t[outLength]; - memset(out, 0, outLength); - - size_t i, j = 0; - - for (i = 0; i + 4 < inLength; i += 4) { - uint8_t b0 = DecodeCharBase64(in[i]); - uint8_t b1 = DecodeCharBase64(in[i + 1]); - uint8_t b2 = DecodeCharBase64(in[i + 2]); - uint8_t b3 = DecodeCharBase64(in[i + 3]); - - out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); - out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); - out[j++] = (uint8_t)((b2 << 6) | b3); - } - - { - uint8_t b0 = DecodeCharBase64(in[i]); - uint8_t b1 = DecodeCharBase64(in[i + 1]); - uint8_t b2 = DecodeCharBase64(in[i + 2]); - uint8_t b3 = DecodeCharBase64(in[i + 3]); - - out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); - if (b2 < 64) out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); - if (b3 < 64) out[j++] = (uint8_t)((b2 << 6) | b3); - } - - return outLength; - } - - - - inline void EncodeBase64( - const uint8_t* in, size_t inLength, - std::string& out) - { - size_t outLength = ((inLength + 2) / 3) * 4; - - size_t j = out.size(); - out.resize(j + outLength); - - for (size_t i = 0; i < inLength; i += 3) { - uint8_t b = (in[i] & 0xFC) >> 2; - out[j++] = EncodeCharBase64(b); - - b = (in[i] & 0x03) << 4; - if (i + 1 < inLength) { - b |= (in[i + 1] & 0xF0) >> 4; - out[j++] = EncodeCharBase64(b); - - b = (in[i + 1] & 0x0F) << 2; - if (i + 2 < inLength) { - b |= (in[i + 2] & 0xC0) >> 6; - out[j++] = EncodeCharBase64(b); - - b = in[i + 2] & 0x3F; - out[j++] = EncodeCharBase64(b); - } - else { - out[j++] = EncodeCharBase64(b); - out[j++] = '='; - } - } - else { - out[j++] = EncodeCharBase64(b); - out[j++] = '='; - out[j++] = '='; - } - } - } - -} - } // ns glTF diff --git a/code/glTF2/glTF2AssetWriter.inl b/code/glTF2/glTF2AssetWriter.inl index bec88ceb8..44a1d3e73 100644 --- a/code/glTF2/glTF2AssetWriter.inl +++ b/code/glTF2/glTF2AssetWriter.inl @@ -202,6 +202,11 @@ namespace glTF2 { } + inline void Write(Value& /*obj*/, Light& /*c*/, AssetWriter& /*w*/) + { + + } + inline void Write(Value& obj, Image& img, AssetWriter& w) { if (img.bufferView) { @@ -213,7 +218,7 @@ namespace glTF2 { if (img.HasData()) { uri = "data:" + (img.mimeType.empty() ? "application/octet-stream" : img.mimeType); uri += ";base64,"; - Util::EncodeBase64(img.GetData(), img.GetDataLength(), uri); + glTFCommon::Util::EncodeBase64(img.GetData(), img.GetDataLength(), uri); } else { uri = img.uri; @@ -353,7 +358,7 @@ namespace glTF2 { WriteVec(pbrSpecularGlossiness, pbrSG.specularFactor, "specularFactor", defaultSpecularFactor, w.mAl); if (pbrSG.glossinessFactor != 1) { - WriteFloat(obj, pbrSG.glossinessFactor, "glossinessFactor", w.mAl); + WriteFloat(pbrSpecularGlossiness, pbrSG.glossinessFactor, "glossinessFactor", w.mAl); } WriteTex(pbrSpecularGlossiness, pbrSG.diffuseTexture, "diffuseTexture", w.mAl); diff --git a/code/glTF2/glTF2Exporter.cpp b/code/glTF2/glTF2Exporter.cpp index 4724c2ef4..dc0b76443 100644 --- a/code/glTF2/glTF2Exporter.cpp +++ b/code/glTF2/glTF2Exporter.cpp @@ -320,7 +320,9 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTe if (path[0] == '*') { // embedded aiTexture* tex = mScene->mTextures[atoi(&path[1])]; - uint8_t* data = reinterpret_cast(tex->pcData); + // copy data since lifetime control is handed over to the asset + uint8_t* data = new uint8_t[tex->mWidth]; + memcpy(data, tex->pcData, tex->mWidth); texture->source->SetData(data, tex->mWidth, *mAsset); if (tex->achFormatHint[0]) { diff --git a/code/glTF2/glTF2Exporter.h b/code/glTF2/glTF2Exporter.h index 2dc083709..b527c4bc9 100644 --- a/code/glTF2/glTF2Exporter.h +++ b/code/glTF2/glTF2Exporter.h @@ -74,6 +74,7 @@ namespace glTF2 struct Texture; // Vec/matrix types, as raw float arrays + typedef float (vec2)[2]; typedef float (vec3)[3]; typedef float (vec4)[4]; } diff --git a/code/glTF2/glTF2Importer.cpp b/code/glTF2/glTF2Importer.cpp index a2b18fc49..43eabdab7 100644 --- a/code/glTF2/glTF2Importer.cpp +++ b/code/glTF2/glTF2Importer.cpp @@ -43,18 +43,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_GLTF_IMPORTER #include "glTF2/glTF2Importer.h" +#include "PostProcessing/MakeVerboseFormat.h" #include "glTF2/glTF2Asset.h" #include "glTF2/glTF2AssetWriter.h" -#include "PostProcessing/MakeVerboseFormat.h" +#include #include #include -#include -#include #include -#include #include -#include +#include +#include +#include #include #include @@ -64,13 +64,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; using namespace glTF2; +using namespace glTFCommon; namespace { - // generate bi-tangents from normals and tangents according to spec - struct Tangent { - aiVector3D xyz; - ai_real w; - }; +// generate bi-tangents from normals and tangents according to spec +struct Tangent { + aiVector3D xyz; + ai_real w; +}; } // namespace // @@ -78,84 +79,82 @@ namespace { // static const aiImporterDesc desc = { - "glTF2 Importer", - "", - "", - "", - aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, - 0, - 0, - 0, - 0, - "gltf glb" + "glTF2 Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, + 0, + 0, + 0, + 0, + "gltf glb" }; -glTF2Importer::glTF2Importer() -: BaseImporter() -, meshOffsets() -, embeddedTexIdxs() -, mScene( NULL ) { - // empty +glTF2Importer::glTF2Importer() : + BaseImporter(), + meshOffsets(), + embeddedTexIdxs(), + mScene(NULL) { + // empty } glTF2Importer::~glTF2Importer() { - // empty + // empty } -const aiImporterDesc* glTF2Importer::GetInfo() const +const aiImporterDesc *glTF2Importer::GetInfo() const { + return &desc; +} + +bool glTF2Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /* checkSig */) const { + const std::string &extension = GetExtension(pFile); + + if (extension != "gltf" && extension != "glb") + return false; + + if (pIOHandler) { + glTF2::Asset asset(pIOHandler); + asset.Load(pFile, extension == "glb"); + std::string version = asset.asset.version; + return !version.empty() && version[0] == '2'; + } + + return false; +} + +static aiTextureMapMode ConvertWrappingMode(SamplerWrap gltfWrapMode) { + switch (gltfWrapMode) { + case SamplerWrap::Mirrored_Repeat: + return aiTextureMapMode_Mirror; + + case SamplerWrap::Clamp_To_Edge: + return aiTextureMapMode_Clamp; + + case SamplerWrap::UNSET: + case SamplerWrap::Repeat: + default: + return aiTextureMapMode_Wrap; + } +} + +/*static void CopyValue(const glTF2::vec3& v, aiColor3D& out) { - return &desc; + out.r = v[0]; out.g = v[1]; out.b = v[2]; } -bool glTF2Importer::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool /* checkSig */) const -{ - const std::string &extension = GetExtension(pFile); - - if (extension != "gltf" && extension != "glb") - return false; - - if (pIOHandler) { - glTF2::Asset asset(pIOHandler); - asset.Load(pFile, extension == "glb"); - std::string version = asset.asset.version; - return !version.empty() && version[0] == '2'; - } - - return false; -} - -static aiTextureMapMode ConvertWrappingMode(SamplerWrap gltfWrapMode) -{ - switch (gltfWrapMode) { - case SamplerWrap::Mirrored_Repeat: - return aiTextureMapMode_Mirror; - - case SamplerWrap::Clamp_To_Edge: - return aiTextureMapMode_Clamp; - - case SamplerWrap::UNSET: - case SamplerWrap::Repeat: - default: - return aiTextureMapMode_Wrap; - } -} - -//static void CopyValue(const glTF2::vec3& v, aiColor3D& out) -//{ -// out.r = v[0]; out.g = v[1]; out.b = v[2]; -//} static void CopyValue(const glTF2::vec4& v, aiColor4D& out) { out.r = v[0]; out.g = v[1]; out.b = v[2]; out.a = v[3]; -} +}*/ /*static void CopyValue(const glTF2::vec4& v, aiColor3D& out) { out.r = v[0]; out.g = v[1]; out.b = v[2]; }*/ -static void CopyValue(const glTF2::vec3& v, aiColor4D& out) +/*static void CopyValue(const glTF2::vec3& v, aiColor4D& out) { out.r = v[0]; out.g = v[1]; out.b = v[2]; out.a = 1.0; } @@ -168,997 +167,1160 @@ static void CopyValue(const glTF2::vec3& v, aiVector3D& out) static void CopyValue(const glTF2::vec4& v, aiQuaternion& out) { out.x = v[0]; out.y = v[1]; out.z = v[2]; out.w = v[3]; -} +}*/ -static void CopyValue(const glTF2::mat4& v, aiMatrix4x4& o) +/*static void CopyValue(const glTF2::mat4& v, aiMatrix4x4& o) { o.a1 = v[ 0]; o.b1 = v[ 1]; o.c1 = v[ 2]; o.d1 = v[ 3]; o.a2 = v[ 4]; o.b2 = v[ 5]; o.c2 = v[ 6]; o.d2 = v[ 7]; o.a3 = v[ 8]; o.b3 = v[ 9]; o.c3 = v[10]; o.d3 = v[11]; o.a4 = v[12]; o.b4 = v[13]; o.c4 = v[14]; o.d4 = v[15]; +}*/ + +inline void SetMaterialColorProperty(Asset & /*r*/, vec4 &prop, aiMaterial *mat, const char *pKey, unsigned int type, unsigned int idx) { + aiColor4D col; + CopyValue(prop, col); + mat->AddProperty(&col, 1, pKey, type, idx); } -inline void SetMaterialColorProperty(Asset& /*r*/, vec4& prop, aiMaterial* mat, const char* pKey, unsigned int type, unsigned int idx) -{ - aiColor4D col; - CopyValue(prop, col); - mat->AddProperty(&col, 1, pKey, type, idx); +inline void SetMaterialColorProperty(Asset & /*r*/, vec3 &prop, aiMaterial *mat, const char *pKey, unsigned int type, unsigned int idx) { + aiColor4D col; + glTFCommon::CopyValue(prop, col); + mat->AddProperty(&col, 1, pKey, type, idx); } -inline void SetMaterialColorProperty(Asset& /*r*/, vec3& prop, aiMaterial* mat, const char* pKey, unsigned int type, unsigned int idx) -{ - aiColor4D col; - CopyValue(prop, col); - mat->AddProperty(&col, 1, pKey, type, idx); -} +inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset & /*r*/, glTF2::TextureInfo prop, aiMaterial *mat, aiTextureType texType, unsigned int texSlot = 0) { + if (prop.texture && prop.texture->source) { + aiString uri(prop.texture->source->uri); -inline void SetMaterialTextureProperty(std::vector& embeddedTexIdxs, Asset& /*r*/, glTF2::TextureInfo prop, aiMaterial* mat, aiTextureType texType, unsigned int texSlot = 0) -{ - if (prop.texture && prop.texture->source) { - aiString uri(prop.texture->source->uri); - - int texIdx = embeddedTexIdxs[prop.texture->source.GetIndex()]; - if (texIdx != -1) { // embedded - // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) - uri.data[0] = '*'; - uri.length = 1 + ASSIMP_itoa10(uri.data + 1, MAXLEN - 1, texIdx); - } + int texIdx = embeddedTexIdxs[prop.texture->source.GetIndex()]; + if (texIdx != -1) { // embedded + // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) + uri.data[0] = '*'; + uri.length = 1 + ASSIMP_itoa10(uri.data + 1, MAXLEN - 1, texIdx); + } mat->AddProperty(&uri, AI_MATKEY_TEXTURE(texType, texSlot)); - mat->AddProperty(&prop.texCoord, 1, _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, texType, texSlot); - if (prop.texture->sampler) { - Ref sampler = prop.texture->sampler; + if (prop.textureTransformSupported) { + aiUVTransform transform; + transform.mTranslation.x = prop.TextureTransformExt_t.offset[0]; + transform.mTranslation.y = prop.TextureTransformExt_t.offset[0]; + transform.mRotation = prop.TextureTransformExt_t.rotation; + transform.mScaling.x = prop.TextureTransformExt_t.scale[0]; + transform.mScaling.y = prop.TextureTransformExt_t.scale[1]; + mat->AddProperty(&transform, 1, _AI_MATKEY_UVTRANSFORM_BASE, texType, texSlot); + } - aiString name(sampler->name); - aiString id(sampler->id); + if (prop.texture->sampler) { + Ref sampler = prop.texture->sampler; - mat->AddProperty(&name, AI_MATKEY_GLTF_MAPPINGNAME(texType, texSlot)); - mat->AddProperty(&id, AI_MATKEY_GLTF_MAPPINGID(texType, texSlot)); + aiString name(sampler->name); + aiString id(sampler->id); - aiTextureMapMode wrapS = ConvertWrappingMode(sampler->wrapS); - aiTextureMapMode wrapT = ConvertWrappingMode(sampler->wrapT); - mat->AddProperty(&wrapS, 1, AI_MATKEY_MAPPINGMODE_U(texType, texSlot)); - mat->AddProperty(&wrapT, 1, AI_MATKEY_MAPPINGMODE_V(texType, texSlot)); + mat->AddProperty(&name, AI_MATKEY_GLTF_MAPPINGNAME(texType, texSlot)); + mat->AddProperty(&id, AI_MATKEY_GLTF_MAPPINGID(texType, texSlot)); - if (sampler->magFilter != SamplerMagFilter::UNSET) { - mat->AddProperty(&sampler->magFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(texType, texSlot)); - } + aiTextureMapMode wrapS = ConvertWrappingMode(sampler->wrapS); + aiTextureMapMode wrapT = ConvertWrappingMode(sampler->wrapT); + mat->AddProperty(&wrapS, 1, AI_MATKEY_MAPPINGMODE_U(texType, texSlot)); + mat->AddProperty(&wrapT, 1, AI_MATKEY_MAPPINGMODE_V(texType, texSlot)); - if (sampler->minFilter != SamplerMinFilter::UNSET) { - mat->AddProperty(&sampler->minFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(texType, texSlot)); - } - } - } + if (sampler->magFilter != SamplerMagFilter::UNSET) { + mat->AddProperty(&sampler->magFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(texType, texSlot)); + } + + if (sampler->minFilter != SamplerMinFilter::UNSET) { + mat->AddProperty(&sampler->minFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(texType, texSlot)); + } + } + } } -inline void SetMaterialTextureProperty(std::vector& embeddedTexIdxs, Asset& r, glTF2::NormalTextureInfo& prop, aiMaterial* mat, aiTextureType texType, unsigned int texSlot = 0) -{ - SetMaterialTextureProperty( embeddedTexIdxs, r, (glTF2::TextureInfo) prop, mat, texType, texSlot ); +inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset &r, glTF2::NormalTextureInfo &prop, aiMaterial *mat, aiTextureType texType, unsigned int texSlot = 0) { + SetMaterialTextureProperty(embeddedTexIdxs, r, (glTF2::TextureInfo)prop, mat, texType, texSlot); - if (prop.texture && prop.texture->source) { - mat->AddProperty(&prop.scale, 1, AI_MATKEY_GLTF_TEXTURE_SCALE(texType, texSlot)); - } + if (prop.texture && prop.texture->source) { + mat->AddProperty(&prop.scale, 1, AI_MATKEY_GLTF_TEXTURE_SCALE(texType, texSlot)); + } } -inline void SetMaterialTextureProperty(std::vector& embeddedTexIdxs, Asset& r, glTF2::OcclusionTextureInfo& prop, aiMaterial* mat, aiTextureType texType, unsigned int texSlot = 0) -{ - SetMaterialTextureProperty( embeddedTexIdxs, r, (glTF2::TextureInfo) prop, mat, texType, texSlot ); +inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset &r, glTF2::OcclusionTextureInfo &prop, aiMaterial *mat, aiTextureType texType, unsigned int texSlot = 0) { + SetMaterialTextureProperty(embeddedTexIdxs, r, (glTF2::TextureInfo)prop, mat, texType, texSlot); - if (prop.texture && prop.texture->source) { - mat->AddProperty(&prop.strength, 1, AI_MATKEY_GLTF_TEXTURE_STRENGTH(texType, texSlot)); - } + if (prop.texture && prop.texture->source) { + mat->AddProperty(&prop.strength, 1, AI_MATKEY_GLTF_TEXTURE_STRENGTH(texType, texSlot)); + } } -static aiMaterial* ImportMaterial(std::vector& embeddedTexIdxs, Asset& r, Material& mat) -{ - aiMaterial* aimat = new aiMaterial(); +static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, Material &mat) { + aiMaterial *aimat = new aiMaterial(); - if (!mat.name.empty()) { - aiString str(mat.name); + if (!mat.name.empty()) { + aiString str(mat.name); - aimat->AddProperty(&str, AI_MATKEY_NAME); - } + aimat->AddProperty(&str, AI_MATKEY_NAME); + } - 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_COLOR_DIFFUSE); + SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR); - 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_DIFFUSE); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE); - SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.metallicRoughnessTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); + 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_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR); + aimat->AddProperty(&mat.pbrMetallicRoughness.roughnessFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR); - float roughnessAsShininess = 1 - mat.pbrMetallicRoughness.roughnessFactor; - roughnessAsShininess *= roughnessAsShininess * 1000; - aimat->AddProperty(&roughnessAsShininess, 1, AI_MATKEY_SHININESS); + float roughnessAsShininess = 1 - mat.pbrMetallicRoughness.roughnessFactor; + roughnessAsShininess *= roughnessAsShininess * 1000; + aimat->AddProperty(&roughnessAsShininess, 1, AI_MATKEY_SHININESS); - SetMaterialTextureProperty(embeddedTexIdxs, r, mat.normalTexture, aimat, aiTextureType_NORMALS); - SetMaterialTextureProperty(embeddedTexIdxs, r, mat.occlusionTexture, aimat, aiTextureType_LIGHTMAP); - SetMaterialTextureProperty(embeddedTexIdxs, r, mat.emissiveTexture, aimat, aiTextureType_EMISSIVE); - SetMaterialColorProperty(r, mat.emissiveFactor, aimat, AI_MATKEY_COLOR_EMISSIVE); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.normalTexture, aimat, aiTextureType_NORMALS); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.occlusionTexture, aimat, aiTextureType_LIGHTMAP); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.emissiveTexture, aimat, aiTextureType_EMISSIVE); + SetMaterialColorProperty(r, mat.emissiveFactor, aimat, AI_MATKEY_COLOR_EMISSIVE); - aimat->AddProperty(&mat.doubleSided, 1, AI_MATKEY_TWOSIDED); + aimat->AddProperty(&mat.doubleSided, 1, AI_MATKEY_TWOSIDED); - aiString alphaMode(mat.alphaMode); - aimat->AddProperty(&alphaMode, AI_MATKEY_GLTF_ALPHAMODE); - aimat->AddProperty(&mat.alphaCutoff, 1, AI_MATKEY_GLTF_ALPHACUTOFF); + aiString alphaMode(mat.alphaMode); + aimat->AddProperty(&alphaMode, AI_MATKEY_GLTF_ALPHAMODE); + aimat->AddProperty(&mat.alphaCutoff, 1, AI_MATKEY_GLTF_ALPHACUTOFF); - //pbrSpecularGlossiness - if (mat.pbrSpecularGlossiness.isPresent) { - PbrSpecularGlossiness &pbrSG = mat.pbrSpecularGlossiness.value; + //pbrSpecularGlossiness + 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); + 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); + float glossinessAsShininess = pbrSG.glossinessFactor * 1000.0f; + aimat->AddProperty(&glossinessAsShininess, 1, AI_MATKEY_SHININESS); + aimat->AddProperty(&pbrSG.glossinessFactor, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR); - SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.diffuseTexture, aimat, aiTextureType_DIFFUSE); + SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.diffuseTexture, aimat, aiTextureType_DIFFUSE); - SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.specularGlossinessTexture, aimat, aiTextureType_SPECULAR); - } - if (mat.unlit) { - aimat->AddProperty(&mat.unlit, 1, AI_MATKEY_GLTF_UNLIT); - } + SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.specularGlossinessTexture, aimat, aiTextureType_SPECULAR); + } + if (mat.unlit) { + aimat->AddProperty(&mat.unlit, 1, AI_MATKEY_GLTF_UNLIT); + } - return aimat; + return aimat; } -void glTF2Importer::ImportMaterials(glTF2::Asset& r) -{ - const unsigned int numImportedMaterials = unsigned(r.materials.Size()); - Material defaultMaterial; +void glTF2Importer::ImportMaterials(glTF2::Asset &r) { + const unsigned int numImportedMaterials = unsigned(r.materials.Size()); + Material defaultMaterial; - mScene->mNumMaterials = numImportedMaterials + 1; - mScene->mMaterials = new aiMaterial*[mScene->mNumMaterials]; - mScene->mMaterials[numImportedMaterials] = ImportMaterial(embeddedTexIdxs, r, defaultMaterial); + mScene->mNumMaterials = numImportedMaterials + 1; + mScene->mMaterials = new aiMaterial *[mScene->mNumMaterials]; + mScene->mMaterials[numImportedMaterials] = ImportMaterial(embeddedTexIdxs, r, defaultMaterial); - for (unsigned int i = 0; i < numImportedMaterials; ++i) { - mScene->mMaterials[i] = ImportMaterial(embeddedTexIdxs, r, r.materials[i]); - } + for (unsigned int i = 0; i < numImportedMaterials; ++i) { + mScene->mMaterials[i] = ImportMaterial(embeddedTexIdxs, r, r.materials[i]); + } } - -static inline void SetFace(aiFace& face, int a) -{ - face.mNumIndices = 1; - face.mIndices = new unsigned int[1]; - face.mIndices[0] = a; +static inline void SetFace(aiFace &face, int a) { + face.mNumIndices = 1; + face.mIndices = new unsigned int[1]; + face.mIndices[0] = a; } -static inline void SetFace(aiFace& face, int a, int b) -{ - face.mNumIndices = 2; - face.mIndices = new unsigned int[2]; - face.mIndices[0] = a; - face.mIndices[1] = b; +static inline void SetFace(aiFace &face, int a, int b) { + face.mNumIndices = 2; + face.mIndices = new unsigned int[2]; + face.mIndices[0] = a; + face.mIndices[1] = b; } -static inline void SetFace(aiFace& face, int a, int b, int c) -{ - face.mNumIndices = 3; - face.mIndices = new unsigned int[3]; - face.mIndices[0] = a; - face.mIndices[1] = b; - face.mIndices[2] = c; +static inline void SetFace(aiFace &face, int a, int b, int c) { + face.mNumIndices = 3; + face.mIndices = new unsigned int[3]; + face.mIndices[0] = a; + face.mIndices[1] = b; + face.mIndices[2] = c; } #ifdef ASSIMP_BUILD_DEBUG -static inline bool CheckValidFacesIndices(aiFace* faces, unsigned nFaces, unsigned nVerts) -{ - for (unsigned i = 0; i < nFaces; ++i) { - for (unsigned j = 0; j < faces[i].mNumIndices; ++j) { - unsigned idx = faces[i].mIndices[j]; - if (idx >= nVerts) - return false; - } - } - return true; +static inline bool CheckValidFacesIndices(aiFace *faces, unsigned nFaces, unsigned nVerts) { + for (unsigned i = 0; i < nFaces; ++i) { + for (unsigned j = 0; j < faces[i].mNumIndices; ++j) { + unsigned idx = faces[i].mIndices[j]; + if (idx >= nVerts) + return false; + } + } + return true; } #endif // ASSIMP_BUILD_DEBUG -void glTF2Importer::ImportMeshes(glTF2::Asset& r) -{ - std::vector meshes; +void glTF2Importer::ImportMeshes(glTF2::Asset &r) { + std::vector meshes; - unsigned int k = 0; + unsigned int k = 0; - for (unsigned int m = 0; m < r.meshes.Size(); ++m) { - Mesh& mesh = r.meshes[m]; + for (unsigned int m = 0; m < r.meshes.Size(); ++m) { + Mesh &mesh = r.meshes[m]; - meshOffsets.push_back(k); - k += unsigned(mesh.primitives.size()); + meshOffsets.push_back(k); + k += unsigned(mesh.primitives.size()); - for (unsigned int p = 0; p < mesh.primitives.size(); ++p) { - Mesh::Primitive& prim = mesh.primitives[p]; + for (unsigned int p = 0; p < mesh.primitives.size(); ++p) { + Mesh::Primitive &prim = mesh.primitives[p]; - aiMesh* aim = new aiMesh(); - meshes.push_back(aim); + aiMesh *aim = new aiMesh(); + meshes.push_back(aim); - aim->mName = mesh.name.empty() ? mesh.id : mesh.name; + aim->mName = mesh.name.empty() ? mesh.id : mesh.name; - if (mesh.primitives.size() > 1) { - size_t& len = aim->mName.length; - aim->mName.data[len] = '-'; - len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p); - } + if (mesh.primitives.size() > 1) { + ai_uint32 &len = aim->mName.length; + aim->mName.data[len] = '-'; + len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p); + } - switch (prim.mode) { - case PrimitiveMode_POINTS: - aim->mPrimitiveTypes |= aiPrimitiveType_POINT; - break; + switch (prim.mode) { + case PrimitiveMode_POINTS: + aim->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; - case PrimitiveMode_LINES: - case PrimitiveMode_LINE_LOOP: - case PrimitiveMode_LINE_STRIP: - aim->mPrimitiveTypes |= aiPrimitiveType_LINE; - break; + case PrimitiveMode_LINES: + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: + aim->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; - case PrimitiveMode_TRIANGLES: - case PrimitiveMode_TRIANGLE_STRIP: - case PrimitiveMode_TRIANGLE_FAN: - aim->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; - break; + case PrimitiveMode_TRIANGLES: + case PrimitiveMode_TRIANGLE_STRIP: + case PrimitiveMode_TRIANGLE_FAN: + aim->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + } - } + Mesh::Primitive::Attributes &attr = prim.attributes; - Mesh::Primitive::Attributes& attr = prim.attributes; + if (attr.position.size() > 0 && attr.position[0]) { + aim->mNumVertices = static_cast(attr.position[0]->count); + attr.position[0]->ExtractData(aim->mVertices); + } - if (attr.position.size() > 0 && attr.position[0]) { - aim->mNumVertices = static_cast(attr.position[0]->count); - attr.position[0]->ExtractData(aim->mVertices); - } + if (attr.normal.size() > 0 && attr.normal[0]) { + attr.normal[0]->ExtractData(aim->mNormals); - if (attr.normal.size() > 0 && attr.normal[0]) { - attr.normal[0]->ExtractData(aim->mNormals); + // only extract tangents if normals are present + if (attr.tangent.size() > 0 && attr.tangent[0]) { + // generate bitangents from normals and tangents according to spec + Tangent *tangents = nullptr; - // only extract tangents if normals are present - if (attr.tangent.size() > 0 && attr.tangent[0]) { - // generate bitangents from normals and tangents according to spec - Tangent *tangents = nullptr; + attr.tangent[0]->ExtractData(tangents); - attr.tangent[0]->ExtractData(tangents); + aim->mTangents = new aiVector3D[aim->mNumVertices]; + aim->mBitangents = new aiVector3D[aim->mNumVertices]; - aim->mTangents = new aiVector3D[aim->mNumVertices]; - aim->mBitangents = new aiVector3D[aim->mNumVertices]; + for (unsigned int i = 0; i < aim->mNumVertices; ++i) { + aim->mTangents[i] = tangents[i].xyz; + aim->mBitangents[i] = (aim->mNormals[i] ^ tangents[i].xyz) * tangents[i].w; + } - for (unsigned int i = 0; i < aim->mNumVertices; ++i) { - aim->mTangents[i] = tangents[i].xyz; - aim->mBitangents[i] = (aim->mNormals[i] ^ tangents[i].xyz) * tangents[i].w; - } + delete[] tangents; + } + } - delete [] tangents; - } - } + for (size_t c = 0; c < attr.color.size() && c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) { + if (attr.color[c]->count != aim->mNumVertices) { + DefaultLogger::get()->warn("Color stream size in mesh \"" + mesh.name + + "\" does not match the vertex count"); + continue; + } + attr.color[c]->ExtractData(aim->mColors[c]); + } + for (size_t tc = 0; tc < attr.texcoord.size() && tc < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) { + if (attr.texcoord[tc]->count != aim->mNumVertices) { + DefaultLogger::get()->warn("Texcoord stream size in mesh \"" + mesh.name + + "\" does not match the vertex count"); + continue; + } - for (size_t c = 0; c < attr.color.size() && c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) { - if (attr.color[c]->count != aim->mNumVertices) { - DefaultLogger::get()->warn("Color stream size in mesh \"" + mesh.name + - "\" does not match the vertex count"); - continue; - } - aim->mColors[c] = new aiColor4D[attr.color[c]->count]; - attr.color[c]->ExtractData(aim->mColors[c]); - } - for (size_t tc = 0; tc < attr.texcoord.size() && tc < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) { - if (attr.texcoord[tc]->count != aim->mNumVertices) { - DefaultLogger::get()->warn("Texcoord stream size in mesh \"" + mesh.name + - "\" does not match the vertex count"); - continue; - } + attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]); + aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents(); - attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]); - aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents(); + aiVector3D *values = aim->mTextureCoords[tc]; + for (unsigned int i = 0; i < aim->mNumVertices; ++i) { + values[i].y = 1 - values[i].y; // Flip Y coords + } + } - aiVector3D* values = aim->mTextureCoords[tc]; - for (unsigned int i = 0; i < aim->mNumVertices; ++i) { - values[i].y = 1 - values[i].y; // Flip Y coords - } - } + std::vector &targets = prim.targets; + if (targets.size() > 0) { + aim->mNumAnimMeshes = (unsigned int)targets.size(); + aim->mAnimMeshes = new aiAnimMesh *[aim->mNumAnimMeshes]; + for (size_t i = 0; i < targets.size(); i++) { + aim->mAnimMeshes[i] = aiCreateAnimMesh(aim); + aiAnimMesh &aiAnimMesh = *(aim->mAnimMeshes[i]); + Mesh::Primitive::Target &target = targets[i]; - std::vector& targets = prim.targets; - if (targets.size() > 0) { - aim->mNumAnimMeshes = (unsigned int)targets.size(); - aim->mAnimMeshes = new aiAnimMesh*[aim->mNumAnimMeshes]; - for (size_t i = 0; i < targets.size(); i++) { - aim->mAnimMeshes[i] = aiCreateAnimMesh(aim); - aiAnimMesh& aiAnimMesh = *(aim->mAnimMeshes[i]); - Mesh::Primitive::Target& target = targets[i]; + if (target.position.size() > 0) { + aiVector3D *positionDiff = nullptr; + target.position[0]->ExtractData(positionDiff); + for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { + aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId]; + } + delete[] positionDiff; + } + if (target.normal.size() > 0) { + aiVector3D *normalDiff = nullptr; + target.normal[0]->ExtractData(normalDiff); + for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { + aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId]; + } + delete[] normalDiff; + } + if (target.tangent.size() > 0) { + Tangent *tangent = nullptr; + attr.tangent[0]->ExtractData(tangent); - if (target.position.size() > 0) { - aiVector3D *positionDiff = nullptr; - target.position[0]->ExtractData(positionDiff); - for(unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { - aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId]; - } - delete [] positionDiff; - } - if (target.normal.size() > 0) { - aiVector3D *normalDiff = nullptr; - target.normal[0]->ExtractData(normalDiff); - for(unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { - aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId]; - } - delete [] normalDiff; - } - if (target.tangent.size() > 0) { - Tangent *tangent = nullptr; - attr.tangent[0]->ExtractData(tangent); + aiVector3D *tangentDiff = nullptr; + target.tangent[0]->ExtractData(tangentDiff); - aiVector3D *tangentDiff = nullptr; - target.tangent[0]->ExtractData(tangentDiff); + for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) { + tangent[vertexId].xyz += tangentDiff[vertexId]; + aiAnimMesh.mTangents[vertexId] = tangent[vertexId].xyz; + aiAnimMesh.mBitangents[vertexId] = (aiAnimMesh.mNormals[vertexId] ^ tangent[vertexId].xyz) * tangent[vertexId].w; + } + delete[] tangent; + delete[] tangentDiff; + } + if (mesh.weights.size() > i) { + aiAnimMesh.mWeight = mesh.weights[i]; + } + } + } - for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) { - tangent[vertexId].xyz += tangentDiff[vertexId]; - aiAnimMesh.mTangents[vertexId] = tangent[vertexId].xyz; - aiAnimMesh.mBitangents[vertexId] = (aiAnimMesh.mNormals[vertexId] ^ tangent[vertexId].xyz) * tangent[vertexId].w; - } - delete [] tangent; - delete [] tangentDiff; - } - if (mesh.weights.size() > i) { - aiAnimMesh.mWeight = mesh.weights[i]; - } - } - } + aiFace *faces = 0; + size_t nFaces = 0; + if (prim.indices) { + size_t count = prim.indices->count; - aiFace* faces = 0; - size_t nFaces = 0; + Accessor::Indexer data = prim.indices->GetIndexer(); + ai_assert(data.IsValid()); - if (prim.indices) { - size_t count = prim.indices->count; + switch (prim.mode) { + case PrimitiveMode_POINTS: { + nFaces = count; + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; ++i) { + SetFace(faces[i], data.GetUInt(i)); + } + break; + } - Accessor::Indexer data = prim.indices->GetIndexer(); - ai_assert(data.IsValid()); + case PrimitiveMode_LINES: { + nFaces = count / 2; + if (nFaces * 2 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); + count = nFaces * 2; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 2) { + SetFace(faces[i / 2], data.GetUInt(i), data.GetUInt(i + 1)); + } + break; + } - switch (prim.mode) { - case PrimitiveMode_POINTS: { - nFaces = count; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < count; ++i) { - SetFace(faces[i], data.GetUInt(i)); - } - break; - } + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: { + nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); + faces = new aiFace[nFaces]; + SetFace(faces[0], data.GetUInt(0), data.GetUInt(1)); + for (unsigned int i = 2; i < count; ++i) { + SetFace(faces[i - 1], faces[i - 2].mIndices[1], data.GetUInt(i)); + } + if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop + SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]); + } + break; + } - case PrimitiveMode_LINES: { - nFaces = count / 2; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < count; i += 2) { - SetFace(faces[i / 2], data.GetUInt(i), data.GetUInt(i + 1)); - } - break; - } + case PrimitiveMode_TRIANGLES: { + nFaces = count / 3; + if (nFaces * 3 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); + count = nFaces * 3; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 3) { + SetFace(faces[i / 3], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); + } + break; + } + case PrimitiveMode_TRIANGLE_STRIP: { + nFaces = count - 2; + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < nFaces; ++i) { + //The ordering is to ensure that the triangles are all drawn with the same orientation + if ((i + 1) % 2 == 0) { + //For even n, vertices n + 1, n, and n + 2 define triangle n + SetFace(faces[i], data.GetUInt(i + 1), data.GetUInt(i), data.GetUInt(i + 2)); + } else { + //For odd n, vertices n, n+1, and n+2 define triangle n + SetFace(faces[i], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); + } + } + break; + } + case PrimitiveMode_TRIANGLE_FAN: + nFaces = count - 2; + faces = new aiFace[nFaces]; + SetFace(faces[0], data.GetUInt(0), data.GetUInt(1), data.GetUInt(2)); + for (unsigned int i = 1; i < nFaces; ++i) { + SetFace(faces[i], faces[0].mIndices[0], faces[i - 1].mIndices[2], data.GetUInt(i + 2)); + } + break; + } + } else { // no indices provided so directly generate from counts - case PrimitiveMode_LINE_LOOP: - case PrimitiveMode_LINE_STRIP: { - nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); - faces = new aiFace[nFaces]; - SetFace(faces[0], data.GetUInt(0), data.GetUInt(1)); - for (unsigned int i = 2; i < count; ++i) { - SetFace(faces[i - 1], faces[i - 2].mIndices[1], data.GetUInt(i)); - } - if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop - SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]); - } - break; - } + // use the already determined count as it includes checks + unsigned int count = aim->mNumVertices; - case PrimitiveMode_TRIANGLES: { - nFaces = count / 3; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < count; i += 3) { - SetFace(faces[i / 3], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); - } - break; - } - case PrimitiveMode_TRIANGLE_STRIP: { - nFaces = count - 2; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < nFaces; ++i) { - //The ordering is to ensure that the triangles are all drawn with the same orientation - if ((i + 1) % 2 == 0) - { - //For even n, vertices n + 1, n, and n + 2 define triangle n - SetFace(faces[i], data.GetUInt(i + 1), data.GetUInt(i), data.GetUInt(i + 2)); - } - else - { - //For odd n, vertices n, n+1, and n+2 define triangle n - SetFace(faces[i], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); - } - } - break; - } - case PrimitiveMode_TRIANGLE_FAN: - nFaces = count - 2; - faces = new aiFace[nFaces]; - SetFace(faces[0], data.GetUInt(0), data.GetUInt(1), data.GetUInt(2)); - for (unsigned int i = 1; i < nFaces; ++i) { - SetFace(faces[i], faces[0].mIndices[0], faces[i - 1].mIndices[2], data.GetUInt(i + 2)); - } - break; - } - } - else { // no indices provided so directly generate from counts + switch (prim.mode) { + case PrimitiveMode_POINTS: { + nFaces = count; + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; ++i) { + SetFace(faces[i], i); + } + break; + } - // use the already determined count as it includes checks - unsigned int count = aim->mNumVertices; + case PrimitiveMode_LINES: { + nFaces = count / 2; + if (nFaces * 2 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); + count = (unsigned int)nFaces * 2; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 2) { + SetFace(faces[i / 2], i, i + 1); + } + break; + } - switch (prim.mode) { - case PrimitiveMode_POINTS: { - nFaces = count; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < count; ++i) { - SetFace(faces[i], i); - } - break; - } + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: { + nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); + faces = new aiFace[nFaces]; + SetFace(faces[0], 0, 1); + for (unsigned int i = 2; i < count; ++i) { + SetFace(faces[i - 1], faces[i - 2].mIndices[1], i); + } + if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop + SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]); + } + break; + } - case PrimitiveMode_LINES: { - nFaces = count / 2; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < count; i += 2) { - SetFace(faces[i / 2], i, i + 1); - } - break; - } + case PrimitiveMode_TRIANGLES: { + nFaces = count / 3; + if (nFaces * 3 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); + count = (unsigned int)nFaces * 3; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 3) { + SetFace(faces[i / 3], i, i + 1, i + 2); + } + break; + } + case PrimitiveMode_TRIANGLE_STRIP: { + nFaces = count - 2; + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < nFaces; ++i) { + //The ordering is to ensure that the triangles are all drawn with the same orientation + if ((i + 1) % 2 == 0) { + //For even n, vertices n + 1, n, and n + 2 define triangle n + SetFace(faces[i], i + 1, i, i + 2); + } else { + //For odd n, vertices n, n+1, and n+2 define triangle n + SetFace(faces[i], i, i + 1, i + 2); + } + } + break; + } + case PrimitiveMode_TRIANGLE_FAN: + nFaces = count - 2; + faces = new aiFace[nFaces]; + SetFace(faces[0], 0, 1, 2); + for (unsigned int i = 1; i < nFaces; ++i) { + SetFace(faces[i], faces[0].mIndices[0], faces[i - 1].mIndices[2], i + 2); + } + break; + } + } - case PrimitiveMode_LINE_LOOP: - case PrimitiveMode_LINE_STRIP: { - nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); - faces = new aiFace[nFaces]; - SetFace(faces[0], 0, 1); - for (unsigned int i = 2; i < count; ++i) { - SetFace(faces[i - 1], faces[i - 2].mIndices[1], i); - } - if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop - SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]); - } - break; - } + if (faces) { + aim->mFaces = faces; + aim->mNumFaces = static_cast(nFaces); + ai_assert(CheckValidFacesIndices(faces, static_cast(nFaces), aim->mNumVertices)); + } - case PrimitiveMode_TRIANGLES: { - nFaces = count / 3; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < count; i += 3) { - SetFace(faces[i / 3], i, i + 1, i + 2); - } - break; - } - case PrimitiveMode_TRIANGLE_STRIP: { - nFaces = count - 2; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < nFaces; ++i) { - //The ordering is to ensure that the triangles are all drawn with the same orientation - if ((i+1) % 2 == 0) - { - //For even n, vertices n + 1, n, and n + 2 define triangle n - SetFace(faces[i], i+1, i, i+2); - } - else - { - //For odd n, vertices n, n+1, and n+2 define triangle n - SetFace(faces[i], i, i+1, i+2); - } - } - break; - } - case PrimitiveMode_TRIANGLE_FAN: - nFaces = count - 2; - faces = new aiFace[nFaces]; - SetFace(faces[0], 0, 1, 2); - for (unsigned int i = 1; i < nFaces; ++i) { - SetFace(faces[i], faces[0].mIndices[0], faces[i - 1].mIndices[2], i + 2); - } - break; - } - } + if (prim.material) { + aim->mMaterialIndex = prim.material.GetIndex(); + } else { + aim->mMaterialIndex = mScene->mNumMaterials - 1; + } + } + } - if (faces) { - aim->mFaces = faces; - aim->mNumFaces = static_cast(nFaces); - ai_assert(CheckValidFacesIndices(faces, static_cast(nFaces), aim->mNumVertices)); - } + meshOffsets.push_back(k); - if (prim.material) { - aim->mMaterialIndex = prim.material.GetIndex(); - } - else { - aim->mMaterialIndex = mScene->mNumMaterials - 1; - } - - } - } - - meshOffsets.push_back(k); - - CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes); + CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes); } -void glTF2Importer::ImportCameras(glTF2::Asset& r) -{ - if (!r.cameras.Size()) return; +void glTF2Importer::ImportCameras(glTF2::Asset &r) { + if (!r.cameras.Size()) return; - mScene->mNumCameras = r.cameras.Size(); - mScene->mCameras = new aiCamera*[r.cameras.Size()]; + mScene->mNumCameras = r.cameras.Size(); + mScene->mCameras = new aiCamera *[r.cameras.Size()]; - for (size_t i = 0; i < r.cameras.Size(); ++i) { - Camera& cam = r.cameras[i]; + for (size_t i = 0; i < r.cameras.Size(); ++i) { + Camera &cam = r.cameras[i]; - aiCamera* aicam = mScene->mCameras[i] = new aiCamera(); + aiCamera *aicam = mScene->mCameras[i] = new aiCamera(); - // cameras point in -Z by default, rest is specified in node transform - aicam->mLookAt = aiVector3D(0.f,0.f,-1.f); + // cameras point in -Z by default, rest is specified in node transform + aicam->mLookAt = aiVector3D(0.f, 0.f, -1.f); - if (cam.type == Camera::Perspective) { + if (cam.type == Camera::Perspective) { - aicam->mAspect = cam.cameraProperties.perspective.aspectRatio; - aicam->mHorizontalFOV = cam.cameraProperties.perspective.yfov * aicam->mAspect; - aicam->mClipPlaneFar = cam.cameraProperties.perspective.zfar; - aicam->mClipPlaneNear = cam.cameraProperties.perspective.znear; - } - else { - // assimp does not support orthographic cameras - } - } + aicam->mAspect = cam.cameraProperties.perspective.aspectRatio; + aicam->mHorizontalFOV = cam.cameraProperties.perspective.yfov * ((aicam->mAspect == 0.f) ? 1.f : aicam->mAspect); + aicam->mClipPlaneFar = cam.cameraProperties.perspective.zfar; + aicam->mClipPlaneNear = cam.cameraProperties.perspective.znear; + } else { + aicam->mClipPlaneFar = cam.cameraProperties.ortographic.zfar; + aicam->mClipPlaneNear = cam.cameraProperties.ortographic.znear; + aicam->mHorizontalFOV = 0.0; + aicam->mAspect = 1.0f; + if (0.f != cam.cameraProperties.ortographic.ymag) { + aicam->mAspect = cam.cameraProperties.ortographic.xmag / cam.cameraProperties.ortographic.ymag; + } + } + } } -static void GetNodeTransform(aiMatrix4x4& matrix, const glTF2::Node& node) { - if (node.matrix.isPresent) { - CopyValue(node.matrix.value, matrix); - } - else { - if (node.translation.isPresent) { - aiVector3D trans; - CopyValue(node.translation.value, trans); - aiMatrix4x4 t; - aiMatrix4x4::Translation(trans, t); - matrix = matrix * t; - } +void glTF2Importer::ImportLights(glTF2::Asset &r) { + if (!r.lights.Size()) + return; - if (node.rotation.isPresent) { - aiQuaternion rot; - CopyValue(node.rotation.value, rot); - matrix = matrix * aiMatrix4x4(rot.GetMatrix()); - } + mScene->mNumLights = r.lights.Size(); + mScene->mLights = new aiLight *[r.lights.Size()]; - if (node.scale.isPresent) { - aiVector3D scal(1.f); - CopyValue(node.scale.value, scal); - aiMatrix4x4 s; - aiMatrix4x4::Scaling(scal, s); - matrix = matrix * s; - } - } + for (size_t i = 0; i < r.lights.Size(); ++i) { + Light &light = r.lights[i]; + + aiLight *ail = mScene->mLights[i] = new aiLight(); + + switch (light.type) { + case Light::Directional: + ail->mType = aiLightSource_DIRECTIONAL; + break; + case Light::Point: + ail->mType = aiLightSource_POINT; + break; + case Light::Spot: + ail->mType = aiLightSource_SPOT; + break; + } + + if (ail->mType != aiLightSource_POINT) { + ail->mDirection = aiVector3D(0.0f, 0.0f, -1.0f); + ail->mUp = aiVector3D(0.0f, 1.0f, 0.0f); + } + + vec3 colorWithIntensity = { light.color[0] * light.intensity, light.color[1] * light.intensity, light.color[2] * light.intensity }; + CopyValue(colorWithIntensity, ail->mColorAmbient); + CopyValue(colorWithIntensity, ail->mColorDiffuse); + CopyValue(colorWithIntensity, ail->mColorSpecular); + + if (ail->mType == aiLightSource_DIRECTIONAL) { + ail->mAttenuationConstant = 1.0; + ail->mAttenuationLinear = 0.0; + ail->mAttenuationQuadratic = 0.0; + } else { + //in PBR attenuation is calculated using inverse square law which can be expressed + //using assimps equation: 1/(att0 + att1 * d + att2 * d*d) with the following parameters + //this is correct equation for the case when range (see + //https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual) + //is not present. When range is not present it is assumed that it is infinite and so numerator is 1. + //When range is present then numerator might be any value in range [0,1] and then assimps equation + //will not suffice. In this case range is added into metadata in ImportNode function + //and its up to implementation to read it when it wants to + ail->mAttenuationConstant = 0.0; + ail->mAttenuationLinear = 0.0; + ail->mAttenuationQuadratic = 1.0; + } + + if (ail->mType == aiLightSource_SPOT) { + ail->mAngleInnerCone = light.innerConeAngle; + ail->mAngleOuterCone = light.outerConeAngle; + } + } } -static void BuildVertexWeightMapping(Mesh::Primitive& primitive, std::vector>& map) -{ - Mesh::Primitive::Attributes& attr = primitive.attributes; - if (attr.weight.empty() || attr.joint.empty()) { - return; - } - if (attr.weight[0]->count != attr.joint[0]->count) { - return; - } +static void GetNodeTransform(aiMatrix4x4 &matrix, const glTF2::Node &node) { + if (node.matrix.isPresent) { + CopyValue(node.matrix.value, matrix); + } else { + if (node.translation.isPresent) { + aiVector3D trans; + CopyValue(node.translation.value, trans); + aiMatrix4x4 t; + aiMatrix4x4::Translation(trans, t); + matrix = matrix * t; + } - size_t num_vertices = attr.weight[0]->count; + if (node.rotation.isPresent) { + aiQuaternion rot; + CopyValue(node.rotation.value, rot); + matrix = matrix * aiMatrix4x4(rot.GetMatrix()); + } - struct Weights { float values[4]; }; - Weights* weights = nullptr; - attr.weight[0]->ExtractData(weights); - - struct Indices8 { uint8_t values[4]; }; - struct Indices16 { uint16_t values[4]; }; - Indices8* indices8 = nullptr; - Indices16* indices16 = nullptr; - if (attr.joint[0]->GetElementSize() == 4) { - attr.joint[0]->ExtractData(indices8); - }else { - attr.joint[0]->ExtractData(indices16); - } - // - if (nullptr == indices8 && nullptr == indices16) { - // Something went completely wrong! - ai_assert(false); - return; - } - - for (size_t i = 0; i < num_vertices; ++i) { - for (int j = 0; j < 4; ++j) { - const unsigned int bone = (indices8!=nullptr) ? indices8[i].values[j] : indices16[i].values[j]; - const float weight = weights[i].values[j]; - if (weight > 0 && bone < map.size()) { - map[bone].reserve(8); - map[bone].emplace_back(static_cast(i), weight); - } - } - } - - delete[] weights; - delete[] indices8; - delete[] indices16; + if (node.scale.isPresent) { + aiVector3D scal(1.f); + CopyValue(node.scale.value, scal); + aiMatrix4x4 s; + aiMatrix4x4::Scaling(scal, s); + matrix = matrix * s; + } + } } -static std::string GetNodeName(const Node& node) -{ - return node.name.empty() ? node.id : node.name; +static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vector> &map) { + Mesh::Primitive::Attributes &attr = primitive.attributes; + if (attr.weight.empty() || attr.joint.empty()) { + return; + } + if (attr.weight[0]->count != attr.joint[0]->count) { + return; + } + + size_t num_vertices = attr.weight[0]->count; + + struct Weights { + float values[4]; + }; + Weights *weights = nullptr; + attr.weight[0]->ExtractData(weights); + + struct Indices8 { + uint8_t values[4]; + }; + struct Indices16 { + uint16_t values[4]; + }; + Indices8 *indices8 = nullptr; + Indices16 *indices16 = nullptr; + if (attr.joint[0]->GetElementSize() == 4) { + attr.joint[0]->ExtractData(indices8); + } else { + attr.joint[0]->ExtractData(indices16); + } + // + if (nullptr == indices8 && nullptr == indices16) { + // Something went completely wrong! + ai_assert(false); + return; + } + + for (size_t i = 0; i < num_vertices; ++i) { + for (int j = 0; j < 4; ++j) { + const unsigned int bone = (indices8 != nullptr) ? indices8[i].values[j] : indices16[i].values[j]; + const float weight = weights[i].values[j]; + if (weight > 0 && bone < map.size()) { + map[bone].reserve(8); + map[bone].emplace_back(static_cast(i), weight); + } + } + } + + delete[] weights; + delete[] indices8; + delete[] indices16; } -aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector& meshOffsets, glTF2::Ref& ptr) -{ - Node& node = *ptr; - - aiNode* ainode = new aiNode(GetNodeName(node)); - - if (!node.children.empty()) { - ainode->mNumChildren = unsigned(node.children.size()); - ainode->mChildren = new aiNode*[ainode->mNumChildren]; - - for (unsigned int i = 0; i < ainode->mNumChildren; ++i) { - aiNode* child = ImportNode(pScene, r, meshOffsets, node.children[i]); - child->mParent = ainode; - ainode->mChildren[i] = child; - } - } - - GetNodeTransform(ainode->mTransformation, node); - - if (!node.meshes.empty()) { - // GLTF files contain at most 1 mesh per node. - assert(node.meshes.size() == 1); - int mesh_idx = node.meshes[0].GetIndex(); - int count = meshOffsets[mesh_idx + 1] - meshOffsets[mesh_idx]; - - ainode->mNumMeshes = count; - ainode->mMeshes = new unsigned int[count]; - - if (node.skin) { - for (int primitiveNo = 0; primitiveNo < count; ++primitiveNo) { - aiMesh* mesh = pScene->mMeshes[meshOffsets[mesh_idx]+primitiveNo]; - mesh->mNumBones = static_cast(node.skin->jointNames.size()); - mesh->mBones = new aiBone*[mesh->mNumBones]; - - // GLTF and Assimp choose to store bone weights differently. - // GLTF has each vertex specify which bones influence the vertex. - // Assimp has each bone specify which vertices it has influence over. - // To convert this data, we first read over the vertex data and pull - // out the bone-to-vertex mapping. Then, when creating the aiBones, - // we copy the bone-to-vertex mapping into the bone. This is unfortunate - // both because it's somewhat slow and because, for many applications, - // we then need to reconvert the data back into the vertex-to-bone - // mapping which makes things doubly-slow. - std::vector> weighting(mesh->mNumBones); - BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting); - - for (uint32_t i = 0; i < mesh->mNumBones; ++i) { - aiBone* bone = new aiBone(); - - Ref joint = node.skin->jointNames[i]; - if (!joint->name.empty()) { - bone->mName = joint->name; - } else { - // Assimp expects each bone to have a unique name. - static const std::string kDefaultName = "bone_"; - char postfix[10] = {0}; - ASSIMP_itoa10(postfix, i); - bone->mName = (kDefaultName + postfix); - } - GetNodeTransform(bone->mOffsetMatrix, *joint); - - std::vector& weights = weighting[i]; - - bone->mNumWeights = static_cast(weights.size()); - if (bone->mNumWeights > 0) { - bone->mWeights = new aiVertexWeight[bone->mNumWeights]; - memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight)); - } else { - // Assimp expects all bones to have at least 1 weight. - bone->mWeights = new aiVertexWeight[1]; - bone->mNumWeights = 1; - bone->mWeights->mVertexId = 0; - bone->mWeights->mWeight = 0.f; - } - mesh->mBones[i] = bone; - } - } - } - - int k = 0; - for (unsigned int j = meshOffsets[mesh_idx]; j < meshOffsets[mesh_idx + 1]; ++j, ++k) { - ainode->mMeshes[k] = j; - } - } - - if (node.camera) { - pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName; - } - - return ainode; +static std::string GetNodeName(const Node &node) { + return node.name.empty() ? node.id : node.name; } -void glTF2Importer::ImportNodes(glTF2::Asset& r) -{ - if (!r.scene) return; +aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector &meshOffsets, glTF2::Ref &ptr) { + Node &node = *ptr; - std::vector< Ref > rootNodes = r.scene->nodes; + aiNode *ainode = new aiNode(GetNodeName(node)); - // The root nodes - unsigned int numRootNodes = unsigned(rootNodes.size()); - if (numRootNodes == 1) { // a single root node: use it - mScene->mRootNode = ImportNode(mScene, r, meshOffsets, rootNodes[0]); - } - else if (numRootNodes > 1) { // more than one root node: create a fake root - aiNode* root = new aiNode("ROOT"); - root->mChildren = new aiNode*[numRootNodes]; - for (unsigned int i = 0; i < numRootNodes; ++i) { - aiNode* node = ImportNode(mScene, r, meshOffsets, rootNodes[i]); - node->mParent = root; - root->mChildren[root->mNumChildren++] = node; - } - mScene->mRootNode = root; - } + if (!node.children.empty()) { + ainode->mNumChildren = unsigned(node.children.size()); + ainode->mChildren = new aiNode *[ainode->mNumChildren]; - //if (!mScene->mRootNode) { - // mScene->mRootNode = new aiNode("EMPTY"); - //} + for (unsigned int i = 0; i < ainode->mNumChildren; ++i) { + aiNode *child = ImportNode(pScene, r, meshOffsets, node.children[i]); + child->mParent = ainode; + ainode->mChildren[i] = child; + } + } + + GetNodeTransform(ainode->mTransformation, node); + + if (!node.meshes.empty()) { + // GLTF files contain at most 1 mesh per node. + assert(node.meshes.size() == 1); + int mesh_idx = node.meshes[0].GetIndex(); + int count = meshOffsets[mesh_idx + 1] - meshOffsets[mesh_idx]; + + ainode->mNumMeshes = count; + ainode->mMeshes = new unsigned int[count]; + + if (node.skin) { + for (int primitiveNo = 0; primitiveNo < count; ++primitiveNo) { + aiMesh *mesh = pScene->mMeshes[meshOffsets[mesh_idx] + primitiveNo]; + mesh->mNumBones = static_cast(node.skin->jointNames.size()); + mesh->mBones = new aiBone *[mesh->mNumBones]; + + // GLTF and Assimp choose to store bone weights differently. + // GLTF has each vertex specify which bones influence the vertex. + // Assimp has each bone specify which vertices it has influence over. + // To convert this data, we first read over the vertex data and pull + // out the bone-to-vertex mapping. Then, when creating the aiBones, + // we copy the bone-to-vertex mapping into the bone. This is unfortunate + // both because it's somewhat slow and because, for many applications, + // we then need to reconvert the data back into the vertex-to-bone + // mapping which makes things doubly-slow. + std::vector> weighting(mesh->mNumBones); + BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting); + + mat4 *pbindMatrices = nullptr; + node.skin->inverseBindMatrices->ExtractData(pbindMatrices); + + for (uint32_t i = 0; i < mesh->mNumBones; ++i) { + aiBone *bone = new aiBone(); + + Ref joint = node.skin->jointNames[i]; + if (!joint->name.empty()) { + bone->mName = joint->name; + } else { + // Assimp expects each bone to have a unique name. + static const std::string kDefaultName = "bone_"; + char postfix[10] = { 0 }; + ASSIMP_itoa10(postfix, i); + bone->mName = (kDefaultName + postfix); + } + GetNodeTransform(bone->mOffsetMatrix, *joint); + + CopyValue(pbindMatrices[i], bone->mOffsetMatrix); + + std::vector &weights = weighting[i]; + + bone->mNumWeights = static_cast(weights.size()); + if (bone->mNumWeights > 0) { + bone->mWeights = new aiVertexWeight[bone->mNumWeights]; + memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight)); + } else { + // Assimp expects all bones to have at least 1 weight. + bone->mWeights = new aiVertexWeight[1]; + bone->mNumWeights = 1; + bone->mWeights->mVertexId = 0; + bone->mWeights->mWeight = 0.f; + } + mesh->mBones[i] = bone; + } + + if (pbindMatrices) { + delete[] pbindMatrices; + } + } + } + + int k = 0; + for (unsigned int j = meshOffsets[mesh_idx]; j < meshOffsets[mesh_idx + 1]; ++j, ++k) { + ainode->mMeshes[k] = j; + } + } + + if (node.camera) { + pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName; + } + + if (node.light) { + pScene->mLights[node.light.GetIndex()]->mName = ainode->mName; + + //range is optional - see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + //it is added to meta data of parent node, because there is no other place to put it + if (node.light->range.isPresent) { + ainode->mMetaData = aiMetadata::Alloc(1); + ainode->mMetaData->Set(0, "PBR_LightRange", node.light->range.value); + } + } + + return ainode; +} + +void glTF2Importer::ImportNodes(glTF2::Asset &r) { + if (!r.scene) return; + + std::vector> rootNodes = r.scene->nodes; + + // The root nodes + unsigned int numRootNodes = unsigned(rootNodes.size()); + if (numRootNodes == 1) { // a single root node: use it + mScene->mRootNode = ImportNode(mScene, r, meshOffsets, rootNodes[0]); + } else if (numRootNodes > 1) { // more than one root node: create a fake root + aiNode *root = new aiNode("ROOT"); + root->mChildren = new aiNode *[numRootNodes]; + for (unsigned int i = 0; i < numRootNodes; ++i) { + aiNode *node = ImportNode(mScene, r, meshOffsets, rootNodes[i]); + node->mParent = root; + root->mChildren[root->mNumChildren++] = node; + } + mScene->mRootNode = root; + } } struct AnimationSamplers { - AnimationSamplers() : translation(nullptr), rotation(nullptr), scale(nullptr) {} + AnimationSamplers() : + translation(nullptr), + rotation(nullptr), + scale(nullptr), + weight(nullptr) { + // empty + } - Animation::Sampler* translation; - Animation::Sampler* rotation; - Animation::Sampler* scale; + Animation::Sampler *translation; + Animation::Sampler *rotation; + Animation::Sampler *scale; + Animation::Sampler *weight; }; -aiNodeAnim* CreateNodeAnim(glTF2::Asset& r, Node& node, AnimationSamplers& samplers) -{ - aiNodeAnim* anim = new aiNodeAnim(); - anim->mNodeName = GetNodeName(node); +aiNodeAnim *CreateNodeAnim(glTF2::Asset &r, Node &node, AnimationSamplers &samplers) { + aiNodeAnim *anim = new aiNodeAnim(); + anim->mNodeName = GetNodeName(node); - static const float kMillisecondsFromSeconds = 1000.f; + static const float kMillisecondsFromSeconds = 1000.f; - if (samplers.translation) { - float* times = nullptr; - samplers.translation->input->ExtractData(times); - aiVector3D* values = nullptr; - samplers.translation->output->ExtractData(values); - anim->mNumPositionKeys = static_cast(samplers.translation->input->count); - anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; - for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { - anim->mPositionKeys[i].mTime = times[i] * kMillisecondsFromSeconds; - anim->mPositionKeys[i].mValue = values[i]; - } - delete[] times; - delete[] values; - } else if (node.translation.isPresent) { - anim->mNumPositionKeys = 1; - anim->mPositionKeys = new aiVectorKey(); - anim->mPositionKeys->mTime = 0.f; - anim->mPositionKeys->mValue.x = node.translation.value[0]; - anim->mPositionKeys->mValue.y = node.translation.value[1]; - anim->mPositionKeys->mValue.z = node.translation.value[2]; - } + if (samplers.translation) { + float *times = nullptr; + samplers.translation->input->ExtractData(times); + aiVector3D *values = nullptr; + samplers.translation->output->ExtractData(values); + anim->mNumPositionKeys = static_cast(samplers.translation->input->count); + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { + anim->mPositionKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mPositionKeys[i].mValue = values[i]; + } + delete[] times; + delete[] values; + } else if (node.translation.isPresent) { + anim->mNumPositionKeys = 1; + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + anim->mPositionKeys->mTime = 0.f; + anim->mPositionKeys->mValue.x = node.translation.value[0]; + anim->mPositionKeys->mValue.y = node.translation.value[1]; + anim->mPositionKeys->mValue.z = node.translation.value[2]; + } - if (samplers.rotation) { - float* times = nullptr; - samplers.rotation->input->ExtractData(times); - aiQuaternion* values = nullptr; - samplers.rotation->output->ExtractData(values); - anim->mNumRotationKeys = static_cast(samplers.rotation->input->count); - anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; - for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { - anim->mRotationKeys[i].mTime = times[i] * kMillisecondsFromSeconds; - anim->mRotationKeys[i].mValue.x = values[i].w; - anim->mRotationKeys[i].mValue.y = values[i].x; - anim->mRotationKeys[i].mValue.z = values[i].y; - anim->mRotationKeys[i].mValue.w = values[i].z; - } - delete[] times; - delete[] values; - } else if (node.rotation.isPresent) { - anim->mNumRotationKeys = 1; - anim->mRotationKeys = new aiQuatKey(); - anim->mRotationKeys->mTime = 0.f; - anim->mRotationKeys->mValue.x = node.rotation.value[0]; - anim->mRotationKeys->mValue.y = node.rotation.value[1]; - anim->mRotationKeys->mValue.z = node.rotation.value[2]; - anim->mRotationKeys->mValue.w = node.rotation.value[3]; - } + if (samplers.rotation) { + float *times = nullptr; + samplers.rotation->input->ExtractData(times); + aiQuaternion *values = nullptr; + samplers.rotation->output->ExtractData(values); + anim->mNumRotationKeys = static_cast(samplers.rotation->input->count); + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; + for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { + anim->mRotationKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mRotationKeys[i].mValue.x = values[i].w; + anim->mRotationKeys[i].mValue.y = values[i].x; + anim->mRotationKeys[i].mValue.z = values[i].y; + anim->mRotationKeys[i].mValue.w = values[i].z; + } + delete[] times; + delete[] values; + } else if (node.rotation.isPresent) { + anim->mNumRotationKeys = 1; + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; + anim->mRotationKeys->mTime = 0.f; + anim->mRotationKeys->mValue.x = node.rotation.value[0]; + anim->mRotationKeys->mValue.y = node.rotation.value[1]; + anim->mRotationKeys->mValue.z = node.rotation.value[2]; + anim->mRotationKeys->mValue.w = node.rotation.value[3]; + } - if (samplers.scale) { - float* times = nullptr; - samplers.scale->input->ExtractData(times); - aiVector3D* values = nullptr; - samplers.scale->output->ExtractData(values); - anim->mNumScalingKeys = static_cast(samplers.scale->input->count); - anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; - for (unsigned int i = 0; i < anim->mNumScalingKeys; ++i) { - anim->mScalingKeys[i].mTime = times[i] * kMillisecondsFromSeconds; - anim->mScalingKeys[i].mValue = values[i]; - } - delete[] times; - delete[] values; - } else if (node.scale.isPresent) { - anim->mNumScalingKeys = 1; - anim->mScalingKeys = new aiVectorKey(); - anim->mScalingKeys->mTime = 0.f; - anim->mScalingKeys->mValue.x = node.scale.value[0]; - anim->mScalingKeys->mValue.y = node.scale.value[1]; - anim->mScalingKeys->mValue.z = node.scale.value[2]; - } + if (samplers.scale) { + float *times = nullptr; + samplers.scale->input->ExtractData(times); + aiVector3D *values = nullptr; + samplers.scale->output->ExtractData(values); + anim->mNumScalingKeys = static_cast(samplers.scale->input->count); + anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; + for (unsigned int i = 0; i < anim->mNumScalingKeys; ++i) { + anim->mScalingKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mScalingKeys[i].mValue = values[i]; + } + delete[] times; + delete[] values; + } else if (node.scale.isPresent) { + anim->mNumScalingKeys = 1; + anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; + anim->mScalingKeys->mTime = 0.f; + anim->mScalingKeys->mValue.x = node.scale.value[0]; + anim->mScalingKeys->mValue.y = node.scale.value[1]; + anim->mScalingKeys->mValue.z = node.scale.value[2]; + } - return anim; + return anim; } -std::unordered_map GatherSamplers(Animation& anim) -{ - std::unordered_map samplers; - for (unsigned int c = 0; c < anim.channels.size(); ++c) { - Animation::Channel& channel = anim.channels[c]; - if (channel.sampler >= static_cast(anim.samplers.size())) { - continue; - } +aiMeshMorphAnim *CreateMeshMorphAnim(glTF2::Asset &r, Node &node, AnimationSamplers &samplers) { + aiMeshMorphAnim *anim = new aiMeshMorphAnim(); + anim->mName = GetNodeName(node); - const unsigned int node_index = channel.target.node.GetIndex(); + static const float kMillisecondsFromSeconds = 1000.f; - AnimationSamplers& sampler = samplers[node_index]; - if (channel.target.path == AnimationPath_TRANSLATION) { - sampler.translation = &anim.samplers[channel.sampler]; - } else if (channel.target.path == AnimationPath_ROTATION) { - sampler.rotation = &anim.samplers[channel.sampler]; - } else if (channel.target.path == AnimationPath_SCALE) { - sampler.scale = &anim.samplers[channel.sampler]; - } - } + if (nullptr != samplers.weight) { + float *times = nullptr; + samplers.weight->input->ExtractData(times); + float *values = nullptr; + samplers.weight->output->ExtractData(values); + anim->mNumKeys = static_cast(samplers.weight->input->count); - return samplers; + const unsigned int numMorphs = (unsigned int)samplers.weight->output->count / anim->mNumKeys; + + anim->mKeys = new aiMeshMorphKey[anim->mNumKeys]; + unsigned int k = 0u; + for (unsigned int i = 0u; i < anim->mNumKeys; ++i) { + anim->mKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mKeys[i].mNumValuesAndWeights = numMorphs; + anim->mKeys[i].mValues = new unsigned int[numMorphs]; + anim->mKeys[i].mWeights = new double[numMorphs]; + + for (unsigned int j = 0u; j < numMorphs; ++j, ++k) { + anim->mKeys[i].mValues[j] = j; + anim->mKeys[i].mWeights[j] = (0.f > values[k]) ? 0.f : values[k]; + } + } + + delete[] times; + delete[] values; + } + + return anim; } -void glTF2Importer::ImportAnimations(glTF2::Asset& r) -{ - if (!r.scene) return; +std::unordered_map GatherSamplers(Animation &anim) { + std::unordered_map samplers; + for (unsigned int c = 0; c < anim.channels.size(); ++c) { + Animation::Channel &channel = anim.channels[c]; + if (channel.sampler >= static_cast(anim.samplers.size())) { + continue; + } - mScene->mNumAnimations = r.animations.Size(); - if (mScene->mNumAnimations == 0) { - return; - } + const unsigned int node_index = channel.target.node.GetIndex(); - mScene->mAnimations = new aiAnimation*[mScene->mNumAnimations]; - for (unsigned int i = 0; i < r.animations.Size(); ++i) { - Animation& anim = r.animations[i]; + AnimationSamplers &sampler = samplers[node_index]; + if (channel.target.path == AnimationPath_TRANSLATION) { + sampler.translation = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_ROTATION) { + sampler.rotation = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_SCALE) { + sampler.scale = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_WEIGHTS) { + sampler.weight = &anim.samplers[channel.sampler]; + } + } - aiAnimation* ai_anim = new aiAnimation(); - ai_anim->mName = anim.name; - ai_anim->mDuration = 0; - ai_anim->mTicksPerSecond = 0; - - std::unordered_map samplers = GatherSamplers(anim); - - ai_anim->mNumChannels = static_cast(samplers.size()); - if (ai_anim->mNumChannels > 0) { - ai_anim->mChannels = new aiNodeAnim*[ai_anim->mNumChannels]; - int j = 0; - for (auto& iter : samplers) { - ai_anim->mChannels[j] = CreateNodeAnim(r, r.nodes[iter.first], iter.second); - ++j; - } - } - - // Use the latest keyframe for the duration of the animation - double maxDuration = 0; - for (unsigned int j = 0; j < ai_anim->mNumChannels; ++j) { - auto chan = ai_anim->mChannels[j]; - if (chan->mNumPositionKeys) { - auto lastPosKey = chan->mPositionKeys[chan->mNumPositionKeys - 1]; - if (lastPosKey.mTime > maxDuration) { - maxDuration = lastPosKey.mTime; - } - } - if (chan->mNumRotationKeys) { - auto lastRotKey = chan->mRotationKeys[chan->mNumRotationKeys - 1]; - if (lastRotKey.mTime > maxDuration) { - maxDuration = lastRotKey.mTime; - } - } - if (chan->mNumScalingKeys) { - auto lastScaleKey = chan->mScalingKeys[chan->mNumScalingKeys - 1]; - if (lastScaleKey.mTime > maxDuration) { - maxDuration = lastScaleKey.mTime; - } - } - } - ai_anim->mDuration = maxDuration; - - mScene->mAnimations[i] = ai_anim; - } + return samplers; } -void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset& r) -{ - embeddedTexIdxs.resize(r.images.Size(), -1); +void glTF2Importer::ImportAnimations(glTF2::Asset &r) { + if (!r.scene) return; - int numEmbeddedTexs = 0; - for (size_t i = 0; i < r.images.Size(); ++i) { - if (r.images[i].HasData()) - numEmbeddedTexs += 1; - } + mScene->mNumAnimations = r.animations.Size(); + if (mScene->mNumAnimations == 0) { + return; + } - if (numEmbeddedTexs == 0) - return; + mScene->mAnimations = new aiAnimation *[mScene->mNumAnimations]; + for (unsigned int i = 0; i < r.animations.Size(); ++i) { + Animation &anim = r.animations[i]; - mScene->mTextures = new aiTexture*[numEmbeddedTexs]; + aiAnimation *ai_anim = new aiAnimation(); + ai_anim->mName = anim.name; + ai_anim->mDuration = 0; + ai_anim->mTicksPerSecond = 0; - // Add the embedded textures - for (size_t i = 0; i < r.images.Size(); ++i) { - Image &img = r.images[i]; - if (!img.HasData()) continue; + std::unordered_map samplers = GatherSamplers(anim); - int idx = mScene->mNumTextures++; - embeddedTexIdxs[i] = idx; + uint32_t numChannels = 0u; + uint32_t numMorphMeshChannels = 0u; - aiTexture* tex = mScene->mTextures[idx] = new aiTexture(); + for (auto &iter : samplers) { + if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) { + ++numChannels; + } + if (nullptr != iter.second.weight) { + ++numMorphMeshChannels; + } + } - size_t length = img.GetDataLength(); - void* data = img.StealData(); + ai_anim->mNumChannels = numChannels; + if (ai_anim->mNumChannels > 0) { + ai_anim->mChannels = new aiNodeAnim *[ai_anim->mNumChannels]; + int j = 0; + for (auto &iter : samplers) { + if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) { + ai_anim->mChannels[j] = CreateNodeAnim(r, r.nodes[iter.first], iter.second); + ++j; + } + } + } - tex->mWidth = static_cast(length); - tex->mHeight = 0; - tex->pcData = reinterpret_cast(data); + ai_anim->mNumMorphMeshChannels = numMorphMeshChannels; + if (ai_anim->mNumMorphMeshChannels > 0) { + ai_anim->mMorphMeshChannels = new aiMeshMorphAnim *[ai_anim->mNumMorphMeshChannels]; + int j = 0; + for (auto &iter : samplers) { + if (nullptr != iter.second.weight) { + ai_anim->mMorphMeshChannels[j] = CreateMeshMorphAnim(r, r.nodes[iter.first], iter.second); + ++j; + } + } + } - if (!img.mimeType.empty()) { - const char* ext = strchr(img.mimeType.c_str(), '/') + 1; - if (ext) { - if (strcmp(ext, "jpeg") == 0) ext = "jpg"; + // Use the latest keyframe for the duration of the animation + double maxDuration = 0; + unsigned int maxNumberOfKeys = 0; + for (unsigned int j = 0; j < ai_anim->mNumChannels; ++j) { + auto chan = ai_anim->mChannels[j]; + if (chan->mNumPositionKeys) { + auto lastPosKey = chan->mPositionKeys[chan->mNumPositionKeys - 1]; + if (lastPosKey.mTime > maxDuration) { + maxDuration = lastPosKey.mTime; + } + maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumPositionKeys); + } + if (chan->mNumRotationKeys) { + auto lastRotKey = chan->mRotationKeys[chan->mNumRotationKeys - 1]; + if (lastRotKey.mTime > maxDuration) { + maxDuration = lastRotKey.mTime; + } + maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumRotationKeys); + } + if (chan->mNumScalingKeys) { + auto lastScaleKey = chan->mScalingKeys[chan->mNumScalingKeys - 1]; + if (lastScaleKey.mTime > maxDuration) { + maxDuration = lastScaleKey.mTime; + } + maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumScalingKeys); + } + } - size_t len = strlen(ext); - if (len <= 3) { - strcpy(tex->achFormatHint, ext); - } - } - } - } + for (unsigned int j = 0; j < ai_anim->mNumMorphMeshChannels; ++j) { + const auto *const chan = ai_anim->mMorphMeshChannels[j]; + + if (0u != chan->mNumKeys) { + const auto &lastKey = chan->mKeys[chan->mNumKeys - 1u]; + if (lastKey.mTime > maxDuration) { + maxDuration = lastKey.mTime; + } + maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumKeys); + } + } + + ai_anim->mDuration = maxDuration; + ai_anim->mTicksPerSecond = 1000.0; + + mScene->mAnimations[i] = ai_anim; + } } -void glTF2Importer::InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) -{ - // clean all member arrays - meshOffsets.clear(); - embeddedTexIdxs.clear(); +void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset &r) { + embeddedTexIdxs.resize(r.images.Size(), -1); - this->mScene = pScene; + int numEmbeddedTexs = 0; + for (size_t i = 0; i < r.images.Size(); ++i) { + if (r.images[i].HasData()) + numEmbeddedTexs += 1; + } - // read the asset file - glTF2::Asset asset(pIOHandler); - asset.Load(pFile, GetExtension(pFile) == "glb"); + if (numEmbeddedTexs == 0) + return; - // - // Copy the data out - // + mScene->mTextures = new aiTexture *[numEmbeddedTexs]; - ImportEmbeddedTextures(asset); - ImportMaterials(asset); + // Add the embedded textures + for (size_t i = 0; i < r.images.Size(); ++i) { + Image &img = r.images[i]; + if (!img.HasData()) continue; - ImportMeshes(asset); + int idx = mScene->mNumTextures++; + embeddedTexIdxs[i] = idx; - ImportCameras(asset); + aiTexture *tex = mScene->mTextures[idx] = new aiTexture(); - ImportNodes(asset); + size_t length = img.GetDataLength(); + void *data = img.StealData(); - ImportAnimations(asset); + tex->mWidth = static_cast(length); + tex->mHeight = 0; + tex->pcData = reinterpret_cast(data); - if (pScene->mNumMeshes == 0) { - pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; - } + if (!img.mimeType.empty()) { + const char *ext = strchr(img.mimeType.c_str(), '/') + 1; + if (ext) { + if (strcmp(ext, "jpeg") == 0) ext = "jpg"; + + size_t len = strlen(ext); + if (len <= 3) { + strcpy(tex->achFormatHint, ext); + } + } + } + } +} + +void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + // clean all member arrays + meshOffsets.clear(); + embeddedTexIdxs.clear(); + + this->mScene = pScene; + + // read the asset file + glTF2::Asset asset(pIOHandler); + asset.Load(pFile, GetExtension(pFile) == "glb"); + + // + // Copy the data out + // + + ImportEmbeddedTextures(asset); + ImportMaterials(asset); + + ImportMeshes(asset); + + ImportCameras(asset); + ImportLights(asset); + + ImportNodes(asset); + + ImportAnimations(asset); + + if (pScene->mNumMeshes == 0) { + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } } #endif // ASSIMP_BUILD_NO_GLTF_IMPORTER - diff --git a/code/res/assimp.rc b/code/res/assimp.rc index 14ffdf4f5..daecf9cf5 100644 --- a/code/res/assimp.rc +++ b/code/res/assimp.rc @@ -52,8 +52,8 @@ BEGIN VALUE "FileDescription", "Open Asset Import Library" VALUE "FileVersion", VER_FILEVERSION VALUE "InternalName", "assimp " - VALUE "LegalCopyright", "Copyright (C) 2006-2010" - VALUE "OriginalFilename", "assimpNN.dll" + VALUE "LegalCopyright", "Copyright (C) 2006-2019" + VALUE "OriginalFilename", VER_ORIGINAL_FILENAME_STR VALUE "ProductName", "Open Asset Import Library" VALUE "ProductVersion", VER_FILEVERSION_STR ,0 diff --git a/contrib/gtest/test/gtest-param-test_test.cc b/contrib/gtest/test/gtest-param-test_test.cc index 8b278bb94..857f6c5e5 100644 --- a/contrib/gtest/test/gtest-param-test_test.cc +++ b/contrib/gtest/test/gtest-param-test_test.cc @@ -141,7 +141,7 @@ void VerifyGenerator(const ParamGenerator& generator, << ", expected_values[i] is " << PrintValue(expected_values[i]) << ", *it is " << PrintValue(*it) << ", and 'it' is an iterator created with the copy constructor.\n"; - it++; + ++it; } EXPECT_TRUE(it == generator.end()) << "At the presumed end of sequence when accessing via an iterator " @@ -161,7 +161,7 @@ void VerifyGenerator(const ParamGenerator& generator, << ", expected_values[i] is " << PrintValue(expected_values[i]) << ", *it is " << PrintValue(*it) << ", and 'it' is an iterator created with the copy constructor.\n"; - it++; + ++it; } EXPECT_TRUE(it == generator.end()) << "At the presumed end of sequence when accessing via an iterator " @@ -196,7 +196,7 @@ TEST(IteratorTest, ParamIteratorConformsToForwardIteratorConcept) { << "element same as its source points to"; // Verifies that iterator assignment works as expected. - it++; + ++it; EXPECT_FALSE(*it == *it2); it2 = it; EXPECT_TRUE(*it == *it2) << "Assigned iterators must point to the " @@ -215,7 +215,7 @@ TEST(IteratorTest, ParamIteratorConformsToForwardIteratorConcept) { // Verifies that prefix and postfix operator++() advance an iterator // all the same. it2 = it; - it++; + ++it; ++it2; EXPECT_TRUE(*it == *it2); } diff --git a/contrib/irrXML/CXMLReaderImpl.h b/contrib/irrXML/CXMLReaderImpl.h index 6f3bec5fa..a125312a1 100644 --- a/contrib/irrXML/CXMLReaderImpl.h +++ b/contrib/irrXML/CXMLReaderImpl.h @@ -15,6 +15,9 @@ #include //using namespace Assimp; +// For locale independent number conversion +#include +#include #ifdef _DEBUG #define IRR_DEBUGPRINT(x) printf((x)); @@ -178,8 +181,11 @@ public: return 0; core::stringc c = attrvalue; - return static_cast(atof(c.c_str())); - //return fast_atof(c.c_str()); + std::istringstream sstr(c.c_str()); + sstr.imbue(std::locale("C")); // Locale free number convert + float fNum; + sstr >> fNum; + return fNum; } diff --git a/contrib/irrXML/irrXML.h b/contrib/irrXML/irrXML.h index d596ec062..d724b3162 100644 --- a/contrib/irrXML/irrXML.h +++ b/contrib/irrXML/irrXML.h @@ -215,7 +215,7 @@ namespace io two methods to read your data and give a pointer to an instance of your implementation when calling createIrrXMLReader(), createIrrXMLReaderUTF16() or createIrrXMLReaderUTF32() */ - class IFileReadCallBack + class IRRXML_API IFileReadCallBack { public: diff --git a/contrib/zip/.gitignore b/contrib/zip/.gitignore index a7904a1ef..49b2cb2fd 100644 --- a/contrib/zip/.gitignore +++ b/contrib/zip/.gitignore @@ -1,6 +1,7 @@ /build/ /test/build/ /xcodeproj/ +.vscode/ # Object files *.o @@ -54,3 +55,4 @@ zip.dir/ test/test.exe.vcxproj.filters test/test.exe.vcxproj test/test.exe.dir/ + diff --git a/contrib/zip/CMakeLists.txt b/contrib/zip/CMakeLists.txt index b46dbb1db..77916d2e1 100644 --- a/contrib/zip/CMakeLists.txt +++ b/contrib/zip/CMakeLists.txt @@ -1,10 +1,14 @@ -cmake_minimum_required(VERSION 2.8) -project(zip) -enable_language(C) +cmake_minimum_required(VERSION 3.0) + +project(zip + LANGUAGES C + VERSION "0.1.15") set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) +option(CMAKE_DISABLE_TESTING "Disable test creation" OFF) + if (MSVC) - # Use secure functions by defaualt and suppress warnings about "deprecated" functions + # Use secure functions by default and suppress warnings about "deprecated" functions set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT=1") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_NONSTDC_NO_WARNINGS=1 /D _CRT_SECURE_NO_WARNINGS=1") @@ -12,28 +16,80 @@ elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra -Werror -pedantic") + if(ENABLE_COVERAGE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") + endif() endif (MSVC) # zip set(SRC src/miniz.h src/zip.h src/zip.c) add_library(${PROJECT_NAME} ${SRC}) -target_include_directories(${PROJECT_NAME} INTERFACE src) +target_include_directories(${PROJECT_NAME} PUBLIC + $ + $ +) # test if (NOT CMAKE_DISABLE_TESTING) enable_testing() add_subdirectory(test) find_package(Sanitizers) - add_sanitizers(${PROJECT_NAME} test.exe) - add_sanitizers(${PROJECT_NAME} test_miniz.exe) + add_sanitizers(${PROJECT_NAME} ${test_out} ${test_miniz_out}) endif() +#### +# Installation (https://github.com/forexample/package-example) { + +set(CONFIG_INSTALL_DIR "lib/cmake/${PROJECT_NAME}") +set(INCLUDE_INSTALL_DIR "include") + +set(GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") + +# Configuration +set(VERSION_CONFIG "${GENERATED_DIR}/${PROJECT_NAME}ConfigVersion.cmake") +set(PROJECT_CONFIG "${GENERATED_DIR}/${PROJECT_NAME}Config.cmake") +set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets") +set(NAMESPACE "${PROJECT_NAME}::") + +# Include module with fuction 'write_basic_package_version_file' +include(CMakePackageConfigHelpers) + +# Note: PROJECT_VERSION is used as a VERSION +write_basic_package_version_file( + "${VERSION_CONFIG}" COMPATIBILITY SameMajorVersion +) + +# Use variables: +# * TARGETS_EXPORT_NAME +# * PROJECT_NAME +configure_package_config_file( + "cmake/Config.cmake.in" + "${PROJECT_CONFIG}" + INSTALL_DESTINATION "${CONFIG_INSTALL_DIR}" +) + +install( + FILES "${PROJECT_CONFIG}" "${VERSION_CONFIG}" + DESTINATION "${CONFIG_INSTALL_DIR}" +) + +install( + EXPORT "${TARGETS_EXPORT_NAME}" + NAMESPACE "${NAMESPACE}" + DESTINATION "${CONFIG_INSTALL_DIR}" +) + +# } + install(TARGETS ${PROJECT_NAME} + EXPORT ${TARGETS_EXPORT_NAME} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib - COMPONENT library) -install(FILES ${PROJECT_SOURCE_DIR}/src/zip.h DESTINATION include) + INCLUDES DESTINATION ${INCLUDE_INSTALL_DIR} +) +install(FILES ${PROJECT_SOURCE_DIR}/src/zip.h DESTINATION ${INCLUDE_INSTALL_DIR}/zip) # uninstall target (https://gitlab.kitware.com/cmake/community/wikis/FAQ#can-i-do-make-uninstall-with-cmake) if(NOT TARGET uninstall) @@ -45,3 +101,12 @@ if(NOT TARGET uninstall) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake) endif() + +find_package(Doxygen) +if(DOXYGEN_FOUND) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" VERBATIM) +endif() diff --git a/contrib/zip/README.md b/contrib/zip/README.md index d5fb8cd20..14eb9a34c 100644 --- a/contrib/zip/README.md +++ b/contrib/zip/README.md @@ -71,7 +71,7 @@ int arg = 2; zip_extract("foo.zip", "/tmp", on_extract_entry, &arg); ``` -* Extract a zip entry into memory. +* Extract a zip entry into memory. ```c void *buf = NULL; size_t bufsize; @@ -89,7 +89,7 @@ zip_close(zip); free(buf); ``` -* Extract a zip entry into memory (no internal allocation). +* Extract a zip entry into memory (no internal allocation). ```c unsigned char *buf; size_t bufsize; @@ -110,7 +110,7 @@ zip_close(zip); free(buf); ``` -* Extract a zip entry into memory using callback. +* Extract a zip entry into memory using callback. ```c struct buffer_t { char *data; @@ -144,7 +144,7 @@ free(buf.data); ``` -* Extract a zip entry into a file. +* Extract a zip entry into a file. ```c struct zip_t *zip = zip_open("foo.zip", 0, 'r'); { @@ -157,7 +157,7 @@ struct zip_t *zip = zip_open("foo.zip", 0, 'r'); zip_close(zip); ``` -* List of all zip entries +* List of all zip entries ```c struct zip_t *zip = zip_open("foo.zip", 0, 'r'); int i, n = zip_total_entries(zip); @@ -174,7 +174,7 @@ for (i = 0; i < n; ++i) { zip_close(zip); ``` -## Bindings +# Bindings Compile zip library as a dynamic library. ```shell $ mkdir build diff --git a/contrib/zip/appveyor.yml b/contrib/zip/appveyor.yml index 0be6373ca..ea17f5de9 100644 --- a/contrib/zip/appveyor.yml +++ b/contrib/zip/appveyor.yml @@ -1,4 +1,4 @@ -version: zip-0.1.9.{build} +version: zip-0.1.15.{build} build_script: - cmd: >- cd c:\projects\zip diff --git a/contrib/zip/src/miniz.h b/contrib/zip/src/miniz.h index 2c27a94d8..c4fcfb83e 100644 --- a/contrib/zip/src/miniz.h +++ b/contrib/zip/src/miniz.h @@ -221,6 +221,7 @@ #ifndef MINIZ_HEADER_INCLUDED #define MINIZ_HEADER_INCLUDED +#include #include // Defines to completely disable specific portions of miniz.c: @@ -284,7 +285,8 @@ /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ #if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) #if MINIZ_X86_OR_X64_CPU -/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */ +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient + * integer loads and stores from unaligned addresses. */ #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #define MINIZ_UNALIGNED_USE_MEMCPY #else @@ -354,6 +356,44 @@ enum { MZ_FIXED = 4 }; +/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or + * modify this enum. */ +typedef enum { + MZ_ZIP_NO_ERROR = 0, + MZ_ZIP_UNDEFINED_ERROR, + MZ_ZIP_TOO_MANY_FILES, + MZ_ZIP_FILE_TOO_LARGE, + MZ_ZIP_UNSUPPORTED_METHOD, + MZ_ZIP_UNSUPPORTED_ENCRYPTION, + MZ_ZIP_UNSUPPORTED_FEATURE, + MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, + MZ_ZIP_NOT_AN_ARCHIVE, + MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, + MZ_ZIP_UNSUPPORTED_MULTIDISK, + MZ_ZIP_DECOMPRESSION_FAILED, + MZ_ZIP_COMPRESSION_FAILED, + MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, + MZ_ZIP_CRC_CHECK_FAILED, + MZ_ZIP_UNSUPPORTED_CDIR_SIZE, + MZ_ZIP_ALLOC_FAILED, + MZ_ZIP_FILE_OPEN_FAILED, + MZ_ZIP_FILE_CREATE_FAILED, + MZ_ZIP_FILE_WRITE_FAILED, + MZ_ZIP_FILE_READ_FAILED, + MZ_ZIP_FILE_CLOSE_FAILED, + MZ_ZIP_FILE_SEEK_FAILED, + MZ_ZIP_FILE_STAT_FAILED, + MZ_ZIP_INVALID_PARAMETER, + MZ_ZIP_INVALID_FILENAME, + MZ_ZIP_BUF_TOO_SMALL, + MZ_ZIP_INTERNAL_ERROR, + MZ_ZIP_FILE_NOT_FOUND, + MZ_ZIP_ARCHIVE_TOO_LARGE, + MZ_ZIP_VALIDATION_FAILED, + MZ_ZIP_WRITE_CALLBACK_FAILED, + MZ_ZIP_TOTAL_ERRORS +} mz_zip_error; + // Method #define MZ_DEFLATED 8 @@ -696,6 +736,7 @@ typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); +typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); struct mz_zip_internal_state_tag; typedef struct mz_zip_internal_state_tag mz_zip_internal_state; @@ -707,13 +748,27 @@ typedef enum { MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 } mz_zip_mode; -typedef struct mz_zip_archive_tag { +typedef enum { + MZ_ZIP_TYPE_INVALID = 0, + MZ_ZIP_TYPE_USER, + MZ_ZIP_TYPE_MEMORY, + MZ_ZIP_TYPE_HEAP, + MZ_ZIP_TYPE_FILE, + MZ_ZIP_TYPE_CFILE, + MZ_ZIP_TOTAL_TYPES +} mz_zip_type; + +typedef struct { mz_uint64 m_archive_size; mz_uint64 m_central_directory_file_ofs; - mz_uint m_total_files; - mz_zip_mode m_zip_mode; - mz_uint m_file_offset_alignment; + /* We only support up to UINT32_MAX files in zip64 mode. */ + mz_uint32 m_total_files; + mz_zip_mode m_zip_mode; + mz_zip_type m_zip_type; + mz_zip_error m_last_error; + + mz_uint64 m_file_offset_alignment; mz_alloc_func m_pAlloc; mz_free_func m_pFree; @@ -722,6 +777,7 @@ typedef struct mz_zip_archive_tag { mz_file_read_func m_pRead; mz_file_write_func m_pWrite; + mz_file_needs_keepalive m_pNeeds_keepalive; void *m_pIO_opaque; mz_zip_internal_state *m_pState; @@ -1263,6 +1319,9 @@ mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); #endif // #ifndef MINIZ_NO_ZLIB_APIS +#define MZ_UINT16_MAX (0xFFFFU) +#define MZ_UINT32_MAX (0xFFFFFFFFU) + #ifdef __cplusplus } #endif @@ -1311,6 +1370,11 @@ typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) #endif +#define MZ_READ_LE64(p) \ + (((mz_uint64)MZ_READ_LE32(p)) | \ + (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) \ + << 32U)) + #ifdef _MSC_VER #define MZ_FORCEINLINE __forceinline #elif defined(__GNUC__) @@ -4160,6 +4224,17 @@ enum { MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + + /* ZIP64 archive identifier and record sizes */ + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, + MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, + MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, + MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, + MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, + // Central directory header record offsets MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, @@ -4199,6 +4274,31 @@ enum { MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, + + /* ZIP64 End of central directory locator offsets */ + MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ + + /* ZIP64 End of central directory header offsets */ + MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ + MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, + MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 }; typedef struct { @@ -4211,7 +4311,24 @@ struct mz_zip_internal_state_tag { mz_zip_array m_central_dir; mz_zip_array m_central_dir_offsets; mz_zip_array m_sorted_central_dir_offsets; + + /* The flags passed in when the archive is initially opened. */ + uint32_t m_init_flags; + + /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. + */ + mz_bool m_zip64; + + /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 + * will also be slammed to true too, even if we didn't find a zip64 end of + * central dir header, etc.) */ + mz_bool m_zip64_has_extended_info_fields; + + /* These fields are used by the file, FILE, memory, and memory/heap read/write + * helpers. */ MZ_FILE *m_pFile; + mz_uint64 m_file_archive_start_ofs; + void *m_pMem; size_t m_mem_size; size_t m_mem_capacity; @@ -4363,6 +4480,13 @@ static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, #endif /* #ifndef MINIZ_NO_STDIO */ #endif /* #ifndef MINIZ_NO_TIME */ +static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, + mz_zip_error err_num) { + if (pZip) + pZip->m_last_error = err_num; + return MZ_FALSE; +} + static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint32 flags) { (void)flags; @@ -4480,127 +4604,346 @@ mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) { } } -static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, - mz_uint32 flags) { - mz_uint cdir_size, num_this_disk, cdir_disk_index; - mz_uint64 cdir_ofs; +static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, + mz_uint32 record_sig, + mz_uint32 record_size, + mz_int64 *pOfs) { mz_int64 cur_file_ofs; - const mz_uint8 *p; mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; - mz_bool sort_central_dir = - ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); - // Basic sanity checks - reject files which are too small, and check the first - // 4 bytes of the file to make sure a local header is there. - if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + + /* Basic sanity checks - reject files which are too small */ + if (pZip->m_archive_size < record_size) return MZ_FALSE; - // Find the end of central directory record by scanning the file from the end - // towards the beginning. + + /* Find the record by scanning the file from the end towards the beginning. */ cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); for (;;) { int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) return MZ_FALSE; - for (i = n - 4; i >= 0; --i) - if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) - break; + + for (i = n - 4; i >= 0; --i) { + mz_uint s = MZ_READ_LE32(pBuf + i); + if (s == record_sig) { + if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) + break; + } + } + if (i >= 0) { cur_file_ofs += i; break; } + + /* Give up if we've searched the entire file, or we've gone back "too far" + * (~64kb) */ if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= - (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + (MZ_UINT16_MAX + record_size))) return MZ_FALSE; + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); } - // Read and verify the end of central directory record. + + *pOfs = cur_file_ofs; + return MZ_TRUE; +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, + mz_uint flags) { + mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, + cdir_disk_index = 0; + mz_uint64 cdir_ofs = 0; + mz_int64 cur_file_ofs = 0; + const mz_uint8 *p; + + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = + ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + mz_uint32 zip64_end_of_central_dir_locator_u32 + [(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; + + mz_uint32 zip64_end_of_central_dir_header_u32 + [(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pZip64_end_of_central_dir = + (mz_uint8 *)zip64_end_of_central_dir_header_u32; + + mz_uint64 zip64_end_of_central_dir_ofs = 0; + + /* Basic sanity checks - reject files which are too small, and check the first + * 4 bytes of the file to make sure a local header is there. */ + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_locate_header_sig( + pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) + return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); + + /* Read and verify the end of central directory record. */ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) - return MZ_FALSE; - if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != - MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) || - ((pZip->m_total_files = - MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != - MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS))) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) { + if (pZip->m_pRead(pZip->m_pIO_opaque, + cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, + pZip64_locator, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) { + if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) { + zip64_end_of_central_dir_ofs = MZ_READ_LE64( + pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); + if (zip64_end_of_central_dir_ofs > + (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, + pZip64_end_of_central_dir, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) { + if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) { + pZip->m_pState->m_zip64 = MZ_TRUE; + } + } + } + } + } + + pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); + cdir_entries_on_this_disk = + MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + + if (pZip->m_pState->m_zip64) { + mz_uint32 zip64_total_num_of_disks = + MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); + mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); + mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); + mz_uint64 zip64_size_of_central_directory = + MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); + + if (zip64_size_of_end_of_central_dir_record < + (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (zip64_total_num_of_disks != 1U) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + /* Check for miniz's practical limits */ + if (zip64_cdir_total_entries > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; + + if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + cdir_entries_on_this_disk = + (mz_uint32)zip64_cdir_total_entries_on_this_disk; + + /* Check for miniz's current practical limits (sorry, this should be enough + * for millions of files) */ + if (zip64_size_of_central_directory > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + cdir_size = (mz_uint32)zip64_size_of_central_directory; + + num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); + + cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); + + cdir_ofs = + MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); + } + + if (pZip->m_total_files != cdir_entries_on_this_disk) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); - if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < - pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) - return MZ_FALSE; + if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pZip->m_central_directory_file_ofs = cdir_ofs; if (pZip->m_total_files) { mz_uint i, n; - - // Read the entire central directory into a heap block, and allocate another - // heap block to hold the unsorted central dir file record offsets, and - // another to hold the sorted indices. + /* Read the entire central directory into a heap block, and allocate another + * heap block to hold the unsorted central dir file record offsets, and + * possibly another to hold the sorted indices. */ if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (sort_central_dir) { if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - // Now create an index into the central directory file records, do some - // basic sanity checking on each record, and check for zip64 entries (which - // are not yet supported). + /* Now create an index into the central directory file records, do some + * basic sanity checking on each record */ p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) { - mz_uint total_header_size, comp_size, decomp_size, disk_index; + mz_uint total_header_size, disk_index, bit_flags, filename_size, + ext_data_size; + mz_uint64 comp_size, decomp_size, local_header_ofs; + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + if (sort_central_dir) MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); - if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && - (decomp_size != comp_size)) || - (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || - (comp_size == 0xFFFFFFFF)) - return MZ_FALSE; + local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && + (ext_data_size) && + (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == + MZ_UINT32_MAX)) { + /* Attempt to find zip64 extended information field in the entry's extra + * data */ + mz_uint32 extra_size_remaining = ext_data_size; + + if (extra_size_remaining) { + const mz_uint8 *pExtra_data; + void *buf = NULL; + + if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > + n) { + buf = MZ_MALLOC(ext_data_size); + if (buf == NULL) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (pZip->m_pRead(pZip->m_pIO_opaque, + cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + filename_size, + buf, ext_data_size) != ext_data_size) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (mz_uint8 *)buf; + } else { + pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; + } + + do { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > + extra_size_remaining) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { + /* Ok, the archive didn't have any zip64 headers but it uses a + * zip64 extended information field so mark it as zip64 anyway + * (this can occur with infozip's zip util when it reads + * compresses files from stdin). */ + pZip->m_pState->m_zip64 = MZ_TRUE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = + extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + + MZ_FREE(buf); + } + } + + /* I've seen archives that aren't marked as zip64 that uses zip64 ext + * data, argh */ + if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) { + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && + (decomp_size != comp_size)) || + (decomp_size && !comp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); - if ((disk_index != num_this_disk) && (disk_index != 1)) - return MZ_FALSE; - if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + - MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) - return MZ_FALSE; + if ((disk_index == MZ_UINT16_MAX) || + ((disk_index != num_this_disk) && (disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (comp_size != MZ_UINT32_MAX) { + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + n -= total_header_size; p += total_header_size; } diff --git a/contrib/zip/src/zip.c b/contrib/zip/src/zip.c index ff3a8fe1e..1abcfd8fd 100644 --- a/contrib/zip/src/zip.c +++ b/contrib/zip/src/zip.c @@ -24,7 +24,6 @@ ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) && \ (P)[1] == ':') #define FILESYSTEM_PREFIX_LEN(P) (HAS_DEVICE(P) ? 2 : 0) -#define ISSLASH(C) ((C) == '/' || (C) == '\\') #else @@ -48,7 +47,7 @@ int symlink(const char *target, const char *linkpath); // needed on Linux #endif #ifndef ISSLASH -#define ISSLASH(C) ((C) == '/') +#define ISSLASH(C) ((C) == '/' || (C) == '\\') #endif #define CLEANUP(ptr) \ @@ -78,26 +77,34 @@ static const char *base_name(const char *name) { return base; } -static int mkpath(const char *path) { - char const *p; +static int mkpath(char *path) { + char *p; char npath[MAX_PATH + 1]; int len = 0; int has_device = HAS_DEVICE(path); memset(npath, 0, MAX_PATH + 1); - -#ifdef _WIN32 - // only on windows fix the path - npath[0] = path[0]; - npath[1] = path[1]; - len = 2; -#endif // _WIN32 - + if (has_device) { + // only on windows + npath[0] = path[0]; + npath[1] = path[1]; + len = 2; + } for (p = path + len; *p && len < MAX_PATH; p++) { if (ISSLASH(*p) && ((!has_device && len > 0) || (has_device && len > 2))) { - if (MKDIR(npath) == -1) - if (errno != EEXIST) +#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ + defined(__MINGW32__) +#else + if ('\\' == *p) { + *p = '/'; + } +#endif + + if (MKDIR(npath) == -1) { + if (errno != EEXIST) { return -1; + } + } } npath[len++] = *p; } @@ -279,7 +286,14 @@ int zip_entry_open(struct zip_t *zip, const char *entryname) { zip->entry.header_offset = zip->archive.m_archive_size; memset(zip->entry.header, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE * sizeof(mz_uint8)); zip->entry.method = 0; + + // UNIX or APPLE +#if MZ_PLATFORM == 3 || MZ_PLATFORM == 19 + // regular file with rw-r--r-- persmissions + zip->entry.external_attr = (mz_uint32)(0100644) << 16; +#else zip->entry.external_attr = 0; +#endif num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pzip); @@ -660,7 +674,7 @@ ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize) { } if (!mz_zip_reader_extract_to_mem_no_alloc(pzip, (mz_uint)zip->entry.index, - buf, bufsize, 0, NULL, 0)) { + buf, bufsize, 0, NULL, 0)) { return -1; } @@ -670,10 +684,7 @@ ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize) { int zip_entry_fread(struct zip_t *zip, const char *filename) { mz_zip_archive *pzip = NULL; mz_uint idx; -#if defined(_MSC_VER) -#else mz_uint32 xattr = 0; -#endif mz_zip_archive_file_stat info; if (!zip) { @@ -875,12 +886,19 @@ int zip_extract(const char *zipname, const char *dir, goto out; } - if ((((info.m_version_made_by >> 8) == 3) || ((info.m_version_made_by >> 8) == 19)) // if zip is produced on Unix or macOS (3 and 19 from section 4.4.2.2 of zip standard) - && info.m_external_attr & (0x20 << 24)) { // and has sym link attribute (0x80 is file, 0x40 is directory) + if ((((info.m_version_made_by >> 8) == 3) || + ((info.m_version_made_by >> 8) == + 19)) // if zip is produced on Unix or macOS (3 and 19 from + // section 4.4.2.2 of zip standard) + && info.m_external_attr & + (0x20 << 24)) { // and has sym link attribute (0x80 is file, 0x40 + // is directory) #if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ defined(__MINGW32__) -#else - if (info.m_uncomp_size > MAX_PATH || !mz_zip_reader_extract_to_mem_no_alloc(&zip_archive, i, symlink_to, MAX_PATH, 0, NULL, 0)) { +#else + if (info.m_uncomp_size > MAX_PATH || + !mz_zip_reader_extract_to_mem_no_alloc(&zip_archive, i, symlink_to, + MAX_PATH, 0, NULL, 0)) { goto out; } symlink_to[info.m_uncomp_size] = '\0'; diff --git a/contrib/zip/src/zip.h b/contrib/zip/src/zip.h index 5f39df50a..a48d64d6d 100644 --- a/contrib/zip/src/zip.h +++ b/contrib/zip/src/zip.h @@ -20,241 +20,240 @@ extern "C" { #endif #if !defined(_SSIZE_T_DEFINED) && !defined(_SSIZE_T_DEFINED_) && \ - !defined(_SSIZE_T) && !defined(_SSIZE_T_) && !defined(__ssize_t_defined) -#define _SSIZE_T + !defined(__DEFINED_ssize_t) && !defined(__ssize_t_defined) && \ + !defined(_SSIZE_T) && !defined(_SSIZE_T_) + // 64-bit Windows is the only mainstream platform // where sizeof(long) != sizeof(void*) #ifdef _WIN64 -typedef long long ssize_t; /* byte count or error */ +typedef long long ssize_t; /* byte count or error */ #else -typedef long ssize_t; /* byte count or error */ +typedef long ssize_t; /* byte count or error */ #endif + +#define _SSIZE_T_DEFINED +#define _SSIZE_T_DEFINED_ +#define __DEFINED_ssize_t +#define __ssize_t_defined +#define _SSIZE_T +#define _SSIZE_T_ + #endif #ifndef MAX_PATH #define MAX_PATH 32767 /* # chars in a path name including NULL */ #endif +/** + * @mainpage + * + * Documenation for @ref zip. + */ + +/** + * @addtogroup zip + * @{ + */ + +/** + * Default zip compression level. + */ + #define ZIP_DEFAULT_COMPRESSION_LEVEL 6 -/* - This data structure is used throughout the library to represent zip archive - - forward declaration. -*/ +/** + * @struct zip_t + * + * This data structure is used throughout the library to represent zip archive - + * forward declaration. + */ struct zip_t; -/* - Opens zip archive with compression level using the given mode. - - Args: - zipname: zip archive file name. - level: compression level (0-9 are the standard zlib-style levels). - mode: file access mode. - 'r': opens a file for reading/extracting (the file must exists). - 'w': creates an empty file for writing. - 'a': appends to an existing archive. - - Returns: - The zip archive handler or NULL on error -*/ +/** + * Opens zip archive with compression level using the given mode. + * + * @param zipname zip archive file name. + * @param level compression level (0-9 are the standard zlib-style levels). + * @param mode file access mode. + * - 'r': opens a file for reading/extracting (the file must exists). + * - 'w': creates an empty file for writing. + * - 'a': appends to an existing archive. + * + * @return the zip archive handler or NULL on error + */ extern struct zip_t *zip_open(const char *zipname, int level, char mode); -/* - Closes the zip archive, releases resources - always finalize. - - Args: - zip: zip archive handler. -*/ +/** + * Closes the zip archive, releases resources - always finalize. + * + * @param zip zip archive handler. + */ extern void zip_close(struct zip_t *zip); -/* - Opens an entry by name in the zip archive. - For zip archive opened in 'w' or 'a' mode the function will append - a new entry. In readonly mode the function tries to locate the entry - in global dictionary. - - Args: - zip: zip archive handler. - entryname: an entry name in local dictionary. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Opens an entry by name in the zip archive. + * + * For zip archive opened in 'w' or 'a' mode the function will append + * a new entry. In readonly mode the function tries to locate the entry + * in global dictionary. + * + * @param zip zip archive handler. + * @param entryname an entry name in local dictionary. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_entry_open(struct zip_t *zip, const char *entryname); -/* - Opens a new entry by index in the zip archive. - This function is only valid if zip archive was opened in 'r' (readonly) mode. - - Args: - zip: zip archive handler. - index: index in local dictionary. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Opens a new entry by index in the zip archive. + * + * This function is only valid if zip archive was opened in 'r' (readonly) mode. + * + * @param zip zip archive handler. + * @param index index in local dictionary. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_entry_openbyindex(struct zip_t *zip, int index); -/* - Closes a zip entry, flushes buffer and releases resources. - - Args: - zip: zip archive handler. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Closes a zip entry, flushes buffer and releases resources. + * + * @param zip zip archive handler. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_entry_close(struct zip_t *zip); -/* - Returns a local name of the current zip entry. - The main difference between user's entry name and local entry name - is optional relative path. - Following .ZIP File Format Specification - the path stored MUST not contain - a drive or device letter, or a leading slash. - All slashes MUST be forward slashes '/' as opposed to backwards slashes '\' - for compatibility with Amiga and UNIX file systems etc. - - Args: - zip: zip archive handler. - - Returns: - The pointer to the current zip entry name, or NULL on error. -*/ +/** + * Returns a local name of the current zip entry. + * + * The main difference between user's entry name and local entry name + * is optional relative path. + * Following .ZIP File Format Specification - the path stored MUST not contain + * a drive or device letter, or a leading slash. + * All slashes MUST be forward slashes '/' as opposed to backwards slashes '\' + * for compatibility with Amiga and UNIX file systems etc. + * + * @param zip: zip archive handler. + * + * @return the pointer to the current zip entry name, or NULL on error. + */ extern const char *zip_entry_name(struct zip_t *zip); -/* - Returns an index of the current zip entry. - - Args: - zip: zip archive handler. - - Returns: - The index on success, negative number (< 0) on error. -*/ +/** + * Returns an index of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the index on success, negative number (< 0) on error. + */ extern int zip_entry_index(struct zip_t *zip); -/* - Determines if the current zip entry is a directory entry. - - Args: - zip: zip archive handler. - - Returns: - The return code - 1 (true), 0 (false), negative number (< 0) on error. -*/ +/** + * Determines if the current zip entry is a directory entry. + * + * @param zip zip archive handler. + * + * @return the return code - 1 (true), 0 (false), negative number (< 0) on + * error. + */ extern int zip_entry_isdir(struct zip_t *zip); -/* - Returns an uncompressed size of the current zip entry. - - Args: - zip: zip archive handler. - - Returns: - The uncompressed size in bytes. -*/ +/** + * Returns an uncompressed size of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the uncompressed size in bytes. + */ extern unsigned long long zip_entry_size(struct zip_t *zip); -/* - Returns CRC-32 checksum of the current zip entry. - - Args: - zip: zip archive handler. - - Returns: - The CRC-32 checksum. -*/ +/** + * Returns CRC-32 checksum of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the CRC-32 checksum. + */ extern unsigned int zip_entry_crc32(struct zip_t *zip); -/* - Compresses an input buffer for the current zip entry. - - Args: - zip: zip archive handler. - buf: input buffer. - bufsize: input buffer size (in bytes). - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Compresses an input buffer for the current zip entry. + * + * @param zip zip archive handler. + * @param buf input buffer. + * @param bufsize input buffer size (in bytes). + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize); -/* - Compresses a file for the current zip entry. - - Args: - zip: zip archive handler. - filename: input file. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Compresses a file for the current zip entry. + * + * @param zip zip archive handler. + * @param filename input file. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_entry_fwrite(struct zip_t *zip, const char *filename); -/* - Extracts the current zip entry into output buffer. - The function allocates sufficient memory for a output buffer. - - Args: - zip: zip archive handler. - buf: output buffer. - bufsize: output buffer size (in bytes). - - Note: - - remember to release memory allocated for a output buffer. - - for large entries, please take a look at zip_entry_extract function. - - Returns: - The return code - the number of bytes actually read on success. - Otherwise a -1 on error. -*/ +/** + * Extracts the current zip entry into output buffer. + * + * The function allocates sufficient memory for a output buffer. + * + * @param zip zip archive handler. + * @param buf output buffer. + * @param bufsize output buffer size (in bytes). + * + * @note remember to release memory allocated for a output buffer. + * for large entries, please take a look at zip_entry_extract function. + * + * @return the return code - the number of bytes actually read on success. + * Otherwise a -1 on error. + */ extern ssize_t zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize); -/* - Extracts the current zip entry into a memory buffer using no memory - allocation. +/** + * Extracts the current zip entry into a memory buffer using no memory + * allocation. + * + * @param zip zip archive handler. + * @param buf preallocated output buffer. + * @param bufsize output buffer size (in bytes). + * + * @note ensure supplied output buffer is large enough. + * zip_entry_size function (returns uncompressed size for the current + * entry) can be handy to estimate how big buffer is needed. for large + * entries, please take a look at zip_entry_extract function. + * + * @return the return code - the number of bytes actually read on success. + * Otherwise a -1 on error (e.g. bufsize is not large enough). + */ +extern ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, + size_t bufsize); - Args: - zip: zip archive handler. - buf: preallocated output buffer. - bufsize: output buffer size (in bytes). - - Note: - - ensure supplied output buffer is large enough. - - zip_entry_size function (returns uncompressed size for the current entry) - can be handy to estimate how big buffer is needed. - - for large entries, please take a look at zip_entry_extract function. - - Returns: - The return code - the number of bytes actually read on success. - Otherwise a -1 on error (e.g. bufsize is not large enough). -*/ -extern ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize); - -/* - Extracts the current zip entry into output file. - - Args: - zip: zip archive handler. - filename: output file. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Extracts the current zip entry into output file. + * + * @param zip zip archive handler. + * @param filename output file. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_entry_fread(struct zip_t *zip, const char *filename); -/* - Extracts the current zip entry using a callback function (on_extract). - - Args: - zip: zip archive handler. - on_extract: callback function. - arg: opaque pointer (optional argument, - which you can pass to the on_extract callback) - - Returns: - The return code - 0 on success, negative number (< 0) on error. +/** + * Extracts the current zip entry using a callback function (on_extract). + * + * @param zip zip archive handler. + * @param on_extract callback function. + * @param arg opaque pointer (optional argument, which you can pass to the + * on_extract callback) + * + * @return the return code - 0 on success, negative number (< 0) on error. */ extern int zip_entry_extract(struct zip_t *zip, @@ -262,53 +261,49 @@ zip_entry_extract(struct zip_t *zip, const void *data, size_t size), void *arg); -/* - Returns the number of all entries (files and directories) in the zip archive. - - Args: - zip: zip archive handler. - - Returns: - The return code - the number of entries on success, - negative number (< 0) on error. -*/ +/** + * Returns the number of all entries (files and directories) in the zip archive. + * + * @param zip zip archive handler. + * + * @return the return code - the number of entries on success, negative number + * (< 0) on error. + */ extern int zip_total_entries(struct zip_t *zip); -/* - Creates a new archive and puts files into a single zip archive. - - Args: - zipname: zip archive file. - filenames: input files. - len: number of input files. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Creates a new archive and puts files into a single zip archive. + * + * @param zipname zip archive file. + * @param filenames input files. + * @param len: number of input files. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_create(const char *zipname, const char *filenames[], size_t len); -/* - Extracts a zip archive file into directory. - - If on_extract_entry is not NULL, the callback will be called after - successfully extracted each zip entry. - Returning a negative value from the callback will cause abort and return an - error. The last argument (void *arg) is optional, which you can use to pass - data to the on_extract_entry callback. - - Args: - zipname: zip archive file. - dir: output directory. - on_extract_entry: on extract callback. - arg: opaque pointer. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Extracts a zip archive file into directory. + * + * If on_extract_entry is not NULL, the callback will be called after + * successfully extracted each zip entry. + * Returning a negative value from the callback will cause abort and return an + * error. The last argument (void *arg) is optional, which you can use to pass + * data to the on_extract_entry callback. + * + * @param zipname zip archive file. + * @param dir output directory. + * @param on_extract_entry on extract callback. + * @param arg opaque pointer. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_extract(const char *zipname, const char *dir, int (*on_extract_entry)(const char *filename, void *arg), void *arg); +/** @} */ + #ifdef __cplusplus } #endif diff --git a/contrib/zip/test/CMakeLists.txt b/contrib/zip/test/CMakeLists.txt index 9b2a8db10..cc060b00f 100644 --- a/contrib/zip/test/CMakeLists.txt +++ b/contrib/zip/test/CMakeLists.txt @@ -1,19 +1,16 @@ cmake_minimum_required(VERSION 2.8) -if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang") - if(ENABLE_COVERAGE) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g ") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ftest-coverage") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") - endif() -endif () - # test -include_directories(../src) -add_executable(test.exe test.c ../src/zip.c) -add_executable(test_miniz.exe test_miniz.c) +set(test_out test.out) +set(test_miniz_out test_miniz.out) -add_test(NAME test COMMAND test.exe) -add_test(NAME test_miniz COMMAND test_miniz.exe) +add_executable(${test_out} test.c) +target_link_libraries(${test_out} zip) +add_executable(${test_miniz_out} test_miniz.c) +target_link_libraries(${test_miniz_out} zip) + +add_test(NAME ${test_out} COMMAND ${test_out}) +add_test(NAME ${test_miniz_out} COMMAND ${test_miniz_out}) + +set(test_out ${test_out} PARENT_SCOPE) +set(test_miniz_out ${test_miniz_out} PARENT_SCOPE) diff --git a/contrib/zip/test/test.c b/contrib/zip/test/test.c index 454430533..a9b2ddab1 100644 --- a/contrib/zip/test/test.c +++ b/contrib/zip/test/test.c @@ -29,6 +29,8 @@ #define XFILE "7.txt\0" #define XMODE 0100777 +#define UNIXMODE 0100644 + #define UNUSED(x) (void)x static int total_entries = 0; @@ -102,7 +104,8 @@ static void test_read(void) { assert(0 == zip_entry_close(zip)); free(buf); buf = NULL; - + bufsize = 0; + assert(0 == zip_entry_open(zip, "test/test-2.txt")); assert(strlen(TESTDATA2) == zip_entry_size(zip)); assert(CRC32DATA2 == zip_entry_crc32(zip)); @@ -131,7 +134,8 @@ static void test_read(void) { assert(0 == zip_entry_close(zip)); free(buf); buf = NULL; - + bufsize = 0; + buftmp = strlen(TESTDATA1); buf = calloc(buftmp, sizeof(char)); assert(0 == zip_entry_open(zip, "test/test-1.txt")); @@ -433,6 +437,35 @@ static void test_mtime(void) { remove(ZIPNAME); } +static void test_unix_permissions(void) { +#if defined(_WIN64) || defined(_WIN32) || defined(__WIN32__) +#else + // UNIX or APPLE + struct MZ_FILE_STAT_STRUCT file_stats; + + remove(ZIPNAME); + + struct zip_t *zip = zip_open(ZIPNAME, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w'); + assert(zip != NULL); + + assert(0 == zip_entry_open(zip, RFILE)); + assert(0 == zip_entry_write(zip, TESTDATA1, strlen(TESTDATA1))); + assert(0 == zip_entry_close(zip)); + + zip_close(zip); + + remove(RFILE); + + assert(0 == zip_extract(ZIPNAME, ".", NULL, NULL)); + + assert(0 == MZ_FILE_STAT(RFILE, &file_stats)); + assert(UNIXMODE == file_stats.st_mode); + + remove(RFILE); + remove(ZIPNAME); +#endif +} + int main(int argc, char *argv[]) { UNUSED(argc); UNUSED(argv); @@ -453,6 +486,7 @@ int main(int argc, char *argv[]) { test_write_permissions(); test_exe_permissions(); test_mtime(); + test_unix_permissions(); remove(ZIPNAME); return 0; diff --git a/contrib/zip/test/test_miniz.c b/contrib/zip/test/test_miniz.c index ebc0564dc..babcaecdb 100644 --- a/contrib/zip/test/test_miniz.c +++ b/contrib/zip/test/test_miniz.c @@ -23,16 +23,39 @@ int main(int argc, char *argv[]) { uint step = 0; int cmp_status; uLong src_len = (uLong)strlen(s_pStr); - uLong cmp_len = compressBound(src_len); uLong uncomp_len = src_len; + uLong cmp_len; uint8 *pCmp, *pUncomp; + size_t sz; uint total_succeeded = 0; (void)argc, (void)argv; printf("miniz.c version: %s\n", MZ_VERSION); do { + pCmp = (uint8 *)tdefl_compress_mem_to_heap(s_pStr, src_len, &cmp_len, 0); + if (!pCmp) { + printf("tdefl_compress_mem_to_heap failed\n"); + return EXIT_FAILURE; + } + if (src_len <= cmp_len) { + printf("tdefl_compress_mem_to_heap failed: from %u to %u bytes\n", + (mz_uint32)uncomp_len, (mz_uint32)cmp_len); + free(pCmp); + return EXIT_FAILURE; + } + + sz = tdefl_compress_mem_to_mem(pCmp, cmp_len, s_pStr, src_len, 0); + if (sz != cmp_len) { + printf("tdefl_compress_mem_to_mem failed: expected %u, got %u\n", + (mz_uint32)cmp_len, (mz_uint32)sz); + free(pCmp); + return EXIT_FAILURE; + } + // Allocate buffers to hold compressed and uncompressed data. + free(pCmp); + cmp_len = compressBound(src_len); pCmp = (mz_uint8 *)malloc((size_t)cmp_len); pUncomp = (mz_uint8 *)malloc((size_t)src_len); if ((!pCmp) || (!pUncomp)) { diff --git a/include/assimp/BaseImporter.h b/include/assimp/BaseImporter.h index 48dfc8ed8..ad8a3dafd 100644 --- a/include/assimp/BaseImporter.h +++ b/include/assimp/BaseImporter.h @@ -41,15 +41,22 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @file Definition of the base class for all importer worker classes. */ +#pragma once #ifndef INCLUDED_AI_BASEIMPORTER_H #define INCLUDED_AI_BASEIMPORTER_H +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include "Exceptional.h" #include #include +#include #include #include +#include struct aiScene; struct aiImporterDesc; @@ -80,6 +87,10 @@ class IOStream; class ASSIMP_API BaseImporter { friend class Importer; +private: + /* Pushes state into importer for the importer scale */ + virtual void UpdateImporterScale( Importer* pImp ); + public: /** Constructor to be privately used by #Importer */ @@ -132,7 +143,7 @@ public: * a suitable response to the caller. */ aiScene* ReadFile( - const Importer* pImp, + Importer* pImp, const std::string& pFile, IOSystem* pIOHandler ); @@ -161,14 +172,62 @@ public: * some loader features. Importers must provide this information. */ virtual const aiImporterDesc* GetInfo() const = 0; + /** + * Will be called only by scale process when scaling is requested. + */ + virtual void SetFileScale(double scale) + { + fileScale = scale; + } + + virtual double GetFileScale() const + { + return fileScale; + } + + enum ImporterUnits { + M, + MM, + CM, + INCHES, + FEET + }; + + /** + * Assimp Importer + * unit conversions available + * NOTE: Valid options are initialised in the + * constructor in the implementation file to + * work around a VS2013 compiler bug if support + * for that compiler is dropped in the future + * initialisation can be moved back here + * */ + std::map importerUnits; + + virtual void SetApplicationUnits( const ImporterUnits& unit ) + { + importerScale = importerUnits[unit]; + applicationUnits = unit; + } + + virtual const ImporterUnits& GetApplicationUnits() + { + return applicationUnits; + } + // ------------------------------------------------------------------- /** Called by #Importer::GetExtensionList for each loaded importer. * Take the extension list contained in the structure returned by * #GetInfo and insert all file extensions into the given set. * @param extension set to collect file extensions in*/ void GetExtensionList(std::set& extensions); + +protected: + ImporterUnits applicationUnits = ImporterUnits::M; + double importerScale = 1.0; + double fileScale = 1.0; + -protected: // ------------------------------------------------------------------- /** Imports the given file into the given scene structure. The diff --git a/include/assimp/Bitmap.h b/include/assimp/Bitmap.h index e6b5fb132..4c3f5a437 100644 --- a/include/assimp/Bitmap.h +++ b/include/assimp/Bitmap.h @@ -46,10 +46,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Used for file formats which embed their textures into the model file. */ - +#pragma once #ifndef AI_BITMAP_H_INC #define AI_BITMAP_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include "defs.h" #include #include diff --git a/include/assimp/ByteSwapper.h b/include/assimp/ByteSwapper.h index 20a2463fb..3f14c471a 100644 --- a/include/assimp/ByteSwapper.h +++ b/include/assimp/ByteSwapper.h @@ -42,9 +42,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file Helper class tp perform various byte oder swappings (e.g. little to big endian) */ +#pragma once #ifndef AI_BYTESWAPPER_H_INC #define AI_BYTESWAPPER_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include #include diff --git a/include/assimp/CreateAnimMesh.h b/include/assimp/CreateAnimMesh.h index a60173588..1266d1de1 100644 --- a/include/assimp/CreateAnimMesh.h +++ b/include/assimp/CreateAnimMesh.h @@ -43,16 +43,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file CreateAnimMesh.h * Create AnimMesh from Mesh */ +#pragma once #ifndef INCLUDED_AI_CREATE_ANIM_MESH_H #define INCLUDED_AI_CREATE_ANIM_MESH_H +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include -namespace Assimp { +namespace Assimp { -/** Create aiAnimMesh from aiMesh. */ +/** + * Create aiAnimMesh from aiMesh. + * @param mesh The input mesh to create an animated mesh from. + * @return The new created animated mesh. + */ ASSIMP_API aiAnimMesh *aiCreateAnimMesh(const aiMesh *mesh); } // end of namespace Assimp + #endif // INCLUDED_AI_CREATE_ANIM_MESH_H diff --git a/include/assimp/DefaultIOStream.h b/include/assimp/DefaultIOStream.h index 994d728ff..c6d382c1b 100644 --- a/include/assimp/DefaultIOStream.h +++ b/include/assimp/DefaultIOStream.h @@ -41,15 +41,20 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @file Default file I/O using fXXX()-family of functions */ +#pragma once #ifndef AI_DEFAULTIOSTREAM_H_INC #define AI_DEFAULTIOSTREAM_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include #include #include -namespace Assimp { +namespace Assimp { // ---------------------------------------------------------------------------------- //! @class DefaultIOStream @@ -57,8 +62,7 @@ namespace Assimp { //! @note An instance of this class can exist without a valid file handle //! attached to it. All calls fail, but the instance can nevertheless be //! used with no restrictions. -class ASSIMP_API DefaultIOStream : public IOStream -{ +class ASSIMP_API DefaultIOStream : public IOStream { friend class DefaultIOSystem; #if __ANDROID__ # if __ANDROID_API__ > 9 @@ -82,7 +86,6 @@ public: size_t pSize, size_t pCount); - // ------------------------------------------------------------------- /// Write to stream size_t Write(const void* pvBuffer, @@ -107,16 +110,13 @@ public: void Flush(); private: - // File data-structure, using clib FILE* mFile; - // Filename std::string mFilename; - // Cached file size mutable size_t mCachedSize; }; // ---------------------------------------------------------------------------------- -inline +AI_FORCE_INLINE DefaultIOStream::DefaultIOStream() AI_NO_EXCEPT : mFile(nullptr) , mFilename("") @@ -125,7 +125,7 @@ DefaultIOStream::DefaultIOStream() AI_NO_EXCEPT } // ---------------------------------------------------------------------------------- -inline +AI_FORCE_INLINE DefaultIOStream::DefaultIOStream (FILE* pFile, const std::string &strFilename) : mFile(pFile) , mFilename(strFilename) @@ -137,4 +137,3 @@ DefaultIOStream::DefaultIOStream (FILE* pFile, const std::string &strFilename) } // ns assimp #endif //!!AI_DEFAULTIOSTREAM_H_INC - diff --git a/include/assimp/DefaultIOSystem.h b/include/assimp/DefaultIOSystem.h index 2dd5c801b..46f6d447c 100644 --- a/include/assimp/DefaultIOSystem.h +++ b/include/assimp/DefaultIOSystem.h @@ -41,9 +41,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @file Default implementation of IOSystem using the standard C file functions */ +#pragma once #ifndef AI_DEFAULTIOSYSTEM_H_INC #define AI_DEFAULTIOSYSTEM_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include namespace Assimp { diff --git a/include/assimp/Defines.h b/include/assimp/Defines.h index 15e1d83c2..be3e2fafd 100644 --- a/include/assimp/Defines.h +++ b/include/assimp/Defines.h @@ -38,6 +38,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ +#pragma once +#ifndef AI_DEFINES_H_INC +#define AI_DEFINES_H_INC + +#ifdef __GNUC__ +# pragma GCC system_header +#endif + // We need those constants, workaround for any platforms where nobody defined them yet #if (!defined SIZE_MAX) # define SIZE_MAX (~((size_t)0)) @@ -47,3 +55,4 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # define UINT_MAX (~((unsigned int)0)) #endif +#endif // AI_DEINES_H_INC diff --git a/include/assimp/Exceptional.h b/include/assimp/Exceptional.h index 5109b8f07..6bb6ce1e3 100644 --- a/include/assimp/Exceptional.h +++ b/include/assimp/Exceptional.h @@ -38,11 +38,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -#ifndef INCLUDED_EXCEPTIONAL_H -#define INCLUDED_EXCEPTIONAL_H +#pragma once +#ifndef AI_INCLUDED_EXCEPTIONAL_H +#define AI_INCLUDED_EXCEPTIONAL_H + +#ifdef __GNUC__ +# pragma GCC system_header +#endif #include #include + using std::runtime_error; #ifdef _MSC_VER @@ -53,17 +59,14 @@ using std::runtime_error; /** FOR IMPORTER PLUGINS ONLY: Simple exception class to be thrown if an * unrecoverable error occurs while importing. Loading APIs return * NULL instead of a valid aiScene then. */ -class DeadlyImportError - : public runtime_error -{ +class DeadlyImportError : public runtime_error { public: /** Constructor with arguments */ explicit DeadlyImportError( const std::string& errorText) - : runtime_error(errorText) - { + : runtime_error(errorText) { + // empty } -private: }; typedef DeadlyImportError DeadlyExportError; @@ -84,7 +87,7 @@ struct ExceptionSwallower { template struct ExceptionSwallower { T* operator ()() const { - return NULL; + return nullptr; } }; @@ -122,4 +125,4 @@ struct ExceptionSwallower { }\ } -#endif // INCLUDED_EXCEPTIONAL_H +#endif // AI_INCLUDED_EXCEPTIONAL_H diff --git a/include/assimp/Exporter.hpp b/include/assimp/Exporter.hpp index ea0303e80..20e7c6c6f 100644 --- a/include/assimp/Exporter.hpp +++ b/include/assimp/Exporter.hpp @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -48,6 +46,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_EXPORT_HPP_INC #define AI_EXPORT_HPP_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #ifndef ASSIMP_BUILD_NO_EXPORT #include "cexport.h" diff --git a/include/assimp/GenericProperty.h b/include/assimp/GenericProperty.h index 183ecd519..7796d595b 100644 --- a/include/assimp/GenericProperty.h +++ b/include/assimp/GenericProperty.h @@ -40,12 +40,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ +#pragma once #ifndef AI_GENERIC_PROPERTY_H_INCLUDED #define AI_GENERIC_PROPERTY_H_INCLUDED +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include -#include "Hash.h" +#include #include diff --git a/include/assimp/Hash.h b/include/assimp/Hash.h index 30657be19..905644078 100644 --- a/include/assimp/Hash.h +++ b/include/assimp/Hash.h @@ -39,10 +39,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ - +#pragma once #ifndef AI_HASH_H_INCLUDED #define AI_HASH_H_INCLUDED +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include diff --git a/include/assimp/IOStream.hpp b/include/assimp/IOStream.hpp index 0623d0f70..39932cd94 100644 --- a/include/assimp/IOStream.hpp +++ b/include/assimp/IOStream.hpp @@ -48,14 +48,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_IOSTREAM_H_INC #define AI_IOSTREAM_H_INC -#include "types.h" +#ifdef __GNUC__ +# pragma GCC system_header +#endif + +#include #ifndef __cplusplus # error This header requires C++ to be used. aiFileIO.h is the \ corresponding C interface. #endif -namespace Assimp { +namespace Assimp { // ---------------------------------------------------------------------------------- /** @brief CPP-API: Class to handle file I/O for C++ @@ -125,13 +129,13 @@ public: }; //! class IOStream // ---------------------------------------------------------------------------------- -inline +AI_FORCE_INLINE IOStream::IOStream() AI_NO_EXCEPT { // empty } // ---------------------------------------------------------------------------------- -inline +AI_FORCE_INLINE IOStream::~IOStream() { // empty } diff --git a/include/assimp/IOStreamBuffer.h b/include/assimp/IOStreamBuffer.h index 58abd97a0..97c84b23e 100644 --- a/include/assimp/IOStreamBuffer.h +++ b/include/assimp/IOStreamBuffer.h @@ -1,5 +1,3 @@ -#pragma once - /* Open Asset Import Library (assimp) ---------------------------------------------------------------------- @@ -42,10 +40,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ +#pragma once +#ifndef AI_IOSTREAMBUFFER_H_INC +#define AI_IOSTREAMBUFFER_H_INC + +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include - -#include "ParsingUtils.h" +#include #include @@ -124,7 +129,7 @@ private: }; template -inline +AI_FORCE_INLINE IOStreamBuffer::IOStreamBuffer( size_t cache ) : m_stream( nullptr ) , m_filesize( 0 ) @@ -138,13 +143,13 @@ IOStreamBuffer::IOStreamBuffer( size_t cache ) } template -inline +AI_FORCE_INLINE IOStreamBuffer::~IOStreamBuffer() { // empty } template -inline +AI_FORCE_INLINE bool IOStreamBuffer::open( IOStream *stream ) { // file still opened! if ( nullptr != m_stream ) { @@ -174,7 +179,7 @@ bool IOStreamBuffer::open( IOStream *stream ) { } template -inline +AI_FORCE_INLINE bool IOStreamBuffer::close() { if ( nullptr == m_stream ) { return false; @@ -192,19 +197,19 @@ bool IOStreamBuffer::close() { } template -inline +AI_FORCE_INLINE size_t IOStreamBuffer::size() const { return m_filesize; } template -inline +AI_FORCE_INLINE size_t IOStreamBuffer::cacheSize() const { return m_cacheSize; } template -inline +AI_FORCE_INLINE bool IOStreamBuffer::readNextBlock() { m_stream->Seek( m_filePos, aiOrigin_SET ); size_t readLen = m_stream->Read( &m_cache[ 0 ], sizeof( T ), m_cacheSize ); @@ -222,25 +227,25 @@ bool IOStreamBuffer::readNextBlock() { } template -inline +AI_FORCE_INLINE size_t IOStreamBuffer::getNumBlocks() const { return m_numBlocks; } template -inline +AI_FORCE_INLINE size_t IOStreamBuffer::getCurrentBlockIndex() const { return m_blockIdx; } template -inline +AI_FORCE_INLINE size_t IOStreamBuffer::getFilePos() const { return m_filePos; } template -inline +AI_FORCE_INLINE bool IOStreamBuffer::getNextDataLine( std::vector &buffer, T continuationToken ) { buffer.resize( m_cacheSize ); if ( m_cachePos >= m_cacheSize || 0 == m_filePos ) { @@ -289,13 +294,13 @@ bool IOStreamBuffer::getNextDataLine( std::vector &buffer, T continuationT return true; } -static inline +static AI_FORCE_INLINE bool isEndOfCache( size_t pos, size_t cacheSize ) { return ( pos == cacheSize ); } template -inline +AI_FORCE_INLINE bool IOStreamBuffer::getNextLine(std::vector &buffer) { buffer.resize(m_cacheSize); if ( isEndOfCache( m_cachePos, m_cacheSize ) || 0 == m_filePos) { @@ -335,7 +340,7 @@ bool IOStreamBuffer::getNextLine(std::vector &buffer) { } template -inline +AI_FORCE_INLINE bool IOStreamBuffer::getNextBlock( std::vector &buffer) { // Return the last block-value if getNextLine was used before if ( 0 != m_cachePos ) { @@ -353,3 +358,5 @@ bool IOStreamBuffer::getNextBlock( std::vector &buffer) { } } // !ns Assimp + +#endif // AI_IOSTREAMBUFFER_H_INC diff --git a/include/assimp/IOSystem.hpp b/include/assimp/IOSystem.hpp index 78139c283..f1fb3b0c2 100644 --- a/include/assimp/IOSystem.hpp +++ b/include/assimp/IOSystem.hpp @@ -50,6 +50,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_IOSYSTEM_H_INC #define AI_IOSYSTEM_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #ifndef __cplusplus # error This header requires C++ to be used. aiFileIO.h is the \ corresponding C interface. diff --git a/include/assimp/Importer.hpp b/include/assimp/Importer.hpp index 4941df412..bf449a9a2 100644 --- a/include/assimp/Importer.hpp +++ b/include/assimp/Importer.hpp @@ -48,6 +48,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_ASSIMP_HPP_INC #define AI_ASSIMP_HPP_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #ifndef __cplusplus # error This header requires C++ to be used. Use assimp.h for plain C. #endif // __cplusplus diff --git a/include/assimp/LineSplitter.h b/include/assimp/LineSplitter.h index 4afe45b92..6c1097bb6 100644 --- a/include/assimp/LineSplitter.h +++ b/include/assimp/LineSplitter.h @@ -48,9 +48,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef INCLUDED_LINE_SPLITTER_H #define INCLUDED_LINE_SPLITTER_H +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include -#include "StreamReader.h" -#include "ParsingUtils.h" +#include +#include namespace Assimp { @@ -140,7 +144,7 @@ private: bool mSwallow, mSkip_empty_lines, mTrim; }; -inline +AI_FORCE_INLINE LineSplitter::LineSplitter(StreamReaderLE& stream, bool skip_empty_lines, bool trim ) : mIdx(0) , mCur() @@ -153,12 +157,12 @@ LineSplitter::LineSplitter(StreamReaderLE& stream, bool skip_empty_lines, bool t mIdx = 0; } -inline +AI_FORCE_INLINE LineSplitter::~LineSplitter() { // empty } -inline +AI_FORCE_INLINE LineSplitter& LineSplitter::operator++() { if (mSwallow) { mSwallow = false; @@ -199,12 +203,12 @@ LineSplitter& LineSplitter::operator++() { return *this; } -inline +AI_FORCE_INLINE LineSplitter &LineSplitter::operator++(int) { return ++(*this); } -inline +AI_FORCE_INLINE const char *LineSplitter::operator[] (size_t idx) const { const char* s = operator->()->c_str(); @@ -222,7 +226,7 @@ const char *LineSplitter::operator[] (size_t idx) const { } template -inline +AI_FORCE_INLINE void LineSplitter::get_tokens(const char* (&tokens)[N]) const { const char* s = operator->()->c_str(); @@ -238,44 +242,44 @@ void LineSplitter::get_tokens(const char* (&tokens)[N]) const { } } -inline +AI_FORCE_INLINE const std::string* LineSplitter::operator -> () const { return &mCur; } -inline +AI_FORCE_INLINE std::string LineSplitter::operator* () const { return mCur; } -inline +AI_FORCE_INLINE LineSplitter::operator bool() const { return mStream.GetRemainingSize() > 0; } -inline +AI_FORCE_INLINE LineSplitter::operator line_idx() const { return mIdx; } -inline +AI_FORCE_INLINE LineSplitter::line_idx LineSplitter::get_index() const { return mIdx; } -inline +AI_FORCE_INLINE StreamReaderLE &LineSplitter::get_stream() { return mStream; } -inline +AI_FORCE_INLINE bool LineSplitter::match_start(const char* check) { const size_t len = ::strlen(check); return len <= mCur.length() && std::equal(check, check + len, mCur.begin()); } -inline +AI_FORCE_INLINE void LineSplitter::swallow_next_increment() { mSwallow = true; } diff --git a/include/assimp/LogAux.h b/include/assimp/LogAux.h index 558485272..bcead78dd 100644 --- a/include/assimp/LogAux.h +++ b/include/assimp/LogAux.h @@ -43,9 +43,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file LogAux.h * @brief Common logging usage patterns for importer implementations */ +#pragma once #ifndef INCLUDED_AI_LOGAUX_H #define INCLUDED_AI_LOGAUX_H +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include #include diff --git a/include/assimp/MathFunctions.h b/include/assimp/MathFunctions.h index cb3b69607..b6c5872a7 100644 --- a/include/assimp/MathFunctions.h +++ b/include/assimp/MathFunctions.h @@ -39,22 +39,28 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ +#pragma once + +#ifdef __GNUC__ +# pragma GCC system_header +#endif + /** @file MathFunctions.h - * @brief Implementation of the math functions (gcd and lcm) +* @brief Implementation of math utility functions. * - * Copied from BoostWorkaround/math - */ +*/ + +#include namespace Assimp { namespace Math { // TODO: use binary GCD for unsigned integers .... template < typename IntegerType > -IntegerType gcd( IntegerType a, IntegerType b ) -{ +inline +IntegerType gcd( IntegerType a, IntegerType b ) { const IntegerType zero = (IntegerType)0; - while ( true ) - { + while ( true ) { if ( a == zero ) return b; b %= a; @@ -66,12 +72,19 @@ IntegerType gcd( IntegerType a, IntegerType b ) } template < typename IntegerType > -IntegerType lcm( IntegerType a, IntegerType b ) -{ +inline +IntegerType lcm( IntegerType a, IntegerType b ) { const IntegerType t = gcd (a,b); - if (!t)return t; + if (!t) + return t; return a / t * b; } +template +inline +T getEpsilon() { + return std::numeric_limits::epsilon(); +} + } } diff --git a/include/assimp/MemoryIOWrapper.h b/include/assimp/MemoryIOWrapper.h index c52278718..5598d4fc5 100644 --- a/include/assimp/MemoryIOWrapper.h +++ b/include/assimp/MemoryIOWrapper.h @@ -42,12 +42,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file MemoryIOWrapper.h * Handy IOStream/IOSystem implemetation to read directly from a memory buffer */ +#pragma once #ifndef AI_MEMORYIOSTREAM_H_INC #define AI_MEMORYIOSTREAM_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include #include + #include namespace Assimp { diff --git a/include/assimp/ParsingUtils.h b/include/assimp/ParsingUtils.h index 6b9574fc6..302560124 100644 --- a/include/assimp/ParsingUtils.h +++ b/include/assimp/ParsingUtils.h @@ -44,11 +44,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file ParsingUtils.h * @brief Defines helper functions for text parsing */ +#pragma once #ifndef AI_PARSING_UTILS_H_INC #define AI_PARSING_UTILS_H_INC -#include "StringComparison.h" -#include "StringUtils.h" +#ifdef __GNUC__ +# pragma GCC system_header +#endif + +#include +#include #include namespace Assimp { diff --git a/include/assimp/Profiler.h b/include/assimp/Profiler.h index 6ff9d41c0..624029be9 100644 --- a/include/assimp/Profiler.h +++ b/include/assimp/Profiler.h @@ -43,12 +43,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file Profiler.h * @brief Utility to measure the respective runtime of each import step */ -#ifndef INCLUDED_PROFILER_H -#define INCLUDED_PROFILER_H +#pragma once +#ifndef AI_INCLUDED_PROFILER_H +#define AI_INCLUDED_PROFILER_H + +#ifdef __GNUC__ +# pragma GCC system_header +#endif #include #include -#include "TinyFormatter.h" +#include #include @@ -67,7 +72,6 @@ public: // empty } -public: /** Start a named timer */ void BeginRegion(const std::string& region) { @@ -95,5 +99,5 @@ private: } } -#endif +#endif // AI_INCLUDED_PROFILER_H diff --git a/include/assimp/ProgressHandler.hpp b/include/assimp/ProgressHandler.hpp index 4e47f1d0a..8991a6461 100644 --- a/include/assimp/ProgressHandler.hpp +++ b/include/assimp/ProgressHandler.hpp @@ -47,9 +47,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_PROGRESSHANDLER_H_INC #define AI_PROGRESSHANDLER_H_INC -#include "types.h" +#ifdef __GNUC__ +# pragma GCC system_header +#endif -namespace Assimp { +#include + +namespace Assimp { // ------------------------------------------------------------------------------------ /** @brief CPP-API: Abstract interface for custom progress report receivers. diff --git a/include/assimp/RemoveComments.h b/include/assimp/RemoveComments.h index 404b49671..f12942053 100644 --- a/include/assimp/RemoveComments.h +++ b/include/assimp/RemoveComments.h @@ -4,7 +4,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -43,9 +42,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file Declares a helper class, "CommentRemover", which can be * used to remove comments (single and multi line) from a text file. */ +#pragma once #ifndef AI_REMOVE_COMMENTS_H_INC #define AI_REMOVE_COMMENTS_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif #include @@ -58,8 +61,7 @@ namespace Assimp { * to those in C or C++ so this code has been moved to a separate * module. */ -class ASSIMP_API CommentRemover -{ +class ASSIMP_API CommentRemover { // class cannot be instanced CommentRemover() {} diff --git a/include/assimp/SGSpatialSort.h b/include/assimp/SGSpatialSort.h index 5b4f3f41f..fdb5ce817 100644 --- a/include/assimp/SGSpatialSort.h +++ b/include/assimp/SGSpatialSort.h @@ -42,9 +42,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** Small helper classes to optimize finding vertices close to a given location */ +#pragma once #ifndef AI_D3DSSPATIALSORT_H_INC #define AI_D3DSSPATIALSORT_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include #include diff --git a/include/assimp/SceneCombiner.h b/include/assimp/SceneCombiner.h index 679a2acea..0683c1e05 100644 --- a/include/assimp/SceneCombiner.h +++ b/include/assimp/SceneCombiner.h @@ -43,17 +43,22 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file Declares a helper class, "SceneCombiner" providing various * utilities to merge scenes. */ +#pragma once #ifndef AI_SCENE_COMBINER_H_INC #define AI_SCENE_COMBINER_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include #include + #include #include #include #include - #include struct aiScene; @@ -65,8 +70,10 @@ struct aiLight; struct aiMetadata; struct aiBone; struct aiMesh; +struct aiAnimMesh; struct aiAnimation; struct aiNodeAnim; +struct aiMeshMorphAnim; namespace Assimp { @@ -363,6 +370,7 @@ public: static void Copy (aiMesh** dest, const aiMesh* src); // similar to Copy(): + static void Copy (aiAnimMesh** dest, const aiAnimMesh* src); static void Copy (aiMaterial** dest, const aiMaterial* src); static void Copy (aiTexture** dest, const aiTexture* src); static void Copy (aiAnimation** dest, const aiAnimation* src); @@ -370,6 +378,7 @@ public: static void Copy (aiBone** dest, const aiBone* src); static void Copy (aiLight** dest, const aiLight* src); static void Copy (aiNodeAnim** dest, const aiNodeAnim* src); + static void Copy (aiMeshMorphAnim** dest, const aiMeshMorphAnim* src); static void Copy (aiMetadata** dest, const aiMetadata* src); // recursive, of course diff --git a/include/assimp/SkeletonMeshBuilder.h b/include/assimp/SkeletonMeshBuilder.h index f9b8d9f55..ad979a33f 100644 --- a/include/assimp/SkeletonMeshBuilder.h +++ b/include/assimp/SkeletonMeshBuilder.h @@ -47,9 +47,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * for animation skeletons. */ +#pragma once #ifndef AI_SKELETONMESHBUILDER_H_INC #define AI_SKELETONMESHBUILDER_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include diff --git a/include/assimp/SmoothingGroups.h b/include/assimp/SmoothingGroups.h index 92d65cea0..c1a93947f 100644 --- a/include/assimp/SmoothingGroups.h +++ b/include/assimp/SmoothingGroups.h @@ -43,10 +43,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file Defines the helper data structures for importing 3DS files. http://www.jalix.org/ressources/graphics/3DS/_unofficials/3ds-unofficial.txt */ +#pragma once #ifndef AI_SMOOTHINGGROUPS_H_INC #define AI_SMOOTHINGGROUPS_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include + #include #include diff --git a/include/assimp/SmoothingGroups.inl b/include/assimp/SmoothingGroups.inl index 84ea4a1b0..37ea083db 100644 --- a/include/assimp/SmoothingGroups.inl +++ b/include/assimp/SmoothingGroups.inl @@ -41,13 +41,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file Generation of normal vectors basing on smoothing groups */ +#pragma once #ifndef AI_SMOOTHINGGROUPS_INL_INCLUDED #define AI_SMOOTHINGGROUPS_INL_INCLUDED -// internal headers +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include -// CRT header #include using namespace Assimp; diff --git a/include/assimp/SpatialSort.h b/include/assimp/SpatialSort.h index 61b345bcb..9f9354315 100644 --- a/include/assimp/SpatialSort.h +++ b/include/assimp/SpatialSort.h @@ -41,9 +41,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** Small helper classes to optimise finding vertizes close to a given location */ +#pragma once #ifndef AI_SPATIALSORT_H_INC #define AI_SPATIALSORT_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include diff --git a/include/assimp/StandardShapes.h b/include/assimp/StandardShapes.h index 3791569b8..c594cb63f 100644 --- a/include/assimp/StandardShapes.h +++ b/include/assimp/StandardShapes.h @@ -41,11 +41,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @file Declares a helper class, "StandardShapes" which generates - * vertices for standard shapes, such as cylnders, cones, spheres .. + * vertices for standard shapes, such as cylinders, cones, spheres .. */ +#pragma once #ifndef AI_STANDARD_SHAPES_H_INC #define AI_STANDARD_SHAPES_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include diff --git a/include/assimp/StreamReader.h b/include/assimp/StreamReader.h index 9116c1426..cb24f1595 100644 --- a/include/assimp/StreamReader.h +++ b/include/assimp/StreamReader.h @@ -44,15 +44,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file Defines the StreamReader class which reads data from * a binary stream with a well-defined endianness. */ - +#pragma once #ifndef AI_STREAMREADER_H_INCLUDED #define AI_STREAMREADER_H_INCLUDED +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include +#include +#include -#include "ByteSwapper.h" -#include "Exceptional.h" #include namespace Assimp { diff --git a/include/assimp/StreamWriter.h b/include/assimp/StreamWriter.h index c7cf6c0d7..489e8adfe 100644 --- a/include/assimp/StreamWriter.h +++ b/include/assimp/StreamWriter.h @@ -43,11 +43,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file Defines the StreamWriter class which writes data to * a binary stream with a well-defined endianness. */ - +#pragma once #ifndef AI_STREAMWRITER_H_INCLUDED #define AI_STREAMWRITER_H_INCLUDED -#include "ByteSwapper.h" +#ifdef __GNUC__ +# pragma GCC system_header +#endif + +#include #include #include diff --git a/include/assimp/StringComparison.h b/include/assimp/StringComparison.h index 8acef277b..d3ca3e971 100644 --- a/include/assimp/StringComparison.h +++ b/include/assimp/StringComparison.h @@ -49,12 +49,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. These functions are not consistently available on all platforms, or the provided implementations behave too differently. */ +#pragma once #ifndef INCLUDED_AI_STRING_WORKERS_H #define INCLUDED_AI_STRING_WORKERS_H +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include -#include "StringComparison.h" +#include #include #include diff --git a/include/assimp/StringUtils.h b/include/assimp/StringUtils.h index d68b7fa47..af481f819 100644 --- a/include/assimp/StringUtils.h +++ b/include/assimp/StringUtils.h @@ -39,9 +39,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ +#pragma once #ifndef INCLUDED_AI_STRINGUTILS_H #define INCLUDED_AI_STRINGUTILS_H +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include diff --git a/include/assimp/Subdivision.h b/include/assimp/Subdivision.h index 43feb73b3..e9450267e 100644 --- a/include/assimp/Subdivision.h +++ b/include/assimp/Subdivision.h @@ -45,7 +45,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_SUBDISIVION_H_INC #define AI_SUBDISIVION_H_INC -#include +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include struct aiMesh; diff --git a/include/assimp/TinyFormatter.h b/include/assimp/TinyFormatter.h index 1226b482e..6227e42c5 100644 --- a/include/assimp/TinyFormatter.h +++ b/include/assimp/TinyFormatter.h @@ -45,9 +45,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * to get rid of the boost::format dependency. Much slinker, * basically just extends stringstream. */ +#pragma once #ifndef INCLUDED_TINY_FORMATTER_H #define INCLUDED_TINY_FORMATTER_H +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include namespace Assimp { @@ -65,24 +70,15 @@ namespace Formatter { * @endcode */ template < typename T, typename CharTraits = std::char_traits, - typename Allocator = std::allocator -> -class basic_formatter -{ - + typename Allocator = std::allocator > +class basic_formatter { public: + typedef class std::basic_string string; + typedef class std::basic_ostringstream stringstream; - typedef class std::basic_string< - T,CharTraits,Allocator - > string; - - typedef class std::basic_ostringstream< - T,CharTraits,Allocator - > stringstream; - -public: - - basic_formatter() {} + basic_formatter() { + // empty + } /* Allow basic_formatter's to be used almost interchangeably * with std::(w)string or const (w)char* arguments because the @@ -104,14 +100,10 @@ public: } #endif - -public: - operator string () const { return underlying.str(); } - /* note - this is declared const because binding temporaries does only * work for const references, so many function prototypes will * include const basic_formatter& s but might still want to diff --git a/include/assimp/Vertex.h b/include/assimp/Vertex.h index 2a7f0256a..5e63db5fe 100644 --- a/include/assimp/Vertex.h +++ b/include/assimp/Vertex.h @@ -47,12 +47,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. that are not currently well-defined (and would cause compile errors due to missing operators in the math library), are commented. */ +#pragma once #ifndef AI_VERTEX_H_INC #define AI_VERTEX_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include #include + #include namespace Assimp { @@ -91,23 +97,14 @@ namespace Assimp { * to *all* vertex components equally. This is useful for stuff like interpolation * or subdivision, but won't work if special handling is required for some vertex components. */ // ------------------------------------------------------------------------------------------------ -class Vertex -{ +class Vertex { friend Vertex operator + (const Vertex&,const Vertex&); friend Vertex operator - (const Vertex&,const Vertex&); - -// friend Vertex operator + (const Vertex&,ai_real); -// friend Vertex operator - (const Vertex&,ai_real); friend Vertex operator * (const Vertex&,ai_real); friend Vertex operator / (const Vertex&,ai_real); - -// friend Vertex operator + (ai_real, const Vertex&); -// friend Vertex operator - (ai_real, const Vertex&); friend Vertex operator * (ai_real, const Vertex&); -// friend Vertex operator / (ai_real, const Vertex&); public: - Vertex() {} // ---------------------------------------------------------------------------- @@ -158,8 +155,6 @@ public: } } -public: - Vertex& operator += (const Vertex& v) { *this = *this+v; return *this; @@ -170,18 +165,6 @@ public: return *this; } - -/* - Vertex& operator += (ai_real v) { - *this = *this+v; - return *this; - } - - Vertex& operator -= (ai_real v) { - *this = *this-v; - return *this; - } -*/ Vertex& operator *= (ai_real v) { *this = *this*v; return *this; @@ -192,12 +175,9 @@ public: return *this; } -public: - // ---------------------------------------------------------------------------- /** Convert back to non-interleaved storage */ void SortBack(aiMesh* out, unsigned int idx) const { - ai_assert(idxmNumVertices); out->mVertices[idx] = position; @@ -291,8 +271,6 @@ public: aiColor4D colors[AI_MAX_NUMBER_OF_COLOR_SETS]; }; - - // ------------------------------------------------------------------------------------------------ AI_FORCE_INLINE Vertex operator + (const Vertex& v0,const Vertex& v1) { return Vertex::BinaryOp(v0,v1); @@ -302,19 +280,6 @@ AI_FORCE_INLINE Vertex operator - (const Vertex& v0,const Vertex& v1) { return Vertex::BinaryOp(v0,v1); } - -// ------------------------------------------------------------------------------------------------ -/* -AI_FORCE_INLINE Vertex operator + (const Vertex& v0,ai_real f) { - return Vertex::BinaryOp(v0,f); -} - -AI_FORCE_INLINE Vertex operator - (const Vertex& v0,ai_real f) { - return Vertex::BinaryOp(v0,f); -} - -*/ - AI_FORCE_INLINE Vertex operator * (const Vertex& v0,ai_real f) { return Vertex::BinaryOp(v0,f); } @@ -323,26 +288,10 @@ AI_FORCE_INLINE Vertex operator / (const Vertex& v0,ai_real f) { return Vertex::BinaryOp(v0,1.f/f); } -// ------------------------------------------------------------------------------------------------ -/* -AI_FORCE_INLINE Vertex operator + (ai_real f,const Vertex& v0) { - return Vertex::BinaryOp(f,v0); -} - -AI_FORCE_INLINE Vertex operator - (ai_real f,const Vertex& v0) { - return Vertex::BinaryOp(f,v0); -} -*/ - AI_FORCE_INLINE Vertex operator * (ai_real f,const Vertex& v0) { return Vertex::BinaryOp(f,v0); } -/* -AI_FORCE_INLINE Vertex operator / (ai_real f,const Vertex& v0) { - return Vertex::BinaryOp(f,v0); } -*/ -} -#endif +#endif // AI_VERTEX_H_INC diff --git a/include/assimp/XMLTools.h b/include/assimp/XMLTools.h index b0d327687..95f12cdeb 100644 --- a/include/assimp/XMLTools.h +++ b/include/assimp/XMLTools.h @@ -40,9 +40,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ +#pragma once #ifndef INCLUDED_ASSIMP_XML_TOOLS_H #define INCLUDED_ASSIMP_XML_TOOLS_H +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include namespace Assimp { diff --git a/include/assimp/Macros.h b/include/assimp/ZipArchiveIOSystem.h similarity index 51% rename from include/assimp/Macros.h rename to include/assimp/ZipArchiveIOSystem.h index 651530337..516ea84de 100644 --- a/include/assimp/Macros.h +++ b/include/assimp/ZipArchiveIOSystem.h @@ -5,6 +5,8 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team + + All rights reserved. Redistribution and use of this software in source and binary forms, @@ -39,11 +41,54 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ -/* Helper macro to set a pointer to NULL in debug builds - */ -#if (defined ASSIMP_BUILD_DEBUG) -# define AI_DEBUG_INVALIDATE_PTR(x) x = NULL; -#else -# define AI_DEBUG_INVALIDATE_PTR(x) +/** @file ZipArchiveIOSystem.h + * @brief Implementation of IOSystem to read a ZIP file from another IOSystem +*/ + +#pragma once +#ifndef AI_ZIPARCHIVEIOSYSTEM_H_INC +#define AI_ZIPARCHIVEIOSYSTEM_H_INC + +#ifdef __GNUC__ +# pragma GCC system_header #endif +#include +#include + +namespace Assimp { + +class ZipArchiveIOSystem : public IOSystem { +public: + //! Open a Zip using the proffered IOSystem + ZipArchiveIOSystem(IOSystem* pIOHandler, const char *pFilename, const char* pMode = "r"); + ZipArchiveIOSystem(IOSystem* pIOHandler, const std::string& rFilename, const char* pMode = "r"); + virtual ~ZipArchiveIOSystem(); + bool Exists(const char* pFilename) const override; + char getOsSeparator() const override; + IOStream* Open(const char* pFilename, const char* pMode = "rb") override; + void Close(IOStream* pFile) override; + + // Specific to ZIP + //! The file was opened and is a ZIP + bool isOpen() const; + + //! Get the list of all files with their simplified paths + //! Intended for use within Assimp library boundaries + void getFileList(std::vector& rFileList) const; + + //! Get the list of all files with extension (must be lowercase) + //! Intended for use within Assimp library boundaries + void getFileListExtension(std::vector& rFileList, const std::string& extension) const; + + static bool isZipArchive(IOSystem* pIOHandler, const char *pFilename); + static bool isZipArchive(IOSystem* pIOHandler, const std::string& rFilename); + +private: + class Implement; + Implement *pImpl = nullptr; +}; + +} // Namespace Assimp + +#endif // AI_ZIPARCHIVEIOSYSTEM_H_INC diff --git a/include/assimp/aabb.h b/include/assimp/aabb.h index a20f31742..83bb62256 100644 --- a/include/assimp/aabb.h +++ b/include/assimp/aabb.h @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -45,6 +43,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_AABB_H_INC #define AI_AABB_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include struct aiAABB { @@ -69,8 +71,9 @@ struct aiAABB { // empty } -#endif +#endif // __cplusplus + }; -#endif +#endif // AI_AABB_H_INC diff --git a/include/assimp/ai_assert.h b/include/assimp/ai_assert.h index e5de5d3f3..2b32b01d3 100644 --- a/include/assimp/ai_assert.h +++ b/include/assimp/ai_assert.h @@ -44,6 +44,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_ASSERT_H_INC #define AI_ASSERT_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #ifdef ASSIMP_BUILD_DEBUG # include # define ai_assert(expression) assert( expression ) diff --git a/include/assimp/anim.h b/include/assimp/anim.h index 02e92739e..e208b11ad 100644 --- a/include/assimp/anim.h +++ b/include/assimp/anim.h @@ -50,6 +50,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_ANIM_H_INC #define AI_ANIM_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include diff --git a/include/assimp/camera.h b/include/assimp/camera.h index e573eea5d..adb749ff5 100644 --- a/include/assimp/camera.h +++ b/include/assimp/camera.h @@ -47,6 +47,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_CAMERA_H_INC #define AI_CAMERA_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include "types.h" #ifdef __cplusplus @@ -113,7 +117,6 @@ struct aiCamera */ C_STRUCT aiVector3D mPosition; - /** 'Up' - vector of the camera coordinate system relative to * the coordinate space defined by the corresponding node. * @@ -134,7 +137,6 @@ struct aiCamera */ C_STRUCT aiVector3D mLookAt; - /** Half horizontal field of view angle, in radians. * * The field of view angle is the angle between the center diff --git a/include/assimp/cexport.h b/include/assimp/cexport.h index 1d62dc26b..cbc0253d5 100644 --- a/include/assimp/cexport.h +++ b/include/assimp/cexport.h @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2011, assimp team +Copyright (c) 2006-2019, assimp team All rights reserved. @@ -46,6 +46,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_EXPORT_H_INC #define AI_EXPORT_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #ifndef ASSIMP_BUILD_NO_EXPORT // Public ASSIMP data structures diff --git a/include/assimp/cfileio.h b/include/assimp/cfileio.h index 8f7ca4546..be90999d8 100644 --- a/include/assimp/cfileio.h +++ b/include/assimp/cfileio.h @@ -48,10 +48,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_FILEIO_H_INC #define AI_FILEIO_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include + #ifdef __cplusplus extern "C" { #endif + struct aiFileIO; struct aiFile; diff --git a/include/assimp/cimport.h b/include/assimp/cimport.h index dbd10f137..66b1c9a17 100644 --- a/include/assimp/cimport.h +++ b/include/assimp/cimport.h @@ -48,8 +48,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_ASSIMP_H_INC #define AI_ASSIMP_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include -#include "importerdesc.h" +#include #ifdef __cplusplus extern "C" { diff --git a/include/assimp/color4.h b/include/assimp/color4.h index 3c97c8eda..fa86128f4 100644 --- a/include/assimp/color4.h +++ b/include/assimp/color4.h @@ -47,7 +47,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_COLOR4D_H_INC #define AI_COLOR4D_H_INC -#include "defs.h" +#ifdef __GNUC__ +# pragma GCC system_header +#endif + +#include #ifdef __cplusplus @@ -56,8 +60,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * alpha component. Color values range from 0 to 1. */ // ---------------------------------------------------------------------------------- template -class aiColor4t -{ +class aiColor4t { public: aiColor4t() AI_NO_EXCEPT : r(), g(), b(), a() {} aiColor4t (TReal _r, TReal _g, TReal _b, TReal _a) @@ -65,14 +68,12 @@ public: explicit aiColor4t (TReal _r) : r(_r), g(_r), b(_r), a(_r) {} aiColor4t (const aiColor4t& o) = default; -public: // combined operators const aiColor4t& operator += (const aiColor4t& o); const aiColor4t& operator -= (const aiColor4t& o); const aiColor4t& operator *= (TReal f); const aiColor4t& operator /= (TReal f); -public: // comparison bool operator == (const aiColor4t& other) const; bool operator != (const aiColor4t& other) const; @@ -85,8 +86,6 @@ public: /** check whether a color is (close to) black */ inline bool IsBlack() const; -public: - // Red, green, blue and alpha color values TReal r, g, b, a; }; // !struct aiColor4D diff --git a/include/assimp/color4.inl b/include/assimp/color4.inl index afa53dcb5..d4a2a9810 100644 --- a/include/assimp/color4.inl +++ b/include/assimp/color4.inl @@ -48,36 +48,61 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_COLOR4D_INL_INC #define AI_COLOR4D_INL_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #ifdef __cplusplus -#include "color4.h" +#include // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE const aiColor4t& aiColor4t::operator += (const aiColor4t& o) { - r += o.r; g += o.g; b += o.b; a += o.a; +AI_FORCE_INLINE +const aiColor4t& aiColor4t::operator += (const aiColor4t& o) { + r += o.r; + g += o.g; + b += o.b; + a += o.a; + return *this; } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE const aiColor4t& aiColor4t::operator -= (const aiColor4t& o) { - r -= o.r; g -= o.g; b -= o.b; a -= o.a; +AI_FORCE_INLINE +const aiColor4t& aiColor4t::operator -= (const aiColor4t& o) { + r -= o.r; + g -= o.g; + b -= o.b; + a -= o.a; + return *this; } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE const aiColor4t& aiColor4t::operator *= (TReal f) { - r *= f; g *= f; b *= f; a *= f; +AI_FORCE_INLINE +const aiColor4t& aiColor4t::operator *= (TReal f) { + r *= f; + g *= f; + b *= f; + a *= f; + return *this; } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE const aiColor4t& aiColor4t::operator /= (TReal f) { - r /= f; g /= f; b /= f; a /= f; +AI_FORCE_INLINE +const aiColor4t& aiColor4t::operator /= (TReal f) { + r /= f; + g /= f; + b /= f; + a /= f; + return *this; } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE TReal aiColor4t::operator[](unsigned int i) const { +AI_FORCE_INLINE +TReal aiColor4t::operator[](unsigned int i) const { switch ( i ) { case 0: return r; @@ -94,7 +119,8 @@ AI_FORCE_INLINE TReal aiColor4t::operator[](unsigned int i) const { } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE TReal& aiColor4t::operator[](unsigned int i) { +AI_FORCE_INLINE +TReal& aiColor4t::operator[](unsigned int i) { switch ( i ) { case 0: return r; @@ -111,17 +137,20 @@ AI_FORCE_INLINE TReal& aiColor4t::operator[](unsigned int i) { } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE bool aiColor4t::operator== (const aiColor4t& other) const { +AI_FORCE_INLINE +bool aiColor4t::operator== (const aiColor4t& other) const { return r == other.r && g == other.g && b == other.b && a == other.a; } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE bool aiColor4t::operator!= (const aiColor4t& other) const { +AI_FORCE_INLINE +bool aiColor4t::operator!= (const aiColor4t& other) const { return r != other.r || g != other.g || b != other.b || a != other.a; } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE bool aiColor4t::operator< (const aiColor4t& other) const { +AI_FORCE_INLINE +bool aiColor4t::operator< (const aiColor4t& other) const { return r < other.r || ( r == other.r && ( g < other.g || ( @@ -136,14 +165,17 @@ AI_FORCE_INLINE bool aiColor4t::operator< (const aiColor4t& other) ) ); } + // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE aiColor4t operator + (const aiColor4t& v1, const aiColor4t& v2) { +AI_FORCE_INLINE +aiColor4t operator + (const aiColor4t& v1, const aiColor4t& v2) { return aiColor4t( v1.r + v2.r, v1.g + v2.g, v1.b + v2.b, v1.a + v2.a); } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE aiColor4t operator - (const aiColor4t& v1, const aiColor4t& v2) { +AI_FORCE_INLINE +aiColor4t operator - (const aiColor4t& v1, const aiColor4t& v2) { return aiColor4t( v1.r - v2.r, v1.g - v2.g, v1.b - v2.b, v1.a - v2.a); } // ------------------------------------------------------------------------------------------------ @@ -153,53 +185,63 @@ AI_FORCE_INLINE aiColor4t operator * (const aiColor4t& v1, const a } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE aiColor4t operator / (const aiColor4t& v1, const aiColor4t& v2) { +AI_FORCE_INLINE +aiColor4t operator / (const aiColor4t& v1, const aiColor4t& v2) { return aiColor4t( v1.r / v2.r, v1.g / v2.g, v1.b / v2.b, v1.a / v2.a); } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE aiColor4t operator * ( TReal f, const aiColor4t& v) { +AI_FORCE_INLINE +aiColor4t operator * ( TReal f, const aiColor4t& v) { return aiColor4t( f*v.r, f*v.g, f*v.b, f*v.a); } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE aiColor4t operator * ( const aiColor4t& v, TReal f) { +AI_FORCE_INLINE +aiColor4t operator * ( const aiColor4t& v, TReal f) { return aiColor4t( f*v.r, f*v.g, f*v.b, f*v.a); } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE aiColor4t operator / ( const aiColor4t& v, TReal f) { +AI_FORCE_INLINE +aiColor4t operator / ( const aiColor4t& v, TReal f) { return v * (1/f); } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE aiColor4t operator / ( TReal f,const aiColor4t& v) { +AI_FORCE_INLINE +aiColor4t operator / ( TReal f,const aiColor4t& v) { return aiColor4t(f,f,f,f)/v; } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE aiColor4t operator + ( const aiColor4t& v, TReal f) { +AI_FORCE_INLINE +aiColor4t operator + ( const aiColor4t& v, TReal f) { return aiColor4t( f+v.r, f+v.g, f+v.b, f+v.a); } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE aiColor4t operator - ( const aiColor4t& v, TReal f) { +AI_FORCE_INLINE +aiColor4t operator - ( const aiColor4t& v, TReal f) { return aiColor4t( v.r-f, v.g-f, v.b-f, v.a-f); } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE aiColor4t operator + ( TReal f, const aiColor4t& v) { +AI_FORCE_INLINE +aiColor4t operator + ( TReal f, const aiColor4t& v) { return aiColor4t( f+v.r, f+v.g, f+v.b, f+v.a); } // ------------------------------------------------------------------------------------------------ template -AI_FORCE_INLINE aiColor4t operator - ( TReal f, const aiColor4t& v) { +AI_FORCE_INLINE +aiColor4t operator - ( TReal f, const aiColor4t& v) { return aiColor4t( f-v.r, f-v.g, f-v.b, f-v.a); } // ------------------------------------------------------------------------------------------------ template -inline bool aiColor4t :: IsBlack() const { +AI_FORCE_INLINE +bool aiColor4t::IsBlack() const { // The alpha component doesn't care here. black is black. static const TReal epsilon = 10e-3f; return std::fabs( r ) < epsilon && std::fabs( g ) < epsilon && std::fabs( b ) < epsilon; diff --git a/include/assimp/config.h.in b/include/assimp/config.h.in index d08b929a1..3a6379bf4 100644 --- a/include/assimp/config.h.in +++ b/include/assimp/config.h.in @@ -142,7 +142,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @brief Specifies the maximum angle that may be between two vertex tangents * that their tangents and bi-tangents are smoothed. * - * This applies to the CalcTangentSpace-Step. TFvhe angle is specified + * This applies to the CalcTangentSpace-Step. The angle is specified * in degrees. The maximum value is 175. * Property type: float. Default value: 45 degrees */ @@ -999,6 +999,13 @@ enum aiComponent # define AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT 1.0f #endif // !! AI_DEBONE_THRESHOLD +#define AI_CONFIG_APP_SCALE_KEY "APP_SCALE_FACTOR" + +#if (!defined AI_CONFIG_APP_SCALE_KEY) +# define AI_CONFIG_APP_SCALE_KEY 1.0 +#endif // AI_CONFIG_APP_SCALE_KEY + + // ---------- All the Build/Compile-time defines ------------ /** @brief Specifies if double precision is supported inside assimp diff --git a/include/assimp/defs.h b/include/assimp/defs.h index 05a5e3fd4..d8fc98179 100644 --- a/include/assimp/defs.h +++ b/include/assimp/defs.h @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -50,6 +48,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_DEFINES_H_INC #define AI_DEFINES_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include ////////////////////////////////////////////////////////////////////////// @@ -126,16 +128,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * GENBOUNDINGBOXES */ ////////////////////////////////////////////////////////////////////////// -#ifdef _MSC_VER +#ifdef _WIN32 # undef ASSIMP_API - ////////////////////////////////////////////////////////////////////////// /* Define 'ASSIMP_BUILD_DLL_EXPORT' to build a DLL of the library */ ////////////////////////////////////////////////////////////////////////// # ifdef ASSIMP_BUILD_DLL_EXPORT # define ASSIMP_API __declspec(dllexport) # define ASSIMP_API_WINONLY __declspec(dllexport) -# pragma warning (disable : 4251) ////////////////////////////////////////////////////////////////////////// /* Define 'ASSIMP_DLL' before including Assimp to link to ASSIMP in @@ -148,7 +148,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # define ASSIMP_API # define ASSIMP_API_WINONLY # endif +#elif defined(SWIG) + /* Do nothing, the relevant defines are all in AssimpSwigPort.i */ + +#else +# define ASSIMP_API __attribute__ ((visibility("default"))) +# define ASSIMP_API_WINONLY +#endif + +#ifdef _MSC_VER +# ifdef ASSIMP_BUILD_DLL_EXPORT +# pragma warning (disable : 4251) +# endif /* Force the compiler to inline a function, if possible */ # define AI_FORCE_INLINE __forceinline @@ -156,17 +168,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /* Tells the compiler that a function never returns. Used in code analysis * to skip dead paths (e.g. after an assertion evaluated to false). */ # define AI_WONT_RETURN __declspec(noreturn) - #elif defined(SWIG) /* Do nothing, the relevant defines are all in AssimpSwigPort.i */ #else - # define AI_WONT_RETURN - -# define ASSIMP_API __attribute__ ((visibility("default"))) -# define ASSIMP_API_WINONLY # define AI_FORCE_INLINE inline #endif // (defined _MSC_VER) @@ -291,14 +298,19 @@ static const ai_real ai_epsilon = (ai_real) 0.00001; #endif -/* To avoid running out of memory - * This can be adjusted for specific use cases - * It's NOT a total limit, just a limit for individual allocations +/** + * To avoid running out of memory + * This can be adjusted for specific use cases + * It's NOT a total limit, just a limit for individual allocations */ #define AI_MAX_ALLOC(type) ((256U * 1024 * 1024) / sizeof(type)) #ifndef _MSC_VER -# define AI_NO_EXCEPT noexcept +# if __cplusplus >= 201103L // C++11 +# define AI_NO_EXCEPT noexcept +# else +# define AI_NO_EXCEPT +# endif #else # if (_MSC_VER >= 1915 ) # define AI_NO_EXCEPT noexcept @@ -307,4 +319,13 @@ static const ai_real ai_epsilon = (ai_real) 0.00001; # endif #endif // _MSC_VER +/** + * Helper macro to set a pointer to NULL in debug builds + */ +#if (defined ASSIMP_BUILD_DEBUG) +# define AI_DEBUG_INVALIDATE_PTR(x) x = NULL; +#else +# define AI_DEBUG_INVALIDATE_PTR(x) +#endif + #endif // !! AI_DEFINES_H_INC diff --git a/include/assimp/fast_atof.h b/include/assimp/fast_atof.h index 62ea969e9..6e9a1bba7 100644 --- a/include/assimp/fast_atof.h +++ b/include/assimp/fast_atof.h @@ -13,10 +13,14 @@ // to ensure long numbers are handled correctly // ------------------------------------------------------------------------------------ - +#pragma once #ifndef FAST_A_TO_F_H_INCLUDED #define FAST_A_TO_F_H_INCLUDED +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include #include diff --git a/include/assimp/importerdesc.h b/include/assimp/importerdesc.h index 36e387f01..0a6919c1a 100644 --- a/include/assimp/importerdesc.h +++ b/include/assimp/importerdesc.h @@ -48,11 +48,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_IMPORTER_DESC_H_INC #define AI_IMPORTER_DESC_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + /** Mixed set of flags for #aiImporterDesc, indicating some features * common to many importers*/ -enum aiImporterFlags -{ +enum aiImporterFlags { /** Indicates that there is a textual encoding of the * file format; and that it is supported.*/ aiImporterFlags_SupportTextFlavour = 0x1, @@ -87,8 +90,7 @@ enum aiImporterFlags * as importers/exporters are added to Assimp, so it might be useful * to have a common mechanism to query some rough importer * characteristics. */ -struct aiImporterDesc -{ +struct aiImporterDesc { /** Full name of the importer (i.e. Blender3D importer)*/ const char* mName; diff --git a/include/assimp/light.h b/include/assimp/light.h index 1667cfb8c..bdb2368c4 100644 --- a/include/assimp/light.h +++ b/include/assimp/light.h @@ -49,7 +49,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_LIGHT_H_INC #define AI_LIGHT_H_INC -#include "types.h" +#ifdef __GNUC__ +# pragma GCC system_header +#endif + +#include #ifdef __cplusplus extern "C" { diff --git a/include/assimp/material.h b/include/assimp/material.h index 4b5a1293d..19a7c6970 100644 --- a/include/assimp/material.h +++ b/include/assimp/material.h @@ -48,7 +48,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_MATERIAL_H_INC #define AI_MATERIAL_H_INC -#include "types.h" +#ifdef __GNUC__ +# pragma GCC system_header +#endif + +#include #ifdef __cplusplus extern "C" { @@ -196,34 +200,40 @@ enum aiTextureType * (#aiMaterialProperty::mSemantic) for all material properties * *not* related to textures. */ - aiTextureType_NONE = 0x0, + aiTextureType_NONE = 0, + + /** LEGACY API MATERIALS + * Legacy refers to materials which + * Were originally implemented in the specifications around 2000. + * These must never be removed, as most engines support them. + */ /** The texture is combined with the result of the diffuse * lighting equation. */ - aiTextureType_DIFFUSE = 0x1, + aiTextureType_DIFFUSE = 1, /** The texture is combined with the result of the specular * lighting equation. */ - aiTextureType_SPECULAR = 0x2, + aiTextureType_SPECULAR = 2, /** The texture is combined with the result of the ambient * lighting equation. */ - aiTextureType_AMBIENT = 0x3, + aiTextureType_AMBIENT = 3, /** The texture is added to the result of the lighting * calculation. It isn't influenced by incoming light. */ - aiTextureType_EMISSIVE = 0x4, + aiTextureType_EMISSIVE = 4, /** The texture is a height map. * * By convention, higher gray-scale values stand for * higher elevations from the base height. */ - aiTextureType_HEIGHT = 0x5, + aiTextureType_HEIGHT = 5, /** The texture is a (tangent space) normal-map. * @@ -231,7 +241,7 @@ enum aiTextureType * normal maps. Assimp does (intentionally) not * distinguish here. */ - aiTextureType_NORMALS = 0x6, + aiTextureType_NORMALS = 6, /** The texture defines the glossiness of the material. * @@ -240,21 +250,21 @@ enum aiTextureType * function defined to map the linear color values in the * texture to a suitable exponent. Have fun. */ - aiTextureType_SHININESS = 0x7, + aiTextureType_SHININESS = 7, /** The texture defines per-pixel opacity. * * Usually 'white' means opaque and 'black' means * 'transparency'. Or quite the opposite. Have fun. */ - aiTextureType_OPACITY = 0x8, + aiTextureType_OPACITY = 8, /** Displacement texture * * The exact purpose and format is application-dependent. * Higher color values stand for higher vertex displacements. */ - aiTextureType_DISPLACEMENT = 0x9, + aiTextureType_DISPLACEMENT = 9, /** Lightmap texture (aka Ambient Occlusion) * @@ -263,14 +273,28 @@ enum aiTextureType * scaling value for the final color value of a pixel. Its * intensity is not affected by incoming light. */ - aiTextureType_LIGHTMAP = 0xA, + aiTextureType_LIGHTMAP = 10, /** Reflection texture * * Contains the color of a perfect mirror reflection. * Rarely used, almost never for real-time applications. */ - aiTextureType_REFLECTION = 0xB, + aiTextureType_REFLECTION = 11, + + /** PBR Materials + * PBR definitions from maya and other modelling packages now use this standard. + * This was originally introduced around 2012. + * Support for this is in game engines like Godot, Unreal or Unity3D. + * Modelling packages which use this are very common now. + */ + + aiTextureType_BASE_COLOR = 12, + aiTextureType_NORMAL_CAMERA = 13, + aiTextureType_EMISSION_COLOR = 14, + aiTextureType_METALNESS = 15, + aiTextureType_DIFFUSE_ROUGHNESS = 16, + aiTextureType_AMBIENT_OCCLUSION = 17, /** Unknown texture * @@ -278,7 +302,7 @@ enum aiTextureType * above is considered to be 'unknown'. It is still imported, * but is excluded from any further post-processing. */ - aiTextureType_UNKNOWN = 0xC, + aiTextureType_UNKNOWN = 18, #ifndef SWIG diff --git a/include/assimp/material.inl b/include/assimp/material.inl index b05d6af6c..8ae6b88d3 100644 --- a/include/assimp/material.inl +++ b/include/assimp/material.inl @@ -49,14 +49,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_MATERIAL_INL_INC #define AI_MATERIAL_INL_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + // --------------------------------------------------------------------------- -inline aiPropertyTypeInfo ai_real_to_property_type_info(float) -{ +AI_FORCE_INLINE +aiPropertyTypeInfo ai_real_to_property_type_info(float) { return aiPTI_Float; } -inline aiPropertyTypeInfo ai_real_to_property_type_info(double) -{ +AI_FORCE_INLINE +aiPropertyTypeInfo ai_real_to_property_type_info(double) { return aiPTI_Double; } // --------------------------------------------------------------------------- @@ -64,30 +68,30 @@ inline aiPropertyTypeInfo ai_real_to_property_type_info(double) //! @cond never // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::GetTexture( aiTextureType type, - unsigned int index, - C_STRUCT aiString* path, - aiTextureMapping* mapping /*= NULL*/, - unsigned int* uvindex /*= NULL*/, - ai_real* blend /*= NULL*/, - aiTextureOp* op /*= NULL*/, - aiTextureMapMode* mapmode /*= NULL*/) const -{ +AI_FORCE_INLINE +aiReturn aiMaterial::GetTexture( aiTextureType type, + unsigned int index, + C_STRUCT aiString* path, + aiTextureMapping* mapping /*= NULL*/, + unsigned int* uvindex /*= NULL*/, + ai_real* blend /*= NULL*/, + aiTextureOp* op /*= NULL*/, + aiTextureMapMode* mapmode /*= NULL*/) const { return ::aiGetMaterialTexture(this,type,index,path,mapping,uvindex,blend,op,mapmode); } // --------------------------------------------------------------------------- -inline unsigned int aiMaterial::GetTextureCount(aiTextureType type) const -{ +AI_FORCE_INLINE +unsigned int aiMaterial::GetTextureCount(aiTextureType type) const { return ::aiGetMaterialTextureCount(this,type); } // --------------------------------------------------------------------------- template -inline aiReturn aiMaterial::Get(const char* pKey,unsigned int type, - unsigned int idx, Type* pOut, - unsigned int* pMax) const -{ +AI_FORCE_INLINE +aiReturn aiMaterial::Get(const char* pKey,unsigned int type, + unsigned int idx, Type* pOut, + unsigned int* pMax) const { unsigned int iNum = pMax ? *pMax : 1; const aiMaterialProperty* prop; @@ -114,9 +118,9 @@ inline aiReturn aiMaterial::Get(const char* pKey,unsigned int type, // --------------------------------------------------------------------------- template -inline aiReturn aiMaterial::Get(const char* pKey,unsigned int type, - unsigned int idx,Type& pOut) const -{ +AI_FORCE_INLINE +aiReturn aiMaterial::Get(const char* pKey,unsigned int type, + unsigned int idx,Type& pOut) const { const aiMaterialProperty* prop; const aiReturn ret = ::aiGetMaterialProperty(this,pKey,type,idx, (const aiMaterialProperty**)&prop); @@ -136,60 +140,56 @@ inline aiReturn aiMaterial::Get(const char* pKey,unsigned int type, } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::Get(const char* pKey,unsigned int type, - unsigned int idx,ai_real* pOut, - unsigned int* pMax) const -{ +AI_FORCE_INLINE +aiReturn aiMaterial::Get(const char* pKey,unsigned int type, + unsigned int idx,ai_real* pOut, + unsigned int* pMax) const { return ::aiGetMaterialFloatArray(this,pKey,type,idx,pOut,pMax); } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::Get(const char* pKey,unsigned int type, - unsigned int idx,int* pOut, - unsigned int* pMax) const -{ +AI_FORCE_INLINE +aiReturn aiMaterial::Get(const char* pKey,unsigned int type, + unsigned int idx,int* pOut, + unsigned int* pMax) const { return ::aiGetMaterialIntegerArray(this,pKey,type,idx,pOut,pMax); } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::Get(const char* pKey,unsigned int type, - unsigned int idx,ai_real& pOut) const -{ +AI_FORCE_INLINE +aiReturn aiMaterial::Get(const char* pKey,unsigned int type, + unsigned int idx,ai_real& pOut) const { return aiGetMaterialFloat(this,pKey,type,idx,&pOut); } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::Get(const char* pKey,unsigned int type, - unsigned int idx,int& pOut) const -{ +AI_FORCE_INLINE +aiReturn aiMaterial::Get(const char* pKey,unsigned int type, + unsigned int idx,int& pOut) const { return aiGetMaterialInteger(this,pKey,type,idx,&pOut); } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::Get(const char* pKey,unsigned int type, - unsigned int idx,aiColor4D& pOut) const -{ +AI_FORCE_INLINE +aiReturn aiMaterial::Get(const char* pKey,unsigned int type, + unsigned int idx,aiColor4D& pOut) const { return aiGetMaterialColor(this,pKey,type,idx,&pOut); } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::Get(const char* pKey,unsigned int type, - unsigned int idx,aiColor3D& pOut) const -{ +AI_FORCE_INLINE aiReturn aiMaterial::Get(const char* pKey,unsigned int type, + unsigned int idx,aiColor3D& pOut) const { aiColor4D c; const aiReturn ret = aiGetMaterialColor(this,pKey,type,idx,&c); pOut = aiColor3D(c.r,c.g,c.b); return ret; } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::Get(const char* pKey,unsigned int type, - unsigned int idx,aiString& pOut) const -{ +AI_FORCE_INLINE aiReturn aiMaterial::Get(const char* pKey,unsigned int type, + unsigned int idx,aiString& pOut) const { return aiGetMaterialString(this,pKey,type,idx,&pOut); } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::Get(const char* pKey,unsigned int type, - unsigned int idx,aiUVTransform& pOut) const -{ +AI_FORCE_INLINE aiReturn aiMaterial::Get(const char* pKey,unsigned int type, + unsigned int idx,aiUVTransform& pOut) const { return aiGetMaterialUVTransform(this,pKey,type,idx,&pOut); } - // --------------------------------------------------------------------------- template aiReturn aiMaterial::AddProperty (const TYPE* pInput, @@ -204,84 +204,83 @@ aiReturn aiMaterial::AddProperty (const TYPE* pInput, } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::AddProperty(const float* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE aiReturn aiMaterial::AddProperty(const float* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(float), pKey,type,index,aiPTI_Float); } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::AddProperty(const double* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE +aiReturn aiMaterial::AddProperty(const double* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(double), pKey,type,index,aiPTI_Double); } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::AddProperty(const aiUVTransform* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE +aiReturn aiMaterial::AddProperty(const aiUVTransform* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(aiUVTransform), pKey,type,index,ai_real_to_property_type_info(pInput->mRotation)); } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::AddProperty(const aiColor4D* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE +aiReturn aiMaterial::AddProperty(const aiColor4D* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(aiColor4D), pKey,type,index,ai_real_to_property_type_info(pInput->a)); } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::AddProperty(const aiColor3D* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE +aiReturn aiMaterial::AddProperty(const aiColor3D* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(aiColor3D), pKey,type,index,ai_real_to_property_type_info(pInput->b)); } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::AddProperty(const aiVector3D* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE +aiReturn aiMaterial::AddProperty(const aiVector3D* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(aiVector3D), pKey,type,index,ai_real_to_property_type_info(pInput->x)); } // --------------------------------------------------------------------------- -inline aiReturn aiMaterial::AddProperty(const int* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE +aiReturn aiMaterial::AddProperty(const int* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(int), pKey,type,index,aiPTI_Integer); @@ -296,12 +295,12 @@ inline aiReturn aiMaterial::AddProperty(const int* pInput, // --------------------------------------------------------------------------- template<> -inline aiReturn aiMaterial::AddProperty(const float* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE +aiReturn aiMaterial::AddProperty(const float* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(float), pKey,type,index,aiPTI_Float); @@ -309,12 +308,12 @@ inline aiReturn aiMaterial::AddProperty(const float* pInput, // --------------------------------------------------------------------------- template<> -inline aiReturn aiMaterial::AddProperty(const double* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE +aiReturn aiMaterial::AddProperty(const double* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(double), pKey,type,index,aiPTI_Double); @@ -322,12 +321,12 @@ inline aiReturn aiMaterial::AddProperty(const double* pInput, // --------------------------------------------------------------------------- template<> -inline aiReturn aiMaterial::AddProperty(const aiUVTransform* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE +aiReturn aiMaterial::AddProperty(const aiUVTransform* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(aiUVTransform), pKey,type,index,aiPTI_Float); @@ -335,12 +334,12 @@ inline aiReturn aiMaterial::AddProperty(const aiUVTransform* pInp // --------------------------------------------------------------------------- template<> -inline aiReturn aiMaterial::AddProperty(const aiColor4D* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE +aiReturn aiMaterial::AddProperty(const aiColor4D* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(aiColor4D), pKey,type,index,aiPTI_Float); @@ -348,12 +347,12 @@ inline aiReturn aiMaterial::AddProperty(const aiColor4D* pInput, // --------------------------------------------------------------------------- template<> -inline aiReturn aiMaterial::AddProperty(const aiColor3D* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE +aiReturn aiMaterial::AddProperty(const aiColor3D* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(aiColor3D), pKey,type,index,aiPTI_Float); @@ -361,12 +360,12 @@ inline aiReturn aiMaterial::AddProperty(const aiColor3D* pInput, // --------------------------------------------------------------------------- template<> -inline aiReturn aiMaterial::AddProperty(const aiVector3D* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE +aiReturn aiMaterial::AddProperty(const aiVector3D* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(aiVector3D), pKey,type,index,aiPTI_Float); @@ -374,12 +373,12 @@ inline aiReturn aiMaterial::AddProperty(const aiVector3D* pInput, // --------------------------------------------------------------------------- template<> -inline aiReturn aiMaterial::AddProperty(const int* pInput, - const unsigned int pNumValues, - const char* pKey, - unsigned int type, - unsigned int index) -{ +AI_FORCE_INLINE +aiReturn aiMaterial::AddProperty(const int* pInput, + const unsigned int pNumValues, + const char* pKey, + unsigned int type, + unsigned int index) { return AddBinaryProperty((const void*)pInput, pNumValues * sizeof(int), pKey,type,index,aiPTI_Integer); diff --git a/include/assimp/matrix3x3.h b/include/assimp/matrix3x3.h index 22b69561f..2c26cf92b 100644 --- a/include/assimp/matrix3x3.h +++ b/include/assimp/matrix3x3.h @@ -48,7 +48,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_MATRIX3X3_H_INC #define AI_MATRIX3X3_H_INC -#include "defs.h" +#ifdef __GNUC__ +# pragma GCC system_header +#endif + +#include #ifdef __cplusplus @@ -65,10 +69,8 @@ template class aiVector2t; * defined thereby. */ template -class aiMatrix3x3t -{ +class aiMatrix3x3t { public: - aiMatrix3x3t() AI_NO_EXCEPT : a1(static_cast(1.0f)), a2(), a3(), b1(), b2(static_cast(1.0f)), b3(), @@ -82,8 +84,6 @@ public: c1(_c1), c2(_c2), c3(_c3) {} -public: - // matrix multiplication. aiMatrix3x3t& operator *= (const aiMatrix3x3t& m); aiMatrix3x3t operator * (const aiMatrix3x3t& m) const; @@ -101,8 +101,6 @@ public: template operator aiMatrix3x3t () const; -public: - // ------------------------------------------------------------------- /** @brief Construction from a 4x4 matrix. The remaining parts * of the matrix are ignored. @@ -122,7 +120,6 @@ public: aiMatrix3x3t& Inverse(); TReal Determinant() const; -public: // ------------------------------------------------------------------- /** @brief Returns a rotation matrix for a rotation around z * @param a Rotation angle, in radians diff --git a/include/assimp/matrix3x3.inl b/include/assimp/matrix3x3.inl index d9d45a3e9..1ce8c9691 100644 --- a/include/assimp/matrix3x3.inl +++ b/include/assimp/matrix3x3.inl @@ -48,10 +48,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_MATRIX3X3_INL_INC #define AI_MATRIX3X3_INL_INC -#ifdef __cplusplus -#include "matrix3x3.h" +#ifdef __GNUC__ +# pragma GCC system_header +#endif + +#ifdef __cplusplus +#include +#include -#include "matrix4x4.h" #include #include #include @@ -59,8 +63,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // ------------------------------------------------------------------------------------------------ // Construction from a 4x4 matrix. The remaining parts of the matrix are ignored. template -inline aiMatrix3x3t::aiMatrix3x3t( const aiMatrix4x4t& pMatrix) -{ +AI_FORCE_INLINE +aiMatrix3x3t::aiMatrix3x3t( const aiMatrix4x4t& pMatrix) { a1 = pMatrix.a1; a2 = pMatrix.a2; a3 = pMatrix.a3; b1 = pMatrix.b1; b2 = pMatrix.b2; b3 = pMatrix.b3; c1 = pMatrix.c1; c2 = pMatrix.c2; c3 = pMatrix.c3; @@ -68,8 +72,8 @@ inline aiMatrix3x3t::aiMatrix3x3t( const aiMatrix4x4t& pMatrix) // ------------------------------------------------------------------------------------------------ template -inline aiMatrix3x3t& aiMatrix3x3t::operator *= (const aiMatrix3x3t& m) -{ +AI_FORCE_INLINE +aiMatrix3x3t& aiMatrix3x3t::operator *= (const aiMatrix3x3t& m) { *this = aiMatrix3x3t(m.a1 * a1 + m.b1 * a2 + m.c1 * a3, m.a2 * a1 + m.b2 * a2 + m.c2 * a3, m.a3 * a1 + m.b3 * a2 + m.c3 * a3, @@ -85,8 +89,7 @@ inline aiMatrix3x3t& aiMatrix3x3t::operator *= (const aiMatrix3x3t // ------------------------------------------------------------------------------------------------ template template -aiMatrix3x3t::operator aiMatrix3x3t () const -{ +aiMatrix3x3t::operator aiMatrix3x3t () const { return aiMatrix3x3t(static_cast(a1),static_cast(a2),static_cast(a3), static_cast(b1),static_cast(b2),static_cast(b3), static_cast(c1),static_cast(c2),static_cast(c3)); @@ -94,8 +97,8 @@ aiMatrix3x3t::operator aiMatrix3x3t () const // ------------------------------------------------------------------------------------------------ template -inline aiMatrix3x3t aiMatrix3x3t::operator* (const aiMatrix3x3t& m) const -{ +AI_FORCE_INLINE +aiMatrix3x3t aiMatrix3x3t::operator* (const aiMatrix3x3t& m) const { aiMatrix3x3t temp( *this); temp *= m; return temp; @@ -103,7 +106,8 @@ inline aiMatrix3x3t aiMatrix3x3t::operator* (const aiMatrix3x3t -inline TReal* aiMatrix3x3t::operator[] (unsigned int p_iIndex) { +AI_FORCE_INLINE +TReal* aiMatrix3x3t::operator[] (unsigned int p_iIndex) { switch ( p_iIndex ) { case 0: return &a1; @@ -119,7 +123,8 @@ inline TReal* aiMatrix3x3t::operator[] (unsigned int p_iIndex) { // ------------------------------------------------------------------------------------------------ template -inline const TReal* aiMatrix3x3t::operator[] (unsigned int p_iIndex) const { +AI_FORCE_INLINE +const TReal* aiMatrix3x3t::operator[] (unsigned int p_iIndex) const { switch ( p_iIndex ) { case 0: return &a1; @@ -135,8 +140,8 @@ inline const TReal* aiMatrix3x3t::operator[] (unsigned int p_iIndex) cons // ------------------------------------------------------------------------------------------------ template -inline bool aiMatrix3x3t::operator== (const aiMatrix4x4t& m) const -{ +AI_FORCE_INLINE +bool aiMatrix3x3t::operator== (const aiMatrix4x4t& m) const { return a1 == m.a1 && a2 == m.a2 && a3 == m.a3 && b1 == m.b1 && b2 == m.b2 && b3 == m.b3 && c1 == m.c1 && c2 == m.c2 && c3 == m.c3; @@ -144,14 +149,15 @@ inline bool aiMatrix3x3t::operator== (const aiMatrix4x4t& m) const // ------------------------------------------------------------------------------------------------ template -inline bool aiMatrix3x3t::operator!= (const aiMatrix4x4t& m) const -{ +AI_FORCE_INLINE +bool aiMatrix3x3t::operator!= (const aiMatrix4x4t& m) const { return !(*this == m); } // --------------------------------------------------------------------------- template -inline bool aiMatrix3x3t::Equal(const aiMatrix4x4t& m, TReal epsilon) const { +AI_FORCE_INLINE +bool aiMatrix3x3t::Equal(const aiMatrix4x4t& m, TReal epsilon) const { return std::abs(a1 - m.a1) <= epsilon && std::abs(a2 - m.a2) <= epsilon && @@ -166,8 +172,8 @@ inline bool aiMatrix3x3t::Equal(const aiMatrix4x4t& m, TReal epsil // ------------------------------------------------------------------------------------------------ template -inline aiMatrix3x3t& aiMatrix3x3t::Transpose() -{ +AI_FORCE_INLINE +aiMatrix3x3t& aiMatrix3x3t::Transpose() { // (TReal&) don't remove, GCC complains cause of packed fields std::swap( (TReal&)a2, (TReal&)b1); std::swap( (TReal&)a3, (TReal&)c1); @@ -177,15 +183,15 @@ inline aiMatrix3x3t& aiMatrix3x3t::Transpose() // ---------------------------------------------------------------------------------------- template -inline TReal aiMatrix3x3t::Determinant() const -{ +AI_FORCE_INLINE +TReal aiMatrix3x3t::Determinant() const { return a1*b2*c3 - a1*b3*c2 + a2*b3*c1 - a2*b1*c3 + a3*b1*c2 - a3*b2*c1; } // ---------------------------------------------------------------------------------------- template -inline aiMatrix3x3t& aiMatrix3x3t::Inverse() -{ +AI_FORCE_INLINE +aiMatrix3x3t& aiMatrix3x3t::Inverse() { // Compute the reciprocal determinant TReal det = Determinant(); if(det == static_cast(0.0)) @@ -219,8 +225,8 @@ inline aiMatrix3x3t& aiMatrix3x3t::Inverse() // ------------------------------------------------------------------------------------------------ template -inline aiMatrix3x3t& aiMatrix3x3t::RotationZ(TReal a, aiMatrix3x3t& out) -{ +AI_FORCE_INLINE +aiMatrix3x3t& aiMatrix3x3t::RotationZ(TReal a, aiMatrix3x3t& out) { out.a1 = out.b2 = std::cos(a); out.b1 = std::sin(a); out.a2 = - out.b1; @@ -234,8 +240,8 @@ inline aiMatrix3x3t& aiMatrix3x3t::RotationZ(TReal a, aiMatrix3x3t // ------------------------------------------------------------------------------------------------ // Returns a rotation matrix for a rotation around an arbitrary axis. template -inline aiMatrix3x3t& aiMatrix3x3t::Rotation( TReal a, const aiVector3t& axis, aiMatrix3x3t& out) -{ +AI_FORCE_INLINE +aiMatrix3x3t& aiMatrix3x3t::Rotation( TReal a, const aiVector3t& axis, aiMatrix3x3t& out) { TReal c = std::cos( a), s = std::sin( a), t = 1 - c; TReal x = axis.x, y = axis.y, z = axis.z; @@ -249,8 +255,8 @@ inline aiMatrix3x3t& aiMatrix3x3t::Rotation( TReal a, const aiVect // ------------------------------------------------------------------------------------------------ template -inline aiMatrix3x3t& aiMatrix3x3t::Translation( const aiVector2t& v, aiMatrix3x3t& out) -{ +AI_FORCE_INLINE +aiMatrix3x3t& aiMatrix3x3t::Translation( const aiVector2t& v, aiMatrix3x3t& out) { out = aiMatrix3x3t(); out.a3 = v.x; out.b3 = v.y; @@ -268,9 +274,8 @@ inline aiMatrix3x3t& aiMatrix3x3t::Translation( const aiVector2t -inline aiMatrix3x3t& aiMatrix3x3t::FromToMatrix(const aiVector3t& from, - const aiVector3t& to, aiMatrix3x3t& mtx) -{ +AI_FORCE_INLINE aiMatrix3x3t& aiMatrix3x3t::FromToMatrix(const aiVector3t& from, + const aiVector3t& to, aiMatrix3x3t& mtx) { const TReal e = from * to; const TReal f = (e < 0)? -e:e; @@ -352,6 +357,5 @@ inline aiMatrix3x3t& aiMatrix3x3t::FromToMatrix(const aiVector3t +#include #ifdef __cplusplus @@ -66,8 +70,7 @@ template class aiQuaterniont; * defined thereby. */ template -class aiMatrix4x4t -{ +class aiMatrix4x4t { public: /** set to identity */ @@ -91,8 +94,6 @@ public: aiMatrix4x4t(const aiVector3t& scaling, const aiQuaterniont& rotation, const aiVector3t& position); -public: - // array access operators /** @fn TReal* operator[] (unsigned int p_iIndex) * @param [in] p_iIndex - index of the row. @@ -120,8 +121,6 @@ public: template operator aiMatrix4x4t () const; -public: - // ------------------------------------------------------------------- /** @brief Transpose the matrix */ aiMatrix4x4t& Transpose(); @@ -182,7 +181,6 @@ public: void DecomposeNoScaling (aiQuaterniont& rotation, aiVector3t& position) const; - // ------------------------------------------------------------------- /** @brief Creates a trafo matrix from a set of euler angles * @param x Rotation angle for the x-axis, in radians @@ -192,7 +190,6 @@ public: aiMatrix4x4t& FromEulerAnglesXYZ(TReal x, TReal y, TReal z); aiMatrix4x4t& FromEulerAnglesXYZ(const aiVector3t& blubb); -public: // ------------------------------------------------------------------- /** @brief Returns a rotation matrix for a rotation around the x axis * @param a Rotation angle, in radians @@ -256,7 +253,6 @@ public: static aiMatrix4x4t& FromToMatrix(const aiVector3t& from, const aiVector3t& to, aiMatrix4x4t& out); -public: TReal a1, a2, a3, a4; TReal b1, b2, b3, b4; TReal c1, c2, c3, c4; diff --git a/include/assimp/matrix4x4.inl b/include/assimp/matrix4x4.inl index ebc67a06e..84079974f 100644 --- a/include/assimp/matrix4x4.inl +++ b/include/assimp/matrix4x4.inl @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -53,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "matrix4x4.h" #include "matrix3x3.h" #include "quaternion.h" +#include "MathFunctions.h" #include #include @@ -61,12 +60,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // ---------------------------------------------------------------------------------------- template aiMatrix4x4t::aiMatrix4x4t() AI_NO_EXCEPT : - a1(1.0f), a2(), a3(), a4(), - b1(), b2(1.0f), b3(), b4(), - c1(), c2(), c3(1.0f), c4(), - d1(), d2(), d3(), d4(1.0f) -{ - + a1(1.0f), a2(), a3(), a4(), + b1(), b2(1.0f), b3(), b4(), + c1(), c2(), c3(1.0f), c4(), + d1(), d2(), d3(), d4(1.0f) { + // empty } // ---------------------------------------------------------------------------------------- @@ -75,19 +73,17 @@ aiMatrix4x4t::aiMatrix4x4t (TReal _a1, TReal _a2, TReal _a3, TReal _a4, TReal _b1, TReal _b2, TReal _b3, TReal _b4, TReal _c1, TReal _c2, TReal _c3, TReal _c4, TReal _d1, TReal _d2, TReal _d3, TReal _d4) : - a1(_a1), a2(_a2), a3(_a3), a4(_a4), - b1(_b1), b2(_b2), b3(_b3), b4(_b4), - c1(_c1), c2(_c2), c3(_c3), c4(_c4), - d1(_d1), d2(_d2), d3(_d3), d4(_d4) -{ - + a1(_a1), a2(_a2), a3(_a3), a4(_a4), + b1(_b1), b2(_b2), b3(_b3), b4(_b4), + c1(_c1), c2(_c2), c3(_c3), c4(_c4), + d1(_d1), d2(_d2), d3(_d3), d4(_d4) { + // empty } // ------------------------------------------------------------------------------------------------ template template -aiMatrix4x4t::operator aiMatrix4x4t () const -{ +aiMatrix4x4t::operator aiMatrix4x4t () const { return aiMatrix4x4t(static_cast(a1),static_cast(a2),static_cast(a3),static_cast(a4), static_cast(b1),static_cast(b2),static_cast(b3),static_cast(b4), static_cast(c1),static_cast(c2),static_cast(c3),static_cast(c4), @@ -97,8 +93,8 @@ aiMatrix4x4t::operator aiMatrix4x4t () const // ---------------------------------------------------------------------------------------- template -inline aiMatrix4x4t::aiMatrix4x4t (const aiMatrix3x3t& m) -{ +AI_FORCE_INLINE +aiMatrix4x4t::aiMatrix4x4t (const aiMatrix3x3t& m) { a1 = m.a1; a2 = m.a2; a3 = m.a3; a4 = static_cast(0.0); b1 = m.b1; b2 = m.b2; b3 = m.b3; b4 = static_cast(0.0); c1 = m.c1; c2 = m.c2; c3 = m.c3; c4 = static_cast(0.0); @@ -107,8 +103,8 @@ inline aiMatrix4x4t::aiMatrix4x4t (const aiMatrix3x3t& m) // ---------------------------------------------------------------------------------------- template -inline aiMatrix4x4t::aiMatrix4x4t (const aiVector3t& scaling, const aiQuaterniont& rotation, const aiVector3t& position) -{ +AI_FORCE_INLINE +aiMatrix4x4t::aiMatrix4x4t (const aiVector3t& scaling, const aiQuaterniont& rotation, const aiVector3t& position) { // build a 3x3 rotation matrix aiMatrix3x3t m = rotation.GetMatrix(); @@ -135,8 +131,8 @@ inline aiMatrix4x4t::aiMatrix4x4t (const aiVector3t& scaling, cons // ---------------------------------------------------------------------------------------- template -inline aiMatrix4x4t& aiMatrix4x4t::operator *= (const aiMatrix4x4t& m) -{ +AI_FORCE_INLINE +aiMatrix4x4t& aiMatrix4x4t::operator *= (const aiMatrix4x4t& m) { *this = aiMatrix4x4t( m.a1 * a1 + m.b1 * a2 + m.c1 * a3 + m.d1 * a4, m.a2 * a1 + m.b2 * a2 + m.c2 * a3 + m.d2 * a4, @@ -159,8 +155,7 @@ inline aiMatrix4x4t& aiMatrix4x4t::operator *= (const aiMatrix4x4t // ---------------------------------------------------------------------------------------- template -inline aiMatrix4x4t aiMatrix4x4t::operator* (const TReal& aFloat) const -{ +AI_FORCE_INLINE aiMatrix4x4t aiMatrix4x4t::operator* (const TReal& aFloat) const { aiMatrix4x4t temp( a1 * aFloat, a2 * aFloat, @@ -183,8 +178,8 @@ inline aiMatrix4x4t aiMatrix4x4t::operator* (const TReal& aFloat) // ---------------------------------------------------------------------------------------- template -inline aiMatrix4x4t aiMatrix4x4t::operator+ (const aiMatrix4x4t& m) const -{ +AI_FORCE_INLINE +aiMatrix4x4t aiMatrix4x4t::operator+ (const aiMatrix4x4t& m) const { aiMatrix4x4t temp( m.a1 + a1, m.a2 + a2, @@ -207,18 +202,16 @@ inline aiMatrix4x4t aiMatrix4x4t::operator+ (const aiMatrix4x4t -inline aiMatrix4x4t aiMatrix4x4t::operator* (const aiMatrix4x4t& m) const -{ +AI_FORCE_INLINE +aiMatrix4x4t aiMatrix4x4t::operator* (const aiMatrix4x4t& m) const { aiMatrix4x4t temp( *this); temp *= m; return temp; } - // ---------------------------------------------------------------------------------------- template -inline aiMatrix4x4t& aiMatrix4x4t::Transpose() -{ +AI_FORCE_INLINE aiMatrix4x4t& aiMatrix4x4t::Transpose() { // (TReal&) don't remove, GCC complains cause of packed fields std::swap( (TReal&)b1, (TReal&)a2); std::swap( (TReal&)c1, (TReal&)a3); @@ -229,11 +222,10 @@ inline aiMatrix4x4t& aiMatrix4x4t::Transpose() return *this; } - // ---------------------------------------------------------------------------------------- template -inline TReal aiMatrix4x4t::Determinant() const -{ +AI_FORCE_INLINE +TReal aiMatrix4x4t::Determinant() const { return a1*b2*c3*d4 - a1*b2*c4*d3 + a1*b3*c4*d2 - a1*b3*c2*d4 + a1*b4*c2*d3 - a1*b4*c3*d2 - a2*b3*c4*d1 + a2*b3*c1*d4 - a2*b4*c1*d3 + a2*b4*c3*d1 - a2*b1*c3*d4 + a2*b1*c4*d3 @@ -244,8 +236,8 @@ inline TReal aiMatrix4x4t::Determinant() const // ---------------------------------------------------------------------------------------- template -inline aiMatrix4x4t& aiMatrix4x4t::Inverse() -{ +AI_FORCE_INLINE +aiMatrix4x4t& aiMatrix4x4t::Inverse() { // Compute the reciprocal determinant const TReal det = Determinant(); if(det == static_cast(0.0)) @@ -289,9 +281,10 @@ inline aiMatrix4x4t& aiMatrix4x4t::Inverse() // ---------------------------------------------------------------------------------------- template -inline TReal* aiMatrix4x4t::operator[](unsigned int p_iIndex) { +AI_FORCE_INLINE +TReal* aiMatrix4x4t::operator[](unsigned int p_iIndex) { if (p_iIndex > 3) { - return NULL; + return nullptr; } switch ( p_iIndex ) { case 0: @@ -310,9 +303,10 @@ inline TReal* aiMatrix4x4t::operator[](unsigned int p_iIndex) { // ---------------------------------------------------------------------------------------- template -inline const TReal* aiMatrix4x4t::operator[](unsigned int p_iIndex) const { +AI_FORCE_INLINE +const TReal* aiMatrix4x4t::operator[](unsigned int p_iIndex) const { if (p_iIndex > 3) { - return NULL; + return nullptr; } switch ( p_iIndex ) { @@ -332,8 +326,8 @@ inline const TReal* aiMatrix4x4t::operator[](unsigned int p_iIndex) const // ---------------------------------------------------------------------------------------- template -inline bool aiMatrix4x4t::operator== (const aiMatrix4x4t& m) const -{ +AI_FORCE_INLINE +bool aiMatrix4x4t::operator== (const aiMatrix4x4t& m) const { return (a1 == m.a1 && a2 == m.a2 && a3 == m.a3 && a4 == m.a4 && b1 == m.b1 && b2 == m.b2 && b3 == m.b3 && b4 == m.b4 && c1 == m.c1 && c2 == m.c2 && c3 == m.c3 && c4 == m.c4 && @@ -342,14 +336,15 @@ inline bool aiMatrix4x4t::operator== (const aiMatrix4x4t& m) const // ---------------------------------------------------------------------------------------- template -inline bool aiMatrix4x4t::operator!= (const aiMatrix4x4t& m) const -{ +AI_FORCE_INLINE +bool aiMatrix4x4t::operator!= (const aiMatrix4x4t& m) const { return !(*this == m); } // --------------------------------------------------------------------------- template -inline bool aiMatrix4x4t::Equal(const aiMatrix4x4t& m, TReal epsilon) const { +AI_FORCE_INLINE +bool aiMatrix4x4t::Equal(const aiMatrix4x4t& m, TReal epsilon) const { return std::abs(a1 - m.a1) <= epsilon && std::abs(a2 - m.a2) <= epsilon && @@ -401,13 +396,10 @@ inline bool aiMatrix4x4t::Equal(const aiMatrix4x4t& m, TReal epsil \ do {} while(false) - - - template -inline void aiMatrix4x4t::Decompose (aiVector3t& pScaling, aiQuaterniont& pRotation, - aiVector3t& pPosition) const -{ +AI_FORCE_INLINE +void aiMatrix4x4t::Decompose (aiVector3t& pScaling, aiQuaterniont& pRotation, + aiVector3t& pPosition) const { ASSIMP_MATRIX4_4_DECOMPOSE_PART; // build a 3x3 rotation matrix @@ -420,8 +412,8 @@ inline void aiMatrix4x4t::Decompose (aiVector3t& pScaling, aiQuate } template -inline void aiMatrix4x4t::Decompose(aiVector3t& pScaling, aiVector3t& pRotation, aiVector3t& pPosition) const -{ +AI_FORCE_INLINE +void aiMatrix4x4t::Decompose(aiVector3t& pScaling, aiVector3t& pRotation, aiVector3t& pPosition) const { ASSIMP_MATRIX4_4_DECOMPOSE_PART; /* @@ -442,7 +434,7 @@ inline void aiMatrix4x4t::Decompose(aiVector3t& pScaling, aiVector */ // Use a small epsilon to solve floating-point inaccuracies - const TReal epsilon = 10e-3f; + const TReal epsilon = Assimp::Math::getEpsilon(); pRotation.y = std::asin(-vCols[0].z);// D. Angle around oY. @@ -475,10 +467,10 @@ inline void aiMatrix4x4t::Decompose(aiVector3t& pScaling, aiVector #undef ASSIMP_MATRIX4_4_DECOMPOSE_PART template -inline void aiMatrix4x4t::Decompose(aiVector3t& pScaling, aiVector3t& pRotationAxis, TReal& pRotationAngle, - aiVector3t& pPosition) const -{ -aiQuaterniont pRotation; +AI_FORCE_INLINE +void aiMatrix4x4t::Decompose(aiVector3t& pScaling, aiVector3t& pRotationAxis, TReal& pRotationAngle, + aiVector3t& pPosition) const { + aiQuaterniont pRotation; Decompose(pScaling, pRotation, pPosition); pRotation.Normalize(); @@ -500,9 +492,9 @@ aiQuaterniont pRotation; // ---------------------------------------------------------------------------------------- template -inline void aiMatrix4x4t::DecomposeNoScaling (aiQuaterniont& rotation, - aiVector3t& position) const -{ +AI_FORCE_INLINE +void aiMatrix4x4t::DecomposeNoScaling (aiQuaterniont& rotation, + aiVector3t& position) const { const aiMatrix4x4t& _this = *this; // extract translation @@ -516,15 +508,15 @@ inline void aiMatrix4x4t::DecomposeNoScaling (aiQuaterniont& rotat // ---------------------------------------------------------------------------------------- template -inline aiMatrix4x4t& aiMatrix4x4t::FromEulerAnglesXYZ(const aiVector3t& blubb) -{ +AI_FORCE_INLINE +aiMatrix4x4t& aiMatrix4x4t::FromEulerAnglesXYZ(const aiVector3t& blubb) { return FromEulerAnglesXYZ(blubb.x,blubb.y,blubb.z); } // ---------------------------------------------------------------------------------------- template -inline aiMatrix4x4t& aiMatrix4x4t::FromEulerAnglesXYZ(TReal x, TReal y, TReal z) -{ +AI_FORCE_INLINE +aiMatrix4x4t& aiMatrix4x4t::FromEulerAnglesXYZ(TReal x, TReal y, TReal z) { aiMatrix4x4t& _this = *this; TReal cx = std::cos(x); @@ -552,8 +544,8 @@ inline aiMatrix4x4t& aiMatrix4x4t::FromEulerAnglesXYZ(TReal x, TRe // ---------------------------------------------------------------------------------------- template -inline bool aiMatrix4x4t::IsIdentity() const -{ +AI_FORCE_INLINE +bool aiMatrix4x4t::IsIdentity() const { // Use a small epsilon to solve floating-point inaccuracies const static TReal epsilon = 10e-3f; @@ -577,8 +569,8 @@ inline bool aiMatrix4x4t::IsIdentity() const // ---------------------------------------------------------------------------------------- template -inline aiMatrix4x4t& aiMatrix4x4t::RotationX(TReal a, aiMatrix4x4t& out) -{ +AI_FORCE_INLINE +aiMatrix4x4t& aiMatrix4x4t::RotationX(TReal a, aiMatrix4x4t& out) { /* | 1 0 0 0 | M = | 0 cos(A) -sin(A) 0 | @@ -592,8 +584,8 @@ inline aiMatrix4x4t& aiMatrix4x4t::RotationX(TReal a, aiMatrix4x4t // ---------------------------------------------------------------------------------------- template -inline aiMatrix4x4t& aiMatrix4x4t::RotationY(TReal a, aiMatrix4x4t& out) -{ +AI_FORCE_INLINE +aiMatrix4x4t& aiMatrix4x4t::RotationY(TReal a, aiMatrix4x4t& out) { /* | cos(A) 0 sin(A) 0 | M = | 0 1 0 0 | @@ -608,8 +600,8 @@ inline aiMatrix4x4t& aiMatrix4x4t::RotationY(TReal a, aiMatrix4x4t // ---------------------------------------------------------------------------------------- template -inline aiMatrix4x4t& aiMatrix4x4t::RotationZ(TReal a, aiMatrix4x4t& out) -{ +AI_FORCE_INLINE +aiMatrix4x4t& aiMatrix4x4t::RotationZ(TReal a, aiMatrix4x4t& out) { /* | cos(A) -sin(A) 0 0 | M = | sin(A) cos(A) 0 0 | @@ -624,26 +616,25 @@ inline aiMatrix4x4t& aiMatrix4x4t::RotationZ(TReal a, aiMatrix4x4t // ---------------------------------------------------------------------------------------- // Returns a rotation matrix for a rotation around an arbitrary axis. template -inline aiMatrix4x4t& aiMatrix4x4t::Rotation( TReal a, const aiVector3t& axis, aiMatrix4x4t& out) -{ - TReal c = std::cos( a), s = std::sin( a), t = 1 - c; - TReal x = axis.x, y = axis.y, z = axis.z; +AI_FORCE_INLINE +aiMatrix4x4t& aiMatrix4x4t::Rotation( TReal a, const aiVector3t& axis, aiMatrix4x4t& out) { + TReal c = std::cos( a), s = std::sin( a), t = 1 - c; + TReal x = axis.x, y = axis.y, z = axis.z; - // Many thanks to MathWorld and Wikipedia - out.a1 = t*x*x + c; out.a2 = t*x*y - s*z; out.a3 = t*x*z + s*y; - out.b1 = t*x*y + s*z; out.b2 = t*y*y + c; out.b3 = t*y*z - s*x; - out.c1 = t*x*z - s*y; out.c2 = t*y*z + s*x; out.c3 = t*z*z + c; - out.a4 = out.b4 = out.c4 = static_cast(0.0); - out.d1 = out.d2 = out.d3 = static_cast(0.0); - out.d4 = static_cast(1.0); + // Many thanks to MathWorld and Wikipedia + out.a1 = t*x*x + c; out.a2 = t*x*y - s*z; out.a3 = t*x*z + s*y; + out.b1 = t*x*y + s*z; out.b2 = t*y*y + c; out.b3 = t*y*z - s*x; + out.c1 = t*x*z - s*y; out.c2 = t*y*z + s*x; out.c3 = t*z*z + c; + out.a4 = out.b4 = out.c4 = static_cast(0.0); + out.d1 = out.d2 = out.d3 = static_cast(0.0); + out.d4 = static_cast(1.0); - return out; + return out; } // ---------------------------------------------------------------------------------------- template -inline aiMatrix4x4t& aiMatrix4x4t::Translation( const aiVector3t& v, aiMatrix4x4t& out) -{ +AI_FORCE_INLINE aiMatrix4x4t& aiMatrix4x4t::Translation( const aiVector3t& v, aiMatrix4x4t& out) { out = aiMatrix4x4t(); out.a4 = v.x; out.b4 = v.y; @@ -653,8 +644,8 @@ inline aiMatrix4x4t& aiMatrix4x4t::Translation( const aiVector3t -inline aiMatrix4x4t& aiMatrix4x4t::Scaling( const aiVector3t& v, aiMatrix4x4t& out) -{ +AI_FORCE_INLINE +aiMatrix4x4t& aiMatrix4x4t::Scaling( const aiVector3t& v, aiMatrix4x4t& out) { out = aiMatrix4x4t(); out.a1 = v.x; out.b2 = v.y; @@ -673,9 +664,9 @@ inline aiMatrix4x4t& aiMatrix4x4t::Scaling( const aiVector3t -inline aiMatrix4x4t& aiMatrix4x4t::FromToMatrix(const aiVector3t& from, - const aiVector3t& to, aiMatrix4x4t& mtx) -{ +AI_FORCE_INLINE +aiMatrix4x4t& aiMatrix4x4t::FromToMatrix(const aiVector3t& from, + const aiVector3t& to, aiMatrix4x4t& mtx) { aiMatrix3x3t m3; aiMatrix3x3t::FromToMatrix(from,to,m3); mtx = aiMatrix4x4t(m3); diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index f1628f1f5..fbf2a857a 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -48,6 +48,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_MESH_H_INC #define AI_MESH_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include #include @@ -248,6 +252,9 @@ struct aiVertexWeight { }; +// Forward declare aiNode (pointer use only) +struct aiNode; + // --------------------------------------------------------------------------- /** @brief A single bone of a mesh. * @@ -264,6 +271,16 @@ struct aiBone { //! The maximum value for this member is #AI_MAX_BONE_WEIGHTS. unsigned int mNumWeights; +#ifndef ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS + // The bone armature node - used for skeleton conversion + // you must enable aiProcess_PopulateArmatureData to populate this + C_STRUCT aiNode* mArmature; + + // The bone node in the scene - used for skeleton conversion + // you must enable aiProcess_PopulateArmatureData to populate this + C_STRUCT aiNode* mNode; + +#endif //! The influence weights of this bone, by vertex index. C_STRUCT aiVertexWeight* mWeights; @@ -418,11 +435,11 @@ struct aiAnimMesh /**Anim Mesh name */ C_STRUCT aiString mName; - /** Replacement for aiMesh::mVertices. If this array is non-NULL, + /** Replacement for aiMesh::mVertices. If this array is non-nullptr, * it *must* contain mNumVertices entries. The corresponding - * array in the host mesh must be non-NULL as well - animation + * array in the host mesh must be non-nullptr as well - animation * meshes may neither add or nor remove vertex components (if - * a replacement array is NULL and the corresponding source + * a replacement array is nullptr and the corresponding source * array is not, the source data is taken instead)*/ C_STRUCT aiVector3D* mVertices; @@ -596,7 +613,7 @@ struct aiMesh C_STRUCT aiVector3D* mVertices; /** Vertex normals. - * The array contains normalized vectors, NULL if not present. + * The array contains normalized vectors, nullptr if not present. * The array is mNumVertices in size. Normals are undefined for * point and line primitives. A mesh consisting of points and * lines only may not have normal vectors. Meshes with mixed @@ -619,7 +636,7 @@ struct aiMesh /** Vertex tangents. * The tangent of a vertex points in the direction of the positive - * X texture axis. The array contains normalized vectors, NULL if + * X texture axis. The array contains normalized vectors, nullptr if * not present. The array is mNumVertices in size. A mesh consisting * of points and lines only may not have normal vectors. Meshes with * mixed primitive types (i.e. lines and triangles) may have @@ -633,7 +650,7 @@ struct aiMesh /** Vertex bitangents. * The bitangent of a vertex points in the direction of the positive - * Y texture axis. The array contains normalized vectors, NULL if not + * Y texture axis. The array contains normalized vectors, nullptr if not * present. The array is mNumVertices in size. * @note If the mesh contains tangents, it automatically also contains * bitangents. @@ -642,14 +659,14 @@ struct aiMesh /** Vertex color sets. * A mesh may contain 0 to #AI_MAX_NUMBER_OF_COLOR_SETS vertex - * colors per vertex. NULL if not present. Each array is + * colors per vertex. nullptr if not present. Each array is * mNumVertices in size if present. */ C_STRUCT aiColor4D* mColors[AI_MAX_NUMBER_OF_COLOR_SETS]; /** Vertex texture coords, also known as UV channels. * A mesh may contain 0 to AI_MAX_NUMBER_OF_TEXTURECOORDS per - * vertex. NULL if not present. The array is mNumVertices in size. + * vertex. nullptr if not present. The array is mNumVertices in size. */ C_STRUCT aiVector3D* mTextureCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; @@ -671,7 +688,7 @@ struct aiMesh C_STRUCT aiFace* mFaces; /** The number of bones this mesh contains. - * Can be 0, in which case the mBones array is NULL. + * Can be 0, in which case the mBones array is nullptr. */ unsigned int mNumBones; @@ -769,7 +786,10 @@ struct aiMesh // DO NOT REMOVE THIS ADDITIONAL CHECK if (mNumBones && mBones) { for( unsigned int a = 0; a < mNumBones; a++) { - delete mBones[a]; + if(mBones[a]) + { + delete mBones[a]; + } } delete [] mBones; } diff --git a/include/assimp/metadata.h b/include/assimp/metadata.h index 3a1dd1442..849d90f48 100644 --- a/include/assimp/metadata.h +++ b/include/assimp/metadata.h @@ -48,6 +48,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_METADATA_H_INC #define AI_METADATA_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #if defined(_MSC_VER) && (_MSC_VER <= 1500) # include "Compiler/pstdint.h" #else diff --git a/include/assimp/pbrmaterial.h b/include/assimp/pbrmaterial.h index ce5f82217..892a6347f 100644 --- a/include/assimp/pbrmaterial.h +++ b/include/assimp/pbrmaterial.h @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -44,9 +42,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file pbrmaterial.h * @brief Defines the material system of the library */ +#pragma once #ifndef AI_PBRMATERIAL_H_INC #define AI_PBRMATERIAL_H_INC +#ifdef __GNUC__ +# 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 diff --git a/include/assimp/postprocess.h b/include/assimp/postprocess.h index 2a7441421..4b6732e80 100644 --- a/include/assimp/postprocess.h +++ b/include/assimp/postprocess.h @@ -47,7 +47,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_POSTPROCESS_H_INC #define AI_POSTPROCESS_H_INC -#include "types.h" +#include + +#ifdef __GNUC__ +# pragma GCC system_header +#endif #ifdef __cplusplus extern "C" { @@ -316,6 +320,19 @@ enum aiPostProcessSteps */ aiProcess_FixInfacingNormals = 0x2000, + + + // ------------------------------------------------------------------------- + /** + * This step generically populates aiBone->mArmature and aiBone->mNode generically + * The point of these is it saves you later having to calculate these elements + * This is useful when handling rest information or skin information + * If you have multiple armatures on your models we strongly recommend enabling this + * Instead of writing your own multi-root, multi-armature lookups we have done the + * hard work for you :) + */ + aiProcess_PopulateArmatureData = 0x4000, + // ------------------------------------------------------------------------- /**


This step splits meshes with more than one primitive type in * homogeneous sub-meshes. @@ -533,6 +550,8 @@ enum aiPostProcessSteps */ aiProcess_Debone = 0x4000000, + + // ------------------------------------------------------------------------- /**
This step will perform a global scale of the model. * diff --git a/include/assimp/qnan.h b/include/assimp/qnan.h index 0918bde5e..06780da5b 100644 --- a/include/assimp/qnan.h +++ b/include/assimp/qnan.h @@ -50,19 +50,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * but last time I checked compiler coverage was so bad that I decided * to reinvent the wheel. */ - +#pragma once #ifndef AI_QNAN_H_INCLUDED #define AI_QNAN_H_INCLUDED +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #include + #include #include // --------------------------------------------------------------------------- /** Data structure to represent the bit pattern of a 32 Bit * IEEE 754 floating-point number. */ -union _IEEESingle -{ +union _IEEESingle { float Float; struct { @@ -75,8 +79,7 @@ union _IEEESingle // --------------------------------------------------------------------------- /** Data structure to represent the bit pattern of a 64 Bit * IEEE 754 floating-point number. */ -union _IEEEDouble -{ +union _IEEEDouble { double Double; struct { @@ -89,8 +92,7 @@ union _IEEEDouble // --------------------------------------------------------------------------- /** Check whether a given float is qNaN. * @param in Input value */ -AI_FORCE_INLINE bool is_qnan(float in) -{ +AI_FORCE_INLINE bool is_qnan(float in) { // the straightforward solution does not work: // return (in != in); // compiler generates code like this @@ -107,8 +109,7 @@ AI_FORCE_INLINE bool is_qnan(float in) // --------------------------------------------------------------------------- /** Check whether a given double is qNaN. * @param in Input value */ -AI_FORCE_INLINE bool is_qnan(double in) -{ +AI_FORCE_INLINE bool is_qnan(double in) { // the straightforward solution does not work: // return (in != in); // compiler generates code like this @@ -127,8 +128,7 @@ AI_FORCE_INLINE bool is_qnan(double in) * * Denorms return false, they're treated like normal values. * @param in Input value */ -AI_FORCE_INLINE bool is_special_float(float in) -{ +AI_FORCE_INLINE bool is_special_float(float in) { _IEEESingle temp; memcpy(&temp, &in, sizeof(float)); return (temp.IEEE.Exp == (1u << 8)-1); @@ -139,8 +139,7 @@ AI_FORCE_INLINE bool is_special_float(float in) * * Denorms return false, they're treated like normal values. * @param in Input value */ -AI_FORCE_INLINE bool is_special_float(double in) -{ +AI_FORCE_INLINE bool is_special_float(double in) { _IEEESingle temp; memcpy(&temp, &in, sizeof(float)); return (temp.IEEE.Exp == (1u << 11)-1); @@ -150,15 +149,13 @@ AI_FORCE_INLINE bool is_special_float(double in) /** Check whether a float is NOT qNaN. * @param in Input value */ template -AI_FORCE_INLINE bool is_not_qnan(TReal in) -{ +AI_FORCE_INLINE bool is_not_qnan(TReal in) { return !is_qnan(in); } // --------------------------------------------------------------------------- /** @brief Get a fresh qnan. */ -AI_FORCE_INLINE ai_real get_qnan() -{ +AI_FORCE_INLINE ai_real get_qnan() { return std::numeric_limits::quiet_NaN(); } diff --git a/include/assimp/quaternion.h b/include/assimp/quaternion.h index 96574d24b..ae45959b4 100644 --- a/include/assimp/quaternion.h +++ b/include/assimp/quaternion.h @@ -49,7 +49,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifdef __cplusplus -#include "defs.h" +#ifdef __GNUC__ +# pragma GCC system_header +#endif + +#include template class aiVector3t; template class aiMatrix3x3t; diff --git a/include/assimp/quaternion.inl b/include/assimp/quaternion.inl index c26648215..3ce514d1b 100644 --- a/include/assimp/quaternion.inl +++ b/include/assimp/quaternion.inl @@ -48,8 +48,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_QUATERNION_INL_INC #define AI_QUATERNION_INL_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #ifdef __cplusplus -#include "quaternion.h" +#include #include diff --git a/include/assimp/scene.h b/include/assimp/scene.h index e973f6c5b..b76709eb1 100644 --- a/include/assimp/scene.h +++ b/include/assimp/scene.h @@ -48,16 +48,21 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_SCENE_H_INC #define AI_SCENE_H_INC -#include "types.h" -#include "texture.h" -#include "mesh.h" -#include "light.h" -#include "camera.h" -#include "material.h" -#include "anim.h" -#include "metadata.h" +#ifdef __GNUC__ +# pragma GCC system_header +#endif + +#include +#include +#include +#include +#include +#include +#include +#include #ifdef __cplusplus +# include extern "C" { #endif @@ -105,13 +110,13 @@ struct ASSIMP_API aiNode /** The transformation relative to the node's parent. */ C_STRUCT aiMatrix4x4 mTransformation; - /** Parent node. NULL if this node is the root node. */ + /** Parent node. nullptr if this node is the root node. */ C_STRUCT aiNode* mParent; /** The number of child nodes of this node. */ unsigned int mNumChildren; - /** The child nodes of this node. NULL if mNumChildren is 0. */ + /** The child nodes of this node. nullptr if mNumChildren is 0. */ C_STRUCT aiNode** mChildren; /** The number of meshes of this node. */ @@ -122,7 +127,7 @@ struct ASSIMP_API aiNode */ unsigned int* mMeshes; - /** Metadata associated with this node or NULL if there is no metadata. + /** Metadata associated with this node or nullptr if there is no metadata. * Whether any metadata is generated depends on the source file format. See the * @link importer_notes @endlink page for more information on every source file * format. Importers that don't document any metadata don't write any. @@ -144,7 +149,7 @@ struct ASSIMP_API aiNode * of the scene. * * @param name Name to search for - * @return NULL or a valid Node if the search was successful. + * @return nullptr or a valid Node if the search was successful. */ inline const aiNode* FindNode(const aiString& name) const { @@ -339,7 +344,7 @@ struct aiScene #ifdef __cplusplus - //! Default constructor - set everything to 0/NULL + //! Default constructor - set everything to 0/nullptr ASSIMP_API aiScene(); //! Destructor @@ -348,33 +353,33 @@ struct aiScene //! Check whether the scene contains meshes //! Unless no special scene flags are set this will always be true. inline bool HasMeshes() const { - return mMeshes != NULL && mNumMeshes > 0; + return mMeshes != nullptr && mNumMeshes > 0; } //! Check whether the scene contains materials //! Unless no special scene flags are set this will always be true. inline bool HasMaterials() const { - return mMaterials != NULL && mNumMaterials > 0; + return mMaterials != nullptr && mNumMaterials > 0; } //! Check whether the scene contains lights inline bool HasLights() const { - return mLights != NULL && mNumLights > 0; + return mLights != nullptr && mNumLights > 0; } //! Check whether the scene contains textures inline bool HasTextures() const { - return mTextures != NULL && mNumTextures > 0; + return mTextures != nullptr && mNumTextures > 0; } //! Check whether the scene contains cameras inline bool HasCameras() const { - return mCameras != NULL && mNumCameras > 0; + return mCameras != nullptr && mNumCameras > 0; } //! Check whether the scene contains animations inline bool HasAnimations() const { - return mAnimations != NULL && mNumAnimations > 0; + return mAnimations != nullptr && mNumAnimations > 0; } //! Returns a short filename from a full path diff --git a/include/assimp/texture.h b/include/assimp/texture.h index dc6cbef65..0867659f4 100644 --- a/include/assimp/texture.h +++ b/include/assimp/texture.h @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -53,13 +51,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_TEXTURE_H_INC #define AI_TEXTURE_H_INC -#include "types.h" +#ifdef __GNUC__ +# pragma GCC system_header +#endif + +#include #ifdef __cplusplus extern "C" { #endif - // -------------------------------------------------------------------------------- /** \def AI_EMBEDDED_TEXNAME_PREFIX @@ -79,7 +80,6 @@ extern "C" { # define AI_MAKE_EMBEDDED_TEXNAME(_n_) AI_EMBEDDED_TEXNAME_PREFIX # _n_ #endif - #include "./Compiler/pushpack1.h" // -------------------------------------------------------------------------------- @@ -87,8 +87,7 @@ extern "C" { * * Used by aiTexture. */ -struct aiTexel -{ +struct aiTexel { unsigned char b,g,r,a; #ifdef __cplusplus diff --git a/include/assimp/types.h b/include/assimp/types.h index 331b8cd03..e32cae331 100644 --- a/include/assimp/types.h +++ b/include/assimp/types.h @@ -48,22 +48,30 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_TYPES_H_INC #define AI_TYPES_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + // Some runtime headers #include #include #include #include +#include // Our compile configuration -#include "defs.h" +#include // Some types moved to separate header due to size of operators -#include "vector3.h" -#include "vector2.h" -#include "color4.h" -#include "matrix3x3.h" -#include "matrix4x4.h" -#include "quaternion.h" +#include +#include +#include +#include +#include +#include + +typedef int32_t ai_int32; +typedef uint32_t ai_uint32 ; #ifdef __cplusplus #include @@ -71,7 +79,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include // for aiString::Set(const std::string&) namespace Assimp { - //! @cond never +//! @cond never namespace Intern { // -------------------------------------------------------------------- /** @brief Internal helper class to utilize our internal new/delete @@ -269,8 +277,8 @@ struct aiString } /** Copy constructor */ - aiString(const aiString& rOther) : - length(rOther.length) + aiString(const aiString& rOther) + : length(rOther.length) { // Crop the string to the maximum length length = length>=MAXLEN?MAXLEN-1:length; @@ -280,7 +288,7 @@ struct aiString /** Constructor from std::string */ explicit aiString(const std::string& pString) : - length(pString.length()) + length( (ai_uint32) pString.length()) { length = length>=MAXLEN?MAXLEN-1:length; memcpy( data, pString.c_str(), length); @@ -292,15 +300,15 @@ struct aiString if( pString.length() > MAXLEN - 1) { return; } - length = pString.length(); + length = (ai_uint32)pString.length(); memcpy( data, pString.c_str(), length); data[length] = 0; } /** Copy a const char* to the aiString */ void Set( const char* sz) { - const size_t len = ::strlen(sz); - if( len > MAXLEN - 1) { + const ai_int32 len = (ai_uint32) ::strlen(sz); + if( len > (ai_int32)MAXLEN - 1) { return; } length = len; @@ -346,7 +354,7 @@ struct aiString /** Append a string to the string */ void Append (const char* app) { - const size_t len = ::strlen(app); + const ai_uint32 len = (ai_uint32) ::strlen(app); if (!len) { return; } @@ -379,7 +387,7 @@ struct aiString /** Binary length of the string excluding the terminal 0. This is NOT the * logical length of strings containing UTF-8 multi-byte sequences! It's * the number of bytes from the beginning of the string to its end.*/ - size_t length; + ai_uint32 length; /** String buffer. Size limit is MAXLEN */ char data[MAXLEN]; diff --git a/include/assimp/vector2.h b/include/assimp/vector2.h index d5ef00154..c8b1ebbbc 100644 --- a/include/assimp/vector2.h +++ b/include/assimp/vector2.h @@ -47,6 +47,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_VECTOR2D_H_INC #define AI_VECTOR2D_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #ifdef __cplusplus # include #else diff --git a/include/assimp/vector2.inl b/include/assimp/vector2.inl index 3b7a7beab..4bbf432ff 100644 --- a/include/assimp/vector2.inl +++ b/include/assimp/vector2.inl @@ -48,8 +48,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_VECTOR2D_INL_INC #define AI_VECTOR2D_INL_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #ifdef __cplusplus -#include "vector2.h" +#include #include diff --git a/include/assimp/vector3.h b/include/assimp/vector3.h index 7ff25cf0a..fffeb12ad 100644 --- a/include/assimp/vector3.h +++ b/include/assimp/vector3.h @@ -47,13 +47,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_VECTOR3D_H_INC #define AI_VECTOR3D_H_INC +#ifdef __GNUC__ +# pragma GCC system_header +#endif + #ifdef __cplusplus # include #else # include #endif -#include "defs.h" +#include #ifdef __cplusplus @@ -63,16 +67,13 @@ template class aiMatrix4x4t; // --------------------------------------------------------------------------- /** Represents a three-dimensional vector. */ template -class aiVector3t -{ +class aiVector3t { public: aiVector3t() AI_NO_EXCEPT : x(), y(), z() {} aiVector3t(TReal _x, TReal _y, TReal _z) : x(_x), y(_y), z(_z) {} explicit aiVector3t (TReal _xyz ) : x(_xyz), y(_xyz), z(_xyz) {} aiVector3t( const aiVector3t& o ) = default; -public: - // combined operators const aiVector3t& operator += (const aiVector3t& o); const aiVector3t& operator -= (const aiVector3t& o); @@ -97,7 +98,6 @@ public: template operator aiVector3t () const; -public: /** @brief Set the components of a vector * @param pX X component * @param pY Y component diff --git a/include/assimp/vector3.inl b/include/assimp/vector3.inl index 2fce6edde..6682d3b32 100644 --- a/include/assimp/vector3.inl +++ b/include/assimp/vector3.inl @@ -49,7 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define AI_VECTOR3D_INL_INC #ifdef __cplusplus -#include "vector3.h" +#include #include diff --git a/include/assimp/version.h b/include/assimp/version.h index c62a40e11..90645a38f 100644 --- a/include/assimp/version.h +++ b/include/assimp/version.h @@ -49,7 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_VERSION_H_INC #define AI_VERSION_H_INC -#include "defs.h" +#include #ifdef __cplusplus extern "C" { @@ -62,6 +62,13 @@ extern "C" { */ ASSIMP_API const char* aiGetLegalString (void); +// --------------------------------------------------------------------------- +/** @brief Returns the current patch version number of Assimp. + * @return Patch version of the Assimp runtime the application was + * linked/built against + */ +ASSIMP_API unsigned int aiGetVersionPatch(void); + // --------------------------------------------------------------------------- /** @brief Returns the current minor version number of Assimp. * @return Minor version of the Assimp runtime the application was diff --git a/port/PyAssimp/pyassimp/helper.py b/port/PyAssimp/pyassimp/helper.py index 4003fb5ad..5c1aca827 100644 --- a/port/PyAssimp/pyassimp/helper.py +++ b/port/PyAssimp/pyassimp/helper.py @@ -274,8 +274,8 @@ def hasattr_silent(object, name): """ try: - if not object: - return False + if not object: + return False return hasattr(object, name) except AttributeError: return False diff --git a/revision.h.in b/revision.h.in index 0ad58feb7..6d09afbc6 100644 --- a/revision.h.in +++ b/revision.h.in @@ -13,6 +13,16 @@ #define STR(x) STR_HELP(x) #define VER_FILEVERSION VER_MAJOR,VER_MINOR,VER_PATCH,VER_BUILD +#if (GitVersion == 0) #define VER_FILEVERSION_STR STR(VER_MAJOR) "." STR(VER_MINOR) "." STR(VER_PATCH) "." STR(VER_BUILD) +#else +#define VER_FILEVERSION_STR STR(VER_MAJOR) "." STR(VER_MINOR) "." STR(VER_PATCH) "." STR(VER_BUILD) " (Commit @GIT_COMMIT_HASH@)" +#endif + +#ifdef NDEBUG +#define VER_ORIGINAL_FILENAME_STR "assimp@LIBRARY_SUFFIX@.dll" +#else +#define VER_ORIGINAL_FILENAME_STR "assimp@LIBRARY_SUFFIX@@CMAKE_DEBUG_POSTFIX@.dll" +#endif // NDEBUG #endif // ASSIMP_REVISION_H_INC diff --git a/samples/SimpleOpenGL/CMakeLists.txt b/samples/SimpleOpenGL/CMakeLists.txt index e594f2dae..93f389741 100644 --- a/samples/SimpleOpenGL/CMakeLists.txt +++ b/samples/SimpleOpenGL/CMakeLists.txt @@ -25,6 +25,7 @@ INCLUDE_DIRECTORIES( ${Assimp_SOURCE_DIR}/include ${Assimp_SOURCE_DIR}/code ${OPENGL_INCLUDE_DIR} + ${GLUT_INCLUDE_DIR} ${Assimp_SOURCE_DIR}/samples/freeglut/include ) diff --git a/samples/SimpleOpenGL/Sample_SimpleOpenGL.c b/samples/SimpleOpenGL/Sample_SimpleOpenGL.c index 2dec5bc01..a8a3532cf 100644 --- a/samples/SimpleOpenGL/Sample_SimpleOpenGL.c +++ b/samples/SimpleOpenGL/Sample_SimpleOpenGL.c @@ -26,9 +26,9 @@ #include /* the global Assimp scene object */ -const struct aiScene* scene = NULL; +const C_STRUCT aiScene* scene = NULL; GLuint scene_list = 0; -struct aiVector3D scene_min, scene_max, scene_center; +C_STRUCT aiVector3D scene_min, scene_max, scene_center; /* current rotation angle */ static float angle = 0.f; @@ -49,22 +49,22 @@ void reshape(int width, int height) } /* ---------------------------------------------------------------------------- */ -void get_bounding_box_for_node (const struct aiNode* nd, - struct aiVector3D* min, - struct aiVector3D* max, - struct aiMatrix4x4* trafo +void get_bounding_box_for_node (const C_STRUCT aiNode* nd, + C_STRUCT aiVector3D* min, + C_STRUCT aiVector3D* max, + C_STRUCT aiMatrix4x4* trafo ){ - struct aiMatrix4x4 prev; + C_STRUCT aiMatrix4x4 prev; unsigned int n = 0, t; prev = *trafo; aiMultiplyMatrix4(trafo,&nd->mTransformation); for (; n < nd->mNumMeshes; ++n) { - const struct aiMesh* mesh = scene->mMeshes[nd->mMeshes[n]]; + const C_STRUCT aiMesh* mesh = scene->mMeshes[nd->mMeshes[n]]; for (t = 0; t < mesh->mNumVertices; ++t) { - struct aiVector3D tmp = mesh->mVertices[t]; + C_STRUCT aiVector3D tmp = mesh->mVertices[t]; aiTransformVecByMatrix4(&tmp,trafo); min->x = aisgl_min(min->x,tmp.x); @@ -84,9 +84,9 @@ void get_bounding_box_for_node (const struct aiNode* nd, } /* ---------------------------------------------------------------------------- */ -void get_bounding_box (struct aiVector3D* min, struct aiVector3D* max) +void get_bounding_box(C_STRUCT aiVector3D* min, C_STRUCT aiVector3D* max) { - struct aiMatrix4x4 trafo; + C_STRUCT aiMatrix4x4 trafo; aiIdentityMatrix4(&trafo); min->x = min->y = min->z = 1e10f; @@ -95,7 +95,7 @@ void get_bounding_box (struct aiVector3D* min, struct aiVector3D* max) } /* ---------------------------------------------------------------------------- */ -void color4_to_float4(const struct aiColor4D *c, float f[4]) +void color4_to_float4(const C_STRUCT aiColor4D *c, float f[4]) { f[0] = c->r; f[1] = c->g; @@ -113,16 +113,16 @@ void set_float4(float f[4], float a, float b, float c, float d) } /* ---------------------------------------------------------------------------- */ -void apply_material(const struct aiMaterial *mtl) +void apply_material(const C_STRUCT aiMaterial *mtl) { float c[4]; GLenum fill_mode; int ret1, ret2; - struct aiColor4D diffuse; - struct aiColor4D specular; - struct aiColor4D ambient; - struct aiColor4D emission; + C_STRUCT aiColor4D diffuse; + C_STRUCT aiColor4D specular; + C_STRUCT aiColor4D ambient; + C_STRUCT aiColor4D emission; ai_real shininess, strength; int two_sided; int wireframe; @@ -179,11 +179,11 @@ void apply_material(const struct aiMaterial *mtl) } /* ---------------------------------------------------------------------------- */ -void recursive_render (const struct aiScene *sc, const struct aiNode* nd) +void recursive_render (const C_STRUCT aiScene *sc, const C_STRUCT aiNode* nd) { unsigned int i; unsigned int n = 0, t; - struct aiMatrix4x4 m = nd->mTransformation; + C_STRUCT aiMatrix4x4 m = nd->mTransformation; /* update transform */ aiTransposeMatrix4(&m); @@ -192,7 +192,7 @@ void recursive_render (const struct aiScene *sc, const struct aiNode* nd) /* draw all meshes assigned to this node */ for (; n < nd->mNumMeshes; ++n) { - const struct aiMesh* mesh = scene->mMeshes[nd->mMeshes[n]]; + const C_STRUCT aiMesh* mesh = scene->mMeshes[nd->mMeshes[n]]; apply_material(sc->mMaterials[mesh->mMaterialIndex]); @@ -203,7 +203,7 @@ void recursive_render (const struct aiScene *sc, const struct aiNode* nd) } for (t = 0; t < mesh->mNumFaces; ++t) { - const struct aiFace* face = &mesh->mFaces[t]; + const C_STRUCT aiFace* face = &mesh->mFaces[t]; GLenum face_mode; switch(face->mNumIndices) { @@ -324,7 +324,7 @@ int loadasset (const char* path) /* ---------------------------------------------------------------------------- */ int main(int argc, char **argv) { - struct aiLogStream stream; + C_STRUCT aiLogStream stream; glutInitWindowSize(900,600); glutInitWindowPosition(100,100); diff --git a/samples/SimpleTexturedDirectx11/CMakeLists.txt b/samples/SimpleTexturedDirectx11/CMakeLists.txt new file mode 100644 index 000000000..373b5a9db --- /dev/null +++ b/samples/SimpleTexturedDirectx11/CMakeLists.txt @@ -0,0 +1,46 @@ +FIND_PACKAGE(DirectX) + +IF ( MSVC ) + SET(M_LIB) +ENDIF ( MSVC ) + +if ( MSVC ) + ADD_DEFINITIONS( -D_SCL_SECURE_NO_WARNINGS ) + ADD_DEFINITIONS( -D_CRT_SECURE_NO_WARNINGS ) + REMOVE_DEFINITIONS( -DUNICODE -D_UNICODE ) +endif ( MSVC ) + +INCLUDE_DIRECTORIES( + ${Assimp_SOURCE_DIR}/include + ${Assimp_SOURCE_DIR}/code + ${OPENGL_INCLUDE_DIR} + ${GLUT_INCLUDE_DIR} + ${Assimp_SOURCE_DIR}/samples/freeglut/include +) + +LINK_DIRECTORIES( + ${Assimp_BINARY_DIR} + ${Assimp_BINARY_DIR}/lib +) + +ADD_EXECUTABLE( assimp_simpletextureddirectx11 WIN32 + SimpleTexturedDirectx11/Mesh.h + SimpleTexturedDirectx11/ModelLoader.cpp + SimpleTexturedDirectx11/ModelLoader.h + #SimpleTexturedDirectx11/PixelShader.hlsl + SimpleTexturedDirectx11/TextureLoader.cpp + SimpleTexturedDirectx11/TextureLoader.h + #SimpleTexturedDirectx11/VertexShader.hlsl + SimpleTexturedDirectx11/main.cpp +) + +SET_PROPERTY(TARGET assimp_simpletextureddirectx11 PROPERTY DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) + +TARGET_LINK_LIBRARIES( assimp_simpletextureddirectx11 assimp ${DirectX_LIBRARY} comctl32.lib winmm.lib ) +SET_TARGET_PROPERTIES( assimp_simpletextureddirectx11 PROPERTIES + OUTPUT_NAME assimp_simpletextureddirectx11 +) + +INSTALL( TARGETS assimp_simpletextureddirectx11 + DESTINATION "${ASSIMP_BIN_INSTALL_DIR}" COMPONENT assimp-dev +) diff --git a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11.sln b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11.sln deleted file mode 100644 index 381ac8f94..000000000 --- a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11.sln +++ /dev/null @@ -1,28 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26228.9 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SimpleTexturedDirectx11", "SimpleTexturedDirectx11\SimpleTexturedDirectx11.vcxproj", "{E3B160B5-E71F-4F3F-9310-B8F156F736D8}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Debug|x64.ActiveCfg = Debug|x64 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Debug|x64.Build.0 = Debug|x64 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Debug|x86.ActiveCfg = Debug|Win32 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Debug|x86.Build.0 = Debug|Win32 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Release|x64.ActiveCfg = Release|x64 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Release|x64.Build.0 = Release|x64 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Release|x86.ActiveCfg = Release|Win32 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp index a2d3faeb3..10ba07a98 100644 --- a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp +++ b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp @@ -180,6 +180,8 @@ string ModelLoader::determineTextureType(const aiScene * scene, aiMaterial * mat { return "textures are on disk"; } + + return "."; } int ModelLoader::getTextureIndex(aiString * str) diff --git a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj deleted file mode 100644 index 6584b7d7c..000000000 --- a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj +++ /dev/null @@ -1,146 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8} - SimpleTexturedDirectx11 - 10.0.14393.0 - - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - $(IncludePath);E:\OpenGL VS Files\include - $(LibraryPath);E:\OpenGL VS Files\lib - - - - Level3 - Disabled - true - - - assimp-vc140-mt.lib;%(AdditionalDependencies) - - - - - Level3 - Disabled - true - - - - - Level3 - MaxSpeed - true - true - true - - - true - true - - - - - Level3 - MaxSpeed - true - true - true - - - true - true - - - - - - - - - - Pixel - Pixel - Pixel - Pixel - - - Vertex - Vertex - Vertex - Vertex - - - - - - - - - - - \ No newline at end of file diff --git a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj.filters b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj.filters deleted file mode 100644 index 3568b73c5..000000000 --- a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj.filters +++ /dev/null @@ -1,50 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {b6a86d3e-70a5-4d1e-ba05-c20902300206} - - - - - Source Files - - - Source Files - - - Source Files - - - - - Shaders - - - Shaders - - - - - Header Files - - - Header Files - - - Header Files - - - \ No newline at end of file diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 000000000..b652d6aae --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,3 @@ +# Ignore Unit Test Output files + +*_out.* diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 913813c3b..23caae88c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -118,6 +118,7 @@ SET( IMPORTERS unit/utColladaImportExport.cpp unit/utCSMImportExport.cpp unit/utB3DImportExport.cpp + unit/utM3DImportExport.cpp unit/utMDCImportExport.cpp unit/utAssbinImportExport.cpp unit/ImportExport/utAssjsonImportExport.cpp @@ -148,6 +149,7 @@ SET( POST_PROCESSES unit/utRemoveRedundantMaterials.cpp unit/utRemoveVCProcess.cpp unit/utScaleProcess.cpp + unit/utArmaturePopulate.cpp unit/utJoinVertices.cpp unit/utRemoveComments.cpp unit/utRemoveComponent.cpp diff --git a/test/models/Collada/duck.zae b/test/models/Collada/duck.zae new file mode 100644 index 000000000..7e6748cb0 Binary files /dev/null and b/test/models/Collada/duck.zae differ diff --git a/test/models/Collada/duck_nomanifest.zae b/test/models/Collada/duck_nomanifest.zae new file mode 100644 index 000000000..d5559ccb2 Binary files /dev/null and b/test/models/Collada/duck_nomanifest.zae differ diff --git a/test/models/FBX/box_orphant_embedded_texture.fbx b/test/models/FBX/box_orphant_embedded_texture.fbx new file mode 100644 index 000000000..d874751d0 --- /dev/null +++ b/test/models/FBX/box_orphant_embedded_texture.fbx @@ -0,0 +1,723 @@ +; FBX 7.5.0 project file +; ---------------------------------------------------- + +FBXHeaderExtension: { + FBXHeaderVersion: 1003 + FBXVersion: 7500 + CreationTimeStamp: { + Version: 1000 + Year: 2019 + Month: 3 + Day: 1 + Hour: 12 + Minute: 46 + Second: 3 + Millisecond: 995 + } + Creator: "FBX SDK/FBX Plugins version 2018.1.1" + SceneInfo: "SceneInfo::GlobalInfo", "UserData" { + Type: "UserData" + Version: 100 + MetaData: { + Version: 100 + Title: "" + Subject: "" + Author: "" + Keywords: "" + Revision: "" + Comment: "" + } + Properties70: { + P: "DocumentUrl", "KString", "Url", "", "U:\Some\Absolute\Path\box_embedded_texture.fbx" + P: "SrcDocumentUrl", "KString", "Url", "", "U:\Some\Absolute\Path\box_embedded_texture.fbx" + P: "Original", "Compound", "", "" + P: "Original|ApplicationVendor", "KString", "", "", "Autodesk" + P: "Original|ApplicationName", "KString", "", "", "Maya" + P: "Original|ApplicationVersion", "KString", "", "", "201800" + P: "Original|DateTime_GMT", "DateTime", "", "", "01/03/2019 12:46:03.994" + P: "Original|FileName", "KString", "", "", "U:\Some\Absolute\Path\box_embedded_texture.fbx" + P: "LastSaved", "Compound", "", "" + P: "LastSaved|ApplicationVendor", "KString", "", "", "Autodesk" + P: "LastSaved|ApplicationName", "KString", "", "", "Maya" + P: "LastSaved|ApplicationVersion", "KString", "", "", "201800" + P: "LastSaved|DateTime_GMT", "DateTime", "", "", "01/03/2019 12:46:03.994" + P: "Original|ApplicationActiveProject", "KString", "", "", "U:\Some\Absolute\Path" + } + } +} +GlobalSettings: { + Version: 1000 + Properties70: { + P: "UpAxis", "int", "Integer", "",1 + P: "UpAxisSign", "int", "Integer", "",1 + P: "FrontAxis", "int", "Integer", "",2 + P: "FrontAxisSign", "int", "Integer", "",1 + P: "CoordAxis", "int", "Integer", "",0 + P: "CoordAxisSign", "int", "Integer", "",1 + P: "OriginalUpAxis", "int", "Integer", "",1 + P: "OriginalUpAxisSign", "int", "Integer", "",1 + P: "UnitScaleFactor", "double", "Number", "",100 + P: "OriginalUnitScaleFactor", "double", "Number", "",1 + P: "AmbientColor", "ColorRGB", "Color", "",0,0,0 + P: "DefaultCamera", "KString", "", "", "Producer Perspective" + P: "TimeMode", "enum", "", "",6 + P: "TimeProtocol", "enum", "", "",2 + P: "SnapOnFrameMode", "enum", "", "",0 + P: "TimeSpanStart", "KTime", "Time", "",0 + P: "TimeSpanStop", "KTime", "Time", "",153953860000 + P: "CustomFrameRate", "double", "Number", "",-1 + P: "TimeMarker", "Compound", "", "" + P: "CurrentTimeMarker", "int", "Integer", "",-1 + } +} + +; Documents Description +;------------------------------------------------------------------ + +Documents: { + Count: 1 + Document: 2957686739424, "", "Scene" { + Properties70: { + P: "SourceObject", "object", "", "" + P: "ActiveAnimStackName", "KString", "", "", "Take 001" + } + RootNode: 0 + } +} + +; Document References +;------------------------------------------------------------------ + +References: { +} + +; Object definitions +;------------------------------------------------------------------ + +Definitions: { + Version: 100 + Count: 17 + ObjectType: "GlobalSettings" { + Count: 1 + } + ObjectType: "AnimationStack" { + Count: 1 + PropertyTemplate: "FbxAnimStack" { + Properties70: { + P: "Description", "KString", "", "", "" + P: "LocalStart", "KTime", "Time", "",0 + P: "LocalStop", "KTime", "Time", "",0 + P: "ReferenceStart", "KTime", "Time", "",0 + P: "ReferenceStop", "KTime", "Time", "",0 + } + } + } + ObjectType: "AnimationLayer" { + Count: 1 + PropertyTemplate: "FbxAnimLayer" { + Properties70: { + P: "Weight", "Number", "", "A",100 + P: "Mute", "bool", "", "",0 + P: "Solo", "bool", "", "",0 + P: "Lock", "bool", "", "",0 + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "BlendMode", "enum", "", "",0 + P: "RotationAccumulationMode", "enum", "", "",0 + P: "ScaleAccumulationMode", "enum", "", "",0 + P: "BlendModeBypass", "ULongLong", "", "",0 + } + } + } + ObjectType: "Geometry" { + Count: 1 + PropertyTemplate: "FbxMesh" { + Properties70: { + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "BBoxMin", "Vector3D", "Vector", "",0,0,0 + P: "BBoxMax", "Vector3D", "Vector", "",0,0,0 + P: "Primary Visibility", "bool", "", "",1 + P: "Casts Shadows", "bool", "", "",1 + P: "Receive Shadows", "bool", "", "",1 + } + } + } + ObjectType: "Material" { + Count: 1 + PropertyTemplate: "FbxSurfacePhong" { + Properties70: { + P: "ShadingModel", "KString", "", "", "Phong" + P: "MultiLayer", "bool", "", "",0 + P: "EmissiveColor", "Color", "", "A",0,0,0 + P: "EmissiveFactor", "Number", "", "A",1 + P: "AmbientColor", "Color", "", "A",0.2,0.2,0.2 + P: "AmbientFactor", "Number", "", "A",1 + P: "DiffuseColor", "Color", "", "A",0.8,0.8,0.8 + P: "DiffuseFactor", "Number", "", "A",1 + P: "Bump", "Vector3D", "Vector", "",0,0,0 + P: "NormalMap", "Vector3D", "Vector", "",0,0,0 + P: "BumpFactor", "double", "Number", "",1 + P: "TransparentColor", "Color", "", "A",0,0,0 + P: "TransparencyFactor", "Number", "", "A",0 + P: "DisplacementColor", "ColorRGB", "Color", "",0,0,0 + P: "DisplacementFactor", "double", "Number", "",1 + P: "VectorDisplacementColor", "ColorRGB", "Color", "",0,0,0 + P: "VectorDisplacementFactor", "double", "Number", "",1 + P: "SpecularColor", "Color", "", "A",0.2,0.2,0.2 + P: "SpecularFactor", "Number", "", "A",1 + P: "ShininessExponent", "Number", "", "A",20 + P: "ReflectionColor", "Color", "", "A",0,0,0 + P: "ReflectionFactor", "Number", "", "A",1 + } + } + } + ObjectType: "Texture" { + Count: 1 + PropertyTemplate: "FbxFileTexture" { + Properties70: { + P: "TextureTypeUse", "enum", "", "",0 + P: "Texture alpha", "Number", "", "A",1 + P: "CurrentMappingType", "enum", "", "",0 + P: "WrapModeU", "enum", "", "",0 + P: "WrapModeV", "enum", "", "",0 + P: "UVSwap", "bool", "", "",0 + P: "PremultiplyAlpha", "bool", "", "",1 + P: "Translation", "Vector", "", "A",0,0,0 + P: "Rotation", "Vector", "", "A",0,0,0 + P: "Scaling", "Vector", "", "A",1,1,1 + P: "TextureRotationPivot", "Vector3D", "Vector", "",0,0,0 + P: "TextureScalingPivot", "Vector3D", "Vector", "",0,0,0 + P: "CurrentTextureBlendMode", "enum", "", "",1 + P: "UVSet", "KString", "", "", "default" + P: "UseMaterial", "bool", "", "",0 + P: "UseMipMap", "bool", "", "",0 + } + } + } + ObjectType: "Model" { + Count: 1 + PropertyTemplate: "FbxNode" { + Properties70: { + P: "QuaternionInterpolate", "enum", "", "",0 + P: "RotationOffset", "Vector3D", "Vector", "",0,0,0 + P: "RotationPivot", "Vector3D", "Vector", "",0,0,0 + P: "ScalingOffset", "Vector3D", "Vector", "",0,0,0 + P: "ScalingPivot", "Vector3D", "Vector", "",0,0,0 + P: "TranslationActive", "bool", "", "",0 + P: "TranslationMin", "Vector3D", "Vector", "",0,0,0 + P: "TranslationMax", "Vector3D", "Vector", "",0,0,0 + P: "TranslationMinX", "bool", "", "",0 + P: "TranslationMinY", "bool", "", "",0 + P: "TranslationMinZ", "bool", "", "",0 + P: "TranslationMaxX", "bool", "", "",0 + P: "TranslationMaxY", "bool", "", "",0 + P: "TranslationMaxZ", "bool", "", "",0 + P: "RotationOrder", "enum", "", "",0 + P: "RotationSpaceForLimitOnly", "bool", "", "",0 + P: "RotationStiffnessX", "double", "Number", "",0 + P: "RotationStiffnessY", "double", "Number", "",0 + P: "RotationStiffnessZ", "double", "Number", "",0 + P: "AxisLen", "double", "Number", "",10 + P: "PreRotation", "Vector3D", "Vector", "",0,0,0 + P: "PostRotation", "Vector3D", "Vector", "",0,0,0 + P: "RotationActive", "bool", "", "",0 + P: "RotationMin", "Vector3D", "Vector", "",0,0,0 + P: "RotationMax", "Vector3D", "Vector", "",0,0,0 + P: "RotationMinX", "bool", "", "",0 + P: "RotationMinY", "bool", "", "",0 + P: "RotationMinZ", "bool", "", "",0 + P: "RotationMaxX", "bool", "", "",0 + P: "RotationMaxY", "bool", "", "",0 + P: "RotationMaxZ", "bool", "", "",0 + P: "InheritType", "enum", "", "",0 + P: "ScalingActive", "bool", "", "",0 + P: "ScalingMin", "Vector3D", "Vector", "",0,0,0 + P: "ScalingMax", "Vector3D", "Vector", "",1,1,1 + P: "ScalingMinX", "bool", "", "",0 + P: "ScalingMinY", "bool", "", "",0 + P: "ScalingMinZ", "bool", "", "",0 + P: "ScalingMaxX", "bool", "", "",0 + P: "ScalingMaxY", "bool", "", "",0 + P: "ScalingMaxZ", "bool", "", "",0 + P: "GeometricTranslation", "Vector3D", "Vector", "",0,0,0 + P: "GeometricRotation", "Vector3D", "Vector", "",0,0,0 + P: "GeometricScaling", "Vector3D", "Vector", "",1,1,1 + P: "MinDampRangeX", "double", "Number", "",0 + P: "MinDampRangeY", "double", "Number", "",0 + P: "MinDampRangeZ", "double", "Number", "",0 + P: "MaxDampRangeX", "double", "Number", "",0 + P: "MaxDampRangeY", "double", "Number", "",0 + P: "MaxDampRangeZ", "double", "Number", "",0 + P: "MinDampStrengthX", "double", "Number", "",0 + P: "MinDampStrengthY", "double", "Number", "",0 + P: "MinDampStrengthZ", "double", "Number", "",0 + P: "MaxDampStrengthX", "double", "Number", "",0 + P: "MaxDampStrengthY", "double", "Number", "",0 + P: "MaxDampStrengthZ", "double", "Number", "",0 + P: "PreferedAngleX", "double", "Number", "",0 + P: "PreferedAngleY", "double", "Number", "",0 + P: "PreferedAngleZ", "double", "Number", "",0 + P: "LookAtProperty", "object", "", "" + P: "UpVectorProperty", "object", "", "" + P: "Show", "bool", "", "",1 + P: "NegativePercentShapeSupport", "bool", "", "",1 + P: "DefaultAttributeIndex", "int", "Integer", "",-1 + P: "Freeze", "bool", "", "",0 + P: "LODBox", "bool", "", "",0 + P: "Lcl Translation", "Lcl Translation", "", "A",0,0,0 + P: "Lcl Rotation", "Lcl Rotation", "", "A",0,0,0 + P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1 + P: "Visibility", "Visibility", "", "A",1 + P: "Visibility Inheritance", "Visibility Inheritance", "", "",1 + } + } + } + ObjectType: "AnimationCurveNode" { + Count: 8 + PropertyTemplate: "FbxAnimCurveNode" { + Properties70: { + P: "d", "Compound", "", "" + } + } + } + ObjectType: "CollectionExclusive" { + Count: 1 + PropertyTemplate: "FbxDisplayLayer" { + Properties70: { + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "Show", "bool", "", "",1 + P: "Freeze", "bool", "", "",0 + P: "LODBox", "bool", "", "",0 + } + } + } + ObjectType: "Video" { + Count: 1 + PropertyTemplate: "FbxVideo" { + Properties70: { + P: "Path", "KString", "XRefUrl", "", "" + P: "RelPath", "KString", "XRefUrl", "", "" + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "ClipIn", "KTime", "Time", "",0 + P: "ClipOut", "KTime", "Time", "",0 + P: "Offset", "KTime", "Time", "",0 + P: "PlaySpeed", "double", "Number", "",0 + P: "FreeRunning", "bool", "", "",0 + P: "Loop", "bool", "", "",0 + P: "Mute", "bool", "", "",0 + P: "AccessMode", "enum", "", "",0 + P: "ImageSequence", "bool", "", "",0 + P: "ImageSequenceOffset", "int", "Integer", "",0 + P: "FrameRate", "double", "Number", "",0 + P: "LastFrame", "int", "Integer", "",0 + P: "Width", "int", "Integer", "",0 + P: "Height", "int", "Integer", "",0 + P: "StartFrame", "int", "Integer", "",0 + P: "StopFrame", "int", "Integer", "",0 + P: "InterlaceMode", "enum", "", "",0 + } + } + } +} + +; Object properties +;------------------------------------------------------------------ + +Objects: { + Geometry: 2957764348544, "Geometry::", "Mesh" { + Vertices: *24 { + a: -0.5,-0.5,-0.5,0.5,-0.50000011920929,-0.5,-0.5,0.50000011920929,-0.5,0.5,0.50000011920929,-0.5,-0.5,-0.500000059604645,0.5,0.5,-0.500000059604645,0.5,-0.5,0.500000059604645,0.5,0.5,0.500000059604645,0.5 + } + PolygonVertexIndex: *24 { + a: 0,2,3,-2,4,5,7,-7,0,1,5,-5,1,3,7,-6,3,2,6,-8,2,0,4,-7 + } + Edges: *12 { + a: 0,1,2,3,4,5,6,7,9,11,13,17 + } + GeometryVersion: 124 + LayerElementNormal: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Normals: *72 { + a: 0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,-5.96046447753906e-08,-1,0,-5.96046447753906e-08,-1,0,-5.96046447753906e-08,-1,0,-5.96046447753906e-08,-1,0,1,0,0,1,0,0,1,0,0,1,0,0,0,1,5.96046447753906e-08,0,1,5.96046447753906e-08,0,1,5.96046447753906e-08,0,1,5.96046447753906e-08,-1,0,0,-1,0,0,-1,0,0,-1,0,0 + } + NormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementBinormal: 0 { + Version: 102 + Name: "UVChannel_1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Binormals: *72 { + a: 1.19209289550781e-07,1,0,5.96046447753906e-08,1,0,0,1,0,5.96046447753906e-08,1,0,0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,1,-0,-5.96046447753906e-08,1,-0,-5.96046447753906e-08,1,-0,-5.96046447753906e-08,1,-0,-5.96046447753906e-08,1,0,0,1,0,0,1,0,0,1,0,0,1 + } + BinormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + + } + LayerElementBinormal: 1 { + Version: 102 + Name: "UVChannel_3" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Binormals: *72 { + a: -1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-0,0,-1,-0,0,-1,0,-0,-1,-0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,5.96046447753906e-08,-1,0,5.96046447753906e-08,-1,0,5.96046447753906e-08,-1,0,5.96046447753906e-08,-1,-0,0,-1,-0,0,-1,-0,0,-1,-0,0,-1 + } + BinormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + + } + LayerElementTangent: 0 { + Version: 102 + Name: "UVChannel_1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Tangents: *72 { + a: -1,1.19209289550781e-07,0,-1,5.96046447753906e-08,0,-1,-0,0,-1,5.96046447753906e-08,0,1,-0,-0,1,-0,0,1,-0,0,1,-0,0,1,-5.96046447753906e-08,-0,1,-5.96046447753906e-08,0,1,-5.96046447753906e-08,0,1,-5.96046447753906e-08,0,-0,1,-0,0,1,-0,0,1,-0,0,1,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,0,-1,-0,0,-1,0,0,-1,0,0,-1,0 + } + TangentsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementTangent: 1 { + Version: 102 + Name: "UVChannel_3" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Tangents: *72 { + a: -0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,0,1,0,0,1,0,-0,1,-0,0,1,0,-1,5.96046447753906e-08,0,-1,5.96046447753906e-08,0,-1,5.96046447753906e-08,-0,-1,5.96046447753906e-08,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,0,1,-0,0,1,-0,0,1,-0,0,1,-0 + } + TangentsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementUV: 0 { + Version: 101 + Name: "UVChannel_1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "IndexToDirect" + UV: *48 { + a: 1,0,0,0,1,1,0,1,0,0,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,0,1,1,1 + } + UVIndex: *24 { + a: 0,2,3,1,4,5,7,6,8,9,11,10,12,13,15,14,16,17,19,18,20,21,23,22 + } + } + LayerElementUV: 1 { + Version: 101 + Name: "UVChannel_3" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "IndexToDirect" + UV: *48 { + a: 0.28125,0.28125,0,0.28125,0,0,0.28125,0,0.34375,1,0.34375,0.71875,0.625,0.71875,0.625,1,0.28125,0.65625,0,0.65625,0,0.375,0.28125,0.375,0.625,0.28125,0.34375,0.28125,0.34375,0,0.625,0,0.28125,1,0,1,0,0.71875,0.28125,0.71875,0.625,0.65625,0.34375,0.65625,0.34375,0.375,0.625,0.375 + } + UVIndex: *24 { + a: 0,1,2,3,4,5,6,7,20,21,22,23,8,9,10,11,16,17,18,19,12,13,14,15 + } + } + LayerElementSmoothing: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByEdge" + ReferenceInformationType: "Direct" + Smoothing: *12 { + a: 0,0,0,0,0,0,0,0,0,0,0,0 + } + } + LayerElementMaterial: 0 { + Version: 101 + Name: "" + MappingInformationType: "AllSame" + ReferenceInformationType: "IndexToDirect" + Materials: *1 { + a: 0 + } + } + Layer: 0 { + Version: 100 + LayerElement: { + Type: "LayerElementNormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementBinormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementTangent" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementMaterial" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementSmoothing" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementUV" + TypedIndex: 0 + } + } + Layer: 1 { + Version: 100 + LayerElement: { + Type: "LayerElementBinormal" + TypedIndex: 1 + } + LayerElement: { + Type: "LayerElementTangent" + TypedIndex: 1 + } + LayerElement: { + Type: "LayerElementUV" + TypedIndex: 1 + } + } + } + Model: 2957618625584, "Model::Box", "Mesh" { + Version: 232 + Properties70: { + P: "RotationActive", "bool", "", "",1 + P: "InheritType", "enum", "", "",1 + P: "ScalingMax", "Vector3D", "Vector", "",0,0,0 + P: "DefaultAttributeIndex", "int", "Integer", "",0 + P: "Lcl Translation", "Lcl Translation", "", "A",0,0.5,2.18556946492754e-06 + P: "Lcl Rotation", "Lcl Rotation", "", "A",-90,0,0 + P: "currentUVSet", "KString", "", "U", "UVChannel_1" + P: "mr displacement use global settings", "Bool", "", "A+U",1 + P: "mr displacement view dependent", "Bool", "", "A+U",1 + P: "mr displacement method", "Integer", "", "A+U",6,6,6 + P: "mr displacement smoothing on", "Bool", "", "A+U",1 + P: "mr displacement edge length", "Number", "", "A+U",2,2,2 + P: "mr displacement max displace", "Number", "", "A+U",20,20,20 + P: "mr displacement parametric subdivision level", "Integer", "", "A+U",5,5,5 + P: "MaxHandle", "Integer", "", "A+UH",1,0,0 + } + Shading: T + Culling: "CullingOff" + } + Material: 2957776713840, "Material::Default", "" { + Version: 102 + ShadingModel: "phong" + MultiLayer: 0 + Properties70: { + P: "AmbientColor", "Color", "", "A",0,0,0 + P: "DiffuseColor", "Color", "", "A",1,1,1 + P: "TransparencyFactor", "Number", "", "A",1 + P: "SpecularColor", "Color", "", "A",0,0,0 + P: "ShininessExponent", "Number", "", "A",2 + P: "Emissive", "Vector3D", "Vector", "",0,0,0 + P: "Ambient", "Vector3D", "Vector", "",0,0,0 + P: "Diffuse", "Vector3D", "Vector", "",1,1,1 + P: "Specular", "Vector3D", "Vector", "",0,0,0 + P: "Shininess", "double", "Number", "",2 + P: "Opacity", "double", "Number", "",1 + P: "Reflectivity", "double", "Number", "",0 + } + } + Video: 2957776707120, "Video::Map #2", "Clip" { + Type: "Clip" + Properties70: { + P: "Path", "KString", "XRefUrl", "", "U:/Some/Absolute/Primitives/GridGrey.tga" + P: "RelPath", "KString", "XRefUrl", "", "..\Primitives\GridGrey.tga" + } + UseMipMap: 0 + Filename: "U:/Some/Absolute/Primitives/GridGrey.tga" + RelativeFilename: "..\Primitives\GridGrey.tga" + } + Video: 2957776707121, "Video::Map #2", "Clip" { + Type: "Clip" + Properties70: { + P: "Path", "KString", "XRefUrl", "", "U:/Some/Absolute/Primitives/GridGrey.tga" + P: "RelPath", "KString", "XRefUrl", "", "..\Primitives\GridGrey.tga" + } + UseMipMap: 0 + Filename: "U:/Some/Absolute/Primitives/GridGrey.tga" + RelativeFilename: "..\Primitives\GridGrey.tga" + Content: , + "AAAKAAAAAAAAAAAAAAEAARgAh+rq6v+Pj4/wj4+Phurq6ofq6uq2dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+3dHR0hurq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6gCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+Pj/+Pj4//j4+P/4+Pj/+Pj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj4//j4+P/4+Pj/+Pj4//j4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+P/4+Pj/+Pj4//j4+P/4+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+Pj4Hq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6ofq6uq2dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+3dHR0hurq6ofq6ur/j4+P8I+Pj4bq6uo=" + } + Texture: 2957776714320, "Texture::Map #2", "" { + Type: "TextureVideoClip" + Version: 202 + TextureName: "Texture::Map #2" + Properties70: { + P: "CurrentTextureBlendMode", "enum", "", "",0 + P: "UVSet", "KString", "", "", "UVChannel_1" + P: "UseMaterial", "bool", "", "",1 + } + Media: "Video::Map #2" + FileName: "U:/Some/Absolute/Primitives/GridGrey.tga" + RelativeFilename: "..\Primitives\GridGrey.tga" + ModelUVTranslation: 0,0 + ModelUVScaling: 1,1 + Texture_Alpha_Source: "None" + Cropping: 0,0,0,0 + } + Texture: 2957776714321, "Texture::Map #2", "" { + Type: "TextureVideoClip" + Version: 202 + TextureName: "Texture::Map #2" + Properties70: { + P: "CurrentTextureBlendMode", "enum", "", "",0 + P: "UVSet", "KString", "", "", "UVChannel_1" + P: "UseMaterial", "bool", "", "",1 + } + Media: "Video::Map #2" + FileName: "U:/Some/Absolute/Primitives/GridGrey.tga" + RelativeFilename: "..\Primitives\GridGrey.tga" + ModelUVTranslation: 0,0 + ModelUVScaling: 1,1 + Texture_Alpha_Source: "None" + Cropping: 0,0,0,0 + } + AnimationStack: 2957627494560, "AnimStack::Take 001", "" { + Properties70: { + P: "LocalStop", "KTime", "Time", "",153953860000 + P: "ReferenceStop", "KTime", "Time", "",153953860000 + } + } + AnimationCurveNode: 2957627500176, "AnimCurveNode::mr displacement use global settings", "" { + Properties70: { + P: "d|mr displacement use global settings", "Bool", "", "A",1 + } + } + AnimationCurveNode: 2957627490192, "AnimCurveNode::mr displacement view dependent", "" { + Properties70: { + P: "d|mr displacement view dependent", "Bool", "", "A",1 + } + } + AnimationCurveNode: 2957627487904, "AnimCurveNode::mr displacement method", "" { + Properties70: { + P: "d|mr displacement method", "Integer", "", "A",6 + } + } + AnimationCurveNode: 2957627492688, "AnimCurveNode::mr displacement smoothing on", "" { + Properties70: { + P: "d|mr displacement smoothing on", "Bool", "", "A",1 + } + } + AnimationCurveNode: 2957627492064, "AnimCurveNode::mr displacement edge length", "" { + Properties70: { + P: "d|mr displacement edge length", "Number", "", "A",2 + } + } + AnimationCurveNode: 2957627492896, "AnimCurveNode::mr displacement max displace", "" { + Properties70: { + P: "d|mr displacement max displace", "Number", "", "A",20 + } + } + AnimationCurveNode: 2957627492272, "AnimCurveNode::mr displacement parametric subdivision level", "" { + Properties70: { + P: "d|mr displacement parametric subdivision level", "Integer", "", "A",5 + } + } + AnimationCurveNode: 2957627488320, "AnimCurveNode::MaxHandle", "" { + Properties70: { + P: "d|MaxHandle", "Integer", "", "A",1 + } + } + AnimationLayer: 2957566198176, "AnimLayer::BaseLayer", "" { + } + CollectionExclusive: 2959111896352, "DisplayLayer::Box", "DisplayLayer" { + Properties70: { + P: "Color", "ColorRGB", "Color", "",0.607999980449677,0,0.157000005245209 + } + } +} + +; Object connections +;------------------------------------------------------------------ + +Connections: { + + ;Model::Box, Model::RootNode + C: "OO",2957618625584,0 + + ;AnimLayer::BaseLayer, AnimStack::Take 001 + C: "OO",2957566198176,2957627494560 + + ;AnimCurveNode::mr displacement use global settings, AnimLayer::BaseLayer + C: "OO",2957627500176,2957566198176 + + ;AnimCurveNode::mr displacement view dependent, AnimLayer::BaseLayer + C: "OO",2957627490192,2957566198176 + + ;AnimCurveNode::mr displacement method, AnimLayer::BaseLayer + C: "OO",2957627487904,2957566198176 + + ;AnimCurveNode::mr displacement smoothing on, AnimLayer::BaseLayer + C: "OO",2957627492688,2957566198176 + + ;AnimCurveNode::mr displacement edge length, AnimLayer::BaseLayer + C: "OO",2957627492064,2957566198176 + + ;AnimCurveNode::mr displacement max displace, AnimLayer::BaseLayer + C: "OO",2957627492896,2957566198176 + + ;AnimCurveNode::mr displacement parametric subdivision level, AnimLayer::BaseLayer + C: "OO",2957627492272,2957566198176 + + ;AnimCurveNode::MaxHandle, AnimLayer::BaseLayer + C: "OO",2957627488320,2957566198176 + + ;Texture::Map #2, Material::Default + C: "OP",2957776714320,2957776713840, "DiffuseColor" + + ;Video::Map #2, Texture::Map #2 + C: "OO",2957776707120,2957776714320 + + ;Video::Map #2, Texture::Map #2 + C: "OO",2957776707121,2957776714321 + + ;Geometry::, Model::Box + C: "OO",2957764348544,2957618625584 + + ;Material::Default, Model::Box + C: "OO",2957776713840,2957618625584 + + ;AnimCurveNode::mr displacement use global settings, Model::Box + C: "OP",2957627500176,2957618625584, "mr displacement use global settings" + + ;AnimCurveNode::mr displacement view dependent, Model::Box + C: "OP",2957627490192,2957618625584, "mr displacement view dependent" + + ;AnimCurveNode::mr displacement method, Model::Box + C: "OP",2957627487904,2957618625584, "mr displacement method" + + ;AnimCurveNode::mr displacement smoothing on, Model::Box + C: "OP",2957627492688,2957618625584, "mr displacement smoothing on" + + ;AnimCurveNode::mr displacement edge length, Model::Box + C: "OP",2957627492064,2957618625584, "mr displacement edge length" + + ;AnimCurveNode::mr displacement max displace, Model::Box + C: "OP",2957627492896,2957618625584, "mr displacement max displace" + + ;AnimCurveNode::mr displacement parametric subdivision level, Model::Box + C: "OP",2957627492272,2957618625584, "mr displacement parametric subdivision level" + + ;AnimCurveNode::MaxHandle, Model::Box + C: "OP",2957627488320,2957618625584, "MaxHandle" + + ;Model::Box, DisplayLayer::Box + C: "OO",2957618625584,2959111896352 +} +;Takes section +;---------------------------------------------------- + +Takes: { + Current: "Take 001" + Take: "Take 001" { + FileName: "Take_001.tak" + LocalTime: 0,153953860000 + ReferenceTime: 0,153953860000 + } +} diff --git a/test/models/FBX/close_to_identity_transforms.fbx b/test/models/FBX/close_to_identity_transforms.fbx new file mode 100644 index 000000000..c0b990282 --- /dev/null +++ b/test/models/FBX/close_to_identity_transforms.fbx @@ -0,0 +1,855 @@ +; FBX 7.5.0 project file +; ---------------------------------------------------- + +FBXHeaderExtension: { + FBXHeaderVersion: 1003 + FBXVersion: 7500 + CreationTimeStamp: { + Version: 1000 + Year: 2019 + Month: 5 + Day: 14 + Hour: 17 + Minute: 27 + Second: 42 + Millisecond: 70 + } + Creator: "FBX SDK/FBX Plugins version 2018.1.1" + SceneInfo: "SceneInfo::GlobalInfo", "UserData" { + Type: "UserData" + Version: 100 + MetaData: { + Version: 100 + Title: "" + Subject: "" + Author: "" + Keywords: "" + Revision: "" + Comment: "" + } + Properties70: { + P: "DocumentUrl", "KString", "Url", "", "U:\Some\Absolute\Path\cubes_with_mirroring_and_pivot.fbx" + P: "SrcDocumentUrl", "KString", "Url", "", "U:\Some\Absolute\Path\cubes_with_mirroring_and_pivot.fbx" + P: "Original", "Compound", "", "" + P: "Original|ApplicationVendor", "KString", "", "", "Autodesk" + P: "Original|ApplicationName", "KString", "", "", "Maya" + P: "Original|ApplicationVersion", "KString", "", "", "201800" + P: "Original|DateTime_GMT", "DateTime", "", "", "14/05/2019 16:27:42.070" + P: "Original|FileName", "KString", "", "", "U:\Some\Absolute\Path\cubes_with_mirroring_and_pivot.fbx" + P: "LastSaved", "Compound", "", "" + P: "LastSaved|ApplicationVendor", "KString", "", "", "Autodesk" + P: "LastSaved|ApplicationName", "KString", "", "", "Maya" + P: "LastSaved|ApplicationVersion", "KString", "", "", "201800" + P: "LastSaved|DateTime_GMT", "DateTime", "", "", "14/05/2019 16:27:42.070" + P: "Original|ApplicationActiveProject", "KString", "", "", "U:\Some\Absolute\Path" + } + } +} +GlobalSettings: { + Version: 1000 + Properties70: { + P: "UpAxis", "int", "Integer", "",1 + P: "UpAxisSign", "int", "Integer", "",1 + P: "FrontAxis", "int", "Integer", "",2 + P: "FrontAxisSign", "int", "Integer", "",1 + P: "CoordAxis", "int", "Integer", "",0 + P: "CoordAxisSign", "int", "Integer", "",1 + P: "OriginalUpAxis", "int", "Integer", "",1 + P: "OriginalUpAxisSign", "int", "Integer", "",1 + P: "UnitScaleFactor", "double", "Number", "",100 + P: "OriginalUnitScaleFactor", "double", "Number", "",1 + P: "AmbientColor", "ColorRGB", "Color", "",0,0,0 + P: "DefaultCamera", "KString", "", "", "Producer Perspective" + P: "TimeMode", "enum", "", "",11 + P: "TimeProtocol", "enum", "", "",2 + P: "SnapOnFrameMode", "enum", "", "",0 + P: "TimeSpanStart", "KTime", "Time", "",1924423250 + P: "TimeSpanStop", "KTime", "Time", "",384884650000 + P: "CustomFrameRate", "double", "Number", "",-1 + P: "TimeMarker", "Compound", "", "" + P: "CurrentTimeMarker", "int", "Integer", "",-1 + } +} + +; Documents Description +;------------------------------------------------------------------ + +Documents: { + Count: 1 + Document: 1827132552544, "", "Scene" { + Properties70: { + P: "SourceObject", "object", "", "" + P: "ActiveAnimStackName", "KString", "", "", "Take 001" + } + RootNode: 0 + } +} + +; Document References +;------------------------------------------------------------------ + +References: { +} + +; Object definitions +;------------------------------------------------------------------ + +Definitions: { + Version: 100 + Count: 13 + ObjectType: "GlobalSettings" { + Count: 1 + } + ObjectType: "AnimationStack" { + Count: 1 + PropertyTemplate: "FbxAnimStack" { + Properties70: { + P: "Description", "KString", "", "", "" + P: "LocalStart", "KTime", "Time", "",0 + P: "LocalStop", "KTime", "Time", "",0 + P: "ReferenceStart", "KTime", "Time", "",0 + P: "ReferenceStop", "KTime", "Time", "",0 + } + } + } + ObjectType: "AnimationLayer" { + Count: 1 + PropertyTemplate: "FbxAnimLayer" { + Properties70: { + P: "Weight", "Number", "", "A",100 + P: "Mute", "bool", "", "",0 + P: "Solo", "bool", "", "",0 + P: "Lock", "bool", "", "",0 + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "BlendMode", "enum", "", "",0 + P: "RotationAccumulationMode", "enum", "", "",0 + P: "ScaleAccumulationMode", "enum", "", "",0 + P: "BlendModeBypass", "ULongLong", "", "",0 + } + } + } + ObjectType: "Geometry" { + Count: 4 + PropertyTemplate: "FbxMesh" { + Properties70: { + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "BBoxMin", "Vector3D", "Vector", "",0,0,0 + P: "BBoxMax", "Vector3D", "Vector", "",0,0,0 + P: "Primary Visibility", "bool", "", "",1 + P: "Casts Shadows", "bool", "", "",1 + P: "Receive Shadows", "bool", "", "",1 + } + } + } + ObjectType: "Material" { + Count: 2 + PropertyTemplate: "FbxSurfaceLambert" { + Properties70: { + P: "ShadingModel", "KString", "", "", "Lambert" + P: "MultiLayer", "bool", "", "",0 + P: "EmissiveColor", "Color", "", "A",0,0,0 + P: "EmissiveFactor", "Number", "", "A",1 + P: "AmbientColor", "Color", "", "A",0.2,0.2,0.2 + P: "AmbientFactor", "Number", "", "A",1 + P: "DiffuseColor", "Color", "", "A",0.8,0.8,0.8 + P: "DiffuseFactor", "Number", "", "A",1 + P: "Bump", "Vector3D", "Vector", "",0,0,0 + P: "NormalMap", "Vector3D", "Vector", "",0,0,0 + P: "BumpFactor", "double", "Number", "",1 + P: "TransparentColor", "Color", "", "A",0,0,0 + P: "TransparencyFactor", "Number", "", "A",0 + P: "DisplacementColor", "ColorRGB", "Color", "",0,0,0 + P: "DisplacementFactor", "double", "Number", "",1 + P: "VectorDisplacementColor", "ColorRGB", "Color", "",0,0,0 + P: "VectorDisplacementFactor", "double", "Number", "",1 + } + } + } + ObjectType: "Model" { + Count: 4 + PropertyTemplate: "FbxNode" { + Properties70: { + P: "QuaternionInterpolate", "enum", "", "",0 + P: "RotationOffset", "Vector3D", "Vector", "",0,0,0 + P: "RotationPivot", "Vector3D", "Vector", "",0,0,0 + P: "ScalingOffset", "Vector3D", "Vector", "",0,0,0 + P: "ScalingPivot", "Vector3D", "Vector", "",0,0,0 + P: "TranslationActive", "bool", "", "",0 + P: "TranslationMin", "Vector3D", "Vector", "",0,0,0 + P: "TranslationMax", "Vector3D", "Vector", "",0,0,0 + P: "TranslationMinX", "bool", "", "",0 + P: "TranslationMinY", "bool", "", "",0 + P: "TranslationMinZ", "bool", "", "",0 + P: "TranslationMaxX", "bool", "", "",0 + P: "TranslationMaxY", "bool", "", "",0 + P: "TranslationMaxZ", "bool", "", "",0 + P: "RotationOrder", "enum", "", "",0 + P: "RotationSpaceForLimitOnly", "bool", "", "",0 + P: "RotationStiffnessX", "double", "Number", "",0 + P: "RotationStiffnessY", "double", "Number", "",0 + P: "RotationStiffnessZ", "double", "Number", "",0 + P: "AxisLen", "double", "Number", "",10 + P: "PreRotation", "Vector3D", "Vector", "",0,0,0 + P: "PostRotation", "Vector3D", "Vector", "",0,0,0 + P: "RotationActive", "bool", "", "",0 + P: "RotationMin", "Vector3D", "Vector", "",0,0,0 + P: "RotationMax", "Vector3D", "Vector", "",0,0,0 + P: "RotationMinX", "bool", "", "",0 + P: "RotationMinY", "bool", "", "",0 + P: "RotationMinZ", "bool", "", "",0 + P: "RotationMaxX", "bool", "", "",0 + P: "RotationMaxY", "bool", "", "",0 + P: "RotationMaxZ", "bool", "", "",0 + P: "InheritType", "enum", "", "",0 + P: "ScalingActive", "bool", "", "",0 + P: "ScalingMin", "Vector3D", "Vector", "",0,0,0 + P: "ScalingMax", "Vector3D", "Vector", "",1,1,1 + P: "ScalingMinX", "bool", "", "",0 + P: "ScalingMinY", "bool", "", "",0 + P: "ScalingMinZ", "bool", "", "",0 + P: "ScalingMaxX", "bool", "", "",0 + P: "ScalingMaxY", "bool", "", "",0 + P: "ScalingMaxZ", "bool", "", "",0 + P: "GeometricTranslation", "Vector3D", "Vector", "",0,0,0 + P: "GeometricRotation", "Vector3D", "Vector", "",0,0,0 + P: "GeometricScaling", "Vector3D", "Vector", "",1,1,1 + P: "MinDampRangeX", "double", "Number", "",0 + P: "MinDampRangeY", "double", "Number", "",0 + P: "MinDampRangeZ", "double", "Number", "",0 + P: "MaxDampRangeX", "double", "Number", "",0 + P: "MaxDampRangeY", "double", "Number", "",0 + P: "MaxDampRangeZ", "double", "Number", "",0 + P: "MinDampStrengthX", "double", "Number", "",0 + P: "MinDampStrengthY", "double", "Number", "",0 + P: "MinDampStrengthZ", "double", "Number", "",0 + P: "MaxDampStrengthX", "double", "Number", "",0 + P: "MaxDampStrengthY", "double", "Number", "",0 + P: "MaxDampStrengthZ", "double", "Number", "",0 + P: "PreferedAngleX", "double", "Number", "",0 + P: "PreferedAngleY", "double", "Number", "",0 + P: "PreferedAngleZ", "double", "Number", "",0 + P: "LookAtProperty", "object", "", "" + P: "UpVectorProperty", "object", "", "" + P: "Show", "bool", "", "",1 + P: "NegativePercentShapeSupport", "bool", "", "",1 + P: "DefaultAttributeIndex", "int", "Integer", "",-1 + P: "Freeze", "bool", "", "",0 + P: "LODBox", "bool", "", "",0 + P: "Lcl Translation", "Lcl Translation", "", "A",0,0,0 + P: "Lcl Rotation", "Lcl Rotation", "", "A",0,0,0 + P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1 + P: "Visibility", "Visibility", "", "A",1 + P: "Visibility Inheritance", "Visibility Inheritance", "", "",1 + } + } + } +} + +; Object properties +;------------------------------------------------------------------ + +Objects: { + Geometry: 1827080157856, "Geometry::", "Mesh" { + Vertices: *24 { + a: -0.5,-0.5,0.5,0.5,-0.5,0.5,-0.5,0.5,0.5,0.5,0.5,0.5,-0.5,0.5,-0.5,0.5,0.5,-0.5,-0.5,-0.5,-0.5,0.5,-0.5,-0.5 + } + PolygonVertexIndex: *24 { + a: 0,1,3,-3,2,3,5,-5,4,5,7,-7,6,7,1,-1,1,7,5,-4,6,0,2,-5 + } + Edges: *12 { + a: 0,1,2,3,5,6,7,9,10,11,13,15 + } + GeometryVersion: 124 + LayerElementNormal: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Normals: *72 { + a: 0,0,1,0,0,1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,1,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0 + } + NormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementBinormal: 0 { + Version: 102 + Name: "map1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Binormals: *72 { + a: 0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,-0,1,0,-0,1,0,0,1,-0,-0,1,0,0,1,0,0,1,0,0,1,0,0,1,0 + } + BinormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + + } + LayerElementTangent: 0 { + Version: 102 + Name: "map1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Tangents: *72 { + a: 1,-0,-0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,0,0,-1,0,0,-1,0,-0,-1,0,0,-1,0,-0,1,0,-0,1,0,-0,1,0,-0,1 + } + TangentsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementUV: 0 { + Version: 101 + Name: "map1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "IndexToDirect" + UV: *28 { + a: 0.375,0,0.625,0,0.375,0.25,0.625,0.25,0.375,0.5,0.625,0.5,0.375,0.75,0.625,0.75,0.375,1,0.625,1,0.875,0,0.875,0.25,0.125,0,0.125,0.25 + } + UVIndex: *24 { + a: 0,1,3,2,2,3,5,4,4,5,7,6,6,7,9,8,1,10,11,3,12,0,2,13 + } + } + LayerElementSmoothing: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByEdge" + ReferenceInformationType: "Direct" + Smoothing: *12 { + a: 0,0,0,0,0,0,0,0,0,0,0,0 + } + } + LayerElementMaterial: 0 { + Version: 101 + Name: "" + MappingInformationType: "AllSame" + ReferenceInformationType: "IndexToDirect" + Materials: *1 { + a: 0 + } + } + Layer: 0 { + Version: 100 + LayerElement: { + Type: "LayerElementNormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementBinormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementTangent" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementMaterial" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementSmoothing" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementUV" + TypedIndex: 0 + } + } + } + Geometry: 1827080155296, "Geometry::", "Mesh" { + Vertices: *24 { + a: -0.5,-0.5,0.5,0.5,-0.5,0.5,-0.5,0.5,0.5,0.5,0.5,0.5,-0.5,0.5,-0.5,0.5,0.5,-0.5,-0.5,-0.5,-0.5,0.5,-0.5,-0.5 + } + PolygonVertexIndex: *24 { + a: 0,1,3,-3,2,3,5,-5,4,5,7,-7,6,7,1,-1,1,7,5,-4,6,0,2,-5 + } + Edges: *12 { + a: 0,1,2,3,5,6,7,9,10,11,13,15 + } + GeometryVersion: 124 + LayerElementNormal: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Normals: *72 { + a: 0,0,1,0,0,1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,1,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0 + } + NormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementBinormal: 0 { + Version: 102 + Name: "map1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Binormals: *72 { + a: 0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,-0,1,0,-0,1,0,0,1,-0,-0,1,0,0,1,0,0,1,0,0,1,0,0,1,0 + } + BinormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + + } + LayerElementTangent: 0 { + Version: 102 + Name: "map1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Tangents: *72 { + a: 1,-0,-0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,0,0,-1,0,0,-1,0,-0,-1,0,0,-1,0,-0,1,0,-0,1,0,-0,1,0,-0,1 + } + TangentsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementUV: 0 { + Version: 101 + Name: "map1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "IndexToDirect" + UV: *28 { + a: 0.375,0,0.625,0,0.375,0.25,0.625,0.25,0.375,0.5,0.625,0.5,0.375,0.75,0.625,0.75,0.375,1,0.625,1,0.875,0,0.875,0.25,0.125,0,0.125,0.25 + } + UVIndex: *24 { + a: 0,1,3,2,2,3,5,4,4,5,7,6,6,7,9,8,1,10,11,3,12,0,2,13 + } + } + LayerElementSmoothing: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByEdge" + ReferenceInformationType: "Direct" + Smoothing: *12 { + a: 0,0,0,0,0,0,0,0,0,0,0,0 + } + } + LayerElementMaterial: 0 { + Version: 101 + Name: "" + MappingInformationType: "AllSame" + ReferenceInformationType: "IndexToDirect" + Materials: *1 { + a: 0 + } + } + Layer: 0 { + Version: 100 + LayerElement: { + Type: "LayerElementNormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementBinormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementTangent" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementMaterial" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementSmoothing" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementUV" + TypedIndex: 0 + } + } + } + Geometry: 1827080156320, "Geometry::", "Mesh" { + Vertices: *24 { + a: -0.5,-0.5,0.5,0.5,-0.5,0.5,-0.5,0.5,0.5,0.5,0.5,0.5,-0.5,0.5,-0.5,0.5,0.5,-0.5,-0.5,-0.5,-0.5,0.5,-0.5,-0.5 + } + PolygonVertexIndex: *24 { + a: 0,1,3,-3,2,3,5,-5,4,5,7,-7,6,7,1,-1,1,7,5,-4,6,0,2,-5 + } + Edges: *12 { + a: 0,1,2,3,5,6,7,9,10,11,13,15 + } + GeometryVersion: 124 + LayerElementNormal: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Normals: *72 { + a: 0,0,1,0,0,1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,1,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0 + } + NormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementBinormal: 0 { + Version: 102 + Name: "map1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Binormals: *72 { + a: 0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,-0,1,0,-0,1,0,0,1,-0,-0,1,0,0,1,0,0,1,0,0,1,0,0,1,0 + } + BinormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + + } + LayerElementTangent: 0 { + Version: 102 + Name: "map1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Tangents: *72 { + a: 1,-0,-0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,0,0,-1,0,0,-1,0,-0,-1,0,0,-1,0,-0,1,0,-0,1,0,-0,1,0,-0,1 + } + TangentsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementUV: 0 { + Version: 101 + Name: "map1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "IndexToDirect" + UV: *28 { + a: 0.375,0,0.625,0,0.375,0.25,0.625,0.25,0.375,0.5,0.625,0.5,0.375,0.75,0.625,0.75,0.375,1,0.625,1,0.875,0,0.875,0.25,0.125,0,0.125,0.25 + } + UVIndex: *24 { + a: 0,1,3,2,2,3,5,4,4,5,7,6,6,7,9,8,1,10,11,3,12,0,2,13 + } + } + LayerElementSmoothing: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByEdge" + ReferenceInformationType: "Direct" + Smoothing: *12 { + a: 0,0,0,0,0,0,0,0,0,0,0,0 + } + } + LayerElementMaterial: 0 { + Version: 101 + Name: "" + MappingInformationType: "AllSame" + ReferenceInformationType: "IndexToDirect" + Materials: *1 { + a: 0 + } + } + Layer: 0 { + Version: 100 + LayerElement: { + Type: "LayerElementNormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementBinormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementTangent" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementMaterial" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementSmoothing" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementUV" + TypedIndex: 0 + } + } + } + Geometry: 1827080139424, "Geometry::", "Mesh" { + Vertices: *588 { + a: -0.499999970197678,-0.5,0.5,0.500000059604645,-0.5,0.5,-0.499999970197678,0.5,0.5,0.500000059604645,0.5,0.5,-0.499999970197678,0.5,-0.49999988079071,0.500000059604645,0.5,-0.49999988079071,-0.499999970197678,-0.5,-0.49999988079071,0.500000059604645,-0.5,-0.49999988079071,0,0,0.5,0,-0.5,0.5,0.500000059604645,0,0.5,0,0.5,0.5,-0.499999970197678,0,0.5,0,0.5,1.19209289550781e-07,0.500000059604645,0.5,1.19209289550781e-07,0,0.5,-0.49999988079071,-0.499999970197678,0.5,1.19209289550781e-07,0,0,-0.49999988079071,0.500000059604645,0,-0.49999988079071,0,-0.5,-0.49999988079071,-0.499999970197678,0,-0.49999988079071,0,-0.5,1.19209289550781e-07,0.500000059604645,-0.5,1.19209289550781e-07,-0.499999970197678,-0.5,1.19209289550781e-07,0.500000059604645,0,1.19209289550781e-07,-0.499999970197678,0,1.19209289550781e-07,-0.25,-0.25,0.5,-0.499999970197678,-0.25,0.5,-0.25,-0.5,0.5,0,-0.25,0.5,-0.25,0,0.5,-0.25,0.5,0.25,-0.499999970197678,0.5,0.25,-0.25,0.5,0.5,0,0.5,0.25,-0.25,0.5,1.19209289550781e-07,-0.25,0.25,-0.49999988079071,-0.499999970197678,0.25,-0.49999988079071,-0.25,0.5,-0.49999988079071,0,0.25,-0.49999988079071,-0.25,0,-0.49999988079071,-0.25,-0.5,-0.24999988079071,-0.499999970197678,-0.5,-0.24999988079071,-0.25,-0.5,-0.49999988079071,0,-0.5,-0.24999988079071,-0.25,-0.5,1.19209289550781e-07,0.500000059604645,-0.25,0.25,0.500000059604645,-0.25,0.5,0.500000059604645,-0.5,0.25,0.500000059604645,-0.25,1.19209289550781e-07,0.500000059604645,0,0.25,-0.499999970197678,-0.25,-0.24999988079071,-0.499999970197678,-0.25,-0.49999988079071,-0.499999970197678,-0.25,1.19209289550781e-07,-0.499999970197678,0,-0.24999988079071,0.250000059604645,-0.25,0.5,0.250000059604645,-0.5,0.5,0.250000059604645,0,0.5,0.250000059604645,0.25,0.5,0.500000059604645,0.25,0.5,0.250000059604645,0.5,0.5,0,0.25,0.5,-0.25,0.25,0.5,-0.499999970197678,0.25,0.5,0.250000059604645,0.5,0.25,0.500000059604645,0.5,0.25,0.250000059604645,0.5,1.19209289550781e-07,0.250000059604645,0.5,-0.24999988079071,0.500000059604645,0.5,-0.24999988079071,0.250000059604645,0.5,-0.49999988079071, +0,0.5,-0.24999988079071,-0.25,0.5,-0.24999988079071,-0.499999970197678,0.5,-0.24999988079071,0.250000059604645,0.25,-0.49999988079071,0.500000059604645,0.25,-0.49999988079071,0.250000059604645,0,-0.49999988079071,0.250000059604645,-0.25,-0.49999988079071,0.500000059604645,-0.25,-0.49999988079071,0.250000059604645,-0.5,-0.49999988079071,0,-0.25,-0.49999988079071,-0.25,-0.25,-0.49999988079071,0.250000059604645,-0.5,-0.24999988079071,0.500000059604645,-0.5,-0.24999988079071,0.250000059604645,-0.5,1.19209289550781e-07,0.250000059604645,-0.5,0.25,0,-0.5,0.25,-0.25,-0.5,0.25,-0.499999970197678,-0.5,0.25,0.500000059604645,-0.25,-0.24999988079071,0.500000059604645,0,-0.24999988079071,0.500000059604645,0.25,-0.24999988079071,0.500000059604645,0.25,1.19209289550781e-07,0.500000059604645,0.25,0.25,-0.499999970197678,-0.25,0.25,-0.499999970197678,0,0.25,-0.499999970197678,0.25,0.25,-0.499999970197678,0.25,1.19209289550781e-07,-0.499999970197678,0.25,-0.24999988079071,-0.594913899898529,0,0.594913899898529,-0.152911216020584,0,0.714658200740814,-0.594913899898529,-0.152911216020584,0.594913899898529,-0.152911216020584,-0.152911216020584,0.714658200740814,-0.594913899898529,0.594913899898529,7.29137497046395e-08,-0.152911216020584,0.714658200740814,7.29137497046395e-08,-0.594913899898529,0.594913899898529,0.152911216020584,-0.152911216020584,0.714658200740814,0.152911216020584,-0.594913899898529,0,-0.594913899898529,-0.152911216020584,0,-0.714658200740814,-0.594913899898529,0.152911216020584,-0.594913899898529,-0.152911216020584,0.152911216020584,-0.714658200740814,-0.594913899898529,-0.594913899898529,7.29137497046395e-08,-0.152911216020584,-0.714658200740814,7.29137497046395e-08,-0.594913899898529,-0.594913899898529,-0.152911216020584,-0.152911216020584,-0.714658200740814,-0.152911216020584,0.594913899898529,0,0.594913899898529,0.714658200740814,0,0.152911216020584,0.594913899898529,-0.152911216020584,0.594913899898529,0.714658200740814,-0.152911216020584,0.152911216020584,-0.714658200740814,0,-0.152911216020584,-0.594913899898529,-0.152911216020584,-0.594913899898529, +-0.714658200740814,-0.152911216020584,-0.152911216020584,8.62321627254444e-17,-0.594913899898529,0.594913899898529,8.62321627254444e-17,-0.152911216020584,0.714658200740814,0.152911230921745,-0.594913899898529,0.594913899898529,0.152911230921745,-0.152911216020584,0.714658200740814,0.152911230921745,0,0.714658200740814,0.594913899898529,0.152911216020584,0.594913899898529,0.152911230921745,0.152911216020584,0.714658200740814,8.62321627254444e-17,0.594913899898529,0.594913899898529,8.62321627254444e-17,0.152911216020584,0.714658200740814,-0.152911216020584,0.594913899898529,0.594913899898529,-0.152911216020584,0.152911216020584,0.714658200740814,8.62321627254444e-17,0.714658200740814,0.152911216020584,0.152911230921745,0.594913899898529,0.594913899898529,0.152911230921745,0.714658200740814,0.152911216020584,0.594913899898529,0.594913899898529,7.29137497046395e-08,0.152911230921745,0.714658200740814,7.29137497046395e-08,0.594913899898529,0.594913899898529,-0.152911216020584,0.152911230921745,0.714658200740814,-0.152911216020584,8.62321627254444e-17,0.594913899898529,-0.594913899898529,8.62321627254444e-17,0.714658200740814,-0.152911216020584,-0.152911216020584,0.594913899898529,-0.594913899898529,-0.152911216020584,0.714658200740814,-0.152911216020584,8.62321627254444e-17,0.152911216020584,-0.714658200740814,0.152911230921745,0.594913899898529,-0.594913899898529,0.152911230921745,0.152911216020584,-0.714658200740814,0.594913899898529,0,-0.594913899898529,0.152911230921745,0,-0.714658200740814,0.594913899898529,-0.152911216020584,-0.594913899898529,0.152911230921745,-0.152911216020584,-0.714658200740814,8.62321627254444e-17,-0.594913899898529,-0.594913899898529,8.62321627254444e-17,-0.152911216020584,-0.714658200740814,-0.152911216020584,-0.594913899898529,-0.594913899898529,-0.152911216020584,-0.152911216020584,-0.714658200740814,8.62321627254444e-17,-0.714658200740814,-0.152911216020584,0.152911230921745,-0.594913899898529,-0.594913899898529,0.152911230921745,-0.714658200740814,-0.152911216020584,0.594913899898529,-0.594913899898529,7.29137497046395e-08, +0.152911230921745,-0.714658200740814,7.29137497046395e-08,0.594913899898529,-0.594913899898529,0.152911216020584,0.152911230921745,-0.714658200740814,0.152911216020584,8.62321627254444e-17,-0.714658200740814,0.152911216020584,-0.152911216020584,-0.594913899898529,0.594913899898529,-0.152911216020584,-0.714658200740814,0.152911216020584,0.714658200740814,-0.152911216020584,7.29137497046395e-08,0.594913899898529,-0.594913899898529,-0.152911216020584,0.714658200740814,-0.152911216020584,-0.152911216020584,0.714658200740814,0,-0.152911216020584,0.594913899898529,0.152911216020584,-0.594913899898529,0.714658200740814,0.152911216020584,-0.152911216020584,0.714658200740814,0.152911216020584,7.29137497046395e-08,0.594913899898529,0.594913899898529,0.152911216020584,0.714658200740814,0.152911216020584,0.152911216020584,-0.714658200740814,-0.152911216020584,7.29137497046395e-08,-0.594913899898529,-0.594913899898529,0.152911216020584,-0.714658200740814,-0.152911216020584,0.152911216020584,-0.714658200740814,0,0.152911216020584,-0.594913899898529,0.152911216020584,0.594913899898529,-0.714658200740814,0.152911216020584,0.152911216020584,-0.714658200740814,0.152911216020584,7.29137497046395e-08,-0.594913899898529,0.594913899898529,-0.152911216020584,-0.714658200740814,0.152911216020584,-0.152911216020584,-0.541863918304443,-0.541864037513733,0.541863918304443,8.62321627254444e-17,0,0.714658200740814,-0.541863918304443,0.541863918304443,0.541863918304443,8.62321627254444e-17,0.714658200740814,7.29137497046395e-08,-0.541863918304443,0.541863918304443,-0.541863799095154,8.62321627254444e-17,0,-0.714658200740814,-0.541863918304443,-0.541864037513733,-0.541863799095154,8.62321627254444e-17,-0.714658200740814,7.29137497046395e-08,0.541863977909088,-0.541864037513733,0.541863918304443,0.714658200740814,0,7.29137497046395e-08,-0.714658200740814,0,7.29137497046395e-08,0.541863977909088,0.541863918304443,0.541863918304443,0.541863977909088,0.541863918304443,-0.541863799095154,0.541863977909088,-0.541864037513733,-0.541863799095154 + } + PolygonVertexIndex: *768 { + a: 99,98,100,-102,103,102,104,-106,107,106,108,-110,111,110,112,-114,115,114,116,-118,118,106,119,-121,122,121,123,-125,125,114,126,-128,129,128,130,-132,132,128,133,-135,136,135,137,-139,140,139,141,-143,143,139,144,-146,147,146,148,-150,151,150,152,-154,154,150,155,-157,158,157,159,-161,161,121,162,-164,164,157,165,-167,167,146,168,-170,170,135,171,-173,173,110,174,-176,176,98,177,-179,179,102,180,-182,100,182,162,-102,162,121,122,-102,122,183,99,-102,104,184,130,-106,130,128,132,-106,132,185,103,-106,108,186,141,-110,141,139,143,-110,143,187,107,-110,112,188,152,-114,152,150,154,-114,154,189,111,-114,116,190,159,-118,159,157,164,-118,164,191,115,-118,119,188,112,-121,112,110,173,-121,173,192,118,-121,123,190,116,-125,116,114,125,-125,125,183,122,-125,126,193,133,-128,133,128,129,-128,129,183,125,-128,130,184,177,-132,177,98,99,-132,99,183,129,-132,133,193,171,-135,171,135,136,-135,136,185,132,-135,137,194,144,-139,144,139,140,-139,140,185,136,-139,141,186,180,-143,180,102,103,-143,103,185,140,-143,144,194,168,-146,168,146,147,-146,147,187,143,-146,148,195,155,-150,155,150,151,-150,151,187,147,-150,152,188,119,-154,119,106,107,-154,107,187,151,-154,155,195,165,-157,165,157,158,-157,158,189,154,-157,159,190,123,-161,123,121,161,-161,161,189,158,-161,162,182,174,-164,174,110,111,-164,111,189,161,-164,165,195,148,-167,148,146,167,-167,167,191,164,-167,168,194,137,-170,137,135,170,-170,170,191,167,-170,171,193,126,-173,126,114,115,-173,115,191,170,-173,174,182,100,-176,100,98,176,-176,176,192,173,-176,177,184,104,-179,104,102,179,-179,179,192,176,-179,180,186,108,-182,108,106,118,-182,118,192,179,-182,30,26,27,-13,35,31,32,-17,40,36,37,-21,45,41,42,-24,50,46,47,-11,54,51,52,-21,29,55,56,-10,57,58,59,-11,61,62,33,-12,34,64,60,-12,66,67,68,-15,70,71,38,-16,39,73,69,-16,75,76,77,-19,79,80,43,-20,44,81,78,-20,83,84,48,-23,85,86,28,-10,49,88,82,-23,89,90,74,-19,91,92,65,-15,53,93,87,-24,94,95,63,-13,96,97,72,-17,27,26,28,-1,28,26,29,-10,29,26,30,-9,32,31,33,-3,33,31,34,-12,34,31,35,-14,37,36,38,-5,38,36,39, +-16,39,36,40,-18,42,41,43,-7,43,41,44,-20,44,41,45,-22,47,46,48,-2,48,46,49,-23,49,46,50,-25,52,51,42,-7,42,51,53,-24,53,51,54,-26,56,55,47,-2,47,55,57,-11,57,55,29,-9,59,58,60,-4,60,58,61,-12,61,58,57,-9,33,62,63,-3,63,62,30,-13,30,62,61,-9,60,64,65,-4,65,64,66,-15,66,64,34,-14,68,67,69,-6,69,67,70,-16,70,67,66,-14,38,71,72,-5,72,71,35,-17,35,71,70,-14,69,73,74,-6,74,73,75,-19,75,73,39,-18,77,76,78,-8,78,76,79,-20,79,76,75,-18,43,80,52,-7,52,80,40,-21,40,80,79,-18,78,81,82,-8,82,81,83,-23,83,81,44,-22,48,84,56,-2,56,84,85,-10,85,84,83,-22,28,86,87,-1,87,86,45,-24,45,86,85,-22,82,88,77,-8,77,88,89,-19,89,88,49,-25,74,90,68,-6,68,90,91,-15,91,90,89,-25,65,92,59,-4,59,92,50,-11,50,92,91,-25,87,93,27,-1,27,93,94,-13,94,93,53,-26,63,95,32,-3,32,95,96,-17,96,95,94,-26,72,97,37,-5,37,97,54,-21,54,97,96,-26 + } + Edges: *384 { + a: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,102,104,105,108,109,110,114,116,117,120,121,122,126,128,129,132,133,134,138,140,141,144,145,146,150,152,153,156,158,162,164,165,168,170,174,176,180,181,182,186,188,193,194,198,205,206,210,212,216,217,218,222,224,229,230,234,241,242,246,248,252,253,254,258,260,266,270,277,278,282,284,290,294,296,301,302,306,314,318,320,326,330,332,338,342,350,354,356,362,366,368,374,378,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,481,482,483,485,490,491,493,494,495,497,502,503,505,506,507,509,514,515,517,518,519,521,526,527,529,530,531,533,538,539,541,543,545,550,551,553,555,557,563,565,566,567,569,575,577,578,581,589,590,593,599,601,602,603,605,611,613,614,617,625,626,629,635,637,638,639,641,647,649,653,661,662,665,671,673,677,683,685,686,689,697,701,707,709,713,719,721,725,733,737,743,745,749,755,757,761 + } + GeometryVersion: 124 + LayerElementNormal: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Normals: *2304 { + a: -0.261487126350403,0,0.965206980705261,-0.261487126350403,0,0.965206980705261,-0.261487126350403,0,0.965206980705261,-0.261487126350403,0,0.965206980705261,-0.261487185955048,0.965206921100616,0,-0.261487185955048,0.965206921100616,0,-0.261487185955048,0.965206921100616,0,-0.261487185955048,0.965206921100616,0,-0.261487126350403,0,-0.965206980705261,-0.261487126350403,0,-0.965206980705261,-0.261487126350403,0,-0.965206980705261,-0.261487126350403,0,-0.965206980705261,-0.261487156152725,-0.965206921100616,0,-0.261487156152725,-0.965206921100616,0,-0.261487156152725,-0.965206921100616,0,-0.261487156152725,-0.965206921100616,0,0.965206980705261,0,0.261487185955048,0.965206980705261,0,0.261487185955048,0.965206980705261,0,0.261487185955048,0.965206980705261,0,0.261487185955048,-0.965206980705261,0,-0.261487185955048,-0.965206980705261,0,-0.261487185955048,-0.965206980705261,0,-0.261487185955048,-0.965206980705261,0,-0.261487185955048,0,-0.261487156152725,0.965206921100616,0,-0.261487156152725,0.965206921100616,0,-0.261487156152725,0.965206921100616,0,-0.261487156152725,0.965206921100616,0.261487126350403,0,0.965206980705261,0.261487126350403,0,0.965206980705261,0.261487126350403,0,0.965206980705261,0.261487126350403,0,0.965206980705261,0,0.261487185955048,0.965206980705261,0,0.261487185955048,0.965206980705261,0,0.261487185955048,0.965206980705261,0,0.261487185955048,0.965206980705261,0,0.965206921100616,0.261487156152725,0,0.965206921100616,0.261487156152725,0,0.965206921100616,0.261487156152725,0,0.965206921100616,0.261487156152725,0.261487156152725,0.965206921100616,0,0.261487156152725,0.965206921100616,0,0.261487156152725,0.965206921100616,0,0.261487156152725,0.965206921100616,0,0,0.965206980705261,-0.261487126350403,0,0.965206980705261,-0.261487126350403,0,0.965206980705261,-0.261487126350403,0,0.965206980705261,-0.261487126350403,0,0.261487156152725,-0.965206921100616,0,0.261487156152725,-0.965206921100616,0,0.261487156152725,-0.965206921100616,0,0.261487156152725,-0.965206921100616,0.261487126350403,0,-0.965206980705261, +0.261487126350403,0,-0.965206980705261,0.261487126350403,0,-0.965206980705261,0.261487126350403,0,-0.965206980705261,0,-0.261487185955048,-0.965206980705261,0,-0.261487185955048,-0.965206980705261,0,-0.261487185955048,-0.965206980705261,0,-0.261487185955048,-0.965206980705261,0,-0.965206921100616,-0.261487156152725,0,-0.965206921100616,-0.261487156152725,0,-0.965206921100616,-0.261487156152725,0,-0.965206921100616,-0.261487156152725,0.261487185955048,-0.965206921100616,0,0.261487185955048,-0.965206921100616,0,0.261487185955048,-0.965206921100616,0,0.261487185955048,-0.965206921100616,0,0,-0.965206980705261,0.261487126350403,0,-0.965206980705261,0.261487126350403,0,-0.965206980705261,0.261487126350403,0,-0.965206980705261,0.261487126350403,0.965206980705261,-0.261487066745758,0,0.965206980705261,-0.261487066745758,0,0.965206980705261,-0.261487066745758,0,0.965206980705261,-0.261487066745758,0,0.965206980705261,0,-0.261487185955048,0.965206980705261,0,-0.261487185955048,0.965206980705261,0,-0.261487185955048,0.965206980705261,0,-0.261487185955048,0.965206980705261,0.261487126350403,0,0.965206980705261,0.261487126350403,0,0.965206980705261,0.261487126350403,0,0.965206980705261,0.261487126350403,0,-0.965206980705261,-0.261487126350403,0,-0.965206980705261,-0.261487126350403,0,-0.965206980705261,-0.261487126350403,0,-0.965206980705261,-0.261487126350403,0,-0.965206980705261,0,0.261487185955048,-0.965206980705261,0,0.261487185955048,-0.965206980705261,0,0.261487185955048,-0.965206980705261,0,0.261487185955048,-0.965206980705261,0.261487066745758,0,-0.965206980705261,0.261487066745758,0,-0.965206980705261,0.261487066745758,0,-0.965206980705261,0.261487066745758,0,-0.211917281150818,-0.211917445063591,0.954034626483917,0,0,1,-0.211917281150818,-0.21191743016243,0.954034626483917,-0.211917266249657,-0.211917445063591,0.954034626483917,0,-0.261487185955048,0.965206980705261,0,-0.261487185955048,0.965206980705261,0,-0.261487185955048,0.965206980705261,0,-0.261487185955048,0.965206980705261,0,0,1,0,0,1,0,0,1,0,0,1,-0.211917445063591,0.954034626483917,0.211917281150818, +0,1,0,-0.211917445063591,0.954034626483917,0.211917281150818,-0.211917445063591,0.954034626483917,0.211917266249657,0,0.965206980705261,0.261487126350403,0,0.965206980705261,0.261487126350403,0,0.965206980705261,0.261487126350403,0,0.965206980705261,0.261487126350403,0,1,0,0,1,0,0,1,0,0,1,0,-0.211917594075203,0.211917579174042,-0.954034507274628,0,0,-1,-0.211917594075203,0.211917579174042,-0.954034507274628,-0.211917594075203,0.211917564272881,-0.954034507274628,0,0.261487185955048,-0.965206980705261,0,0.261487185955048,-0.965206980705261,0,0.261487185955048,-0.965206980705261,0,0.261487185955048,-0.965206980705261,0,0,-1,0,0,-1,0,0,-1,0,0,-1,-0.211917325854301,-0.954034626483917,-0.211917325854301,0,-1,0,-0.211917325854301,-0.954034686088562,-0.211917325854301,-0.211917355656624,-0.954034686088562,-0.211917355656624,0,-0.965206980705261,-0.261487126350403,0,-0.965206980705261,-0.261487126350403,0,-0.965206980705261,-0.261487126350403,0,-0.965206980705261,-0.261487126350403,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0.954034626483917,-0.211917251348495,0.21191731095314,1,0,0,0.954034626483917,-0.211917251348495,0.211917340755463,0.954034745693207,-0.211917266249657,0.211917325854301,0.965206980705261,-0.261487126350403,0,0.965206980705261,-0.261487126350403,0,0.965206980705261,-0.261487126350403,0,0.965206980705261,-0.261487126350403,0,1,0,0,1,0,0,1,0,0,1,0,0,-0.954034686088562,-0.211917355656624,-0.211917459964752,-1,0,0,-0.954034626483917,-0.211917325854301,-0.211917445063591,-0.954034686088562,-0.211917355656624,-0.211917474865913,-0.965206980705261,-0.261487066745758,0,-0.965206980705261,-0.261487066745758,0,-0.965206980705261,-0.261487066745758,0,-0.965206980705261,-0.261487066745758,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0.211917281150818,-0.211917400360107,0.954034626483917,0,0,1,0.211917266249657,-0.211917415261269,0.954034626483917,0.211917266249657,-0.211917415261269,0.954034626483917,0.261487126350403,0,0.965206980705261,0.261487126350403,0,0.965206980705261,0.261487126350403,0,0.965206980705261,0.261487126350403,0,0.965206980705261, +0,0,1,0,0,1,0,0,1,0,0,1,0.211917296051979,0.211917415261269,0.954034626483917,0,0,1,0.211917296051979,0.211917415261269,0.954034626483917,0.211917281150818,0.211917415261269,0.954034626483917,0,0.261487156152725,0.965206921100616,0,0.261487156152725,0.965206921100616,0,0.261487156152725,0.965206921100616,0,0.261487156152725,0.965206921100616,0,0,1,0,0,1,0,0,1,0,0,1,-0.211917281150818,0.211917445063591,0.954034626483917,0,0,1,-0.211917281150818,0.211917445063591,0.954034626483917,-0.211917266249657,0.211917445063591,0.954034626483917,-0.261487126350403,0,0.965206980705261,-0.261487126350403,0,0.965206980705261,-0.261487126350403,0,0.965206980705261,-0.261487126350403,0,0.965206980705261,0,0,1,0,0,1,0,0,1,0,0,1,0.211917445063591,0.954034626483917,0.211917445063591,0,1,0,0.211917415261269,0.954034566879272,0.211917415261269,0.211917445063591,0.954034626483917,0.211917445063591,0.261487185955048,0.965206921100616,0,0.261487185955048,0.965206921100616,0,0.261487185955048,0.965206921100616,0,0.261487185955048,0.965206921100616,0,0,1,0,0,1,0,0,1,0,0,1,0,0.211917445063591,0.954034626483917,-0.211917489767075,0,1,0,0.21191743016243,0.954034566879272,-0.211917489767075,0.211917445063591,0.954034566879272,-0.211917474865913,0,0.965206921100616,-0.261487156152725,0,0.965206921100616,-0.261487156152725,0,0.965206921100616,-0.261487156152725,0,0.965206921100616,-0.261487156152725,0,1,0,0,1,0,0,1,0,0,1,0,-0.211917459964752,0.954034686088562,-0.211917340755463,0,1,0,-0.211917445063591,0.954034626483917,-0.21191731095314,-0.211917489767075,0.954034686088562,-0.211917355656624,-0.261487156152725,0.965206921100616,0,-0.261487156152725,0.965206921100616,0,-0.261487156152725,0.965206921100616,0,-0.261487156152725,0.965206921100616,0,0,1,0,0,1,0,0,1,0,0,1,0,0.211917608976364,0.211917579174042,-0.954034507274628,0,0,-1,0.211917608976364,0.211917564272881,-0.954034566879272,0.211917594075203,0.211917579174042,-0.954034507274628,0.261487126350403,0,-0.965206980705261,0.261487126350403,0,-0.965206980705261,0.261487126350403,0,-0.965206980705261, +0.261487126350403,0,-0.965206980705261,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0.211917579174042,-0.211917534470558,-0.954034507274628,0,0,-1,0.211917594075203,-0.211917549371719,-0.954034507274628,0.211917594075203,-0.211917519569397,-0.954034507274628,0,-0.261487156152725,-0.965206921100616,0,-0.261487156152725,-0.965206921100616,0,-0.261487156152725,-0.965206921100616,0,-0.261487156152725,-0.965206921100616,0,0,-1,0,0,-1,0,0,-1,0,0,-1,-0.211917594075203,-0.211917549371719,-0.954034507274628,0,0,-1,-0.211917579174042,-0.211917534470558,-0.954034566879272,-0.211917594075203,-0.211917519569397,-0.954034507274628,-0.261487126350403,0,-0.965206980705261,-0.261487126350403,0,-0.965206980705261,-0.261487126350403,0,-0.965206980705261,-0.261487126350403,0,-0.965206980705261,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0.211917340755463,-0.954034686088562,-0.211917519569397,0,-1,0,0.211917325854301,-0.954034626483917,-0.211917489767075,0.211917325854301,-0.954034686088562,-0.211917489767075,0.261487156152725,-0.965206921100616,0,0.261487156152725,-0.965206921100616,0,0.261487156152725,-0.965206921100616,0,0.261487156152725,-0.965206921100616,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0.211917281150818,-0.954034626483917,0.211917445063591,0,-1,0,0.211917296051979,-0.954034626483917,0.211917445063591,0.211917266249657,-0.954034626483917,0.211917445063591,0,-0.965206921100616,0.261487156152725,0,-0.965206921100616,0.261487156152725,0,-0.965206921100616,0.261487156152725,0,-0.965206921100616,0.261487156152725,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-0.211917296051979,-0.954034626483917,0.211917296051979,0,-1,0,-0.211917296051979,-0.954034626483917,0.211917296051979,-0.211917281150818,-0.954034686088562,0.211917281150818,-0.261487185955048,-0.965206921100616,0,-0.261487185955048,-0.965206921100616,0,-0.261487185955048,-0.965206921100616,0,-0.261487185955048,-0.965206921100616,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0.954034626483917,-0.211917325854301,-0.211917400360107,1,0,0,0.954034686088562,-0.211917340755463,-0.211917400360107,0.954034686088562,-0.211917355656624,-0.21191743016243,0.965206980705261,0,-0.261487185955048, +0.965206980705261,0,-0.261487185955048,0.965206980705261,0,-0.261487185955048,0.965206980705261,0,-0.261487185955048,1,0,0,1,0,0,1,0,0,1,0,0,0.954034686088562,0.211917370557785,-0.211917489767075,1,0,0,0.954034686088562,0.211917355656624,-0.211917445063591,0.954034626483917,0.211917355656624,-0.211917445063591,0.965206980705261,0.261487066745758,0,0.965206980705261,0.261487066745758,0,0.965206980705261,0.261487066745758,0,0.965206980705261,0.261487066745758,0,1,0,0,1,0,0,1,0,0,1,0,0,0.954034626483917,0.211917296051979,0.211917385458946,1,0,0,0.954034626483917,0.211917296051979,0.211917385458946,0.954034626483917,0.211917266249657,0.211917355656624,0.965206980705261,0,0.261487185955048,0.965206980705261,0,0.261487185955048,0.965206980705261,0,0.261487185955048,0.965206980705261,0,0.261487185955048,1,0,0,1,0,0,1,0,0,1,0,0,-0.954034626483917,-0.211917251348495,0.211917400360107,-1,0,0,-0.954034626483917,-0.211917266249657,0.211917400360107,-0.954034626483917,-0.211917251348495,0.211917400360107,-0.965206980705261,0,0.261487185955048,-0.965206980705261,0,0.261487185955048,-0.965206980705261,0,0.261487185955048,-0.965206980705261,0,0.261487185955048,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-0.954034626483917,0.211917281150818,0.211917445063591,-1,0,0,-0.954034626483917,0.211917281150818,0.211917445063591,-0.954034626483917,0.211917266249657,0.211917445063591,-0.965206980705261,0.261487126350403,0,-0.965206980705261,0.261487126350403,0,-0.965206980705261,0.261487126350403,0,-0.965206980705261,0.261487126350403,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-0.954034566879272,0.211917355656624,-0.211917519569397,-1,0,0,-0.954034686088562,0.211917370557785,-0.211917534470558,-0.954034626483917,0.211917355656624,-0.211917504668236,-0.965206980705261,0,-0.261487185955048,-0.965206980705261,0,-0.261487185955048,-0.965206980705261,0,-0.261487185955048,-0.965206980705261,0,-0.261487185955048,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0, +1,0,0,1,0,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0, +-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0 + } + NormalsW: *768 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementBinormal: 0 { + Version: 102 + Name: "map1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Binormals: *2304 { + a: 0,1,0,0,1,0,0,1,0,0,1,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,-0,1,0,-0,1,0,-0,1,0,-0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0.965207040309906,0.261487185955048,0,0.965207040309906,0.261487185955048,0,0.965207040309906,0.261487185955048,0,0.965207040309906,0.261487185955048,-0,1,0,-0,1,0,-0,1,0,-0,1,0,0,0.965206980705261,-0.261487185955048,0,0.965206980705261,-0.261487185955048,0,0.965206980705261,-0.261487185955048,0,0.965206980705261,-0.261487185955048,0,0.261487185955048,-0.965207040309906,0,0.261487185955048,-0.965207040309906,0,0.261487185955048,-0.965207040309906,0,0.261487185955048,-0.965207040309906,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-0.261487126350403,-0.965206980705261,0,-0.261487126350403,-0.965206980705261,0,-0.261487126350403,-0.965206980705261,0,-0.261487126350403,-0.965206980705261,0,-0.965207040309906,-0.261487185955048,0,-0.965207040309906,-0.261487185955048,0,-0.965207040309906,-0.261487185955048,0,-0.965207040309906,-0.261487185955048,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-0.965206980705261,0.261487185955048,0,-0.965206980705261,0.261487185955048,0,-0.965206980705261,0.261487185955048,0,-0.965206980705261,0.261487185955048,0,-0.261487185955048,0.965207040309906,0,-0.261487185955048,0.965207040309906,0,-0.261487185955048,0.965207040309906,0,-0.261487185955048,0.965207040309906,0,0,1,0,0,1,0,0,1,0,0,1,0,0.261487126350403,0.965206980705261,0,0.261487126350403,0.965206980705261,0,0.261487126350403,0.965206980705261,0,0.261487126350403,0.965206980705261,0.261487066745758,0.965206980705261,0,0.261487066745758,0.965206980705261,0,0.261487066745758,0.965206980705261,0,0.261487066745758,0.965206980705261,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,0,1,0,0,1,0,0,1,0, +0,1,0,0.261487066745758,0.965206980705261,0,0.261487066745758,0.965206980705261,0,0.261487066745758,0.965206980705261,0,0.261487066745758,0.965206980705261,0,-0.0554696954786777,0.977241098880768,0.204750895500183,0.0677256807684898,0.997704029083252,-0,0.0995207726955414,0.966452240943909,0.236782029271126,0.022095151245594,0.974918127059937,0.221464186906815,0,0.965206980705261,0.261487185955048,0,0.965206980705261,0.261487185955048,0,0.965206980705261,0.261487185955048,0,0.965206980705261,0.261487185955048,0,1,-0,0,1,-0,0,1,-0,0,1,-0,-0.0554696545004845,0.204750746488571,-0.977241158485413,0.0677258297801018,0,-0.997703969478607,0.0995210781693459,0.23678195476532,-0.966452240943909,0.0220953319221735,0.221464082598686,-0.974918246269226,0,0.261487126350403,-0.965206980705261,0,0.261487126350403,-0.965206980705261,0,0.261487126350403,-0.965206980705261,0,0.261487126350403,-0.965206980705261,0,0,-1,0,0,-1,0,0,-1,0,0,-1,-0.055469736456871,-0.977241158485413,-0.204751059412956,0.0677258297801018,-0.997703969478607,0,0.0995209664106369,-0.966452121734619,-0.236782252788544,0.0220952294766903,-0.974918127059937,-0.221464350819588,0,-0.965206980705261,-0.261487185955048,0,-0.965206980705261,-0.261487185955048,0,-0.965206980705261,-0.261487185955048,0,-0.965206980705261,-0.261487185955048,0,-1,-0,0,-1,-0,0,-1,-0,0,-1,-0,-0.0554696694016457,-0.204750820994377,0.977241218090057,0.0677259787917137,0,0.997703969478607,0.0995214134454727,-0.236782014369965,0.966452121734619,0.0220954976975918,-0.221464157104492,0.974918186664581,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,0,1,0,0,1,0,0,1,0,0,1,0.204750746488571,0.977241218090057,0.0554696507751942,0,0.997704029083252,-0.0677256807684898,0.236781880259514,0.966452300548553,-0.0995208472013474,0.221463993191719,0.974918186664581,-0.0220952127128839,0.261487126350403,0.965206980705261,0,0.261487126350403,0.965206980705261,0,0.261487126350403,0.965206980705261,0,0.261487126350403,0.965206980705261,0, +-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0.204750791192055,0.977241098880768,-0.0554696619510651,0,0.997704029083252,0.0677257031202316,-0.236781939864159,0.966452240943909,0.099520817399025,-0.221464112401009,0.974918246269226,0.022095188498497,-0.261487066745758,0.965206980705261,0,-0.261487066745758,0.965206980705261,0,-0.261487066745758,0.965206980705261,0,-0.261487066745758,0.965206980705261,0,0,1,0,0,1,0,0,1,0,0,1,0,-0.099520780146122,0.966452240943909,0.236781999468803,-0.0677256733179092,0.997704029083252,0,0.0554696880280972,0.977241098880768,0.204750865697861,-0.022095151245594,0.974918127059937,0.221464157104492,-0,1,0,-0,1,0,-0,1,0,-0,1,0,0,1,-0,0,1,-0,0,1,-0,0,1,-0,-0.0554696880280972,0.977241098880768,-0.204750865697861,0.0677258223295212,0.997703969478607,-0,0.0995210558176041,0.966452181339264,-0.236782059073448,0.0220952890813351,0.974918246269226,-0.221464201807976,0,0.965207040309906,-0.261487185955048,0,0.965207040309906,-0.261487185955048,0,0.965207040309906,-0.261487185955048,0,0.965207040309906,-0.261487185955048,0,1,-0,0,1,-0,0,1,-0,0,1,-0,-0.0995210707187653,0.966452240943909,-0.236782118678093,-0.0677258297801018,0.997703969478607,0,0.0554696954786777,0.977241098880768,-0.204750895500183,-0.0220952928066254,0.974918186664581,-0.221464231610298,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,-0,0,1,-0,0,1,-0,-0.0995210558176041,0.236782118678093,-0.966452240943909,-0.0677258223295212,0,-0.997703969478607,0.0554696917533875,0.204750880599022,-0.977241098880768,-0.0220952853560448,0.221464216709137,-0.974918127059937,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,-0.0554697066545486,-0.204750955104828,-0.977241158485413,0.0677259713411331,0,-0.997703969478607,0.0995213314890862,-0.236782237887383,-0.966452181339264,0.0220954213291407,-0.221464306116104,-0.974918186664581,0,-0.261487185955048,-0.965207040309906,0,-0.261487185955048,-0.965207040309906,0,-0.261487185955048,-0.965207040309906,0,-0.261487185955048,-0.965207040309906,0,0,-1,0,0,-1,0,0,-1,0,0,-1,-0.0995213687419891,-0.236782059073448,-0.966452181339264, +-0.0677259787917137,0,-0.997703969478607,0.0554696545004845,-0.204750776290894,-0.977241158485413,-0.0220954604446888,-0.221464172005653,-0.974918246269226,0,0,-1,0,0,-1,-0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,-0.0995209515094757,-0.966452121734619,-0.236782252788544,-0.0677258223295212,-0.997703969478607,-0,0.0554697290062904,-0.977241098880768,-0.204751014709473,-0.0220952183008194,-0.974918127059937,-0.221464365720749,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,-0,0,-1,-0,0,-1,-0,0,-1,-0,-0.0554697290062904,-0.977241158485413,0.204751014709473,0.0677256733179092,-0.997704029083252,0,0.099520668387413,-0.966452181339264,0.236782178282738,0.022095087915659,-0.974918127059937,0.221464276313782,0,-0.965207040309906,0.261487185955048,0,-0.965207040309906,0.261487185955048,0,-0.965207040309906,0.261487185955048,0,-0.965207040309906,0.261487185955048,0,-1,0,0,-1,0,0,-1,-0,0,-1,0,-0.0995206907391548,-0.966452181339264,0.236782178282738,-0.0677256807684898,-0.997704029083252,-0,0.0554697178304195,-0.977241098880768,0.20475098490715,-0.0220951028168201,-0.974918127059937,0.221464276313782,0,-1,-0,0,-1,-0,-0,-1,0,0,-1,-0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-0.0995213836431503,-0.23678220808506,0.966452121734619,-0.0677259713411331,0,0.997703969478607,0.0554697066545486,-0.204750940203667,0.977241098880768,-0.0220954623073339,-0.221464291214943,0.974918186664581,-0,0,1,-0,0,1,-0,0,1,-0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0.0554696954786777,0.204750895500183,0.977241098880768,0.0677258223295212,0,0.997703969478607,0.0995211154222488,0.236782103776932,0.966452181339264,0.0220953226089478,0.221464246511459,0.974918186664581,0,0.261487185955048,0.965207040309906,0,0.261487185955048,0.965207040309906,0,0.261487185955048,0.965207040309906,0,0.261487185955048,0.965207040309906,0,0,1,0,0,1,0,0,1,0,0,1,-0.0995211601257324,0.23678195476532,0.966452181339264,-0.0677258297801018,0,0.997703969478607,0.0554696619510651,0.204750776290894,0.977241218090057,-0.022095363587141,0.221464082598686,0.974918246269226,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0.236781939864159,0.966452240943909,0.0995208621025085, +-0,0.997704029083252,0.0677257031202316,0.204750806093216,0.977241158485413,-0.0554696656763554,0.221464112401009,0.974918186664581,0.0220951996743679,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0.204750806093216,0.977241098880768,0.0554696694016457,0,0.997703969478607,-0.0677258521318436,-0.236782044172287,0.966452240943909,-0.0995211452245712,-0.221464172005653,0.974918246269226,-0.0220953542739153,-0.261487066745758,0.965206980705261,0,-0.261487066745758,0.965206980705261,0,-0.261487066745758,0.965206980705261,0,-0.261487066745758,0.965206980705261,0,0,1,-0,-0,1,0,-0,1,0,-0,1,0,-0.236781984567642,0.966452300548553,0.09952113032341,-0,0.997703969478607,0.0677258297801018,-0.204750761389732,0.977241158485413,-0.0554696582257748,-0.221464067697525,0.974918246269226,0.022095350548625,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,0,1,-0,-0,1,0,-0.236781880259514,0.966452360153198,-0.0995208248496056,0,0.997704029083252,-0.0677256807684898,-0.20475073158741,0.977241158485413,0.0554696470499039,-0.221464022994041,0.974918246269226,-0.0220951903611422,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0.204750746488571,0.977241158485413,-0.0554696545004845,0,0.997703969478607,0.0677258297801018,0.23678195476532,0.966452240943909,0.0995210781693459,0.221464082598686,0.974918246269226,0.0220953319221735,0.261487126350403,0.965206980705261,0,0.261487126350403,0.965206980705261,0,0.261487126350403,0.965206980705261,0,0.261487126350403,0.965206980705261,0,0,1,0,0,1,0,0,1,0,0,1,0,0.236782059073448,0.966452240943909,-0.0995211154222488,0,0.997703969478607,-0.0677258521318436,0.204750806093216,0.977241098880768,0.0554696656763554,0.221464142203331,0.974918186664581,-0.0220953188836575,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,-0,-1,0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,0,-0,-1,0,-0,-1, +0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,-0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-1,-0,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1, +-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0 + } + BinormalsW: *768 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + + } + LayerElementTangent: 0 { + Version: 102 + Name: "map1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Tangents: *2304 { + a: 0.965206980705261,-0,0.261487126350403,0.965206980705261,-0,0.261487126350403,0.965206980705261,-0,0.261487126350403,0.965206980705261,-0,0.261487126350403,0.965207040309906,0.26148721575737,0,0.965207040309906,0.26148721575737,0,0.965207040309906,0.26148721575737,0,0.965207040309906,0.26148721575737,0,0.965206980705261,-0,-0.261487126350403,0.965206980705261,-0,-0.261487126350403,0.965206980705261,-0,-0.261487126350403,0.965206980705261,-0,-0.261487126350403,0.965206980705261,-0.261487156152725,-0,0.965206980705261,-0.261487156152725,-0,0.965206980705261,-0.261487156152725,-0,0.965206980705261,-0.261487156152725,-0,0.26148721575737,0,-0.965207040309906,0.26148721575737,0,-0.965207040309906,0.26148721575737,0,-0.965207040309906,0.26148721575737,0,-0.965207040309906,-0.26148721575737,0,0.965207040309906,-0.26148721575737,0,0.965207040309906,-0.26148721575737,0,0.965207040309906,-0.26148721575737,0,0.965207040309906,1,0,-0,1,0,-0,1,0,-0,1,0,-0,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,0,1,-0,0,1,-0,-0,1,-0,0,0.965206980705261,-0.261487156152725,0,0.965206980705261,-0.261487156152725,0,0.965206980705261,-0.261487156152725,0,0.965206980705261,-0.261487156152725,0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,1,0,0,1,0,0,1,-0,0,1,0,0,0.965206980705261,0,0.261487126350403,0.965206980705261,0,0.261487126350403,0.965206980705261,0,0.261487126350403,0.965206980705261,0,0.261487126350403,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0.965207040309906,0.26148721575737,-0,0.965207040309906,0.26148721575737,-0,0.965207040309906,0.26148721575737,-0,0.965207040309906,0.26148721575737,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,-0,0,-1,-0,0,-1,-0,0,-1,-0,0,-1,-0.26148721575737,-0,-0.965207040309906,-0.26148721575737,-0,-0.965207040309906,-0.26148721575737,-0,-0.965207040309906,-0.26148721575737,-0,-0.965207040309906,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0.26148721575737,-0,0.965207040309906, +0.26148721575737,-0,0.965207040309906,0.26148721575737,-0,0.965207040309906,0.26148721575737,-0,0.965207040309906,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0.97571212053299,0.00952975451946259,0.218849271535873,0.997704029083252,-0.0677256807684898,0,0.972207129001617,-0.145124465227127,0.183717742562294,0.977037787437439,-0.0680116266012192,0.201919630169868,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,0.97571212053299,0.218849420547485,-0.0095297135412693,0.997703969478607,0,0.0677258297801018,0.972207069396973,0.183717846870422,0.145124778151512,0.977037727832794,0.20191977918148,0.0680118054151535,1,-0,0,1,-0,0,1,-0,0,1,-0,0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,0.975712060928345,-0.00952968932688236,-0.21884959936142,0.997703969478607,0.0677258297801018,0,0.972207009792328,0.145124763250351,-0.183717966079712,0.977037727832794,0.0680118054151535,-0.201919928193092,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,0.97571212053299,-0.218849316239357,0.00952973961830139,0.997703969478607,0,-0.0677259787917137,0.972207069396973,-0.183717638254166,-0.145125105977058,0.977037727832794,-0.201919630169868,-0.068011961877346,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,0.218849301338196,0.00952973961830139,-0.97571212053299,0,-0.0677256807684898,-0.997704029083252,0.1837178170681,-0.145124509930611,-0.972207069396973,0.201919689774513,-0.0680116564035416,-0.977037787437439,-0,0,-1,-0,0,-1,-0,0,-1,-0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,-0.218849420547485,0.0095297172665596,0.97571212053299,0,-0.0677257031202316,0.997704029083252,-0.183717906475067,-0.145124524831772,0.972207069396973,-0.201919823884964,-0.068011686205864,0.977037727832794,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0.972207129001617,0.145124465227127,-0.183717742562294,0.997704029083252,0.0677256733179092,0,0.97571212053299,-0.00952975824475288,-0.218849256634712,0.977037787437439,0.0680116191506386,-0.201919630169868,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403, +1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,0.97571212053299,0.00952975172549486,-0.218849286437035,0.997703969478607,-0.0677258223295212,0,0.972207069396973,-0.14512474834919,-0.183717682957649,0.977037847042084,-0.0680117681622505,-0.20191964507103,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,0,1,-0,0,1,-0,-0,1,-0,0,0.972207069396973,0.14512474834919,0.183717668056488,0.997703969478607,0.0677258297801018,0,0.97571212053299,-0.00952975451946259,0.218849271535873,0.977037847042084,0.0680117756128311,0.201919630169868,0.965206980705261,-0,0.261487126350403,0.965206980705261,-0,0.261487126350403,0.965206980705261,-0,0.261487126350403,0.965206980705261,-0,0.261487126350403,1,-0,-0,1,-0,0,1,-0,0,1,-0,0,0.972207069396973,-0.183717831969261,-0.145124778151512,0.997703969478607,0,-0.0677258223295212,0.97571212053299,-0.218849420547485,0.00952972378581762,0.977037727832794,-0.20191977918148,-0.0680117979645729,0.965207040309906,-0.26148721575737,-0,0.965207040309906,-0.26148721575737,-0,0.965207040309906,-0.26148721575737,-0,0.965207040309906,-0.26148721575737,-0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,0.97571212053299,-0.218849420547485,-0.0095297247171402,0.997703969478607,0,0.0677259713411331,0.972207009792328,-0.183717742562294,0.145125061273575,0.977037727832794,-0.201919749379158,0.0680119395256042,1,-0,0,1,-0,0,1,-0,0,1,-0,0,1,0,0,1,0,0,1,-0,-0,1,0,0,0.972207009792328,0.183717772364616,-0.145125061273575,0.997703969478607,0,-0.0677259787917137,0.97571212053299,0.218849420547485,0.00952971447259188,0.977037727832794,0.20191977918148,-0.0680119544267654,0.965206980705261,0.261487156152725,0,0.965206980705261,0.261487156152725,0,0.965206980705261,0.261487156152725,-0,0.965206980705261,0.261487156152725,0,1,-0,0,1,0,0,1,0,0,1,0,0,0.972207009792328,-0.14512474834919,0.183717995882034,0.997703969478607,-0.0677258223295212,0,0.975712060928345,0.00952969118952751,0.218849584460258,0.977037727832794,-0.0680117979645729,0.201919928193092,0.965206980705261,0,0.261487126350403,0.965206980705261,0,0.261487126350403,0.965206980705261,0,0.261487126350403,0.965206980705261,0,0.261487126350403, +1,-0,0,1,-0,0,1,-0,0,1,-0,0,0.975712060928345,-0.00952969212085009,0.218849584460258,0.997704029083252,0.0677256733179092,0,0.972207069396973,0.145124465227127,0.183718055486679,0.977037727832794,0.0680116564035416,0.201919972896576,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,0,0,0.972207069396973,-0.145124480128288,-0.183718040585518,0.997704029083252,-0.0677256807684898,0,0.975712060928345,0.00952969398349524,-0.218849569559097,0.977037727832794,-0.0680116713047028,-0.201919972896576,0.965206980705261,0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,0.965206980705261,-0,-0.261487126350403,0.965206980705261,0,-0.261487126350403,1,0,0,1,0,0,1,0,0,1,0,0,0.972207069396973,0.183717623353004,0.145125105977058,0.997703969478607,0,0.0677259713411331,0.97571212053299,0.218849316239357,-0.00952974893152714,0.977037847042084,0.201919630169868,0.0680119544267654,0.965206980705261,0.261487156152725,0,0.965206980705261,0.261487156152725,0,0.965206980705261,0.261487156152725,0,0.965206980705261,0.261487156152725,0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,0.97571212053299,0.218849271535873,0.00952975638210773,0.997703969478607,0,-0.0677258223295212,0.972207069396973,0.183717668056488,-0.145124807953835,0.977037847042084,0.201919630169868,-0.0680118054151535,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,0.972207069396973,-0.183717682957649,0.145124822854996,0.997703969478607,0,0.0677258297801018,0.97571212053299,-0.218849286437035,-0.00952974427491426,0.977037847042084,-0.201919630169868,0.0680118054151535,0.965207040309906,-0.26148721575737,0,0.965207040309906,-0.26148721575737,0,0.965207040309906,-0.26148721575737,0,0.965207040309906,-0.26148721575737,0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,-0.183717846870422,0.145124554634094,-0.972207069396973,0,0.0677257031202316,-0.997704029083252,-0.218849360942841,-0.0095297284424305,-0.97571212053299,-0.20191977918148,0.068011686205864,-0.977037727832794,-0.26148721575737,-0,-0.965207040309906,-0.26148721575737,-0,-0.965207040309906,-0.26148721575737,-0,-0.965207040309906,-0.26148721575737,-0,-0.965207040309906, +0,0,-1,0,0,-1,0,0,-1,0,0,-1,-0.218849450349808,0.00952971167862415,-0.97571212053299,0,-0.0677258521318436,-0.997703969478607,-0.1837178170681,-0.145124852657318,-0.972207069396973,-0.201919764280319,-0.0680118426680565,-0.977037727832794,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-0,-1,0,0,-1,0,0,-1,0,0,-1,0.183717772364616,0.145124807953835,-0.972207069396973,0,0.0677258297801018,-0.997703969478607,0.218849375844002,-0.00952972657978535,-0.97571212053299,0.201919689774513,0.0680118054151535,-0.977037727832794,0.26148721575737,0,-0.965207040309906,0.26148721575737,0,-0.965207040309906,0.26148721575737,0,-0.965207040309906,0.26148721575737,0,-0.965207040309906,0,0,-1,0,0,-1,0,-0,-1,0,0,-1,0.183717876672745,0.145124495029449,0.972207069396973,0,0.0677256807684898,0.997704029083252,0.218849375844002,-0.00952972192317247,0.97571212053299,0.20191977918148,0.0680116564035416,0.977037727832794,0.26148721575737,-0,0.965207040309906,0.26148721575737,-0,0.965207040309906,0.26148721575737,-0,0.965207040309906,0.26148721575737,-0,0.965207040309906,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0.218849420547485,0.0095297135412693,0.97571212053299,0,-0.0677258297801018,0.997703969478607,0.183717846870422,-0.145124778151512,0.972207069396973,0.20191977918148,-0.0680118054151535,0.977037727832794,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,-0.183717906475067,0.145124837756157,0.972207009792328,0,0.0677258521318436,0.997703969478607,-0.21884948015213,-0.00952970236539841,0.97571212053299,-0.201919838786125,0.0680118277668953,0.977037727832794,-0.26148721575737,0,0.965207040309906,-0.26148721575737,0,0.965207040309906,-0.26148721575737,0,0.965207040309906,-0.26148721575737,0,0.965207040309906,0,-0,1,0,-0,1,0,-0,1,0,-0,1,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,0,0,1,0,0,1,0,0,1,-0,-0,1,0,0,1,0,0,1,0,0,1,-0,0,1,0,0,1,0,0,1,0,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,-0,0,1,0,0,1,0,0,1,0,0,1,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0, +1,0,0,1,0,0,1,-0,0,1,-0,0,1,-0,-0,1,-0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,0,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,0,0,1,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,-0,0,1,-0,-0,1,-0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,0,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,0,0,1,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,0,1,0,0,1,0,0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,-0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,-0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,-0,0,1,-0,-0,1,-0,0,1,-0,-0,1,0,0,1,0,0,1,0,0,1,-0,0,1,-0,0,1,-0,-0,1,-0,0,1,-0,-0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,-0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,0,0,1,-0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,0,0,1,-0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,0,0,1,-0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,0,0,1,-0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,0,0,1,0,0,1,0,0,1,0,0,1, +0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1 + } + TangentsW: *768 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementUV: 0 { + Version: 101 + Name: "map1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "IndexToDirect" + UV: *542 { + a: 0.375,0,0.625,0,0.375,0.25,0.625,0.25,0.375,0.5,0.625,0.5,0.375,0.75,0.625,0.75,0.375,1,0.625,1,0.875,0,0.875,0.25,0.125,0,0.125,0.25,0.5,0.125,0.5,0,0.5,1,0.625,0.125,0.5,0.25,0.375,0.125,0.5,0.375,0.625,0.375,0.75,0.25,0.5,0.5,0.375,0.375,0.25,0.25,0.5,0.625,0.625,0.625,0.875,0.125,0.5,0.75,0.375,0.625,0.125,0.125,0.5,0.875,0.625,0.875,0.75,0,0.5,1,0.375,0.875,0.25,0,0.75,0.125,0.75,0,0.875,0.125,0.75,0.25,0.25,0.125,0.25,0,0.25,0.25,0.125,0.125,0.4375,0.0625,0.375,0.0625,0.4375,0,0.4375,1,0.5,0.0625,0.4375,0.125,0.4375,0.3125,0.375,0.3125,0.3125,0.25,0.4375,0.25,0.5,0.3125,0.4375,0.375,0.4375,0.5625,0.375,0.5625,0.125,0.1875,0.4375,0.5,0.5,0.5625,0.4375,0.625,0.4375,0.8125,0.375,0.8125,0.1875,0,0.4375,0.75,0.5,0.8125,0.4375,0.875,0.6875,0.0625,0.625,0.0625,0.625,0.9375,0.6875,0,0.75,0.0625,0.6875,0.125,0.1875,0.0625,0.375,0.6875,0.125,0.0625,0.1875,0,0.25,0.0625,0.1875,0.125,0.5625,0.0625,0.5625,0,0.5625,1,0.5625,0.125,0.5625,0.1875,0.625,0.1875,0.5625,0.25,0.5,0.1875,0.4375,0.1875,0.375,0.1875,0.5625,0.3125,0.625,0.3125,0.6875,0.25,0.5625,0.375,0.5625,0.4375,0.625,0.4375,0.8125,0.25,0.5625,0.5,0.5,0.4375,0.4375,0.4375,0.375,0.4375,0.1875,0.25,0.5625,0.5625,0.625,0.5625,0.875,0.1875,0.5625,0.625,0.5625,0.6875,0.625,0.6875,0.875,0.0625,0.5625,0.75,0.5,0.6875,0.4375,0.6875,0.375,0.6875,0.5625,0.8125,0.625,0.8125,0.8125,0,0.5625,0.875,0.5625,0.9375,0.625,0.9375,0.5625,1,0.5,0.9375,0.4375,0.9375,0.4375,1,0.375,0.9375,0.3125,0,0.8125,0.0625,0.8125,0,0.875,0.0625,0.8125,0.125,0.8125,0.1875,0.875,0.1875,0.8125,0.25,0.75,0.1875,0.6875,0.1875,0.6875,0.25,0.3125,0.0625,0.3125,0,0.3125,0.125,0.3125,0.1875,0.3125,0.25,0.25,0.1875,0.1875,0.1875,0.1875,0.25,0.125,0.1875,0.4375,0.125,0.375,0.125,0.375,0.0625,0.4375,0.0625,0.4375,0.375,0.375,0.375,0.375,0.3125,0.4375,0.3125,0.4375,0.625,0.375,0.625,0.375,0.5625,0.4375,0.5625,0.4375,0.875,0.375,0.875,0.375,0.8125,0.4375,0.8125,0.6875,0.125,0.625,0.125,0.625,0.0625,0.6875,0.0625,0.1875,0.125,0.125,0.125,0.125,0.0625,0.1875,0.0625,0.5,0.0625,0.5,0,0.5625,0, +0.5625,0.0625,0.5625,0.125,0.625,0.1875,0.5625,0.1875,0.5,0.1875,0.5,0.25,0.4375,0.25,0.4375,0.1875,0.5,0.3125,0.5625,0.25,0.5625,0.3125,0.5625,0.375,0.625,0.375,0.625,0.4375,0.5625,0.4375,0.5,0.4375,0.5,0.5,0.4375,0.5,0.4375,0.4375,0.5,0.5625,0.5625,0.5,0.5625,0.5625,0.5625,0.625,0.625,0.625,0.625,0.6875,0.5625,0.6875,0.5,0.6875,0.5,0.75,0.4375,0.75,0.4375,0.6875,0.5,0.8125,0.5625,0.75,0.5625,0.8125,0.5625,0.875,0.625,0.875,0.625,0.9375,0.5625,0.9375,0.5,0.9375,0.5,1,0.4375,1,0.4375,0.9375,0.75,0.0625,0.75,0,0.8125,0,0.8125,0.0625,0.8125,0.125,0.875,0.125,0.875,0.1875,0.8125,0.1875,0.75,0.1875,0.75,0.25,0.6875,0.25,0.6875,0.1875,0.25,0.0625,0.25,0,0.3125,0,0.3125,0.0625,0.3125,0.125,0.375,0.1875,0.3125,0.1875,0.25,0.1875,0.25,0.25,0.1875,0.25,0.1875,0.1875,0.375,0,0.4375,0,0.5,0.125,0.375,0.25,0.5,0.375,0.375,0.5,0.5,0.625,0.375,0.75,0.5,0.875,0.625,0,0.6875,0,0.75,0.125,0.125,0,0.1875,0,0.25,0.125,0.625,0.25,0.625,0.3125,0.625,0.5,0.375,0.4375,0.625,0.5625,0.625,0.75,0.375,0.6875,0.625,0.8125,0.625,1,0.5625,1,0.375,1,0.375,0.9375,0.875,0,0.875,0.0625,0.875,0.25,0.8125,0.25,0.3125,0.25,0.125,0.25,0.125,0.1875 + } + UVIndex: *768 { + a: 51,19,47,46,57,24,53,52,63,30,59,58,69,36,65,64,75,17,71,70,81,45,78,76,50,15,83,82,85,17,87,86,89,18,55,90,56,18,88,92,95,21,97,96,100,23,61,101,62,23,99,104,107,27,109,108,112,29,67,113,68,29,111,115,118,33,120,119,122,35,124,123,74,39,128,127,130,40,132,131,134,41,136,135,80,43,138,137,139,19,91,140,142,44,144,143,47,0,48,46,48,15,50,46,50,14,51,46,53,2,55,52,55,18,56,52,56,20,57,52,59,4,61,58,61,23,62,58,62,26,63,58,65,6,67,64,67,29,68,64,68,32,69,64,71,1,73,70,73,39,74,70,74,38,75,70,78,12,79,76,79,43,80,76,80,42,81,76,83,1,71,82,71,17,85,82,85,14,50,82,87,3,88,86,88,18,89,86,89,14,85,86,55,2,91,90,91,19,51,90,51,14,89,90,88,3,93,92,93,21,95,92,95,20,56,92,97,5,99,96,99,23,100,96,100,20,95,96,61,4,102,101,102,24,57,101,57,20,100,101,99,5,105,104,105,27,107,104,107,26,62,104,109,7,111,108,111,29,112,108,112,26,107,108,67,6,114,113,114,30,63,113,63,26,112,113,111,7,116,115,116,33,118,115,118,32,68,115,120,9,121,119,121,35,122,119,122,32,118,119,124,8,125,123,125,36,69,123,69,32,122,123,128,10,129,127,129,40,130,127,130,38,74,127,132,11,133,131,133,41,134,131,134,38,130,131,136,3,87,135,87,17,75,135,75,38,134,135,138,0,47,137,47,19,139,137,139,42,80,137,91,2,141,140,141,44,142,140,142,42,139,140,144,13,145,143,145,45,81,143,81,42,142,143,146,149,148,147,150,153,152,151,154,157,156,155,158,161,160,159,162,165,164,163,166,169,168,167,170,173,172,171,174,176,175,163,177,180,179,178,181,183,182,178,184,187,186,185,188,191,190,189,192,194,193,189,195,198,197,196,199,202,201,200,203,205,204,200,206,209,208,207,210,213,212,211,214,217,216,215,218,221,220,219,222,225,224,223,226,229,228,227,230,232,231,147,233,236,235,234,148,149,238,237,238,149,170,171,170,149,146,239,152,153,179,240,179,153,181,178,181,153,150,241,156,157,190,242,190,157,192,189,192,157,154,243,160,161,201,244,201,161,203,200,203,161,158,245,164,165,247,246,247,165,214,215,214,165,162,248,168,169,250,249,250,169,226,227,226,169,166,251,172,173,164,246,164,173,174,163,174,173,170,239,175,176,182,252,182,176,177,178,177,176,174,239, +179,180,231,240,231,180,146,147,146,180,177,239,182,183,253,252,253,183,184,185,184,183,181,241,186,187,193,254,193,187,188,189,188,187,184,241,190,191,255,242,255,191,150,151,150,191,188,241,193,194,256,254,256,194,195,196,195,194,192,243,197,198,204,257,204,198,199,200,199,198,195,243,201,202,258,244,258,202,154,155,154,202,199,243,204,205,259,257,259,205,206,207,206,205,203,245,208,209,261,260,261,209,210,211,210,209,206,245,212,213,263,262,263,213,158,159,158,213,210,245,216,217,265,264,265,217,218,219,218,217,214,248,220,221,267,266,267,221,222,223,222,221,218,248,224,225,175,252,175,225,162,163,162,225,222,248,228,229,148,237,148,229,230,147,230,229,226,251,231,232,268,240,268,232,233,234,233,232,230,251,235,236,270,269,270,236,166,167,166,236,233,251 + } + } + LayerElementSmoothing: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByEdge" + ReferenceInformationType: "Direct" + Smoothing: *384 { + a: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + } + } + LayerElementMaterial: 0 { + Version: 101 + Name: "" + MappingInformationType: "AllSame" + ReferenceInformationType: "IndexToDirect" + Materials: *1 { + a: 0 + } + } + Layer: 0 { + Version: 100 + LayerElement: { + Type: "LayerElementNormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementBinormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementTangent" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementMaterial" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementSmoothing" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementUV" + TypedIndex: 0 + } + } + } + Model: 1827098352032, "Model::Cube2", "Mesh" { + Version: 232 + Properties70: { + P: "RotationActive", "bool", "", "",1 + P: "InheritType", "enum", "", "",1 + P: "ScalingMax", "Vector3D", "Vector", "",0,0,0 + P: "DefaultAttributeIndex", "int", "Integer", "",0 + P: "Lcl Translation", "Lcl Translation", "", "A",-0.0104023897647858,0.00998288810253143,-0.0104375958442688 + P: "Lcl Scaling", "Lcl Scaling", "", "A",0.1,-0.1,0.1 + P: "currentUVSet", "KString", "", "U", "map1" + } + Shading: T + Culling: "CullingOff" + } + Model: 1827098379872, "Model::Cube1", "Mesh" { + Version: 232 + Properties70: { + P: "RotationActive", "bool", "", "",1 + P: "InheritType", "enum", "", "",1 + P: "ScalingMax", "Vector3D", "Vector", "",0,0,0 + P: "DefaultAttributeIndex", "int", "Integer", "",0 + P: "Lcl Translation", "Lcl Translation", "", "A",1.04023897647858,-0.998288810253143,1.04375958442688 + P: "currentUVSet", "KString", "", "U", "map1" + } + Shading: T + Culling: "CullingOff" + } + Model: 1827098382192, "Model::Cube3", "Mesh" { + Version: 232 + Properties70: { + P: "RotationActive", "bool", "", "",1 + P: "InheritType", "enum", "", "",1 + P: "ScalingMax", "Vector3D", "Vector", "",0,0,0 + P: "DefaultAttributeIndex", "int", "Integer", "",0 + P: "Lcl Translation", "Lcl Translation", "", "A",-0.0106711769104004,0.00998288810253143,0.0939023494720459 + P: "Lcl Scaling", "Lcl Scaling", "", "A",0.1,0.1,0.1 + P: "currentUVSet", "KString", "", "U", "map1" + } + Shading: T + Culling: "CullingOff" + } + Model: 1827098384512, "Model::Cube1", "Mesh" { + Version: 232 + Properties70: { + P: "RotationPivot", "Vector3D", "Vector", "",0.0,0.0019,0.0 + P: "ScalingOffset", "Vector3D", "Vector", "",0.0,0.0019,0.0 + P: "ScalingPivot", "Vector3D", "Vector", "",0.0,0.0019,0.0 + P: "RotationActive", "bool", "", "",1 + P: "InheritType", "enum", "", "",1 + P: "ScalingMax", "Vector3D", "Vector", "",0,0,0 + P: "DefaultAttributeIndex", "int", "Integer", "",0 + P: "Lcl Translation", "Lcl Translation", "", "A",0.0,0.0019,0.0 + P: "Lcl Scaling", "Lcl Scaling", "", "A",1.0,1.0019,1.0 + P: "currentUVSet", "KString", "", "U", "map1" + } + Shading: T + Culling: "CullingOff" + } + Material: 1827074508720, "Material::Mat_Green", "" { + Version: 102 + ShadingModel: "lambert" + MultiLayer: 0 + Properties70: { + P: "AmbientColor", "Color", "", "A",0,0,0 + P: "DiffuseColor", "Color", "", "A",0,1,0 + P: "DiffuseFactor", "Number", "", "A",0.800000011920929 + P: "TransparencyFactor", "Number", "", "A",1 + P: "Emissive", "Vector3D", "Vector", "",0,0,0 + P: "Ambient", "Vector3D", "Vector", "",0,0,0 + P: "Diffuse", "Vector3D", "Vector", "",0,0.800000011920929,0 + P: "Opacity", "double", "Number", "",1 + } + } + Material: 1827074501920, "Material::Mat_Red", "" { + Version: 102 + ShadingModel: "lambert" + MultiLayer: 0 + Properties70: { + P: "AmbientColor", "Color", "", "A",0,0,0 + P: "DiffuseColor", "Color", "", "A",1,0,0 + P: "DiffuseFactor", "Number", "", "A",0.800000011920929 + P: "TransparencyFactor", "Number", "", "A",1 + P: "Emissive", "Vector3D", "Vector", "",0,0,0 + P: "Ambient", "Vector3D", "Vector", "",0,0,0 + P: "Diffuse", "Vector3D", "Vector", "",0.800000011920929,0,0 + P: "Opacity", "double", "Number", "",1 + } + } + AnimationStack: 1825586153728, "AnimStack::Take 001", "" { + Properties70: { + P: "LocalStart", "KTime", "Time", "",1924423250 + P: "LocalStop", "KTime", "Time", "",230930790000 + P: "ReferenceStart", "KTime", "Time", "",1924423250 + P: "ReferenceStop", "KTime", "Time", "",230930790000 + } + } + AnimationLayer: 1827070038960, "AnimLayer::BaseLayer", "" { + } +} + +; Object connections +;------------------------------------------------------------------ + +Connections: { + + ;Model::Cube2, Model::RootNode + C: "OO",1827098352032,0 + + ;Model::Cube3, Model::RootNode + C: "OO",1827098382192,0 + + ;AnimLayer::BaseLayer, AnimStack::Take 001 + C: "OO",1827070038960,1825586153728 + + ;Geometry::, Model::Cube2 + C: "OO",1827080157856,1827098352032 + + ;Material::Mat_Green, Model::Cube2 + C: "OO",1827074508720,1827098352032 + + ;Model::Cube1, Model::Cube2 + C: "OO",1827098379872,1827098352032 + + ;Geometry::, Model::Cube1 + C: "OO",1827080155296,1827098379872 + + ;Material::Mat_Green, Model::Cube1 + C: "OO",1827074508720,1827098379872 + + ;Geometry::, Model::Cube3 + C: "OO",1827080156320,1827098382192 + + ;Material::Mat_Red, Model::Cube3 + C: "OO",1827074501920,1827098382192 + + ;Model::Cube1, Model::Cube3 + C: "OO",1827098384512,1827098382192 + + ;Geometry::, Model::Cube1 + C: "OO",1827080139424,1827098384512 + + ;Material::Mat_Red, Model::Cube1 + C: "OO",1827074501920,1827098384512 +} +;Takes section +;---------------------------------------------------- + +Takes: { + Current: "Take 001" + Take: "Take 001" { + FileName: "Take_001.tak" + LocalTime: 1924423250,230930790000 + ReferenceTime: 1924423250,230930790000 + } +} diff --git a/test/models/FBX/huesitos.fbx b/test/models/FBX/huesitos.fbx new file mode 100644 index 000000000..646271392 Binary files /dev/null and b/test/models/FBX/huesitos.fbx differ diff --git a/test/models/FBX/transparentTest.fbx b/test/models/FBX/transparentTest.fbx new file mode 100644 index 000000000..2c4f1cd82 --- /dev/null +++ b/test/models/FBX/transparentTest.fbx @@ -0,0 +1,839 @@ +Kaydara FBX Binary    FBXHeaderExtension\   FBXHeaderVersionI x   +FBXVersionI   EncryptionTypeI  CreationTimeStamp   VersionI   YearI   MonthI    DayI '   HourI ?   MinuteI W   SecondI t   MillisecondID   ' CreatorS" FBX SDK/FBX Plugins version 2017.1  ' SceneInfoS GlobalInfo SceneInfoS UserData  TypeS UserData0   VersionId  MetaData^   VersionId u   TitleS    SubjectS    AuthorS    KeywordsS    RevisionS    CommentS  Properties70  q PS DocumentUrlS KStringS UrlS SC C:\Users\Destranix\Documents\maya\projects\Test\transparentTest.fbx  t PS SrcDocumentUrlS KStringS UrlS SC C:\Users\Destranix\Documents\maya\projects\Test\transparentTest.fbxL  $ PS OriginalS CompoundS S   B PS Original|ApplicationVendorS KStringS S S Autodesk  < PS Original|ApplicationNameS KStringS S S Maya3  ? PS Original|ApplicationVersionS KStringS S S 2017  M PS Original|DateTime_GMTS DateTimeS S S 11/09/2019 14:11:05.265  t PS Original|FileNameS KStringS S SC C:\Users\Destranix\Documents\maya\projects\Test\transparentTest.fbxC  % PS LastSavedS CompoundS S   C PS LastSaved|ApplicationVendorS KStringS S S Autodesk  = PS LastSaved|ApplicationNameS KStringS S S Maya-  @ PS LastSaved|ApplicationVersionS KStringS S S 2017  N PS LastSaved|DateTime_GMTS DateTimeS S S 11/09/2019 14:11:05.265  p PS! Original|ApplicationActiveProjectS KStringS S S/ C:\Users\Destranix\Documents\maya\projects\Test  PS Original|ApplicationNativeFileS KStringS S SB C:\Users\Destranix\Documents\maya\projects\Test\transparentTest.mb    FileIdR (//ȶø+%   CreationTimeS 2019-09-11 16:11:05:324c  6 CreatorS1 FBX SDK/FBX Plugins version 2017.1 build=20161007 GlobalSettings   VersionI  Properties70  ) PS UpAxisS intS IntegerS I " +  - PS + UpAxisSignS intS IntegerS I \ +  , PS FrontAxisS intS IntegerS I +  0 PS FrontAxisSignS intS IntegerS I +  , PS CoordAxisS intS IntegerS I   0 PS CoordAxisSignS intS IntegerS I Q  1 PS OriginalUpAxisS intS IntegerS I  5 PS OriginalUpAxisSignS intS IntegerS I  8 PS UnitScaleFactorS doubleS NumberS D ?(  @ PS OriginalUnitScaleFactorS doubleS NumberS D ?~  H PS AmbientColorS ColorRGBS ColorS D D D  A PS DefaultCameraS KStringS S S Producer Perspective  % PS TimeModeS enumS S I 7  ) PS TimeProtocolS enumS S I q  , PS SnapOnFrameModeS enumS S I  3 PS TimeSpanStartS KTimeS TimeS LR^r  2 PS TimeSpanStopS KTimeS TimeS LY 8  8 PS CustomFrameRateS doubleS NumberS D l  & PS + TimeMarkerS CompoundS S   4 PS CurrentTimeMarkerS intS IntegerS I  Documents   CountI    DocumentL@q S S Scene Properties70o  & PS SourceObjectS objectS S   ; PS ActiveAnimStackNameS KStringS S S Take 001   RootNodeL ! +References (\ DefinitionsR   VersionId i   CountI@    +ObjectTypeS GlobalSettings   CountI    +ObjectTypeS AnimationStack   CountI    PropertyTemplateS FbxAnimStack Properties70x  + PS DescriptionS KStringS S S   0 PS + LocalStartS KTimeS TimeS L   / PS LocalStopS KTimeS TimeS L 5  4 PS ReferenceStartS KTimeS TimeS L v  3 PS ReferenceStopS KTimeS TimeS L Z   +ObjectTypeS AnimationLayer   CountI M   PropertyTemplateS FbxAnimLayer@ Properties70]  * PS WeightS NumberS S AD Y@  ! PS MuteS boolS S I   ! PS SoloS boolS S I   ! PS LockS boolS S I 9  A PS ColorS ColorRGBS ColorS D?D?D?m  & PS BlendModeS enumS S I   5 PS RotationAccumulationModeS enumS S I   2 PS ScaleAccumulationModeS enumS S I 3  5 PS BlendModeBypassS ULongLongS S L   +ObjectTypeS Geometry   CountI   PropertyTemplateS FbxMesh Properties70&  A PS ColorS ColorRGBS ColorS D?D?D?x  D PS BBoxMinS Vector3DS VectorS D D D   D PS BBoxMaxS Vector3DS VectorS D D D   / PS Primary VisibilityS boolS S I ?  * PS Casts ShadowsS boolS S I y  , PS Receive ShadowsS boolS S I   +ObjectTypeS Material   CountI    PropertyTemplateS FbxSurfacePhong Properties70d  1 PS ShadingModelS KStringS S S Phong  ' PS + MultiLayerS boolS S I   B PS EmissiveColorS ColorS S AD D D )  2 PS EmissiveFactorS NumberS S AD ?x  A PS AmbientColorS ColorS S AD?D?D?  1 PS AmbientFactorS NumberS S AD ?  A PS DiffuseColorS ColorS S AD?D?D?E  1 PS DiffuseFactorS NumberS S AD ?  A PS BumpS Vector3DS VectorS D D D   F PS NormalMapS Vector3DS VectorS D D D )  3 PS + BumpFactorS doubleS NumberS D ?|  E PS TransparentColorS ColorS S AD D D   6 PS TransparencyFactorS NumberS S AD   M PS DisplacementColorS ColorRGBS ColorS D D D d  ; PS DisplacementFactorS doubleS NumberS D ?  S PS VectorDisplacementColorS ColorRGBS ColorS D D D   A PS VectorDisplacementFactorS doubleS NumberS D ?d  B PS SpecularColorS ColorS S AD?D?D?  2 PS SpecularFactorS NumberS S AD ?  5 PS ShininessExponentS NumberS S AD 4@9  D PS ReflectionColorS ColorS S AD D D {  4 PS ReflectionFactorS NumberS S AD ? ^#  +ObjectTypeS Texture   CountI Q#   PropertyTemplateS FbxFileTextureD# Properties70^  + PS TextureTypeUseS enumS S I   1 PS Texture alphaS NumberS S AD ?  / PS CurrentMappingTypeS enumS S I   & PS WrapModeUS enumS S I B  & PS WrapModeVS enumS S I s  # PS UVSwapS boolS S I  - PS PremultiplyAlphaS boolS S I  A PS TranslationS VectorS S AD D D I!  > PS RotationS VectorS S AD D D !  = PS ScalingS VectorS S AD ?D ?D ?!  Q PS TextureRotationPivotS Vector3DS VectorS D D D Q"  P PS TextureScalingPivotS Vector3DS VectorS D D D "  4 PS CurrentTextureBlendModeS enumS S I "  , PS UVSetS KStringS S S default#  ( PS UseMaterialS boolS S I 7#  & PS UseMipMapS boolS S I 7  + +ObjectTypeS Model#   CountI 6  PropertyTemplateS FbxNode6 Properties70$  2 PS QuaternionInterpolateS enumS S I q$  K PS RotationOffsetS Vector3DS VectorS D D D $  J PS RotationPivotS Vector3DS VectorS D D D !%  J PS ScalingOffsetS Vector3DS VectorS D D D x%  I PS ScalingPivotS Vector3DS VectorS D D D %  . PS TranslationActiveS boolS S I &  K PS TranslationMinS Vector3DS VectorS D D D f&  K PS TranslationMaxS Vector3DS VectorS D D D &  , PS TranslationMinXS boolS S I &  , PS TranslationMinYS boolS S I '  , PS TranslationMinZS boolS S I N'  , PS TranslationMaxXS boolS S I '  , PS TranslationMaxYS boolS S I '  , PS TranslationMaxZS boolS S I '  * PS RotationOrderS enumS S I >(  6 PS RotationSpaceForLimitOnlyS boolS S I (  ; PS RotationStiffnessXS doubleS NumberS D (  ; PS RotationStiffnessYS doubleS NumberS D )  ; PS RotationStiffnessZS doubleS NumberS D W)  0 PS AxisLenS doubleS NumberS D $@)  H PS PreRotationS Vector3DS VectorS D D D *  I PS PostRotationS Vector3DS VectorS D D D =*  + PS RotationActiveS boolS S I *  H PS RotationMinS Vector3DS VectorS D D D *  H PS RotationMaxS Vector3DS VectorS D D D +  ) PS RotationMinXS boolS S I W+  ) PS RotationMinYS boolS S I +  ) PS RotationMinZS boolS S I +  ) PS RotationMaxXS boolS S I +  ) PS RotationMaxYS boolS S I 3,  ) PS RotationMaxZS boolS S I i,  ( PS InheritTypeS enumS S I ,  * PS ScalingActiveS boolS S I ,  G PS + ScalingMinS Vector3DS VectorS D D D K-  G PS + ScalingMaxS Vector3DS VectorS D ?D ?D ?-  ( PS ScalingMinXS boolS S I -  ( PS ScalingMinYS boolS S I -  ( PS ScalingMinZS boolS S I #.  ( PS ScalingMaxXS boolS S I Y.  ( PS ScalingMaxYS boolS S I .  ( PS ScalingMaxZS boolS S I .  Q PS GeometricTranslationS Vector3DS VectorS D D D J/  N PS GeometricRotationS Vector3DS VectorS D D D /  M PS GeometricScalingS Vector3DS VectorS D ?D ?D ?/  6 PS MinDampRangeXS doubleS NumberS D -0  6 PS MinDampRangeYS doubleS NumberS D q0  6 PS MinDampRangeZS doubleS NumberS D 0  6 PS MaxDampRangeXS doubleS NumberS D 0  6 PS MaxDampRangeYS doubleS NumberS D =1  6 PS MaxDampRangeZS doubleS NumberS D 1  9 PS MinDampStrengthXS doubleS NumberS D 1  9 PS MinDampStrengthYS doubleS NumberS D 2  9 PS MinDampStrengthZS doubleS NumberS D Y2  9 PS MaxDampStrengthXS doubleS NumberS D 2  9 PS MaxDampStrengthYS doubleS NumberS D 2  9 PS MaxDampStrengthZS doubleS NumberS D ,3  7 PS PreferedAngleXS doubleS NumberS D q3  7 PS PreferedAngleYS doubleS NumberS D 3  7 PS PreferedAngleZS doubleS NumberS D 3  ( PS LookAtPropertyS objectS S $4  * PS UpVectorPropertyS objectS S S4  ! PS ShowS boolS S I 4  8 PS NegativePercentShapeSupportS boolS S I 4  8 PS DefaultAttributeIndexS intS IntegerS I5  # PS FreezeS boolS S I A5  # PS LODBoxS boolS S I 5  N PS Lcl TranslationS Lcl TranslationS S AD D D 5  H PS Lcl RotationS Lcl RotationS S AD D D G6  F PS Lcl ScalingS Lcl ScalingS S AD ?D ?D ?6  2 PS + VisibilityS + VisibilityS S AD ?6  E PS Visibility InheritanceS Visibility InheritanceS S I R   +ObjectTypeS NodeAttributeA7   CountI R   PropertyTemplateS FbxCamera R Properties707  A PS ColorS ColorRGBS ColorS D?D?D? 8  > PS PositionS VectorS S AD D D l8  > PS UpVectorS VectorS S AD D ?D 8  F PS InterestPositionS VectorS S AD D D 8  & PS RollS RollS S AD <9  : PS OpticalCenterXS OpticalCenterXS S AD 9  : PS OpticalCenterYS OpticalCenterYS S AD 9  D PS BackgroundColorS ColorS S AD)\(?D)\(?D)\(?:  - PS TurnTableS NumberS S AD P:  1 PS DisplayTurnTableIconS boolS S I :  * PS UseMotionBlurS boolS S I :  2 PS UseRealTimeMotionBlurS boolS S I ;  9 PS Motion Blur IntensityS NumberS S AD ?I;  , PS AspectRatioModeS enumS S I ;  4 PS AspectWidthS doubleS NumberS D t@;  5 PS AspectHeightS doubleS NumberS D i@<  9 PS PixelAspectRatioS doubleS NumberS D ?R<  / PS FilmOffsetXS NumberS S AD <  / PS FilmOffsetYS NumberS S AD <  2 PS FilmWidthS doubleS NumberS D&1?=  3 PS + FilmHeightS doubleS NumberS D/$?V=  8 PS FilmAspectRatioS doubleS NumberS DUUUUUU?=  9 PS FilmSqueezeRatioS doubleS NumberS D ?=  , PS FilmFormatIndexS enumS S I >  , PS PreScaleS NumberS S AD ?Q>  2 PS FilmTranslateXS NumberS S AD >  2 PS FilmTranslateYS NumberS S AD >  2 PS FilmRollPivotXS NumberS S AD ?  2 PS FilmRollPivotYS NumberS S AD P?  1 PS FilmRollValueS NumberS S AD ?  * PS FilmRollOrderS enumS S I ?  ) PS ApertureModeS enumS S I ?  $ PS GateFitS enumS S I 3@  4 PS FieldOfViewS FieldOfViewS S AD p9@w@  6 PS FieldOfViewXS FieldOfViewXS S AD D@@  6 PS FieldOfViewYS FieldOfViewYS S AD D@@  / PS FocalLengthS NumberS S AD&VrA@/A  ) PS CameraFormatS enumS S I gA  * PS UseFrameColorS boolS S I A  F PS + FrameColorS ColorRGBS ColorS D333333?D333333?D333333?A  % PS ShowNameS boolS S I )B  - PS ShowInfoOnMovingS boolS S I \B  % PS ShowGridS boolS S I B  . PS ShowOpticalCenterS boolS S I B  ' PS + ShowAzimutS boolS S I C  ) PS ShowTimeCodeS boolS S I 8C  & PS ShowAudioS boolS S I C  G PS + AudioColorS Vector3DS VectorS D D ?D C  2 PS NearPlaneS doubleS NumberS D $@ D  1 PS FarPlaneS doubleS NumberS D @@KD  1 PS AutoComputeClipPanesS boolS S I D  / PS ViewCameraToLookAtS boolS S I D  4 PS ViewFrustumNearFarPlaneS boolS S I E  5 PS ViewFrustumBackPlaneModeS enumS S I PE  5 PS BackPlaneDistanceS NumberS S AD @@E  2 PS BackPlaneDistanceModeS enumS S I E  6 PS ViewFrustumFrontPlaneModeS enumS S I F  6 PS FrontPlaneDistanceS NumberS S AD $@YF  3 PS FrontPlaneDistanceModeS enumS S I F  % PS LockModeS boolS S I F  3 PS LockInterestNavigationS boolS S I G  . PS BackPlateFitImageS boolS S I AG  * PS BackPlateCropS boolS S I {G  , PS BackPlateCenterS boolS S I G  / PS BackPlateKeepRatioS boolS S I H  @ PS BackgroundAlphaTresholdS doubleS NumberS D ?>H  * PS ShowBackplateS boolS S I H  4 PS BackPlaneOffsetXS NumberS S AD H  4 PS BackPlaneOffsetYS NumberS S AD I  5 PS BackPlaneRotationS NumberS S AD FI  3 PS BackPlaneScaleXS NumberS S AD ?I  3 PS BackPlaneScaleYS NumberS S AD ?I  , PS Background TextureS objectS S I  / PS FrontPlateFitImageS boolS S I 7J  + PS FrontPlateCropS boolS S I rJ  - PS FrontPlateCenterS boolS S I J  0 PS FrontPlateKeepRatioS boolS S I J  ; PS Foreground OpacityS doubleS NumberS D ?2K  + PS ShowFrontplateS boolS S I uK  5 PS FrontPlaneOffsetXS NumberS S AD K  5 PS FrontPlaneOffsetYS NumberS S AD K  6 PS FrontPlaneRotationS NumberS S AD >L  4 PS FrontPlaneScaleXS NumberS S AD ?L  4 PS FrontPlaneScaleYS NumberS S AD ?L  , PS Foreground TextureS objectS S L  , PS DisplaySafeAreaS boolS S I 6M  4 PS DisplaySafeAreaOnRenderS boolS S I uM  1 PS SafeAreaDisplayStyleS enumS S I M  < PS SafeAreaAspectRatioS doubleS NumberS DFUUUUU?M  / PS Use2DMagnifierZoomS boolS S I ?N  5 PS 2D Magnifier ZoomS NumberS S AD Y@N  2 PS 2D Magnifier XS NumberS S AD I@N  2 PS 2D Magnifier YS NumberS S AD I@N  1 PS CameraProjectionTypeS enumS S I >O  2 PS OrthoZoomS doubleS NumberS D ?|O  0 PS UseRealTimeDOFAndAAS boolS S I O  , PS UseDepthOfFieldS boolS S I O  ( PS FocusSourceS enumS S I -P  3 PS + FocusAngleS doubleS NumberS D @qP  6 PS FocusDistanceS doubleS NumberS D i@P  , PS UseAntialiasingS boolS S I P  > PS AntialiasingIntensityS doubleS NumberS D9}?4Q  / PS AntialiasingMethodS enumS S I tQ  2 PS UseAccumulationBufferS boolS S I Q  5 PS FrameSamplingCountS intS IntegerS I Q  . PS FrameSamplingTypeS enumS S I FT   +ObjectTypeS Implementation[R   CountI 9T   PropertyTemplateS FbxImplementation,T Properties70R  9 PS ShaderLanguageS KStringS S S MentalRaySL1S  5 PS ShaderLanguageVersionS KStringS S S qS  2 PS RenderAPIS KStringS S S MentalRayS  0 PS RenderAPIVersionS KStringS S S S  / PS RootBindingNameS KStringS S S T  % PS ConstantsS CompoundS S V   +ObjectTypeS BindingTableT   CountI V   PropertyTemplateS FbxBindingTableV Properties70U  * PS + TargetNameS KStringS S S ?U  * PS + TargetTypeS KStringS S S U  6 PS CodeAbsoluteURLS KStringS XRefUrlS S U  6 PS CodeRelativeURLS KStringS XRefUrlS S V  - PS CodeTAGS KStringS S S shaderFV  6 PS DescAbsoluteURLS KStringS XRefUrlS S V  6 PS DescRelativeURLS KStringS XRefUrlS S V  - PS DescTAGS KStringS S S shader W   +ObjectTypeS AnimationCurveNode1W   CountI W   PropertyTemplateS FbxAnimCurveNodeW Properties70W   PS dS CompoundS S X   +ObjectTypeS AnimationCurveX   CountI + \  + +ObjectTypeS VideoTX   CountI \  PropertyTemplateS FbxVideo\ Properties70X  * PS ImageSequenceS boolS S I Y  6 PS ImageSequenceOffsetS intS IntegerS I SY  2 PS FrameRateS doubleS NumberS D Y  , PS LastFrameS intS IntegerS I Y  ( PS WidthS intS IntegerS I Y  ) PS HeightS intS IntegerS I 3Z  + PS PathS KStringS XRefUrlS S nZ  - PS + StartFrameS intS IntegerS I Z  , PS StopFrameS intS IntegerS I Z  2 PS PlaySpeedS doubleS NumberS D "[  , PS OffsetS KTimeS TimeS L Z[  * PS InterlaceModeS enumS S I [  ( PS FreeRunningS boolS S I [  ! PS LoopS boolS S I [  ' PS + AccessModeS enumS S I  Objects8f  ! GeometryL* S + GeometryS MeshT]  Verticesd +  ~@ +@  ~@ + @ ~@ +@ @ ~@ + @ ~ +@ @ ~ +  ~ +@  ~]  m PolygonVertexIndexi `                 /^  = Edgesi 0   +      P^   GeometryVersionI| xa   LayerElementNormalI ^   VersionIe ^   NameS ^   MappingInformationTypeS ByPolygonVertex +_  ReferenceInformationTypeS Directka  M NormalsdH @ ? ? ? ? ? ? ? ? ? ? ? ? c   LayerElementUVI a   VersionIe a  NameS map1b   MappingInformationTypeS ByPolygonVertex9b   ReferenceInformationTypeS IndexToDirect5c  UVd ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?c  m UVIndexi `                +   d   LayerElementMaterialI d   VersionIe d   NameS Gd  MappingInformationTypeS AllSame~d   ReferenceInformationTypeS IndexToDirectd   Materialsi  +f   LayerI d   VersionId Le LayerElement#e   TypeS LayerElementNormal?e   +TypedIndexI e LayerElemente   TypeS LayerElementMateriale   +TypedIndexI f LayerElemente   TypeS LayerElementUVf   +TypedIndexI   ! GeometryLҍ* S + GeometryS Mesh1  \ VerticesdTW  O xly?j[(~>B(ZQ[>&SKjR[rO" DpYB"=s朸$|u3{랙|G1=?U}Ogɂu9?v,qse >w56EiGS=qoL<͉5gA5qޫj>j{\\s3S{35|~MasNC56ԯү8q 5Sse&39/ySΣCk;INڽYs3|~xYޢzW-*qiOFj?DAW'񸦌C<2_ ̯G~oV`Pdu~/{URoNmn5wψ~ռeW8-q]G7R8nJ^zJBc$ĢԟuE]~ z5Ы : Jp*>w(7jޓ=xЫ^[sՃc\UsոġWT_wULz%8* (Nl~ߚC.OC=J *sY^_5^QWzEq^=Ps z5+3#39/+>77סo7_Hh>:7]Sjn&B~iRiT͘1 TqN;5ynߞ9grE}ܟp#7#Ljy`u?nwi_7p^;=1ƈy+u#L/j_8xMur \sus]SjNy(Gؾ>(y?[zwJSknOʩu}JQ8q忟 +5dڷ.W3qwE=ꪓ+vo3~uUSO; uE8Ud}Od;Aճ+M32j|YUj nu{JwsV9yA]ǁR-z7yJ1sW*vza~5_]Jp̯'OItOH ++_eT/\'_x:s̯2/#J]1dc~E1_e?__5Ur]̯(_ ̯(Θ_M̯?WG̯__ݑ+1:>Whյ=䨫1`~E+z?Cd1qe">>W"_xe<=♸Q?)e/GYQC0P֏E]%;vLQgU=7~&{\gWmCjnہI\xu9noQWyG]DUagS_@yΛ_ uGSN}&xƠf7 +77C*NQ%g ޼Ğҳ}G^uՃx5ʾE]%?x6gjS{U8x+q| #σ _+'NA]oSC]<>xU϶\שo~~v_j|yt"΂oܾ9/@u>.zlӢR{{J}zj~ƞ|$ps5ogHymvAطKH}ҥhz }\F}?;Ψ޹O T\W֑qz81y>qv~I{5Bjos'qnSs_Ạ~~ռy q)q[帵SlqFcx<oM`(Ez7+q#:G]%I}kԾ,⿓lڇǪ{?|[FOܠ::Eg [UC8K~uU?֒f >]Өzn6mg>c_5oCmC ,ǯ?! /uJROځ%ﳥr4Eq~pug+qj|[4qJws_ }s.WK~ߘճ麸#h\/6ɵb`q* s3}'6`qOWϺ=__5+_#W_QpJ_QqJIx&];y+9_OEuW/ uL%՝=W+_8+?_ͤCdq_-DGܿ('pJpܿuGSz#+Bf__a@yǓus⟎ BcI_TYy8q^q?_]|nGe~9jy%.do5ANּ)m}_5w/ġ9q-q s rۏ& }h*y~ԙs-^1땼NߪveķQ[_"~!;x3qA|y'k!qdfPW\WVէ-vMuU?ѯ~RW@j8h7L:9~-*E^j; 5w[<-DcQWCS8>$p>op}Aq}2>vp=i7oq[ay^qC4f?q=yeZ} kf5}u~}}+/5Skt]oߞ9UvZ.Sp~; ۅ~;ۅ~;Yyv򗯋ps~[+Iq퓉o*v<oq>/iY:>~'x}io:3ep>\$4Np]J(ۉ~o'>ot]ogWm۳~"5v5v~K5v3os~;_>9v7so:vє^,i~9~z/+xM{V~n$O4Vg6Vqh^&;\!aw^NP_ox3#gόf_KMo8p! o{u7uQMy(vs+uno_5orCmC ^ǧs-"9/yy{cko~_8ݡNvHTnJ1jĝǢoK-*qۢd+CE< xG62_qh4OGzH'./GܣuU͛b'z~]3q/8Oq0z<~w WsJڎA][>X3RO *qw";7Azndu7~Pp|?Hܶ 1|?Q_T7~P3qHO|?Hߣ|?HnNF|?( N'{ A9$'c3uk~gWRRj)~kg7|?(t RХ81_lR`(y?[p|?Hu~IT{|?`|?X~Piހ~a%iAO|?HD A]y4|?ϵ~ q|?(p|?(R{|?(u|?H~~~s~`_~x/bڭrA~s3LO?X@gc=Wz_gKT|w2X@EDD~ 3zzXp[']7/v _c=qg dD=c=qg Y'xz3 q7 ggX\z 3}z#3dKaz{3[3P`=Ń㖚i;uߏğ*xL'؛^y~6up_^N'n!ՙ4RiɟWwcb<~I/U.Yd`ȣ57_N<킻2_϶'! +^s‚YgmӢ_5~ġ%[C8<^ܾ0%/1't11>9 y$x\' nn8崤WjPW=ھC]%?Ϳ^yW;%;-2 I_f%4,ꪃwe=xCY?A]Y ++ٕFYof?UsAPcCjl!WĹy[0/w>8UkQW}J7GUC]<5M/zX5WcıJq!:W/X%t hb hbի?XE딨_3+?X5G_M9_e>ߝ5'3gWSkW25+3XEWk"fWBWBWRgW/믨NO#ͯT{;X%XUX%+7`^Cd!_-DJzҮnP'J7'^X/LchzQca >E: X/* /.qj^!Uw̱^֋uqX/Jʭy}%wB?H~b'gOEukı^5lLchPs8`Ga( E.a(E.ah.s^49֋漣NjVN,8֋_X/:qzQaEg% E_N^TE_#~!xb(CzQczQ{/֋8zQQX/JEE`;zzQX/*E֋zޤ\W?֋f?ѯ>֋R^T/ztX/+9KmE)/X/*E)X/*EgzRoX/Ju,x +Yz~a}S&u豢`ىE uJoi ?͓м!3ѯynCS3l/C3'Bͼ'aX߬Kl~Ox;UҌ>RqOJCqO=USx3m5Ŏd|Cnu{SL='Cl nuf78 os;3 u{pX?9?Ss4%_nIyTx\(iWxX7dg㓸Q g͍2R7V7ٱ~rڇи1ޣn3bz>7_u{WL/jV#nuf}i>}r R0#"5w}r?V*8(^XQm'wo}>ooo(vzv/'C<|搇l|dFbC ɐovַz}{XNq+/%c};]i^~b};٢_u{oz8)XNͱBB_9X.a};\'-8ַN+':so*6qoc};]_O}$"XNEEs/ַ8?~G~o's{o'>ot]ogWm۩_q9ַSc}[X^ͱbc};/o\O~gw~-W}{Y[78{n80 Cw{ţʹNk7IA+>zGotӉ[A]%n_&ve<-♸[Q? dt/|%m:ƃzHoq]Eܠ9p Cn?k;ԯүqysRCDjQWq)9^0hu>zbQWdw]?uE_I|R8tGpǡu m+ ;؏؏Z؏#qs_T/^a?b?d H׹=8~u؏C q&؏C +qIy~7Ws!8s{؏dͱc?^ q؏C8'<9PG|~W_זa?%ǡ8c?7ۏ`?q(؏]7y~q~3i~c?#)؏#{U؏#~9gq^KGq//~Ǿǡzu|a?b?84~qƾ؏#9,vG3uG?㐟_|]ǡ~a?_؏Cq~w8r)qF^؏Cy~򋯋89F *qNM8Bp՘%u??~swo7{#v=kuPؾ{d ͳ%Ny;rj̢(Nlǯi{YQ<_Yo}{sp92@>qSuoNZo@牻i+<@|1O;~ޕ|~v*qěu O<^,۠#Wg12Oܢ:%";7@?.$?qěޘ,vZUώ?gWo~9ġ!n5oG-8<,l9/uMrR5NY,&ٟ뾹~c GAaIP*qAz\/<.=!͉y)8>;8z 8AW?H +gOSc q|0c qUs׭9ulfD?HJq9R0w[\' < +)?OyA3?HyJNO$=NAPycαP4oA_y.dE<!ىc C>=Bi<`ZY??He>cgb$?SRް0K/8`?Hl)?(9/?()?Q +%Pp:jS ++|nZ͖_?I]w8/c=pqjo*t8v;ǯMq8f0ՙCW{.q?}cTq"_MFuo?>7s{T'7;B͆yY?Y{?~rXxg5Nuknun_ԯ-rCÝ9 VsS=5w{ksb&伴'J݈#r^o7y@pm Ǟʟi~Uh}JQ8UN?q_J|E|xz3q?Cƿ]<wÇ8x {zhƏ.gz>=Qlu~_:W 5w8}xW&'ǯG]o>>%^E]P'uEv֒Ӡ78 ;~,csc1weE*;;K4ߙꅯ4ߙt{=޿ɺ^#;B|w&]~~goޔz2. +\EgMv[y,w9Gso.w&]gqǒd'ܸhQO\_W{; _ߡqxbNެ +j?d?tۙO̩yz5o;-'Uܯ8rks,\g܏yc9yyغ(vJܱ_W_ӟA%"~ǝe#xsIܪx3񰖌@ 2_d~-KzhBmg4 *nQW5o95+Wm'e?q[ݾf}qsME^jy1c-?HA]%\,>$.s,ũM/em3L|38g:sgo}[B| t3"gq>C >Mvp>`xC@|Wx=Eߚ~I3tjLq>C'88gx&> d3<8!~vp>CPs@q b^(nx77%8A[p>C X G@Bjq> g3W=g;8A /$?8>|?g98A3y×f$8A38 "83||8A38 p>qzs2 p@3 d3z q>@~~q> 3( V+r|3gn(/8Ag<|o/gx!=g~s|p> g<ū={;bvQo.ߙ#ῒiU׾vHl(N׷{?7=%Gy?dk Kv9~f/NqP; }nkqg1+C;a5w~J}1]0 ؉n9~ּLy8;!5o8ğ=%ƣ-qku§u{'ǹu쿿/% ԍ<佶\Q')Nl'ޒefQg''VҾC]%n?uxPګW'`Lzdp\_W윰@UI.ΓF|]'C~~vp ySdr=Γ<od(8OF[p ]y2GWd(8OFp'Cudy2A:Tq -Γ<s'#y2<_'Cz[:>=Γy2>sWDq </'C8OF7 pL? d<8OFԛ+ɈtΓdΓdԯo'8Pp'5y24y2b<3ΓP^p'Cyy29p dNp +£?n1EJ#ݚ_7J fq)Nd%u|Ӹ3[J݈4N?'o:GGsWP݇}.p%+Ԗ95b|͛#Ɠ<~r^]c'\(u#7󳟩_51bWo99 Vooqs]st%"i1Yی<\nP'5o>uW:EpF$'qeߡʟ+-*Go?Z:>[πx&T Od x2ui\U;UǼ!VgSxUϮ\l3殓e7g84[ *!58r;Uj߷qJ#R"x%4WeU}7qoy& +򧏤˄CsNsB>؃꥛MnE<ݳh~;߽9οʺ>_ ;?+ӷϳ)>}ooM'}9Ѣ׭y{hn57ԯrpЫ0/e~9Ĺ(ey3%/OdX7y">kn52qړ=+u8+ *foD㓸QybyW[[+~սo@~̷z-ꪓ~_ x@]%>qyn]n?k;٢_5~01Ʒ|"5oqݢzJBc3HC]kR"*qw?9>'lvsT27Wvqat_޷1W5)>>?+Rn> tMoyMi'n {*ܖWnx3w*p.q{޺|V)v#|~ԯrPI&M9nq-qc:y?Cɛ9MerޛNs/-~}TʾC]%( ꊸ?3AסxRp'Eƿ_& +ȗ7~pxOCN.ǡ9iޘ_Nٕ63g~ܔ~5C9qy|kg~u99/yyx#5ROꊮԟux<޵ zz//_:hTc=}/⎜!nGۧO|la(l-9o ؑ ~s=4o5Θ89x4eqin%I;и7?ŃyLꞇ2='}(;[{6nou[)?SjSr[o j7qù|IC^R?;K{ W9탣us ꤶ^ubfm,IC]$;UQcQWAw K">2LyT8O +R7w;v.}o&qvuiU>QW5lG3 Uf?Sj;#5w'87x4Ԯ o8ǹYO8;"Ws@ΣEknQWɾCԼA]jcє^#'^5'}G暫H8O^˘ ?ن ^syCy<sq@~~0_]q7[W18WW"Կfǭyo9ߪ/ /9*uՃ8zeߡ7?+w?yM<^t|*'{ߥ~rql~2a=.!9MPWqzo lGs.ו~_RW{_v'^W1~9#9S9/zqǸ!'w Dpm_#r^G:f#w`<?lo9ǽzu9yϿ<혻V5v7>se4"_hѓ^:a u^⪧x G''>==W]˝_Hcb\?I_~ԯү8<ܙ.827sqCS؟`ssخB9/yyoMf;ڜ$뤹|wHt{PW[O.uڷPk{eW2_Ju2Y4 !#]m:hYop{vlz.׍PL/C |EGs94 +s=׃A]{9-*0EC{EΣAk;u[U*x\rbN豢}` ++#iv%nkH7~&]f4ohv؁߸/?!]g 7vMyzøqsź_w!4pl>%͐3&ܭuS?Wt/U +C}Ej_o5~9yI` *}PA]%zbPW*A]QqbF<9v`Wf㤎;~cnwN& +.'t+N\V|n{MzwI} Oÿ_q ڔ_b&J0qЫpywrox3dG[,ܿy+_6]\7|]Ys[L~!5%q78׼YlХ0#%p%Sڎ.=b'zڎ/~~ܕ~9ġq[l zĹ|]CpJ~4xUqO +|b?^c_ЫKI5Q蕝1*aھ sN>.2[l|o-'GzcW^ *n^\:_Uyjk~d?SR\\zEjn\‸Ū=νĹfxA^j4}Hqtcl?~Üw|mN,uo4C] ¾C]Wxl Oܪ:W'qwt<'nPW4Wb_ϟuUy46~È ޚ-*5lGs._ ?Sj;8> qC]~Ǩ|_qv ]ቜ< RO *aP'J~izooӧ;~<4c M}nQv;z/&s%O ^Csn}'r _CwECq(OoƉ|_Z;Ϯݔ'o|xP]ž/ +^4 G}\7Ϻ}`3_ߋ!"n/ߓ֮tG5rRsDKiB7,)|]nQ'5G:iF'^ucPo>*FONAAW'qO'M2_J}U7lC !x_#zsu};Qճ+׍3_5_r|ş8X=ニ>Z99q99/Rs ^1+W/?ux;'. V}*v_c{>4?I~>mO^.:ϗ.&-?{|~1>;(t_;R>N3퀏)Q>%*~xͽ#܉?wH|Ы0 +^b?lRc|b#5on']7>n o䥚{d;E=r ?UO_q+q[/[~瀼@v>"yc oup-vgWg?SjnK}M;\|߇۟e?SjJ*q+ *qZmzc^8n onŎ Zmnuf^Ӣ_5k~8Wq;.#/5xQ9y)ߘuo;Hp32+ޡz 󐲏J*ZC\Nףn>gv'VrWna?'Yǹ~WVA]~pqN%q_~s_57/!Û9j'jOqWuݽk߁%gr.!r~"Y9\5Yt]s U{K4o Vl'{a{ a8|QN=O=clnf#;GlޖWWy}3J6k8Dĭ 5o+q^w5s(/ή.{Jw~xkr n^4jT{Ju>ꊸ?o.m_qhĉx3q<'N U' EuUs<''$?q<'x3M[~O<'zsⲎe?SR<(](Ǘ)qszu_9 +ωwg<'Nw?%%+;qzi;'ɻ%o Zg|7-o 'gv#g.I^P$;ԃCBaT~^>nHWH#zuVYޞ+Y{3rWvֱà 2w Ŏmn?Sq(~~t?ۻߞJЎ#i6 zΣy>7;zԚǿ 5`jo:蕚3׭+׵yl=!%ڗ~9!7G8Xĭawor009~p+</t).(f :q'b폵^9ޡzП;UAcPWĕuWo͘Lπx&'^$呯\K0~xzH\KQ?/[UjNvt=r]=>Ug~Ṗb|q[i9w?k)x#Ź}_wo#ȏץ9~FwJ+?ɕ +!x6ҫx8WL zu{f@1.qܷ?ϫ‹Wq?D<h`?~8tǝ!|UwU~ۇ x3[)!.7wXQ?a(ұ|v;E}bu;+_ו6͒g?JqS78<쐗7s^_[ՐG9?G]juv.σC]5ʾC]%(,*PE]#۠ct|*9h?|Y+0~?[zgK^ 'LE_"3WٕFI($UoQ˕8č^皷/8;Es^ϥ>UÎzRԟuD/R)x^+TO֫.Wz\sW]+Z\sW̵^1z\sW̵^1zJkb+Zkb+ZkrW̵^1z\U+b+Zkb+Zkb+Zkb˕^u+ZkbyV_}_ɟCAZ\sW̵^u+ZkrW̵^1z\sW̵^1z\U+b+Zkb+Zkb˕^1z\sW]+Zkb+Zkb+Zkb֫.WzJkb+ZڽX>sW]+Z\sW̵^u+Zkb+Zkb˕^1z\sW̵^1z\sW̵^1zJkb֫.Wz\sW̵^1z\sW̵^1z\sW̵^1z\U+rW̵^1z\sWv~+Z\sW̵^u+ZkrW̵^1z\sW̵^1z\U+b+Zkb+Zkb˕^1z\sW]+Zkb+Zkb+Zkb֫.WzJkb+Zk{W̵^u+ZkrW̵^1zJkb+Zkb֫.Wz\sW̵^1z\sW̵^1z\U+b+Z\sW̵^1z\sW̵^1z\sW̵^1z\sW]˕^1z\sW̵^5ŵ^1zJkb˕^1z\U+b+Zkb+Z\sW̵^1z\sW̵^1z\sW]+ZkrW̵^1z\sW̵^1z\sW̵^1z\sW̵^uҫ.Wz\sW̵^1z$Z֫.Wz\sW]+Z\sW̵^1z\sW̵^1zJkb+Zkb+ZkrW̵^1z\U+b+Zkb+Zkb+Zkb˕^u+Zkb+tZkrW̵^1zJkb˕^1z\sW̵^1z\sW]+Zkb+Zkb֫.Wz\sW̵^u+Zkb+Zkb+Zkb+Z\U+b+ZkA롵^1zJkb˕^1z\U+b+Zkb+Z\sW̵^1z\sW̵^1z\sW]+ZkrW̵^1z\sW̵^1z\sW̵^1z\sW̵^uҫ.Wz\sW̵^1z~?/AsW]+Z\sW̵^u+Zkb+Zkb˕^1z\sW̵^1z\sW̵^1zJkb֫.Wz\sW̵^1z\sW̵^1z\sW̵^1z\U+rW̵^1z\sWq W̵^u+ZkrW̵^1zJkb+Zkb֫.Wz\sW̵^1z\sW̵^1z\U+b+Z\sW̵^1z\sW̵^1z\sW̵^1z\sW]˕^1z\sW̵^V^1zJkb˕^1z\U+b+Zkb+Z\sW̵^1z\sW̵^1z\sW]+ZkrW̵^1z\sW̵^1z\sW̵^1z\sW̵^uҫ.Wz\sW̵^1zyz\U+b֫.Wz\sW]+Zkb+ZkrW̵^1z\sW̵^1z\sW̵^u+Zkb˕^1z\sW̵^1z\sW̵^1z\sW̵^1zJ\sW̵^1z\|/֫.Wz\sW]+Z\sW̵^1z\sW̵^1zJkb+Zkb+ZkrW̵^1z\U+b+Zkb+Zkb+Zkb˕^u+[IQfDvD\XҊ4,! Q\@@ht%J +-$1DIp4!hrDNӟ:ϝ֭:ު~W+r*w˓+rW+rW+rW+r\}E"W_F."W_W+r\}E"W_\|E"W_F."W_W+r\}E"W_W+rW#_W+rUƞC"W_\|E"W_\|E"W_\|E"W_W+rj+r\}E"W_W+rW+rj+r\}E"W_W+r\}E"W_\|5r\}E"W_u;"W_\|E"W_\|E"W_\|E"W_W+rj+r\}E"W_W+rW+rj+r\}E"W_W+r\}E"W_\|5r\}E"W_-ɟ'!Ͽ"W_\|E"W_\|E"W_\|E"W_W+rj+r\}E"W_W+rW+rj+r\}E"W_W+r\}E"W_\|5r\}E"W_^`ϟ꫑W꫑W꫑W+r\}E"W_\|E"W_W+r\}E\}E"W_\|E"W_W+r\}E"W_W꫑F."W_WCz?H\}E\}E\}E"W_W+rW+r\}E"W_W꫑W+rW+r\}E"W_W+r\}Ej+r\}E +_\y\}5r\}5r\}5r\}E"W_W꫑W+r\}E"W_W#_W꫑W+r\}E"W_W+r\}5rW+r\}ծ:/_F."W_F."W_F."W_W+r\}5r\}E"W_W+ry%5r\}E\}E"W_W+r\}E"W_W#_\|E"W_ȧjyjS|E_ |W⫁OjS|y^jS|_ \}5)_ |>W+#?yM~^W⫁O8S|E>WC?S|s$+/?S|5⫁OjS|5)_W+djh¿M꫑W꫑W꫑WzE"W_\}5r\}E"W_\}E"W_\|E"W_W+r_\W+r\}E"W_\|5r\W}'@W꫑W꫑W꫑W+r\}E"W_\|E"W_W+r\}E\}E"W_\|E"W_W+r\}E"W_W꫑F."W_W䃮KmCs<}5)"W_ |>W꫁O\}5)b{}?8)_}?8)_>W?y⫡_ |>W⫁O\}5)_}?H_ |>WG|ؿdy_ jS|5)uя\}5Ө8<}?8⫁v;/_F."W_F."W_F."W_\}EW+rW+r"W_W+r\}5r\}EW#_W+r"W__W+r_W#__W'+rW+rW+rW+r\}E"W_F."W_W+r\}E"W_\|E"W_F."W_W+r\}E"W_W+rW#_W+rU W#_W#_W#_W+r\}E\}E"W_W+r\}5r\}E\}E"W_W+r\}E"W_W#_\|E"W_WqEW#_W#_W#_W+r\}E\}E"W_W+r\}5r\}E\}E"W_W+r\}E"W_W#_\|E"W_Wj+rj+rj+r\}E"W_W#_W+r\}E"W_F."W_W#_W+r\}E"W_W+rj⫑W+r*~|E\}E\}E\}E"W_W+rW+r\}E"W_W꫑W+rW+r\}E"W_W+r\}Ej+r\}E +qj+rj+rj+r\}E"W_W#_W+r\}E"W_F."W_W#_W+r\}E"W_W+rj⫑W+r*nr+j+rj+rj+r\}E"W_W#_W+r\}E"W_F."W_W#_W+r\}E"W_W+rj⫑W+r*lkj+rj+rj+r\}E"W_W#_W+r\}E"W_F."W_W#_W+r\}E"W_W+rj⫑W+r*nțj+rj+rj+r\}E"W_W#_W+r\}E"W_F."W_W#_W+r\}E"W_W+rj⫑W+r*|cW#_W#_W#_W+r\}E\}E"W_W+r\}5r\}E\}E"W_W+r\}E"W_W#_\|E"W_WqǼ|E\}E\}E\}E"W_W+rW+r\}E"W_W꫑W+rW+r\}E"W_W+r\}Ej+r\}E>q /_F."W_F."W_F."W_W+r\}5r\}E"W_W+rj+r\}5r\}E"W_W+r\}E"W_F.\}E"W_Yp^"W_\|E"W_\|E"W_\|E"W_W+rj+r\}E"W_W+rW+rj+r\}E"W_W+r\}E"W_\|5r\}E"W_ۮy\}5r\}5r\}5r\}E"W_W꫑W+r\}E"W_W#_W꫑W+r\}E"W_W+r\}5rW+r|w4O8I|3|ogLGyxL{?I>Mla'*}Pm'c_>{ ޞxG}%oOWouzyR+>`՚|2L<َ_sKgyfZ|aua%Lr y^)i\n5! n%Ĺ9y)ywUKG-sk9o;Z<8\'8^os;iPWPWw2ux2~o$^K<6WoԾF^I[',cv֡ƛ#}5Ra觑zu#i78ӼJ?U +CZ7>q.yHs@^2%~B]q'ytg yOJ8/Wgg:ޯӓ{e㯜cyf=6_o{ꂼ]Rm*yU|qzl}6͞w.OzF=Չ~Je^"9͹0\7HL*i~!i\nqs?]!l߬ܿ{PlO{9\\'axKF;񅙧8/gleWg3_?|U_7R?񳻺.gOꁇu>_nW\.8/;U_~5ϝvM[{)wau/y}nۋ}}S yGJm|U0β}=3ͫ<4?^U.ǭA19 59/bjy:y}{:):_ǫ`u5@]%x*U╌|k#>!/ĻoK}^I|yiYIpn_$6(D0\7biO=3ͫyy^qHcc95Vu}ƯC{-?خ}Oy.6u_% žߜԞ{,{8㺧^^*DϯK=uoڇ^`|Ǧ|]fS?t|#ݯD>>'c|ů|g{_rJ^7|k>]tj^ݲU=S}:_58{ jX̷["!"n%Usɻq^Jyi_ZOCw9u^z܅Wxz'VawG3$-x'^&TȗqUەOPJ|}/[?_?=nzxszG9 Ryi^)p__Jc{ev4o͟\!/%oyiПN8`U+;޾K􏚯[ayC{K)OO~7Un7kwZϱmW+8_b'»γ?/oV/j^]gw|W9Of~w+s]Κg ꅲ}=\8KݛǙU<4^7 qhC:Ĺay)ys]K<:_c<췲R\'m= ux-12'd x5/>5x}'.*>+K:gzP}Oশ^ jYC7ߗV#Ύߝo|{^Dz}(1NjuRuvW0_U>&*xCG]%^x"ce*8ϷB]%/:5>|<;3>5xJ緽◶C|=tOewZG9 R04v<8!ni~oJnǻr+r^Y9q>~z{uvd_z :iU>Kw1_??U-~W{XwVj.>|6+7OMn{*Q'9g<78=/%og?k3Jμ>w]F?t>vVWVq7T\_m1NLJqy~W8< qXϸ}ym>"/e8;FKyl?Oѩ91,tN꺎r/ FG]%a<2ue|[7 x&Om|Z}W?mN['5j?ԃ-]tϮnB]};*y=SI=,ۇai^%y5CɻNfDlsBKެ7A+UFqќvC!?ud_ru^jI|'=x|WbjyF{]oO}yp_cgc]_vxJ._SmzW_񷟽%1a3,r5%_>x5פynzC%oG[`%fgWOy!%qcpj5sٟ\!/ߐR!%yrek`?:<7_5>&T?*Oӏ?d5*v quψx&^-_#QW8_+>z>aYOIŤ^ʼd?Ran>ǙwyuC!qKbW~V5RQWWcɻ9~Ŝ Ox4ǓN_qUWn<寏O}w-o?eZzu'W0-ؾYr#xO}ov+^O?}[߿\'|gKެy^WX zu7a:W#_WOϦ8qp,ywqgW 7C"? N."/eZ<ؾfoo(ۇskIst'꤫}@]MGux'i1ăoek+g@<p-'ޞ}~U}~²~7a@9p݈q1Eyi^%y?PzZO}?n_ȫv7Sqݜ͵%v_*ۙVrn>p\ۇ~kc}y DIJV?|Ҭ4^'z/y\m稹k9_kqy4üjġCX`[8/ǹB^Jny>X5[jXB{ W uw|}@]MTPWe<-Ɠx;?Z0_OS{}xJ<۾uUQ}־*k5zai^e?üUgǵ}X¯z瀼 koyz?tn +uQN~U+:eax&sw竸ßt0_༞c|՝s~~l}OZ3NNxJa[z_]}\=9"w/nŝq 4}b)~zgW+ q(y;ġAҼOc[8{2ǹ[W-9/킻{_ ylĜ:7gjJ{US*F`wxg3? +$yȗW|~þljgzh~څ|k_o[~p4' ZǙUj7Cj5?od6qnB]~PWO*Uo?+kKhj_ϲx]O1grn_|~K?Ou"/[78gr=m}6']mzvwl\wm};tUn6^Uil?/`ɻ>5ng>ǙUjWDJ1ǡl'x!nq89;_r^^r\'ݯqB]7~רkcr+F]5-*x=WfjrSN qk?hk_ey&wdwvW'/՛5fDu|_ޚ}.6_ol&w}q1lb3.)9wwp~OWͻw~ y_ ׭ƙU/k^rVVq߹xrRjK<[k';5w;K͚C|i#5_>R?d<ﻕ?`}+m1ϧ'ZY!~o=jeŷx]u['m3?_?uiPWx>[<\81QWzqGuxj#% 7RsȬ'n?s7?bZY C>|ϩWrk.w3+Umykw..yp8m볞g^xr[_Ng8+K곌wre[u%OD\p,yc^%yՈC5959nghsE>롟J8]?8ӼJ YC7W~֨ԮB^B9;r^XxUcIJ{t:/W4Ǜ'?dj.ɿwdQ 於{LV\as„U; N";S{%gai^%>787r*瀼b͝9/85_y/`29}=`pv?^WDw{DWDssJ^5[M㾷KپY>R<?yE?;+Ҽ~[iK,_8KǙUjW8]ei9n\zs<s9|oysq9?}r?;̇Mu5Jux'PWe~ $:3 kEPW7vs_'cZl_?ux<yO_O'vgS0Wü:ġ9 Vw{mvq4^3BK>W'ue[C]%;jZ}O>o[|7/>4~߮rl?~}o|m櫰o~~"u{HUu5PW22 +uxo|FSx%l}+yW|e3_|~[7~PQWi7--ʮ\p,iqy:+.W{OC훕϶q.y}s+kԕ[V=i{Xg ^x>l}6D=Zˏ|ƱcqoOsʶ;Gw/O`?-k[0S_qc%?1ΒwsWɛկUwPCSrĹasX:ߓ"WDc=&B{-I>׋}+mPWKux#/%F]Y?Muj|%-xWcmߐC +u53'y{n觑zu;짹/3ͫa2ϫBJ!n%PWq.y**CǃO*o+8/}է]|[&wkxs>>Qryo#U>̓v?ўt{]{d|U?}׻s7ݍG~c1[Ϸ'ŃZf_c7Ǘ1;|݈qai^%oVǏ q@Jޮ"%8/vu?51޶͛]x\'uO8^A~PU⭌ݣF0Ծs$ެ!O<\pרI|gCc~O*]ר+LJ~s1β}?;żJ,? q(y\7! n%VX|~߯zs_9/ͤC@]~kU(狺JB<W:7g}|:yzzZd_Dkwau;1_Un 'mo͗>y~uh69e<냎,8~~~n3k{_D^nwzk^o_?|x;_כw$"C?-y<(_7b%.L*y{`W@Jq_ݼCܺüNLq{~ r^}>9~,B9 $g:ioKǫu=0JD5}ux-iPW225>$m!Ǎ}k?>_5xoylB=k~M_o/wRp݈qq1%yi^%o~"%q?csv-oޛ|F]5Oԕx+?y ]W}צK'*urig^y}M.܇~9su>y}N}V{6uo>Ss}2!hnΒW >j/+y8y;[?,͛oΒw~:\dnq:~0N̫y^q(y8 n%oOqw8ͥ9/5Xcc]kkZ<\'ws?>^Mc%3 ?ue|?O<1Ļ3'ZY!KsW|Y}}~k롟aV1kUɻJ9^[Iļ~y^s9 VüWCk%9/XueB]%S_$m7M_֋:/Iu۵V?O87~|}6w|Uom/u|_9E^{7v6}WL`\lm:nqi?f^9%~5|U񺯿lO<*/76}( CĹl87Kɜ +y,y|=类}nE-no/g{ZT[q&wou@K55{>/5ߏ^2MONmM/IRNyOj׭6of?TgWپR-Vr|^ŭeZk>^7\9EK<jyT3,#&ZPWw2|^1#ƟynT>^KK~}/|^~_|^vS_T[?~~yy{O|^jGj׭yyW/KyuCySz?~65/y~ZOy9KyRN7gxз}?y'ݸړ&>Ńq}χ~O[ix <̼'Ϸx~v+wuU?3ӝy=]gc ;&}ꖟe8w7xwꌋSl_x;Jonӝ78k̫ly8<~5! n? g>8rxUK\{Ewuc|{1}\'^ 9˼C5jqux+ +22>/>'ySWL[{$4W>!zPWe +`Ogxx@]<ޔiA=3ͫ9`jCkUw8;fs+s^5*||@{G]u\< j;߯L/-6Ĺf}>7Ծ=u ^}w~"Ń:2[%7yy}vO 1=÷۝K1|)'|}nYs(̓w%k|W%.>w~~KyWOy=K/]27|y?C5C^s<]rr??x8gaU'jq:OJ{'LDXψx&kd +~PW%V:Csk{|w?`-F9K=^Wm8Ӽx9<8\f \m9RfVKCJ=O*9N^|]W/ͽvixiw'ޟ.n"G c^~1qO|Nz +a^1[(]KCw9msL.(9>$;o+y;s%u9&|C?04}i>䅒sL,+|ŭEs8~]K L*ylW8:ǡ7kd9y)PW͙5U'*O@]Y?3}s_3sΟݕ8>]5qKo'|.xf] }>7]  `^Oyp_mdO_ܐǒs,!:ޮ8n7\'y~߿`-jql+ZSd-o|x?95>W,xx[|5W:>6zPR}3^z>s;\8~8ӼJ^ w{8Dĭ *BK^~ۯv]jU`ue.ӢY4r;Wj$8Y˝?RQn^D?ٶn!~-ޕ} 9/vIg{ҼGfn)y>O:[sKa?#c폜{_|onqc^%ߝ %o8Ԉ[9n\fy)ywSK<: +y/y\>=NJ^9Wt}Jwwv~Wwgxؗm} 9_uWMYoy\7o}uO7n)y;0p,y53a^A8t[پ[3- Ύ>9ξ~[s^*l_#=ߏ~"csNoG]M I<>ǃ3?he-Ɵxo|u'J"w[긫}kVoF]M⿌st[W~*Ojߠv~Ou{' L*y3̫?Pq+wkT-\Ys%FK<'ηE]Y?ܝ-Nld_qSI|[dwfH<2׏sϱtj8^K ^7p^+~Y>fO#x|E$s;Ǿ=i%wնy"ww8RIpn/!uJo3<-y7\8_<3ͫBJ-P!n%y/_>ǹuʼnnr\jy/:q|͡Nv%jq'xzǸs/|kԕOsgx&_!}o[#WKzӿ{/u˼āROp]~aWpW8]<ǡ_j}CoJ?_#/e?Ր +y,yX3Y$9͎?5~~ˍ,N.i_aiKrߩ/9]=j[8יx/u=jv~{ywfkvqq*_?k;or^۷Ys]nOJÿ6oݢ*jg<ۙooPW}}?nپb%8#UKy^P!^-8_^ yˊڜvu>:A]DSd<8y2y2$8/y2ws缄vdl}y2xv qxLzt'p}ϓy֮{u_7icw<[xu%dy2g;y29x݇|9`3Ń|[x>zn/x>5%q<ᬒ ~|& |kJ g|{xͩ`&N||&'nNgpJgp~gp|O-O<'c3< ._x>wg<8gqyx> gp g8t[پF]ٿOg8 ϔ| 3X|C<gxp}ἳ;\:༳͛qqٍ${Ru.8Pj8lB?/}^4Km⼳:쀧mﶉ4.<8;_ך7p㼳w>#yg8|uqyu__;8qJ6/w]6ۺwἳ[8lqygq^-8l y|pMV'8k7m8|yg?;ߘ8;qٍ~|q睧Ǯ{';?nם睟4;zyg_wήp9{c_?8\HeUݼק0a󃏔Oxp8?=`c=c~Jy:q%Sg~`\Ur-9uq~_8q~A68#? :A3uq8?hy<p~t0w8?hu38?J]?8?x}8?ƃv?q~'|q~'NO]qqlCG|~8$s+.8cy<q8~pI\y8q|}^qzE7v[k,lX_Ľ.`Cc\GKMoro:en=Oc%>v~r>%ȹKv]o64=ۼݽc=[ܰcۭ?^-/ߞשo]'.Pc|a}{/:vW]wo7ޭ ۍcϡowv]ow64/+_ya__+8`{Ϝ@7?\ro8c[nyv>-n%Y_oyc ;R_Nc^8|FͲ/jE]__^E^4 \Eu*~[/狋]Wr~[rJƱ_Ēc_裩=1!~Jy]%~Q/zqyEcE)pe{8cchs-N/jy~<yGWkuFc7WEϝ@_W%nqr[~e볞G|R{r+[اWv_W._euW.e\=`qrWƱW\=cq?3+͛y+_W9~b,+ _y?'4ӮH.Fd@B;tfViCH)US&PɎcLDjN4ʑ]e쐡PhE)i Hѝ{}g{-J? +/Wz g*󄼲/3mg_~D8 ?3\ y]}:oϙg{Şy/܏~&tg0y[Ş7yK:3g8 ™gXq>n KgYy3ky\ktƙg<`N'~3_ġƙgPgY3(n :mgmkp5<Αyy&ggAy<3P|˞y}ag y Wg<' / a?ogO|x2ϠH3 y^y!? +Np ?8Aur^kbc36k{~Ҝ1ټƓڞ5Npa_qE߬q?ujWmOP~Ej@g&oq?8KPB;A#]Z<'o?.{+}֡?h\ 'A?AO/k= œ-}4?Af ?_-9CP8AϷ?hIP--&W]/~_q?8~)υA#GWy ?۵O? z޾Uۣ+n5ޮ8Eo׹KkJ jJ~_]q+J~e8j'ZѯNYq*p+ѯlW/ ՞=y?)JJ8:p+˷>~%{+g+W>ѯ쾠_/+_WC~ѯ~kW:+Q#U|p*_ ggiz+:`}}w/K;)_+6_-{[|rA={>|܇/͍k|{j|;ٷCju\=ul~.|rM\>' +|S5>t^AܸƧO>ï~yP|_0$ndƍσb?fy Wj{y.|x39N_Y{=;7 <h_UϛW//ߵ>tuo|x>Xwɫ&k3g^w;||aygzs]Jp\gA G?K\lK^2q<|#\jC^ſ'=y%OO&o;t><h?KO}+éN4jR2_S_}]%Ç_ہS_ocGG}[[_S5N}O}{SK}ڞ:*񍅣b_p+zdO}uJ}8~8s7wug+?ѭ<+=W:G+W'b]+énxxN3Şm}eW''O}eQ_<|z+֡,W{TP_yQ_)﩯|tO,S_YPG |W|N} E}q8W3AW:+Q#U_>W'䕞c/J)_)WWG8|u^RWo{M+s3Wv.l:|T>Wq>ZqJ _|3=3M]]J}8|}_5_/NW|F|8Wg _Y _+_WWy]>|`Wf_~WW}ևl?ˁWJy_ka_OZ>t+;/Jy_W'|%Wo3|=|e _ <}Wv_+_2>Wg">_JqW:a_W>|e8|yB^+s䭰<˾.  0 PolygonVertexIndexit  # xCfm۶mm۶m۶m۶mMT=&iktL߿,3;ˁXv.aq\ʲc`Gq98Ɏr,7;ˡXvC .a~\}`{q9<+rV刬0ۅˑX#l.Gav\ʊm`[q9:+ɶr Vm嘬4ۄ˱Xcl.az\ʳuU`kq9>rV儬2[ˉXl.'ar\Nʪe`Kq99ɖr +V-唬6[˩XSl.a|\Ny5`sq9=krֈe2Y6lM \l4\l*.F 6m%˶ъMeh&map6ڲ񸜗cp6ڳu`cp FrA։B3˶хeʆmtcp6l=\l0.F/6m7˶ч emslY_\.@mc˶1ezm eq6nlYW\ .Hmc˶1ue:r6emgmq>mLdq!Zr#6匬1k{mc*k˶15mxq6fl3Y\Y1.lmck˶15eۘmgp6l Y\ڸlY-\%&.RVmc˶UeXmdUp6VʸlY%\ְlkY\u<.zV ,.FVmc+fV +-$.f[Y \m8.vV mc+˶e +mfp6l{Y\},?.~m ˋ˶q ˍ˶qe8rme9p1²^8βm`Yqy:;ɲmbqy&;2maq6β lXz\,.cXZ\, .%m2Kˋ,./aXr\,. m&K˶q%e۸ma q6lX|\,.m!˶exbm0lXh\,.b_XH\>̾|}cq(΂vŽemfp6lY \\ ͟eeeeee+,8 m#m#$Pg\'\0G\\p{\;\[\"op>eۈeۈeۈeۈ?eۈ?mOq6bOp6bq6bp6q6p6q6p6wq6wp6q6p67q67p6q6p6RWq6RWp6Rq&~K6".FZ.F:<.Fz.F,.FFvm#;˶eNmde'p6l1\(.FNvm#;˶emep6l>\l/.FAmۍ˶Qe(vme;p6l6\l+.FIcRl3.Fi m ۈ˶Qme(mgp6*Udkp6*ոl*\*l%.FUm[˶Q-eۨmdKp6jŸl"\:l!.Djk|\l.F6m!˶шehfm4a3q6lcq6ilT\l +.FK6m˶њMeh&meq6ڱqlX\:1lh\Nl.Fg6m ˶ѕ eƆmtgCq6z!l=`\^l.Fo6m˶їep6lX\7. mc0˶1eʺm cp6l#X\3.( mc4˶1uerjVý1eۘmLdq6&VlYK\).Tmckm`Mq6f&lXc\٬.bsXC\.˶eXm,bq6ZlKXM\.2Vmc9˶UeXɪmbq6VJlkXE\.:Vmc=+˶emlbq66RlI\.6Vmc;+˶eɊmbq6vB.}m˶exbmlY\и6>Pl_XH\,.7m; ˶eɂmbq6~@lX@\, ..F o9˶˶˶˶˶ ˶˶˶˶ +˶˶˶˶˶˶˶˶ ˶˶˶˶ ˶mğmmmmmmm$m$m$om$om$oM|b7pm$u\5\U\\Te\%\4E\\ty\9\ Y\ .F&vm#3;˶eNmdcq6cl9Q\.F.vm#7;˶emcq6}l^\l.F!m0ۅ˶Qe(vmcq6ml%V\l .`f\l.Fm,ۀ˶Qe(mT`kq9>mTbq6*UlUJ\l.F5m")wl).FMm[˶Q-eۨme p6l<\l..FC6m˶јehfm4e3p6Ǧm4cp6l-\l2.F+6 m5˶цMehmcp6ڳu`cp6:Ѹl(\l$.F6m+˶э eΆm`Cp6zl \l .F6 m/˶mg}q6>lYo\A.`mc˶1ueƺm g]q9)κ^:mbp6FlcX\=.8mcmc?ˇ˶qe8mbq6\lGXN\,.1m8ˆ , .)m4˄˶qee82mcq6γt<]`iq6.4lXj\,.m*K˶q%e۸Βm`Iq6n$lXb\,.m.K˶qe۸m<`qq68lXl\,.m)˶Eex΢6Eexɢmbq6^HlY\O,4.g +m ˶eƂm|gp6~l?Y\>~lY \?, ._ m@ m# 6p6q6p6?q6?p6q6p6B_q6B_p6Bq6Bp6q6p6q6p6"oq6"op6"q6"p6/q6/p6q6p_S\\Xc\#\8C\\x}\7|˶˶˶˶˶˶ ˶˶˶˶ +˶˶˶˶?˶?˶?˶eNmdfp6lY \l8.Fvv m#;˶emfp6򰃸ly\|l?.F~m ۋ˶Qe(vmfp6lE\bl;.Fq mۊ˶Qmی˶Qme(6mep6ʱl:\ +l-.D|kj\l.Fm*[˶Q-eۨΖm`Kq6j%lb\l.Fm.[˶Qeۨm4`sq69ll\l.F6m)˶?6m˶ќMehmdq6ZIlD\6l.F[6m˶ў˶ёeFmtf#q6l]p\nl.Fw6m˶ѓ emfq6l}Y\~m?˶1ezr|V½1ezm eq6nlYW\ .Hmc˶1ue:meq6ƱvlY[\&6lYk\I.dmc +k˶15eۘƚ6l3XS\ .,mc6k˶15eۘmcq6zl X]\."Vmc1˶eXjm,cq6jl+XU\ +.*Vmc5˶UeX*mcq6ֳr܏m`eq662lXi\ͬ.WdXIk \m8.vV mc+˶e +mfp6l{Y\},?.~m ˋ˶q ˍ˶qe8rme9p6lY6\,+.Og'Y\S,3.i m ˈ˶qee8mgpym\dip6.ԸlY*\+,%.UmK˶q%e۸mdIp6nĸlY"\;,!.] m˶qexmm#?ۇ˶Qe(mbq6 +]lEN\l.F1m8ۆ˶Qme(ɶr Vme݄{m ۈ˶Qme(mgp6*l\Jl5.Fe +m +[˶QeۨƖmTgp6jl5\Zl1.Fmm[˶Q-eۨmgp6l \Fl6.Fc6 m ˶єe˶ьMehΦm`Sp6Zɸl$\l".F6m-˶юehmt`cp6:Ѹl(\l$.F6m+˶э eΆm`Cp6zl \l .d k}Y\~m?˶1ezm bp6lCX\;.0 mc8˶1ue:rM6ue:map6ƲlX;\-.gX\5.$ +mc2k˶1eۘʚmLcp_^\).Lmck˶15eۘme p6lY=\..BVmc˶eXjm,e5p6lY5\*.JV{U2.jV mc ˶UҬ+{mc=+Vmc#+˶eJmla%q6lXq\.Vmc'+˶e +(e +mcq6|lX^\,.!m0˅˶qe8rmcq6ll'XV\N,lXf\,.m,ˀ˶qe8иlY(\/,$.Wm ˶emdAp6~lY \?, ._ m m# 6p6q6p6?q6?p6q6p6B_q6B_p6Bq6Bp6q6p6q eۈeۈeۈex^^ۈlQllg ).FL .F,1.Fl.F!.F\.F<>.F|.F..FB.F"6.Fb.F&.FR.F2:.Fr.F +*.FJ +.F*2.Fj.F".FZ.F:<.Fz.F,.FFvm#;˶eNmde'p6l1\(.FNvm#;˶emep6l>\l/.FAmۍ˶Qe(vMDQme(ζm`[q6J-bq6JMleF\l.F9m<[˶QeۨmTbq6*UlUJ\l.F5m:[˶Q-eۨɖmbq6jEluB\l.F=6m>˶рehm4bq6YlML\l.t\fl.Fs6m˶ђMeh&mfq6ڰ lmx\vl.F{6l .FG6m˶љeFmteq92+ʆ^Άm`Cp6zl \l .F6 m/˶mg}q6>lYo\A.`+!.Pmc˶1uemdq6FNlYG\1.Xmck˶1lk˶1eۘZmLf-q6lSYs\i.kË˶15eۘɚmbq6fFlsXC\.˶eXm,bq6Z܉-a5q6lXu\.eXUk+Y\U2.jV mc ˶UeXmgpmldep66ҸlY)\-$.VVm8.vV mc+˶e +mfp6l{Y\},?.~m ˋ˶qe8rmfp6lGY\c,;.q mˊImˌ˶qee82mep6αlY:\.lY\K,5.e +m +K˶q%K˶q%e۸mdIp[^۸ma q6lX|\,.m!˶exbm0lXh\,.m+ ˶e΂m`Aq6~ lX`\,.m/ ˶_P˶ ?l?l߸l_lAlAllol!|Ŀm?m?m?Mbpm{\;\[\\Hk\+\(K\ \hs\3\m#&m#m#6m#m#. m#~m#>~m#~m#!~m#~m#1~ m# ~m#)~m#~m#9~ m#~m#%~m#~m#5~ m# ~m#-~m#~m#=~m#~m##;˶eNmda'q6lq\.Fvm#';˶emaq6l~\}6 +l\Bl7.Fa mۉ˶Qe(ƶmgp6Jl%\J͸l&\2l#.FYm[˶QeۨmTdkp6*ոl*\*l%.FUm[˶Q-eۨmdKp6jŸl"\:l!.F] m˶Qehm4dsp6ٸl,\&l&.FS6ml:.F36 m9˶тMeh&mbp6Zlm\l<.F;6m=aY~6FG6m˶љeFmteq6alP\l.FO6m˶ћ emeq,p6lX\7. mc0˶1eʺm cp6l#X\3.( mc4˶1uemcp6ƳlX\5.$ +mc2k˶1eۘʚmLcp_^\).Lmck˶15eۘme p mgp6l Y\:ڸ6ZlKXM\.2Vmc9˶UeXɪmbq6VJlkXE\.:Vmc=+Vmc#+˶eJmla%q6lXq\.Vmc'+˶e +maq6lX~\,.m ˃˶qe8rma9q6lXv\,. , .)m4˄˶qee82mcq6γt<]`iq6.4lXj\,.w`XJkWY +\k,9.u mK˶q%e۸mfp6lwY\{,>.}m˶ ˶exbmlY\O,4.g +m ˶eƂm|gp6~l?Y\_,0.om ˶e/e\@o\p6?q6?p6q&o6B+.FH .F(3.Fh.F#.FX.F8=.Fx.F-.FD .F$5.Fd.F%.FT.F49.Ft.k˶˶ ˶˶˶˶˶˶ ˶˶˶˶˶˶ ˶˶˶˶ +˶˶˶˶?˶?˶?˶eNmdfp6lY \l8.Fvv m#;6{F.vm#7;˶emcq6}l^\l.F!m0ۅ˶Qe(vmcq6ml%V\l .`f\l.Fm,ۀ˶Qe(mT`kq9>mTbq6*UlUJ\l.F5m:[˶Q-eۨɖmbq6jEluB\l.F=6m>˶рehm4bq6YlML\l.t\fl.Fs6m˶ђM,'{m5˶цMehmcp6ڳl\l4.F'6 +m3˶хeʆmtcp6\`Cp6zl \l .F6 m/˶mg}q6>lYo\A.`mc˶1ueƺm g]q6F.l#Yg\Q.hmc ˶1emgmq6&6lYk\I.dmc +k˶15eۘƚ6܌`Mq6f&lXc\٬.dXCksY\y>.|Vmc˶eXjm,fp6lKY \e:.rV mc˶Ul˶UeX*mep6ֱlY9\ ,.FVmc+˶eJmle%p6lY1\(.NVmc+˶e +mep6lY>\,/.Amˍ˶qe8rme9p6lY6\,+.Og'Y\N̸lY&\3,#.Y[9,=,.m"K˶qe۸Rm\a)q6lXr\,. m&K˶q%e۸*v%e۸mcq6xlX\\,.#m1˶exbm0lXh\,.m+ ˶e΂m`Aq6~ lX`\,.bX@\, .ƿ2˶ 8α?67.F`.F'.FP.F0;.Fp.F+.FH .F(3.Fh.F#.FX.F8=.awll7l׸lWlQlQllg ).FL .F,1.Fl.F!.F\.F<>.F|.F..FB.F"6.Fb.F&.FR.F2:.Fr.F +*.FJ +.F*2.Fj.F".FZ.F:<.Fz.F,.FFvm#;6{ +Fvm#+;˶eΎm`Gq6r#la\.Fvm#/;˶em`{q6 +=ln\l.Fm(ہ˶Qme(ζm`[q6J-bq6JMleF\l.F9m<[˶QeۨmTbq6*UlUJ\l.F5m:[˶Q-eۨɖmbq6jEluB\l.F=6m>˶рehm4bq6YlML\26Ǧm4cp6l-\l2.F+6 m5˶цMehmcp6ڳl\l4.d(\l$.F6m+˶э eΆm`Cp6zl \l .F6 m/˶mg}q6>lYo\A.`mc˶1ueƺm g]q6F.l#Yg\Q.hmc ˶1emgmq6&6lYk\n&VlYK\).T3׆mck˶15eۘmfp6氆lsY\y>.|Vmc˶eXjm,fp[jm,e5p6lY5\*.JVmc˶UeX*mep6ֱlY9\ ,.FVmc+˶eJmle%p6lY1\(.NVmc+˶e +mep6lY>\,/.Amˍ˶qIˉ˶qe8Ʋmgp)Ʋ$˂˶qee82maq6β lXz\,.m"K˶qe۸Rm\a)q6]cq6dl7XR\, .-m6K˶q%e۸mcq6xlX\\,.#m1˶exbmmc?ˇ˶qe8r}6ƽqe8rme9p6lY6\,+.Og'Y\S,3.i m ˈ˶qee8mgpym\dip6.ԸlY*\+,%.UmK˶q%e۸mdIp6nĸlY"\;,!.] m˶qexm.F|.F..FB.F"6.Fb.F&.FR.F2:.v F +_eH_eH_eH_eH_eH_eHeHeȀemdbq62SlYI\.F6vm#;;˶eɎmbq6rClyA\ .F>m#?ۇ˶Qe(mbq6 +]lEN\l.F1m8ۆ˶Qme(ɶr Vme(6maq6ʲ lz\l.Fm"[˶QeۨVmTa+q6lr\l.F m"9w Zl1.Fmm[˶Q-eۨmgp6l \Fl6.Fc6 m ˶єe˶ьMehΦm`Sp6Zɸl$\l".F6m-˶юehmt`cp6:Ѹl(\l$.F6m+˶э eΆm`Cp6zl \l .F6 m/˶mg}q6>lYo\A.`mc˶1ueƺm g]q6F.l#Yg\NjN6FlcX\=.8mc.|Vmc˶eXjm,fp6lKY \e:.rV mc˶UeX*mfp6ְlkY\u<.zVmc+˶eJmlfp6l \m8.vV (.NVmc+˶l+{mc/+! +%bm۶m۶9m۶m۶m۶힤:emgp6lY\C,7.a mˉ˶qe8Ʋmgp6N.}m˶exbm0lXh\,.m+ ˶e΂m`Aq6~ lX`\,.bX@\, ..F 'mmmmmmÿmǿmmĿm?m?m?m?mmmDmDmD_mD_mD_mD_mDßmDǟ6l1'lǸlGlqlqll{l lkx˶˶˶˶ ˶˶˶˶ +˶˶˶˶?˶?˶?˶eNmdfp6lY \l8.Fvv m#;˶emfp6򰃸ly\|l?.F~m ۋ˶Qe(vmfp6lE\bl;.Fq mۊ˶Qmی˶Qme(6mep6ʱl:\ +l-.FEm[6 +Fm*[˶Q-eۨΖm`Kq6j%lb\l.Fm.[˶Qeۨm4`sq69ll\l.F6m)˶?6m˶ќMehmdq6ZIlD\6l.F[6m˶ўemtdq6:QlH\.l.FW6m˶ѝ emdq6zAl@\>l .F_mem `}p6޸lX/\'.'dYkCYw\a.pmc˶1ue:mfq6ưlcY{\q.xmck˶1lk˶1eۘZmLeq6f /. mc&k˶15eۘma q6lX}\.Vmc!˶eXjm,a5q6lXu\. +Vmc%˶UeX*maq6ֲ +lXy\.Vmc#+&Vmc3+˶eJre6ǽemdEp6v¸lY!\= .^V mcˏ˶e8mdyp6ܸlY.\ĎlGY\c,;.q mˊImˌ˶qee82mep6αlY:\ ,-.EmK˶qe۸Rm\e)p6lY2\,).MmK˶q%e۸me p6lY<\,..Cm˶M ˶exƢmPl_XH\,.aXp\,.m' ˶emaq6 lflö˶˶˶˶˶ ˶˶˶˶ +˶˶˶˶˶˶˶˶ ˶˶˶˶ ˶mğmM`pm!.F\.F<>.F|.F..FB.F"6.Fb.F&.FR.F2:.Fr.F +*.FJ +.F*2.Fj.F".FZ.F:<.Fz.F,.FFvm#;˶eNmde'p6l1\(.FNvm#;˶emep6l>\l/.FAmۍ˶Qe(vme;p6l6\l+.FIcRl3.Fi m ۈ6F9m<[˶QeۨmTbq6*UlUJ\l.F5m:[˶Q-eۨɖmbq6jEluB\l.F=6m>˶рehm4bq6YlML\l.t\fl.Fs6m˶ђMeh&mfq6ڰ lmx\vl.F{6m˶ёeFmtf#q6l]p\nl.Fw6m˶ѓ emfq6l}Y\ʲ~6lX\7. mc0˶1eʺm cp6l#X\3.( mc4˵mc,k˶1emL`mp6&ָlX+\ɬ%.mc*k˶15mxq6fl3Y\Y1.lmck˶15eۘmgp6l Y\E6.bV mc ˶eXƪm,gp6Vl+Y\U2.jV {5".ZVmc+˶岬FVmc+˶eJmle%p6lY1\(.NVmc+˶e +mepy mgp6lY\C,7.a mˉ˶qe8Ʋmgp6N.}m˶~l{m1˶exbm0lXh\,.m+ ˶e΂m`Aq6~ lX`\,.m/ ˶_˶ ?l?l߸l_lAlAllol!l!/lϸlOlalallwll7l׸lWlQl kx˶mğmmmmmmm$m$m$om$om$om$om$ïm$ǯmmįm/m/m/m/mmmdmddgp62Ӹl)\,$.FVvm#;˶emdGp6røl!\< .F^v m#ۏ˶e(md{p6 +ݸl.\"l'.FQmێ6 F m$ۂ1X)m4ۄ˶Qme(6mcq6ʳulZ\l .F%m2[˶QeۨVmTcq6el5R\l .F-m6[˶Q-eۨmcq6yl \\l.F#6m1˶фehfmMehƦm4gSq6Z)l-d\Vl.Fk6m ˶іehmgcq6:1lh\Nl.Fg6m ˶ѕ eƆmtgCq9*+Ά^mbp6zl} \?.F?˶џem dq6^lYO\!.Pa.pmc˶1ue:mfq6ưlcY{\q.xmck˶1eۘZmLf-q6lSYs\i.kË˶15eۘɚmbq6fFlsXC\.˶eXm,bq6ZlKXM\.waXu\. +Vmc%YVjV mc ˶UeXmgp66lY\M4.fV +mc +˶eƊmlgpymdEp6v¸lY!\= .^V mcˏ˶e8mdyp6ܸlY.\#,'.Qmˎ˶qee8tvee82mfp6ΰlgY\s,=.ymK˶qe۸Rm\fp6lWY +\k,9.u ,).MmK˶q%lK{m.K˶qe۸m<`qq68lXl\,.m)˶Eex΢m`Qq6^(bq6^HloXD\,.;m= ˶em|bq6>Pl_XH\,.7m; ˶eɂmbq6~@lX@\, .a.F {mmmmmmÿmǿmmĿm? ˶˶˶6qýl7l׸lWlQlQllg ).FL .F,1.Fl.F!.F\.F<>.F|.F..FB.F"6.Fb.F&.FR.F2:.Fr.F +*.FJ +.F*2.Fj.F".FZ.F:<.Fz.F,.FFvm#;˶eNmde'p6l1\(.FNvm#;˶emep6l>\l/.Dxkn\l.Fm(ہ˶Qme(ζm`[q6J-bq6JMleF\l.F9m<[˶QeۨmTbq6*UlUJ\l.F5m:[˶Q-eۨɖmbq6jEluB\l.F=6m>˶рehm4bq6YlML\l.t\fl.Fs6m˶ђMeh&mfq6ڰ lmx\vl.F{6m˶ё {m3˶хeʆmtcp6l=\l0.F/6m7˶ч emslY_\.>lYo\A.`mc˶1ueƺm g]q6F.l#Yg\Q.hmc ˶1emgmq6&6lYk\I.dmc +k˶15eۘƚ6l3XS\ .,mc6k˶15eۘmcq6z܎-`uq6:lXm\Ŭ.dXMkKY \e:.rV mc˶UeX*mfp6ְlkY\u<.zVmc+˶l+˶eJmle%p6lY1\(.NVmc+˶e +mep6lY>\,/.Amˍ˶qe8rme9p6lY6\,+.Og'Y\S,3.i m ˈ˶qee8mgp6.lY\^.ԸlY*\+,%.U;%,9,. m&K˶q%e۸ma q6lX|\,.m!˶exb&exbm˶exmPl_XH\,.7m; ˶eɂmbq6~@lX@\, .agloö˶6q½lAllol!l!/lϸlOlalallwll7l׸lWlQlQllg ).FL .F,1.Fl.F!.F\.F<>.F|.F..FB.F"6.Fb.F&.FR.F2:.Fr.F +*.FJ +.F*2.Fj.F".FZ.F:<.Fz.F,.FFvm#;˶eNMeΎm`Gq6r#la\.Fvm#/;˶em`{q6 +=ln\l.Fm(ہ˶Qme(ζm`[q6J-bq6JMleF\l.F9m<[˶QeۨmTbq6*UlUJ\l.F5m:[˶Q-eۨɖmbq6jEluB\l.F=6m>˶рehm4bq6YlML\l.t\̲i6l-\l2.F+6 m5˶цMehmcp6ڳl\l4.F'6 +m3˅Y6m+˶э eΆm`Cp6zl \l .F6 m/˶mg}q6>lYo\A.`mc˶1ueƺm g]q6F.l#Yg\Q.hmc ˶1emgmq6&6lYk\I.d).TmckrVXSk3Y\Y1.lmck˶15eۘmgp6l Y\E6.bV mc ˶.l˶UeXmdUp6VʸlY%\5".ZVmc+˶emldep66ҸlY)\-$.VVmc+˶emdEp6v¸lY!\= .^V mcˏ˶e8mdyp6ܸlY.\#,'.Qc,;.q mˊI,3,.m,ˀ˶qe8m\`iq6.4lXj\,.m*K˶q%e۸Β2v%e۸ɒmbq6nDlwXB\,.=m>˶exmPl_XH\,.7m; m' ˶e vĽe/e}\@o\/\ O\\`w\7\W\\Pg\Bp6q6p6q6p6"oq6"op6"q6"p6/q6/p6q6p_S\\Xc\#\8C\\x}\=\]\\Dm\-\$M\ \du\5\U\\Te\%\4E\\ty\? emdbq62SlYI\.F6vm#;;˶eɎmbq6rClyA\ .F>m#?ۇ˶Qe(mbq6 +]lEN\l.F1m8ۆ˶Qme(ɶr Vme(6maq6ʲ lz\l.Fm"[˶QeۨVmTa+q6lr\l.F m&[˶Q-eۨma q6l|\l.F6m"=wFl6.Fc6 m ˶єe˶ьMehΦm`Sp6Zɸl$\l".F6m-˶юehmt`cp6:Ѹl(\l$.F6m+˶э eΆm`Cp6zl \l .F6 m/˶mg}q6>lYo\A.`mc˶1ueƺm g]q6F.l#Yg\Q.hmc ˶1emgmq6&6lYk\NV6&lSX \9.4 eۘmdMp6fƸlY#\9!.\ mc˶1vl˶eXjm,fp6lKY \e:.rV mc˶UeX*mfp6ְlkY\u<.zVmc+˶eJmlfp6l \m8.vV mc+˶e +mfp6l{Y\},?.~DZ,/.Amˍ˶qlˉ{m(ˁ˶qee8βm`Yqy:;ɲmbq6NLlgXF\,.9mp6_m# m#m#0 m#m#(m#m#8 m#m#$m#m#4 m# m#,m#m#<m#m#"m#m#2 +m# +m#*m#m#: ?eۈ?eۈ?eۈ?eۈ?eۈ?eۈeۈeHeHeHen^HM\ \du\5\U\\Te\%\4E\\ty\9\ Y\ .F&vm#3;˶eNmdcq6cl9Q\.F.vm#7;˶emcq6}l^\l.F!m0ۅ˶Qe(vmcq6ml%V\l .`f\l.Fm,ۀ˶Qe(mT`kq6*5lj\l.Fm"1wjl9.Fu m[˶Q-eۨmfp6갅lu\zl>.F}6m˶ѐehfm4fp6lM \l4\l*.F 6m%˶ъMeh&map6ڲl8\l,.F6m#˶щeFmta#p6l0\l(.F6m'˶ы emap6l.Fmc ˶1ezm f=q6lCYw\N̪n6l#X\3.( mc4˶1uemcp6ƳlX\5.$ +mc2kˍmc*k˶15mxq6fl3Y\Y1.lmck˶15eۘmgp6l Y\E6.bV mc ˶eXƪm,gp6Vl+Y\U2.jV mc ˶UeXmgp66lY\M4.fV +-$.VVmc+˶l+{mc'+˶e +maq6lX~\,.m ˃˶qe8rma9q6<cq6ll'XV\N,lXf\,.m,ˀ˶qe8m\`iq6.4lXj\,.m*K˶q%e۸Βm`Iq6n$lXb\,.m.K˶qe۸m<`qq68lXl\,.m)[3m9˶Eexɢ0EƽEex"mep6ޱlY8\,,.Gm ˶eBm|e!p6lY0\>~l?Y\_,0.om ˶e/2e}\@o\/\ O\\`w\7\W\\Pg\'\0G\\p{\;\[\\Hk\+\(K\ \hs\3\m#&m#m#6m#m{{m#q6p6wq6wp6q6p67q67p6q6p6RWq6RWp6Rq6Rp6q6p6q6p62gq623li\.Fvm#+;˶eΎm`Gq6r#la\.Fvm#/;˶em`{q6 +=ln\l.Fm(ہ˶Qme(ζm`[q6J-bq6JMleF\l.F9m".w +l-.FEm[˶QeۨVmTe+p6l2\l).FMm[˶Q-eۨme p6l<\l..FC6m˶јehfm4e3p6Ǧm4cp6l-\l2.F+6 m5˶цMehmcp6ڳl\l4.F'6 +m3˶хeʆmtcp6l=\l0.F/6m7˶ч emslY_\ʳ>6޸lX/\'.mc(˶1ueκm`]p6FθlX'\Ѭ#.mc,ku8mcm!˶exb ĽexƢmlY\>>иlY(\/,$.Wm ˶emdAp6~lY \?, ._ m( m# 6p6q6p6?q6?p6q6p6B_q6B_p6Bq6Bp6q6p6q6p6"oq6"op6"q6"p6/q6/p6q&g_?eۈ?eۈ?eۈ?eۈ?eۈ?eۈeۈeHeHeHeHeHeHeH_eH_eH_eH_eH_eH_eH_eH_eHeHeȀemdbq62SlYI\.F6vm#;;˶eɎmbq6rClyA\ .F>m#?ۇ˶Qe(mbq6 +]lEN\l.F1m8ۆ˶Qme݂{cRl3.Fi m ۈ˶Qme(mgp6*l\Jl5.Fe +m +[˶QeۨƖmTgp6jl5\Zl1.Fm-c-fnڶm۶mmm۶m۶mq4ək>:ǝ̴]ͱ6mX r9kcvnQ;'ظcmlʱ66mgX3rkXr-ک9Ɩkcvru;)ئcmlNȱ6kX۷rڱ9Ǝkcvts;*إcmڎȱ6vkXr=ڡ9ƞkcvphKrWs}9~mkcm˱6hX}r9mk㐶gqh#8cmv툶kqd%8cmvʱ6i;XǶr9mkㄶmqb&8mcmܶʱ6Ni[X-r9 78mcm6ͱ6jXgssF9ƹmk㼶Aq~[?ڸcm\ͱ6.jXsKZ9ƥmk㲶Fqy[=ǻ+j9ƕmk㪶Jqu[9KJkۊ9umk|qC[.ڸ-cmԖɱ6nnKXr[ے9mmkxqG[,ڸ-#]mkpqO[(ڸ-cmȱ6oXrۼ9Cmk6wH+x͙cm<ȱ6oXOr|z{͚cmicX1s9mk6Ze5cm|F]6Rm1cm|߆ϱ6~hX?as09mk6Tk2 cmϱ6hXAs 9mk6Po0`cm ycm cm cm cm cm cm cm cm cm cm cm cm p99w9ƈ9{kckckckckcFi1VI1vq1NQ1na1^A1~~1A^1an1QN1qv1IV1if1YF1yz1EZ1ej1UJ1ur1MR1mb1]B1}|1C\1cl1S{&ژ=cmҞʱ6fmOX'r9kch1W{$ژ=cmʱ6mXr9kcvoP'XݝcMԽ+wh3Xݑcm,nϱ6hXK[s-9-nα6i7X˶s 9kcv]b6X]cmܮα6ViWX+s9kcvYf4X]cm.α6iX s9kcv^a;7بcmlα66igX3s9si9kcvJU;9غcmlṈ6m'X۵sq9kcvLS;:عcmҎHmvDjcvx{;,أcmɱ6jX{r}ځ9ƾkc?˱6oX}s>9^α6n{X=sC9amk[qD58cmvα6n;XǴsc9qmk]qB68mcmԶα6Nn[X-sS9im׆cm6˱6lXgMr99mkܶaq^ 8cm\˱6.lxvQ['ڸcm\ʱ6.mkxk[#wqy[=ڸcm\Vͱ6jXWskJ9Ƶmk㺶Bq}[>ڸ-cmؖͱ6njX7s|P-cmږ̱6nkKXs;b9Ɲmk㮶Hqw[8ڸ-cm̱6k Xs|9ƃmk6Op;x͕cm<̱6ksXs'l9>=fͱ6jXOsgL9Ƴmk6C|>xMcmئͱ6^jX/s|Q{Mcmڦ̱6^kSXskMxMcm&ɱ6nXﴉrwۄ9{mk6~A/cm|ɱ6>ncXrOۘ9}ȱ6>oX_r/ۨ9Wmk6rM)cm|Fȱ6oX?r۰9Omk6tK* cmֆȱ6~oXr?۠9_mk6pO( cm 0z˱66ɱ6α6ʱ6̱6ȱ6+mkckckckSjc~c1\C1|}1B]1bm1RM1ru1JU1je1ZE1zy1FYk#4+$;8'(70/ ??ژ /ژ07ژ('ژ8;ژ$+ژ43ژ,#ژ<=ژ"-ژ25ژ*%ژ:9ژ&)ژ61ژ.!ژ>>ژ!.ژ16ژ=cmܞα6fiOX's9kcX1g{4ژ=cmα6iXsa6oX r۽9BkcvwH+Xݙcm,ȱ6oXKr%ۭ9Rٖn7X˴reۍ9rkcv}B.X]cmԮɱ6VnWXrUە9jkcvyF,X]cm.ɱ6nX봋ruۅ9zkcv~A;/ذcmlɱ66ngXrMۙ9fk9kcvje;%تcmlNʱ6i'X۶r9׎]mЎͱ6vlX;sQ9.kcvD[;<ؽcmͱ6lX{sA9>/mXsY/8cmɱ6j{XrCڞ9ơmk㰶{qx-8cmvɱ6j;XGrcڎ9Ʊmk㸶}q|.8mcmضɱ6Nj[X'rSږ9Ʃmk㴶ykͱ6hXgMs&9moiX s9m׶o6.lXur9%mkҶfqY[#ڸcm\V˱6lXWUr95mkڶbm׵r9 mkƶlqS[&ڸ-cmҖʱ6nmKX%r9mkζhqW[$ڸ-cmʱ6m Xr9mk6oP'x͝cm<ʱ6msX9r9mޞlXOYr93mk6ciϵr9 mk6moirW/sWT9ƫmk6EzkcXs/h9Ɨmk6Ju9cm|F̱6k#X߷sp9Əmk6Ls: cmچ̱6~kCXs?`9Ɵmk6Hw8 cm̱6X~6PO1pw1HW1hg5R#w1x?="-25*%:9&)61.!>>!.16)&9:%*52-"=<#,cmcmcmcmcmcmcmLcmLcmLcmLcmLcmLcmLcmLcmLcmLcmLcmLcmLcmLcmLcmL?cm̐?cm̘?cmԞɱ6fnOXrYۓ9lkcx51XƜkcH1w{8ژ=cm̱6kXs}9ƂkcvOp;Xݕcm,̱6kwXs%m9ƒkcvKlKseM9ƲkcvC|>X]cmخͱ6VjX+sUU9ƪkcvEz8mcmжͱ6NlX'sV9)mkԶE7ks_nqF,8m9ږmmk㜶Qqn08mcmϱ6.hXus:9mk㒶Vqi[3ڸcm\V튶Zqe[5ڸcm\Vα6i+X׶s +9mkㆶ\qc[6ڸ-cmܖα6niKX%s9mk㎶Xqg[4ڸ-cmα6i Xs9mk6_`7x͓cm<α6isX9s9m֞hd5x͒cmmcX1r9mk6jU%cm|Fʱ6m#Xߵr9mk6lS& cm҆ʱ6~mCX!r9mk6hhAr9?mk6`jϴrW_Icm cm cm cm cm cm cm cm cm cm cm cm p99w9ƈ9H799(W9ƨ9h99g9kckckckckckckckckckckckckckckckckckckckckckckckcFL>@W3sYS9ƬkcD1{{<ژ=cmͱ6jXssyC9Ƽkc@1?Xݗcm,ͱ6jX sE]9ƢkcvGx=Xݖcm,nͱ6jx̶t9Xݔcm,ṉ6k7X˷su9ƊkcvMr:X]cmڮ̱6VkWXs5e9ƚkcvIv8X]cm.̱6kXs y9ƆkcvNq;;YMۙ9fk9kcvje;%تcmlNʱ6i'X۶r9kcvlh;cr9kcvdk;"حcm˱6hX{Cr9kcv`o; دcmr~9mkOqP;8cm̱6m{Xsn9mkȶKqT98cmv̱6m;Xǵsv9ޠжͱ6NlX'sV9mҶ]mڶȱ6Nk6k㌶Yqf48mcm6α6iX s9mkザ^qa[7;:9mk㒶Vqi[3ڸcm\Vϱ6hXWUs*9mk㚶Rqm[1ڸcm\ߖϱ6nhX7es29mk㖶Tqk[2ڸ-cmϱ6hXwEs"9mk㞶Pqo[0ڸ-cmϱ6hXys|B{͓cm<α6isX9sI;͑x͞cmlXqr9'mk6fY#cm|F˱6lX_Qr97mk6b]! cmІ˱6~lX?ar9~҆ʱ6~mCX!r9>=]mͱ6jXs@9ƿmkcZ1`}1PO1pw1HW1hg1XG1x{_iCXC6TK1ts1LS1lc1\C1|}1B]1bm1RM1ru1JU1je1ZE1zy1FYk#4+$;8'(70/ ??ژ /ژ07ژ('ژ8;ژ$+ژ43ژ,#ژ<=ژ"-ژ25ژ*%ژ:9ǚژcmLcmL?cm̐?cm̘?cmԞɱ6fnOXrYۓ9lkcx1G{,ژ=cmɱ6nXryۃ9|kcv@/Xݛcm,ɱ6nwXrE۝9bkcv{D-Xݚcm,nmvsL)Xݘcm,nȱ6oX+r۵9JkcvuJ*X]cm֮ȱ6VoXkr5ۥ9ZkcvqN(X]cMLӽ w~;?ؠcmlͱ66jXsMY9ƦkcvFkcvZE;5زcmlNα6n'X۴sm 9vkcv\C;6رcmԎα6vnGX#s]9nkcvXG;4سcmα6nXs}9~mkcm˱6hX}r9mk㐶gqh#8cmv˱6hXG]r9mk㘶cql!Ӵv9 mkĶMqR:8mcmҶ̱6Nm[Xs_nqF,8mcm6ɱ6nxvN(8mcm6ȱ6oXr ۺ9EmkvqI[+ڸcm\ȱ6.oXWr+۪9UmkrqM[)ڸcm\Vȱ6oX7r۲9MmktqK[*ڸ-cm֖ȱ6noXwr;ۢ9]mkpjr{ۂ9}mk6kǵrWys<9mk6Wh3x͑cmhXqs89mk6Vi3cm|Fm6Ze5cm|Fα6i#X߶s|Ocm|߆ϱ6~hX?as|\{ cm҆ʱ6~mCX!r9mk6hW$ cmʱ6mXr?\('8;$+43,#<="-25*%:9&)61.!>>!.16)&9:%*52-"=<#,cmcmcmcmcmcmcmLcmLcmLcmLcmLcmLcM]mL_ϱ6_˱6_ͱ6_ɱ6_α6_ʱ6_̱6_ȱ6ϟϱ6fȟ˱6f̟ͱ6fjX3sYS9ƬkcD1{{<ژ=cmͱ6jXssyC9Ƽkc@1?Xݗcm,ͱ6jX sE]9ƢkcvGx=Xݖcm,nͱ6jx̶t9Xݔcm,ṉ6k7X˷su9ƊkcvMr:X]cmڮ̱6VkWXsɺ6lXkKr9:kcva^ Xcml˱66lXsr9&kcvfY;#vzy;-آcmlNɱ6j'Xmډ9ƶkcv|};.ءcm؎ɱ6vjGX;r]ڑ9Ʈkcvx{;,أcmɱ6jX{r}ځ9ƾkc?˱6oX}s>9AmkWqH38cmvϱ6oxF5wqd%8cmvʱ6i;XǶr9mkㄶmqb&8mcmܶʱ6Ni[X-rY;mps3f9ƙmk㬶Iqv88mcm6̱6kXs z9ƅmk㢶Nqq[;ڸcm\̱6.kkXs+j9ƕmk㪶Jqu[9ڸcm\V̱6k+X׷sr9ƍmk㦶Lqs[:ڸ-cmږ!Dq{[<ڸ-cm툶HjpqO[(ڸ-cmȱ6oXrۼ9Cmk6wH+x͙cm<ȱ6oD-ǧ'۬9Smk6sL)x͘cmoE-cm|Fɱ6n#vW)wm1cm|߆ϱ6~hX?as09mk6Tk2 cmϱ6hXAsD cmα6iXsi9ƀ?@?99 _9Ơ9`99o9Ɛ9P/990O9ư9p99w9ƈ9H799(W9ƨ9h99g9kckckckckckck^jc~n1QN1qv1IV1if1YF1yz1EZ1ej1UJ1ur1MR1mb1]B1}|1C\1cl1S{&ژ=cmҞʱ6fmOX'r9kch1W{$ژ=cmʱ6mXr9kcvoP'Xݝcm,ʱ6mwX;r9kcvkT%c92kcvc\!X]cmЮ˱6VlXt]mܮα6ViWX+s9kcvYf4X]cm.α6iX s9kcv^a;7بcmlα66igX3s9si9kcvJU;9غcmlṈ6m'X۵sq9kcvLS;:عcmҎ̱6vmGXsa9kcvHW;8ػcm̱6mXsY/8cmmwjWqH38cmvϱ6oXG]s#.9QmkSqL18cm׶ϱ6oxvB68mcmԶα6Nn[X-sS9im׆cm6˱6lXgMr99mkܶaq^ 8cm\˱6.lXur9%mkҶfqY[#ڸmkʶjqU[%ڸcm\Vʱ6m+X׵r9_-cmؖͱ6njX7sR;-ڸ-cm֖ȱ6noXwr;ۢ9]mkpqO[(ڸ-cmȱ6oXrۼ9>=ɱ6nsXrGۜ9cmk6{D-ǧ'۬9Smk6sL)x͘cmncXrOۘ9>]m|Fϱ6hX_Qs(9mk6Rm1cm|߆ϱ6~hX?as09m?~iCX!s9mk6Xg4 cmα6iXsm9ƀ?@?99 _9Ơ9`99o9Ɛ9P/990O9ư9p99w9ƈ9H799(W9ƨ9h99g9kI>`q=Ǿs/ < u9]'r3aʌ9w,Xb=<'8r̅+ϼʍOa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^ "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7oOa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^'H0Ĉ I4SH2ԨӠI63`Ȉ1̘sǂ%+ܳa;ybρ#'\ 3 "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7R?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xF- "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7a &Bq$I&Cy +)QBu4iѦC} 1f”sXdŚ{6ly`#O9pę WyV?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xFO'H0Ĉ I4SH2ԨӠI63`Ȉ1̘sǂ%+ܳa;ybρ#'\ U?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xF !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#'H0Ĉ I4SH2ԨӠI63`Ȉ1̘sǂ%+ܳa;ybρ#'\ i &Bq$I&Cy +)QBu4iѦC} 1f”sXdŚ{6ly`#O9pę Wy'H0Ĉ I4SH2ԨӠI63`Ȉ1̘sǂ%+ܳa;ybρ#'\ D$DQbI$E Yr)PD +UjiФE]z0dĘ Sf̹ckٰ<g.\yWny &Bq$I&Cy +)QBu4iѦC} 1f”sXdŚ{6ly`#O9pę Wy? !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#v &Bq$I&Cy +)QBu4iѦC} 1f”sXdŚ{6ly`#O9pę Wy~!J8 H!K<(SJ: hӡK>3aʌ9w,Xb=<'8r̅+ϼʍ !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#g$DQbI$E Yr)PD +UjiФE]z0dĘ Sf̹ckٰ<g.\yWnޡ !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#Oa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^s "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7^?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xF "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7  &Bq$I&Cy +)QBu4iѦC} 1f”sXdŚ{6ly`#O9pę WyQ?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xF/'H0Ĉ I4SH2ԨӠI63`Ȉ1̘sǂ%+ܳa;ybρ#'\ K?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xF !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#$DQbI$E Yr)PD +UjiФE]z0dĘ Sf̹ckٰ<g.\yWn~!J8 H!K<(SJ: hӡK>3aʌ9w,Xb=<'8r̅+ϼʍ$DQbI$E Yr)PD +UjiФE]z0dĘ Sf̹ckٰ<g.\yWnR "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7~!J8 H!K<(SJ: hӡK>3aʌ9w,Xb=<'8r̅+ϼʍ_Oa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^x~!J8 H!K<(SJ: hӡK>3aʌ9w,Xb=<'8r̅+ϼʍ@Z?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xF_Oa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^+ "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7Oa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^'H0Ĉ I4SH2ԨӠI63`Ȉ1̘sǂ%+ܳa;ybρ#'\ + "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7U?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xF} "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7Y &Bq$I&Cy +)QBu4iѦC} 1f”sXdŚ{6ly`#O9pę WyU?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xF_Oa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^x~!J8 H!K<(SJ: hӡK>3aʌ9w,Xb=<'8r̅+ϼʍ@N?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xF_Oa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^k "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7Oa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^'H0Ĉ I4SH2ԨӠI63`Ȉ1̘sǂ%+ܳa;ybρ#'\ u &Bq$I&Cy +)QBu4iѦC} 1f”sXdŚ{6ly`#O9pę Wy !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#A &Bq$I&Cy +)QBu4iѦC} 1f”sXdŚ{6ly`#O9pę Wy~!J8 H!K<(SJ: hӡK>3aʌ9w,Xb=<'8r̅+ϼʍ !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#7$DQbI$E Yr)PD +UjiФE]z0dĘ Sf̹ckٰ<g.\yWn> !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#POa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^7$DQbI$E Yr)PD +UjiФE]z0dĘ Sf̹ckٰ<g.\yWn~!J8 H!K<(SJ: hӡK>3aʌ9w,Xb=<'8r̅+ϼʍ$DQbI$E Yr)PD +UjiФE]z0dĘ Sf̹ckٰ<g.\yWnJ "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7~!J8 H!K<(SJ: hӡK>3aʌ9w,Xb=<'8r̅+ϼʍOa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^~!J8 H!K<(SJ: hӡK>3aʌ9w,Xb=<'8r̅+ϼʍ@Y?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xFOa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^; "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7Oa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^'H0Ĉ I4SH2ԨӠI63`Ȉ1̘sǂ%+ܳa;ybρ#'\ ; "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7W?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xFc "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7U &Bq$I&Cy +)QBu4iѦC} 1f”sXdŚ{6ly`#O9pę Wy !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#$DQbI$E Yr)PD +UjiФE]z0dĘ Sf̹ckٰ<g.\yWn> !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#POa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^w$DQbI$E Yr)PD +UjiФE]z0dĘ Sf̹ckٰ<g.\yWn~!J8 H!K<(SJ: hӡK>3aʌ9w,Xb=<'8r̅+ϼʍ'$DQbI$E Yr)PD +UjiФE]z0dĘ Sf̹ckٰ<g.\yWn "L(1$H"M,9(RL*54hҢM.= 2b̄)3ܱ`Ɋ5lGsȉ3<+7P?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xF'H0Ĉ I4SH2ԨӠI63`Ȉ1̘sǂ%+ܳa;ybρ#'\ |R?AB%FIRɐ%GEJPFMZХGCF0eƜ;,Y [{9q•g^xF !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#'H0Ĉ I4SH2ԨӠI63`Ȉ1̘sǂ%+ܳa;ybρ#'\ c &Bq$I&Cy +)QBu4iѦC} 1f”sXdŚ{6ly`#O9pę WyO'H0Ĉ I4SH2ԨӠI63`Ȉ1̘sǂ%+ܳa;ybρ#'\ 4$DQbI$E Yr)PD +UjiФE]z0dĘ Sf̹ckٰ<g.\yWn} &Bq$I&Cy +)QBu4iѦC} 1f”sXdŚ{6ly`#O9pę Wy !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#i &Bq$I&Cy +)QBu4iѦC} 1f”sXdŚ{6ly`#O9pę Wy~!J8 H!K<(SJ: hӡK>3aʌ9w,Xb=<'8r̅+ϼʍ !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#O$DQbI$E Yr)PD +UjiФE]z0dĘ Sf̹ckٰ<g.\yWn> !D#N$)dȒ#O"%TRN&-tңπ!#L2c Xsφ-x=8s3/r#Oa"D'Ai2dɑ@e*TQAm:tgc&L1KVgÖv<ĞGNp^$DQbI$E Yr)PD +UjiФE]z0dĘ Sf7 oxo-v;y=~>#|q|S op$X|ȗ2_| ɷ6|o~ď ?g_K~ů w7Q?oͼ6;x'ݼ> |1>'$|9>"_+|u7;|}~?'s~/7{ϛx3o᭼ɻx7ὼ>ȇ01>'$|9>"_|57&|=!?3~/%;~m?MVƟwn{y|a>G|Oi>g |/eW_|omwc~O?_~=oyo-v;y=~>#|q>'3|y_+|u7+|~ȏ1? ~ɯ5?ϛx3o᭼ɻx7ὼ>ȇ0| >ɧ4?s|/Eė +_k|oMŷ{|C~ď ?g_K~ů w77x;w|!>G(|O)>g,|/%W*_|o-w.#~O)?_+~o- 7f[yown{y|a>G|Oi>g |/eW_|omw~9_~=oc֟7f[yown{y|a>G|Oi>g |/e:|o>?~9_~=o|~ěy omwNŻy}A>ćcC>'3|y_+|u7;|}~?'s~/7{ϛx3o᭼"ݼ> |1>'$|9>"_|57&|=!?3~/%7{&[x+ow.{x/|!>G(|O)>g,|/  |e$u]޲;oYvvy:[^.oYg@֗!$@ !F,lI0e!Ka K† YR0`Ȉ1̘`Ɋ_cÖ{9͙ WnyśV?AB%FIRɐ%GEJPFMZgc&L1gk6lٱߜpƝO^'H0Ĉ I4)QBu4iѦC/z0dĘ SfYdŚ [v9p7g.\qo>~~ a"D'Ai2dɑ@e*TQAm:tGCF0eƜKVٰeǞGNg.\qo>: &Bq$I&Cy +)QBu4iѦC/z0dĘ ̌9 Xaˎ=̅+73aʌ9 XٱߜpƝO^A &Bq$I&Cy +)QBu4iѦC/z3aʌ9 Xaˎ=̅+73aʌ9 Xaˎ=̅+73aʌ9 ~k6lٱߜpƝO^[$DQbI$E Yr)PD +UjiФE= 2b̄)3,Xb͆-;8r3ܸɋ7~!J8 H!ˏ@e*TQAm:tGCF0eƜKVٰeǞGN|s•w "L(1$H"M,9(RL*5 MZ} 1f”s,YfÖ{9͙ WnyśK?AB%FI~4SH2ԨӠI6|ѣπ!#L2c΂%+lزcρ#'9sʍ;o>~D?AB%FIRɐ%GEJPFMZ} 1f”s,Yfïcρ#'9sʍ;x!G$DQbI$E Yr)PD +UjiФE] 2b̄)3,Xb͆-;8r3ܸɋ7) &Bq$I&Cy +ITRN&-tE>3aʌ9 Xaˎ=̅+7 "L(1$H"͏%GEJPFMZ} 1f”s,YfÖ{9͙ Wnyś &Bq$I&Cy +)QBu4iѦC/z0dĘ SfYdŚ [v̅+7^ &Bq$I&Cy +)Q槨RN&-tE>3aʌ9 Xaˎ=̅+7> &Bq$I&Cy +)QBu4iѦC/z0dĘ SfYXaˎ=̅+73aʌ9 Xaˎ=̅+73aʌ9 Xaˎ=~ߜpƝO^S'H0Ĉ I4SH2ԨӠI6|ѣπ!#~ SfYdŚ [v9p7g.\qo>~L?AB%FIRɐ%GEJP姩ӠI6|ѣπ!#L2c΂%+lزcρ#'9sʍ;x!'H0Ĉ#$I&Cy +)QBu4iѦC/z0dĘ SfYdŚ [v9p7g.\qwy͇@N?AB%FIRɐ%GEJPFMZ} 1f”s,Yklزcρ#'9sʍ;x!'H0Ĉ I4SH2ԨӠI6<= 2b̄)3,Xb͆-;8r3ܸɋ7~!J8 H!K~\?AB%FI~4SH2ԨӠI6|ѣπ!#L2c΂%+lزcρ#'9sʍ;o> &Bq$I&Cy +)QBu4iѦC/z0dĘ SfYdŚ Ύ=̅+7%&L1gk6lٱߜpƝO^$DQbI$E Yr)PD +U~: hӡ= 2b̄)3,Xb͆-;8r3ܸɋ7 &Bq~$)dȒ#O"%TRN&-tE>3aʌ9 Xaˎ=̅+7.O^$DQbI$E Yr)PD +UjiФE]gc&L1gƆ-;8r3ܸɋ7~!J8 H!K<(SJ: hӡӣπ!#L2c΂%+lزcρ#'9sʍ;x!$DQbI$E Yr (SJ: hӡ= 2b̄)3,Xb͆-;8r3ܸɋ7UAD#N$)dȒ#O"%TRN&-tE>3aʌ9 Xaˎ=-\r΃'/| "L(1$H"M,9(RL*54hҢM._3`Ȉ1~s,YfÖ{9͙ WnyśOa"D'Ai2dɑ@e*TQghҢM._3`Ȉ1̘`Ɋ5sȉo\r΃'/|s "L(1$Hɐ%GEJPFMZ} 1f”s,YfÖ{9͙ Wnyx!POa"D'Ai2dɑ@e*TQAm:tGCF0eƜKVsȉo\r΃'/| &Bq$I&Cy +)QBu4iѦC/z3aʌ9 Xaˎ=̅+73aʌ_ak6lٱߜpƝO^$DQbI$E Yr)PD +Ujigiӡ= 2b̄)3,Xb͆-;8r3ܸɋ7? !D#N$)Yr)PD +UjiФE]gc&L1gk6lٱߜpƝO^}Q??@a"D'Ai2dɑ@e*TQAm:tGCF0eƜKVٰeopȉo\r΃'/|4$DQbI$E Yr)PD +UjiФE]g/2b̄)3,Xb͆-;8r3ܸɋ7~!J8 H!K<(STQAm:tGCF0eƜKVٰeǞGN|s•w3aʌ9 ~k6lٱߜpƝO^h'H0Ĉ I4SH2ԨӠI6?G/z0dĘ SfYdŚ [v9p7g.\qo>~!J8 H!ˏ@e*TQAm:tGCF0eƜKVٰeǞGN|s•w3aʌ9 56lٱߜpƝO^+ "L(1$H"M,9(RL*54hҢM.?O>3aʌ9 Xaˎ=̅+7,{osr3N[~.?g|e QW<301xV^>y>a90?1WqT~[ǃ_O=ϫ~ڧNo}~:TO[9{ygo,1 ^5>u.>?1\USC>8 ?{3gVq/G]1V^AuEcOoK*r=A[w;wr*vx OpG_ ?# w?$<+>Ӭw󏼊<^wun<%>~zUѧƄ| y9?o~A||= +Sc^A}u y yN7?9u JqW/?o9ww_xozG>yÞ{S^(/<9{Gg'_y<7\{G|Uc>8հ'Z *|[L΀n *gZQu_7*ѵnoUyg o&oUߎkև>?fOyUًY +>{yG_g٫8>ϖ>Uw>1;/٫8[k {ynr9_yx⤼7sțߋngNug7oDOUA4Mq20˯և8a|y͕o[g}WrDOU_ywr·z*ί~Z ||N|*Tdu>y1 yM+(oC2V]>_?$XgK^̓9>=U}|V^y60Obp_s/wa +{~٫8> +fVk}ث8ȹ/?]f٫8ȹ/?_37ɡ +j<+oNx/?0~C$z*p%3|υW]+q~a/g/*N/SuWq"F"2^S͝wϿwfjg/A>w8s7qֽ~ 3#W}~qD5|0#\yu8p/n?g5UZHu+~y@:*ꩶ^w*EO 3ZVyGER^)DOn79UWt+9/͹+/N8֢jv9؛d/ʷ[qX7Wr슫9{}.8TO9~m'{9EXc׵*ry>>Wqs/͙>~sU9EN!U=M*~7\U_m?Uӊ^A=֡a|}s/v@xD^Aȫ~f٫8>{yF}}[q?]9ꋚ-_WqM>y9EnvcQR=Wꇬ7Q=UqL}@_p_+sv!WJp/'X?+9ߑ+/Nz]_+vW^Q}Qk2_SCO ||N9{3'^޿y߼8^ Tg9«8qAqS^A}u?_:%\WqH6|||\E;NOy>Eȋ^4߿^W| {3碧_)'.qDb>Mq +1V}W^ 0o˿0W+ƾN˱ߟ:A^QO]Ap񋜽)q| +zZipo{u+8/ʮq>|\7ϣ毼3<Ϙgo^e#ˉ!_y1"̳a->{9ErKG^A}]x`=U_T,0J&9j1M7psQ}QX|U]0O,@+8-=gK'p_u;9_SO~r||@_Ep_<"Gy/E-e}{88jի8ԇώ\E͚W)dtN}z +C^zrOc;DZ7Y7*/^YpʛPO~!8>}Q{}WpT_Tp1"W eWqT_Ԝzim9؎fugolgaWqTOͼ9_jk}ث8M<#hsٛp_vkʫ8S_ً\qrc/w"pWpT_T2|橼x#gV}1eN#z*pu)[~/ñ>ޟ8ʋ{W/VQ?\ x{*˵5́"Q5*NN=^\7ɡ*rzf=+&9SaofSyezMgWN/*oW]a}z -KEeǣiʫ8/ʾ9|QS5-t>˽/EȫUߕWqT_h>k{9EϽicEGE{;q&US>BxGT[-r@ nUSUWrDO찐˽ߝ}㬙9د?Mq v ogoOc_?09*.3m8y>񇽊\E>όܯC{YWUa}o > 9{|6^U}ꩪ17ןpOuwX褁olUG-nIy}|{*ˉg́"vKgo}/ʫ8詪*l?AyuS}$z*Cs`CW^;*i->{1Q}Q7Wqzs2KT d{S<: y&j){n`OX}+Eg3uomWq/zJy񪧪Z-Wq/N^?z'a=[_9૾Z^Uwn┢US?yfuW^7;pٿ)/spU_TO{p)p_NҖf?^̓9U_ݨHy"xۥVF^?^]a|驗\ywxSKvW^ő}҂/3zf3{~J#&ֽo3_x"-6_yֻ!{~*N)֘:+/sDO?'[gP6nN^/|2iW>RdO}]G|ʫ8jnO(꩖r/j79p}0^ +x|o˝/*}8_؋y2 JzUE=8ӿ6T_#[| W}QJ|}>{Sr~?8EߺpG^ +/slx٫8^Y >{~ ۗ\T +ɛ⨾"WYjx*T^SW}Qo'8+:Q=UVƓqwӼi9{SZEɓ_yG?U6= o<"Ͽb/3{*ŊGʋ\qT_T?qC(䈞8:S^S!~~Fb'_8_g*꩐״O^W[g>uK]/j/"WE9mvQ\ySucԟ;_gً9𪞪8 NN^9T4nj US߶}*rwމz*̳q룼)Ed'L6oً(ʳG7E;nG_EM5OUlh٫8>y[ɋwa=[꩐?Hp_N?aV)2Z̓GN=> /vwƫ(~}/5{Ǎ6+wQOu\k'Nȹ//ree >{SՃ?_^GT=[絭+ʫ8/*]ws&C=3ʫz'#O>?"W''{q/Eϯ?%?򆼊/E׿g٫8UOU.U^qM=W[׏'sOsMws9૾ͤ;`ox P}+}ƫ;*k٫8U_)E o1GxGE8?6«8-$ؾ~}9E_co_*N=bu^苚v?7ũi:+8^tu#sT{}N=; W^DOk?Mq~a;xS<:}Z].E}`y199Sam>{Ǐ +y%7%S!ov/D^g깃G9˞/do}ƻy0?E8SξR^`ߐWqpT_T6 |*j8/8ҺR^ɡiO@U}ѭ=9_p_cµu}X?2h{GE[]?+ W}Q'}F@.=8BwV ^wqOUη){19y︤z"C/{+9jf? y1Op/G{U䥸~+[C$z*}[|;_ Sa;VRwR}QO%waa|g ͙?9{9Eozi٫8ȹ7Q}Q&oSEŐ y*w o*OzCH̕'rJn >s3j{'|ꋐn?xwq__V4#g/rũ*i}g W=U1g28S;әC 8E.9<蛬^Wꋊ,*E~y_y"/pʋ9U_T-Pk*E Y|USe:\eև/a~7{yMQ˿8?xka/A)/sy8/&Nd{=p_<:bp_9f^𙃼=ytKy/E9-w7)W?ܯw8[p«8KzNԟ(`<~ۙ={˕9)oy;.Eg}^:Sy(/sW=U>_|})NI>'/r!>ֳ}^zvU) Βw_̇>5>+ȾhsͿ/`/֍9_/jx<~*s_:}Q^48OQO)NKW /rbpa/3۝"?~ 9{S0|/-^ũnsay'gEg_AySa>Տw7Q}Q~tW^mEOlo_!o~/&kW{۝s}w\W{*vj"ɋ9r_<{n{19=S(p_Ne/8șsSKu}RoA/Wo׷{1^qT_T~vK9sSaG/*:z\#UG(^R}Q3+p +8苲ϯ]_^}msșoeo}ƷϊseES2 ꋐg_x珏ȕWq/jzy?|/B}!~~2}Q޻~q><*S!o'nX S_lb٫8>|ӥ/ٛp_⫝̸9<)َRON~}y&_zj:?̚K^AlUۅz*?Rث8rO~~ӈ +rE^Ł"pw^_F^A}8swL/{' +be٫8k9{Ms\ +|%Y{9T7l>{9Ec}nn~Y)di2^[2z"p5x}٫89O<6o&Ώiɫ8rOߋnٛAq/Hx}}8*SwSC\EKkd?Tvg}Q;/-ua W}QW+og'/֓9𪞪2JkWq|N=Ӽ1<^AP_un+U +y6h)/s垪A?Wq}Q)U詂l+皯B9EyS]xeƫ(#B~~]ǂltZ"vt~D^A.ΏgU:SoN2$o=Sy*qPOuȯ_!|p{'y)⨞x.s +,7L^Ł"?rCeN詚/ʫ8X5i痲Wq +,?79{^i8|}/E߼6|}ITuc|R^ E؋(EߺWy3}g'`ǷC}7'm} 'ǯQEȫY6_yʕWWyqYW9En\g?nZü>Q^vw/ _ȕWq~UmW=U5 gWqT_{T0wz29T5ܾϧz,>{tU䪧*6q}٫8'Ew{Gy%GT͑p_;>GWT[OٛpOyCNbK8/=0^y%z*sG^󩨧BF7puGׁ|u4G4o_)?Fxu` _?hy>)T?i«8y}FυWqd_4Y_xp_aֹ^r<#/r`>a|w 珜"pu7s|`o=u8+kxU_Tty"oϿً9~DOlg8詊^<9~#|<{ꋊ-o^U >{SS>«8aٖ$/ri!"g7}}AONs_7󸪼/os散?*(:qk}WqT_Ԟ~Lew/*& d͟)ʗ.x/:d̸/)EMqTOU{ܝ_yyr_y*p_~;yxknm_e?{*̿fs#zA[|啽m_yw:hq*Nt^`s_U]=<ߒWqs_»t9~ϼ|. {3ͫ3g~9z~{1^q/꽅w֟8a|o-*ꋲ;N6"My"Sɋj?Q^q~ueQ^5؎_F~˲[;`--46<~* {B٫7G_U䍘e.|*rk |*"!/Fr*r>{9EXr^{*pGn:ھUM[/#go}8>`9rOU}?|kWqs_?}쮐\EO?;+z*pIKDɫ8U_u'o#Ѣ#/㎟Q_yo,?)?9?lq`n_UVE8f<\E%">My{*p/la>~Wqj+/s>9 |bE/r*rn>.g /h4mp}jUgA=r5)}Kx/HQxͷ2|0f{go}凷F2^~R7mpS!#zl丹 +śFZg Fb{yĤ[{S5ʫ8_N^"z" 'KD*r*r>9{ꋪkzE٫8 sq.r*/jY|}"3>9{ꋪ_]?Wq7SSzWq +߉?٫8 |+ꋖϣ=֡ѻXӐ%GEOkn_ KTfãϏ<ٛ⨾lxԟ{2^^9Tg8>Wqs_ϰWq({Kɛp_OSyo_p6wIUmbUSnl٫8rO~Ԁ: go~oW^燻7_EWqs_n֟\E +!4 +?z˾xu{H*/;~#'JkU/*}8z}^ũ{=US. 珜`\ ~N^A}8هG{jZ^A<^Ab`ꋪw+/sj?SaȮ)CO񇼘s0+>8/rױI𙃜"j󘏼^4G_zwxsN^-8aʷ^7+"pn?WAg}Q޻ld]?+/s0O|>s]ʫ8aQoM^W/(/s0"YMʫ8m!9{+WjWrDOבW^ܼ:Q}Qv(OQ^)/GE̍"O|4W]a>EϏ\y%GEf}8zR}«8Ew=:~"W^S/ʫ8_yyY<<2Um8}<^ p_ʷVyGE8}*N#zfǂ}8*p_~;>>(4l:fu|S^ő=U9bg=OFxu˻re?v\r8a|~??"e_y4#^}8#Q0=z"/pp_~}|`/3^08x7}QU|߅r߅y|>ϻ~qD>Oq敏"'8*WpUx1'5yT2Oϧo&=WKץ9/SSY%z}8޿1U_pI~;,މ]gW~E/ʮY,z`ҫ.i~z>~>Ez'//#+/sp_4?X)z}|8Sfβοxlmś"6ߎ\yꩲ]GEqgh{WכgZ|Uo\p\«8/*`gO"oƬb_;^渟wrWq/}|~|؋9Oy[[(~]gur57>wpGi9n\}8->{9Ek"lD >{9E'WUn;OUޘa W}Q[,oF^AzPU_“~Lx>EO_MqTOU :~|W^䊣zb=_swɚ?{1x/T?!U/*OO^A}o}hWqrpoxcOX]uNGi?={567Ng;/z_Vzheև,="p W}Q9}Ǒ 󬾲{9Eg|?z8苪Ed>u?q^z| X|8(U0b9{+EYoڙ^8ʫ8/*}}H^GqT_T z~R߅zK\ES >{Se3fZ|U5,fWqT_T}WqT_Tͯ*T^ 2~+oqอzߒ7ř{=c=Kݔ:y"gjySůM+/s4ucrxU!w>7s毼OyGT_j͟'s]dOZ7[|*Ufɫ8rO[->{Smt́"? -~I^*E_T]&_U/g_Wq({|yAGEX~}Sx%m8u?cq~WK|~yo>Ug'yGrR^N&ֽ +n~3gw; +E./s{*gmQy~m9r o:r-gC->{z!E߈yA}.W를_EaWq00'GUy'EO}N^zTsQs*r)f>yE^A}jŪpg/+Ed/13ꋊ:͚􊾋{*̿ڳ|̇ș"˗oa'm9{9E_2z r*NTT+i~~{Wy'yz*?vnn~=ꩲ8+?țp_N}+[ן9ȹ/l=B>reʮgoЭ{G4>x<_xO fퟕ2EO_fW~7{8a/rW=UB8a??b//*Ge}2/B=yb/Ʒ}Wz[ً\q/W|es_#U0rs_/m9s_iU^Wt}{3OWqd_~Dp_~=IE#~/^=/E~>vn}}N$"p/g^*{QSA=R瞪}V{}~qǷ#z'0WSQO~ʐ}bً\qrܝWχGz!7=z~2/w߄9yo^WMq/c߈ȕ9X7S_oߒ^x>ESC*ϩ/Btԏ)/s"pu=-<a<{3"qeo#g7 >{S-t%/x/B^;dxɡ뙦j/8ȹ/ +'Uϯ}ȋ=WgGy!/{pXOyw/R^|/϶8~/!ȋ\qT_Xt} N$u ꋚ3ߡi9q|A.¤E}8J7^>0ӫ8ȹ/B^>=:(/3M30>uK)Tgߟ5_Q^r|9%\USw~ਾ{9E8a|w^A}o<ٜ*TG}>reb|*O=WpTONGb>㋳WھMqZ.^4_^oOarگI24$zs"WySp9}ֿ*{/l_㙃 o׍1Sy~ ׷yV"'_bl3ʋ9U_T-<65R^`7 +|o2^'_~>,x4"느s8u֡X̾~P^`>|=1OU#.]q9FOCGݯF2^SǽWqZ< +y5Y y1{'aG0șz*O8}m"w7Q}Q=,KEQ~A|ŷ.s=7Q}Qu?+ꋲΈ^u3"WϿ؋+!E2/mھʫ8/j6~lW^`/j[|{S>|\yGEuau)s_DO_cUEഏg^A愸Q:ܠN<{B/~֙_-_Q=UqSʫ8ul2 +yY{*gG/a/rᾨ='/"/丹'?VyxQ}Qu$/A}o{ړc/B:':(/+f>a39>yYs0LxU})ǚg/3Q}Q69{ꋊ dob'X2χz*';r%|N>>' W}Q3vWq*-gٛ⸟sox*zً!S!/cT}a/+Ed#8E8S?(o#=2z|Eׇ~,~-}5}9TO72|wiCG^Aν|s_=ubp_~(>srp|9{zWq0i?#+GSOycK}EIys_t$[qא~{#z|l}~)8ʫz' w/?xyE^}Yʫ8R_U^A=Պ2E_Tw٫8U_[)E?E.ʫ8S:k/v{dB"oyܼW^`>/O\)UG?n~wR=U3kZx򊾋"}z*7覼GEEd'r W}Qy#Ǘ2ITՄWq0)nr'oQO~(Ԣj8츸R^s_|Ty Tț6ZϽS-zËa`ꋊq~/e֙"xvC7qUt<˜Μcb#UB^];ܔ|\pd_4̾~`/ +ԓ`>2}cY|*rʫ8ȹ/{?X7wa|%ʫ8/j6ns^Nꩪo8:A=պ`?{19!/ b⨞2z>Ne_Э{ݮR}Q~ws}Q{^_Sm9r_~ure!_.:^gN[0\|xt~A^|J).zk٫8𪾨pSc ൢWqsON5%UxBx|<؋qO=k+a)ƫމܟf/A}_)/sW}QQ|U^|TOU,{Sy? S싰n'#|R/qexw:09ss_83Dw/P >{ꋲ[M ʫ8TTw+C>*S!o|%zbecW{*pʣ+⨞(ϔWrD_T]4u˽S<}tMq/zWq/\'w W^E\yGE x +⨾kvS+/]xܩ>dkMr|}WqTOռNġGEĵw*EW6OWq/{5U}Tmogk?}a˽s_~~g +2|?Yp}g/3ye/}ɕ!}3_yӈ*qcʫ8=x}R^șsgo~ηCiK~kr"Cr[rF̓9ȹ/?bWq<z'7[.zυ9ʞjQUkfUl0fx g=8}y:USvA!gy8SS m"疿ݚ?{9T|\^A=WWqs_~[U_? 9{;ssONyR[|*r<qQ?s*reK/g$ +W(z\ENKD?Uݹ}Wq +bҥ7X{܀nLggN˹\B+QqG=J6{*3oU/*w\"zHy'PON/*_Wqs_Nև"Y*ߎ\Fk٫8ȹ/fʹU}|*ϩ/|ǑWqs_Nq߷^A}67_USO~Ӓoٛp_~+䨞j'ǗIE)'Z7#/և9ȹ/:d^C{*p +wsreN;墧[&z?MqT_]R}K/]~\j>^y=+/]nuO +r.\qTHWjes_mR^&[^W7?@yΈ?9s_<8*׊P_ǯ|y|P^ᾨ'~ +Kes_ވWq/zyh#s"Wo민zff߇NyC ވd9EWU\O/?{1 y{ʫ8_n<8z_*eʋ9U_?|*68Zb3b9r_ؾʫ8yκ#E!'\s5+.ϯً(ʭ38ʫ8/*.LkQ^l|U`u ʕWWy5#+5^;qPHU㑫vJԯb|Y r ًŜFEM7?WqU}Q=du:7sߧ^䊣zbs}ȋ(Eo|/{1 +j_y>T->{S|JxS!/kx;z MEMq]O{*r> ٫8Eߜt|>Wqs_~}ȝ/*ϩ/jz_o>ʛpO~}`9{3^0vbD8pث8ȹ/;r*z*p=."g W}Q3kٛp_N}~g/}:e 7s]^NxU_TowE}S5x| b\y/Eg##/9s_ go=ͶS ً9r_uhfg gǫ) _ *rU0Z85nʸ{3"c+#g=jKL>{19r_y]~y^f=+/sW}Qqug~q_|ُZsܺU}>,}ogzًu`r\3??AySowU/d"̿A >s_>ۇRʫ8/y1ɡ;aT=RE^5{B^jT;5|a+PON=s[W}Qam_*rmȫ8>FlK^As7Q}QQ^}}sO}VU"gr59)>IyчZ?{9T_ͽD7=_y39Eț+?3)䷽ǝ2"_ {_+ ~;*SE9S^[hx3*E_Tn۰R"C1qWy%GEn^My}N9hKk}p_ٶ E!'R^`E7+h{Q}QqO^-~ +E}>șpOo`SS!g]?#;}0:|b<{3ybTp^s_uQVSOU]_!goC=8g«z'PO^̟9ȹ<۽n͟\Eb٫8 ꋐgmg/+ꋚ3' t8EΜ7S9^Zn!9{^)|r*rcV]y魏/>8Saͭ{2/B^ kT^v3C>r"gNSTsf0{Sz*0k2{EOk? ySu_ϼeo}2(/s +y(/3e_D|p9sO''>rA}ټMqTOunҸ)/r+擽V9{S|: +)gJ>٫8K=8٨[|&9S_m_yR}Qʾ`c) svu`o}8'WaR_{ =5\Wq(pM>yS"3ǘ{B~;9s+G' +z?A.R}Q~t=ΐ7Q}Q5Y|U|;,q_y%GT= ۝"pM1?Wq]/ ,>{^S7*=Sp"W^+{_*q~@^Wq|N}8cc 9Hʛ}8A|;쯐=c=U;WO~Q/w}Qͣ_)U}EgGEUM!"grW0饾 U_T~xkzk>*E=䈞YIyɟ?)|fUpϨ?/?:o}WqT_h}0OS#7?Ěrp_ukR}?=:?r"g}מ~@Mp^ߒWqs_xNu@yU䪧*??+O8Wk''>^ãy?9Wq/w!_p.pjꋐ7O^G^(ͳ0 {/BތrtJTOϏC䲧簩 R^Q}QSW|o y+EyS2㹧:׆Oy>E>O_b/.U<ș"m}Bԟcݔ9/E?Oy~+N'XyA"_t>Mp^*/Nq/0=JW^xS?p}Kw}z*#5 ./>_4~ kT1k鲧,~VuϝOuOzy9⌐)q +!_4sr׋<-{cm!_?KEJ瞫~hGT?~N~/:s{?:GToU@s\T3gOq_~/KS?ϣ߯N|K]s}c׼yY%"xvSϋr~T3w޿@Ιਗ਼ HQ{*y=6?9y\y/g_K_ DkEꟿ|Q{k^t^tح=7ﻺsTo%ޡV'"G-3rȯ=8/Pgę/xGU__eUE퓖8)}q|;σq~TZu3Gǭ{*\r8|Wc)9?੦g?ᕵPh䩦?(8>nqRsԏpfy~X%"qx&U9.X/RrH7y\䘧8חǧ;ׁC(\tq(8 NSu>_Q.y'Eg{YKEUs?.E!k=CϽkykkKH,ߟ~|w_$p뺟\̹FHk/{*q+~}żqv7{k/:G47%O?z\w<_|pw +4~W[x)9z]ӽykTA;ur;ir性nj*}4T3q~Qrq_zg|~S/kUݽT_]/R|uJ7,U\J񻞽{.q:x~s~E?oP.q3*rH~瑵3"|Q罥Ƨ\]4W~!_4 0"<=}]{%"ݨ\/G(8t^J#\h5G%-8U?q|=A.rSsuq/n_=/6"5%"qS~\x&_>岗?rS3o6KEA׏Sk}ќ,떫:q5+ǯz !_O[%z}Z މ|g?P..T5\NzuTxb^_UsGi.Yq={Chx'_{Ѽ/ +/[Ώ8K{?} \N{חr8ᯫߟzsT'_4s'"P}*%uU4oG}kd/jS3<6~83?/T/g5~?K!_^zŸk'x^]/cg^_%pc/A"_4ys"Zr8䋺xj/Q.q E!O ?Uŧ\!!;{*^_D_uO5~-zsqw +Ex>GuE7=qS5yM멋w)8/ǯNETrkǏqiG~3j|u:GF_]/nL%"Oz|Q;VuO!_>T9"1|^TlTs8䋚{yN\(|Qyx%_|䛞s^lC5hT 5gš\/g^㏐E'v\/<95>}s%{*[{Ι_GT]xs"qO~Q|tO]w"S"?j?6q_y)m{y_?!_5ǟYNE˾Q5>=S_V;Psޜx%-8䋆>\8nsw/<4*?@s}.\u4S}=WuNڇ-~8T᭏euKމ|Q{۪~sw/:o }G쒯::GH]{.qT'_~<|})wgS_U8ߟsrCj~q_4sN۫^Qsz/oUokE˿_6?/%"g>6~uHw뜙jz܇^_{|QwWU84~rhϩwU9/S-B_tmjQ=y#_R?Oq_p#J|5ߥ~NVs;/w<'Ury9<8^|T\h~ y-o{.qTw_$3Y?{.qy*q8?ę_(8/4o8n1K)sS]pǧGFs"+>p^W}\j{ɹ5GuEgkS.qTw_$N{YK?׽ؤgp4߫\Hߵ~Ts)?iZ|+GuE_K:vG(8S/j||})wH :K\՝"U\h?[=8s|8+_)8/g:[%\7O%~ǖǏKMz|kEb*ޝy(9fλr)wGh srzoh̷\uEL?%r:{O+r8=|Z9~qTnKgS}7-8{U,8/zfęi-WQ{*sNr3/ +;%r"yZ<[%NvBO?~Zsw_$Nc~%Ψ<̿ 5-8/R}o,[~EtK8KEwTU\ՉHx¯Nh\Zz.qTSx X|IQ}81_%y߿= r~Tw_::g/?P_s1OsKꞻ1OqNq}RοiqS)8/_5~%̏GuT/oGuEC\|%䋚S|s";\ tO%c)!_>L/y)Y=Oqw^9'y=Z~C#>Q#V_\{+-.ħ:R_h/G[97%֫.N?<WOu]X|?ZuqK%=AL:'|e(GN4o_\\L_w]wW)8opvc 񦗽kS.qxo:ǽ Kx3Ӳީę5oQ.q~,:?4Xևo?oP.q›~%?)yJ~{s G7:u?GN%N?KN?7|\_um9;h~>~lz8XNpN;\w^lsKy|^i.p;NS<~,8c<gz~*8!F}\_ u%N[9$~Z甹Sg먃iW[=:uQdP\ℯ_sIK!vG|S.w߹\gjPoubq}W:먒3uTwZ/J\*9z_Ps3|:9wyy,+߶r*9w[J}KޮkPr?}hpǭ^R~rϜ)c\_''d*yOZGy}x߭U?z$̹ϧrAs9 vSQr|շrM|GSܭީ75x(7XdErC=R?O/Vq5վ,%No)T1:K ~&O9x#v!_4j+%`T(8]QsrlYVqz䐿Ҿ,%N<>Jÿe/9s.p;y}Hƙs_|)[9<7?("~;SJ뫃I׃mS.q;oSQr|շrS/rC||(%1sǔ+\/:R?1+d?\N]|-ƙ=Ux.EIy E=(%NK]1iuꬮT>%"QK_Q.q5vYW v)18:DT(Q}+|)*[9䋨NO(sx}[9h?"m性"D?O MΏ_GM_+<7sSRyWy'EX\'_>.?CK䩨Ch0ۋ'E"r/7qqC^gE֟c}V{\t䩼.E/NcY]>Q.qR9ZKNy-?H={*7>=~=CT^o_͹x9Tk?Z8m\W#R.qҾ,̹!OEuϜ މ|U_͹I+_4g(F%6J.!'y&yEt7Lu%N,`t\9vﲧ +T7r}+VeA.qeZȁ3H>\'_D>j+SWBq;Wc\/%>/@;SQrG>FY}+<>rӁ/jeEI/<5Var3ϵ/_uH/{y*wWuNr_x\.|׵9~qSS9GIrfOeu\㧇UsI߿ +ݿ'j>KH!OEuʥ~TT́}V(Q}+|)ѾEՇ(|y|E#ˢ\]&QKEڋ'g%Ng)E՛rC.{-%No׏TT\'_Dή勼>(f/h@՜ ;=䋼>~qڸ)E^O]G/N_y]3<0;|u8g[}^ s_<}iOZ} \~z J/N신"Wix^Zӓ/)8=y*OKE6sr_`ϭJ!_4lW!_Duʥ~ETSY=~\/kUy\/4^=:'rމE>1%_1S.qM(8䩨N߁/K3jL^r>IC(8:g :ru<`6s_<+]B%N*8i]Tγ|CW)]+"?wr<~qSy]9s+/l{Vy?pNv[(8i=S^E[W%{'"S.;O^|Ѱ_|Qg6)9䋬fEQ\N`'wV"Q'?lB\_'K=j0rz}Gub)8䋨Nԟ3qi3%_wJ.~|}|\ mQK1Ӻ(/_\/l?UZ_%>"<՛)7R]]SQ5r|×x "W!?H]:g}" G/N:R.q:<䋼o%N| %_銋jy*#a.x'g"{n#_Z v5V_)8䋨Nԟ3)̟76s(7 |E-+%NKGm搧:nNV"S."C)8䋨NGwҿ3J2w^rCK|^S*}^[9y"S.?ɞ*XYW/Q rG_dIrCR?"o/jNu{IA_rӒ/zXWȱ&>GHeCn)9T-)8= u%Nokr3gȞ[9=ZW-?(>)__QCo䩼.~qSy}H69o,쿚6+֏0;婂EYQr3TiS݊ + v[WKE!_Duʥ~EiSο_mlWTT"_DuWOh诈3᠇~j_Q.qQr?Sy=oO}[9M\G|SQ%_zg0@jK`dEV_)9qUΏ|Qo>ZܴZs<$JOE/N\Q.q|䋼GA jy*Q+|geJ޿杆zՐO)ww"EOh048:Q?\/N[y$ʥ~E_|շrqTruJ[yKET\'_ԙJJu(TiT'i}_ܭ +sw/߳\ jA%"Z=|\쩬uQz!N,y*Q."7{*o䩬Vr]KH/h@K]\._(}~]!N!%G r_x~Yjy*xY|Uiz_a.pz|}_P!OEu(ymWN?<:|QZ/ע\/"EA_rCohQ!_ԚJy0(8Zۗ~4)8&ZrC||({䋨Ri^H<[ET.q\y9?T^}|[9y"S.UWSQ=x?實|r:<}<ʥ~ET/:|xwBe* ࣶr곿\䀧junܴ/_Q.qyR7Q{M:竺s\jϿN!_4R|Q rCh픋R@E-#ҿ߯w*{r}+'J;Vy)_N}TVKѼ|{\1"_Y=+/:R<_uϺqQ9s_SM/Y_AvuV\'W룾7r_N.Vq3Z;GQ.r_\?mz_+CoNKE摴?rCh45+|hy_\-ocKEwL|J|%0_)8]hߔǍ%&~(SPwrӁJ^\䘿=yAs8C<*m\GRY~8%N _r_7#Y]\ĩX+"7ujx? rK߿R.q:;N䋼98䋼.E/~}?\/j(8lTO)8o:mWx0tvy +s/ jNoIꔋv>Yc._6VT\|`\/TW O5]Q.qMߺEKEM\_Ǐ|Qwm(8ZW Zɯ/=+E]V w~qQr瓃M7L}[9i]TS.q:E<_6rE%GG/N<-E^o_͹:Ϟr_y /y]:G/ϳ|QkـR.q:lssIh}PR.qZT^\Jr~|>jG!_ԃ\uQ9~y*^<8)!_>wa?"%N3%_*7zK *b 5^|ny$&O՘_y8i}Uγ|՝3'K<׻JE^oari]T͞*@9%NcA}S.qTTwr|7sSQ=Kތa"NK䋨CKG+/>l}x2;͞QK.J9z| c%_j⯼_Z08䋂I 8]gu:@ruUkٽ 8i=?v~gEuʥޯ;WVIrE^o_)I%_ZQ'%_S\_l<~qzSyyjy*#a+{E^6+q:;~98i=_ο|h(1Qr|WiXWAO>rU~=%No_]~_{=z/Nk}JIu\W=w"kmubS V\/_Duʥ)ST^}ʼnυpkGPgy*ca+`_-Z8'[՜ /S4w$_> ~jTΟӇx7rz|x=,!_Du̹M5)8#Q.r;5T\G_di5␧s\N:WE!OEuʥ~NXUg^m?}>sCQ E x-%N:=)8i]T婨>K신yĉ_y](IrT^W~qZ7"k9G/NZϔ/s\:R{'"o/r5+8|ڋ!z14SQ|rӺr3>+S.q^?ҿrx>)9TT\/2mp=Ҽ/ r_,gꯔK\jT:z|oET\oy}Q6~WtvW+qM8䩨NO[9f_diu7CxiN(7ї(ww"C뺏rޯީs\GrzSY}֯ZHK!_ԙ +E!]i>G>?P.qz^d?OO^rC*~y0Ey y-(8W#~rq_)8[+y*O&%"cZ51}.{jN0*_Duv|壶r{oWE'*^ i +s|)TTi9?lG>RI<=䋰~JѼ}SEkJ`>ju+_Duʥ|շr<18䋨NOC=ET\'_DET\Dr zi/(8䋼>~k";GO[9䋨NO [9<ɧQ?"Gmt䋬u|Wg׉EX\'_Dޮ勼.E/`@Q.7O䋨鼨<)Sż/_wwJ+`K_Cj>a `ǿ||Y\( _ּS7W`i=SW:p"xK!>C9~y*8䋨NO[9䋨N:>߇E<)y}zl/dQr|շrm+䀧"P.p¿A탢r%OuY_Q.qy]0׮7Qrn"Gm +|շr;Qr +s|Q^CE|ӺkwC7]:Oq}QC#ҿ\␧zw'k9G>5~~j﷋C.l勼_]Ujεm]յ/+ݜkz]wʥ3)|՝\'_DTT.ᐽ|QR.qQry$fxM>nN/r''_46rZWm>x=s~qxKETwΜ .*䯺|_!"<՛|s>qw+'}WuwA1#_Dǣ\C"y_#QV"3+OEu"GQyem~E^P}їS>M뫿3h!G%"OKGm/:R?y*o>+S./}Pௐމ3']<ׇ%"J/߹j9;Ov:9`"|Q =}We}+y*Hm3؎Wx;;(s\՝N<zl:'j__SCiᵺ~&%"k_z|Wk&OEu(J+y Y>*IJ_Dr||_V"9{EȉxK'[@>c./"EAOrC [9"?]]5<0X=sq5iݕktzi>Kh+%"k?z|WZϔꯔK||Qzp+'y]^r_-<NjxXtr_EN^GrΜkJ|勼>(h+oꩼ>>S9~+#_G{z8=_WS^:GIr[՜ ETP OEE^ע\r?"9qr#w_C7KC!O5ךע\/GQ.qQr}O r?:8k5K*^or9`/Nk׏i=;X}\uQ!OEr߯ϪVNZ_㿪!"7h_nuQp`gN%"G9G/Ei}z_isKET(|SQ.r w쎗tG}kJ륔%t\tੂյr3uVjwC.rzMz=/zZ/S˃~J\ _uNsKy;=䩂;\uEɗ/|z8q܇密%y*;g=L$_0_?߫,NZϔjNZϗ|"Gx_NTwr<F%NZ,'_Rr|`뫃M!':b?x*Q9#͞ET\G_>j3YG |)y}\W/}ޛ!OާsK!J|08i=SCT^_)gN|)E<_փ+(I뢒#_Du({̾F5s쩨!WSQ}+\y+K.]p䩬}Yk]i=S;WmƙsC|G!_Duʥ~E_ɛWT|}J1%_SET\m|EJ(8䋨NO[9p_}(y$̅~E&J!N _Duʥޮw}/~Ѯ+U&(9q=S˞<RoETIrE)8I~dEI끒#_Du~WEzT^G\Wy/^.8+q:;N_)8i=_E;\wzr~ސ@JUUZ:|) ++Z{!__):-<9%NcIj ._}(8苮ww/=TMBz'y|Z8zE:EK}]i}go zuוZ8f_d)rCꐛ>_ _5jJ񀿚sUɑJ뜲>s_WEg{Y\hPAM9ē(wQPEDE18 AgdpڻPL9"ƱFhTW_=k}עsO{`,ooW^xy*?HTnE赨8a_ss"Q-%yʾW(D!_D9xE}W)/OՀ^/vKzCW1?O^%y<|Q9|u^/j_5P/qR28^//OU[WasKp>EwߏQ3o _Z.'gY^9xy/TaQ>gX_y޿h۩NjT_>9E<䵠NjSqҀ*_8'y~^/#գ^}Q|QmP/qyލ^N੪_S8>'tß8a?s~| +z}8(wzi<"ʗvjS/O92h*L3Q//_|ԲE=W%N8'󗧲s%NkA_ +NjE<_NjE1_\^9%p%_y믨NjS'9ngUa,<Ռ"D}+4%_TX^zCoNKT/Q/qT-Q/q^w oU7+%N,pl SYKa"ZE^_]_8(_Cȯ8Y]a9(^]%"N zxE^<`>)SA%޼< +Uiy_-%9(^O|Ҝ%ojy-f>^vx]E7)_y^RW |QcJQ/qzT5|zӂ,oF?FA_zC6~o! +{Cr_1ղNEez;/j_Q/qz{"OZ>7\8%xjxq}N|\A|QzU3o^vw"}?|"_TNKr^/j_uq௨8WKES//W/JʼtݔtzcJza|`T}a@>S/x'_7y*ZʽWNʎy*ʱ8(^3q"OU[^+v 獰oU[9Sx>w|Q_|+%"ʩƓj#-!_D>z<KjE% j$_Tßi>iAN!_Dy4OEEx-%z'HԋT">~e9y"kvxq*T庞9%N3u/ +8K)+"u]xqj|ZQY~]>^ڎU j{'"|>}Qr^/"QN4>g/'_> +CT*_Ó {;/*_KTS/G_dJjY"G-Ad +9jK]/ϪGxMzE1gES/qE%y5\'xyμ|QK,ԋaS(s +Nj3,i<>^E*'dz_i<"_?XNʎW >"~{SW*;W%y*ϛ_Q*)OzC0|K~"Oey7㯈CzizCrZHp,!_D9xT xe9"ȩ8(_qMr#mYNI,%NGOEyE xSu738 +~JĩIjINelj|-tp/*,_8jW$E6^&_dR/qU*8-x>WKN~~zC4R/qz'+Er(_CrE~;q5pOZ.E y+x~^ 瓘/O^?Hyތz}|Wx]/_yJ>^/E7^/N|09%NcgTWtKaJ_zzSx"壜^/|͒_S#_y_Mq$_PuKT_Q/q¾kR/qT诨8~)>Λz%"|xE䣖/zier]OE_'uA/2?vuzcuScjƋә7/]O +%J_W|P?_MS|^3w"Oy _^^G}?Ռ"NmI +RJ냽6^k/*z*%"WKvxnTK=Ϋ,35y*>'Q/q;y^W/9y" 8IUJOa?'pU=W9pH$J8񵍗\>jY|ԋ믚%Ɠ/r+/OE9_UEy4"_>8'wR<߿^<*v]E!_Ԛz<'w"_ԙ^B%"ʩW>Ee9ߩ KewYNzCrxn_'-z~ϊzN]?qޥӰ <¾.z;/^_e9=r=^zKE!8xB Ep>A(컲9bG"ʗׅ/_y7i?y> pz}8y'yj_sKfG#_Kq9C/q)#Q/qj=zw:TKڎUJ!_yJ>^|Q3'VsKEa烁O!_D9xT}QiYy|T5~;&O9 OEEn~"|Q^Għ^]HG!_T(^_r=/<lg-SpCPN4|QeWiA{: rsũSA'gDRg8T%_yp\|^\ {_Mmka/p;~*壨83zӃ|YNiy*ʱ8vK+/jHO!_7A%"ʩƓ/"4|Qey_ey$k-OUXoCSuE +{;bߐzCe9|7j˛%8Sy.E>^ɽ<ݒ_3}Q |P>^+y*SjG>^/OU[벜^m}Z;WS/\>6z}"ʝ^<ҲE]ՎO8vT3+Ex*Qaω{熋?C/qSu毊_Q/qZ{._9GE3yގ +{S/^/N_y^Rg8%OyY5| |%\s_UvEΰ9Eg8.%_D9)O幮Z&_D9x;~W=~S|Qaϋnp8Q/qvU3^E!_Ԛj8ZWޟB/r}Nx.EvzCO{a<"W~|Qc+ pU9,z/Et?98%ήjy>KEfWKC?w"_D9xN/!_D>zCީZzCxN7KE+0<Q/;Ue9:Z_ U[Wa=9>)^kq(_D +jY[3Q6m'43$N5b,8*=zәg-p?ƋS-x=K/JƏr]N|rSy>y$'_乮r]%U^|ܟS/q +;'OzCr?"_乮^/EgJ!_].O +%"ʩƓ/멖/"}|y-W!_D9 ǝn rtqo=^OEG1GQk)}Sx<{3~)c/oSWWԋu$q˱>}ߍ9o~x}:KS/pSy.z)'_멨8'1^/ES/'_DN/Wqۍ^Tvއsʡ8b9<^7EiY"ʩƓ|Y"ʩƓ/*:e9(^_-鯈 ϣ)<<rJNvZkiUoa%Nr%x-|u-a칮ˢ^/NiB\tzcE|ZW:3,E_׉{+_y`?xq|Yz6^ˎ~T͘ߍ^/"ʩƓw|Y%q}Nrಜnx]Gs]OE>^^E;G׃x[gyωo7䏣8?|.zSq.9|Qe+ /vX|I9<z}|'2O{>9 xım8y6;T͘So8k^ ~w~3_^/*U|u{ԋT +}zNc{xWOasnu\~)WQީw1z;/1GzWԋTxްYj+^`uPQ/qZE]>^jx/O^\^9%NcEvz}8RjE]w%~O8_r񥭳?PG|S3zz. {m|Ϊ^ܜzCKY<7%NĜ~8oNoy=kA_9^y{zm p8o/ruVW3~{ zUyRީztKϟ՘S/q5|QoW^䀧GZKމ|Qk>A]䩊a<_^䀧j*?w"_SC!_?H!OE9xEaXí~e9p@q}w|zӘw*Xky_-!_} xvx?Ÿ;ȩ8u9gy?+%N5Ob|QoĹ|gX_y^\t_Qsc7vFk[GO}特zfNU|{,g0g|}R>nϏfvGykz;I/q[n|E.^<2}?:+^wاϳ?`cQ>xd#~oGu(s!9sv+Nw{|f{ܽ74;6ǟ{/qHw}>k?D{|Qk8:^p?VΟKv\TTG=xK/G{*Á{_mqz{/qb3 t`y'qũL_h}8J,b%%z5K_S/q/XS/q/ϓm{/q/\g7ǿr%rET_|wr%rE8i]7K& ?N̔[/q:Iz3)^(wOW~K^(w_$N#Kqv>2>~8MO/^Ly^|n%rEonz 1z|V^.zy1!z|z;G"mK<%:G|EOn{0h>QN<Ƿq.;G|E<įQHj&sgS5:~޽Q{3:=:2KK4[O:!>8ʇש>uM|P/q/θ{}W{}ֹc>G9ym益8l?^}֡ʏg9/:4?~,wx/q/Xo_Kĩ|Y9G{*wz/q/f8w\\IQ5G&WnQNr23Kw%rEշ{j^('OU58E&x$/zFvٶyNo$N}y*w|g5OՏy$J|/*ܵCʝӎtO%~^4#=8囿)^⨗|Qqm[/q/*KM=y}{|Q|%N7%rE{V|ǏG9e%N|Qq{}Qua it*9KNcJ,z@ <7&IG9k;{|88E|yGĩ6͹{/qJ OwH1kްjCt[99䩊VgKv/6~{/q:橦9G9mG"q原z}+KSQJ7\_qS 8/ğOi0O%~}"8_䋚Kzݯ0z}Ŀ)^h)/Nķ^wkI#)^>1_4'$S]zMrG"qo8J~˽zH+zCyzC^ϭ"WVΟKTOoH^zH//+%"v?KTewAcy'}b'?"ޫ8䋪?3^␧*KE/Ͼ?^/Ӽ1̝9z\57윘?KEW]5UNE|eOq_u(*|M^]䋚ל| _O^q_y6۬K |Q6ևzC;|*wցWCmOR/qSy|>'}zd?^/<ݙ}P/.EwzEy?QNYsTGRN4|Qi{]2_T~}ęcJ?}|~PN']mW$~?{'./⹷dkK|Q'^/rW"z;iS[i;qO%N8?C/qi>Gz%W7k>*wwEʛ|GOEmʉH\Wspۊ`|^oK9oK=/8s۷/ޫy:G9;&= }8͝5U_u+Tʫjs4}]KP/q՟19>Xrh>[<^wz=W%n?{5OL9x&߹^N䋆GW$r<5PsKsq_$y{A+]q|qe?rozGg[x$4:d׬]7_EoJ>^(w_Ly/q4EWz#>}/8GS)o/*Ͻ2?}8N<7^{;G9/zQ&i>ޫ9݌6}/oUyzȉC8?SstSU(/C/qS>5k>ޫ9zM/j.k |Q{W/P/qV??E^h"z?;~|zJosJ=S9}xWisK}sEw^_Gq_y~5{SH];wC="^q!O՝yNr~{HovE^~/*9wcJo~Y?DשsOվ;!_3P/quOzSʫssE~?kuz z3wC(prϯzCމ<?~ _/_a]:GGW㝣y/ɹzC©M|%y&*wHy?Ix;GS)/d&}Q׵}?ޫ8G9{bl._Tǟژ?z8䋪KEg߯zC zC?;֟zC~̶믨9zSu?f?{8 +x}^wxxo_;!_<1ϧ^/Ԭ_^ XE1S/qSS^{5OL9xx0Hr?wO!;r_gxʽwC/Nʝ9>hsElxUn}8 |z=ed/>r_$NqƗևz{\S/rSU=+%Ho߼'7%y+\/qS^>O|_Y3 M9:gOEÇ|HęrTWNߦ9&MUE{Cޣ8.}}% ;>9J?_=5~[e{^둞G!IW#}s4E/U8?;W|%rEķ^(w_$N5ʝZ%rECԫxEw.u䋪:%^{37OՍao^L|TK.2>>8b'\9"ͳ֟zS<4Wsr|R/qtSM7SnQNM3wCnM~IUNE;]x`/.E?I^]苶볟R/qM-}&׷:G TI\/qSޫy}Q7ۿš^jw&R,<ֹSn/^)^}8wG)#wO<8ߕO}Ɨkz}8(}ΙrT퉇{|y*Ϳyo*'D9(}~ʉCfBq_4OuƻOG+^OnC^h"7|zHK*wN) yx/qn"q[/K)7O%~%_(^(wO5q˿x/q/?sz}8Ͱ8Twc;i䑨NjHz}Wtܺ9G)ozqރA/qSM?j>QNi3SuZ'~Kw_ikzCب^_佚q_5UnSկyI/%"͟z㾨K͚gj>rO%~ {ӏ_/R}ޫ)g|qW9q5۾(y:֫8苎š^/lޏQ/x'EqKEz"z3'xnzxzh}Qn>y*ݯnӒϧzӁ}v/@Gq_$Nn{8iRofuSN!_z*w>5OUNEպ%z}4~{]jQN%yr{5Oh7I/G)_>woK_I&}z;?x'>:G)p}r}\/q}K?*wrE'O|_|<|QK䋚=[/q/R^'x;G T\xPs4{^&y}os4Eʽ8Ÿνy*qrlr%rES~žp[4|y};qwH,^/u|K}є;徨7[x\̟8֫;Gi|.]3 m9geG"ͳkr|i|^:?/ӣS|Qz3_y֗\xPs4|Q6g^/@ ;8zSU'}5VC9Rϻ>1{:g|Si>f$wrO5q^p^9Hy?Oi86|Ks{5(w_[R/.EW:{5O/j^^/*ֹ%{}jQN!YLq_Ox{r/&0O%~Yz~Vʝ3<~^'E;;w^}阯Ss4}Q9M|$:G|Eʫ 7`̧^'i!wz59/}Uwg/?'gb>š^⸧xa^h䋺}8ڷS{}őu#G{*qϥ7KW/J_^wֳ4?ZqW㝣.|z/qI HKru_9">NiKC"OU>cquܛ:xr␧jKr?|QSsny =Ix=ֳYU|K/ϗwC|o~}W^O|l}}iz}9o+9z\MZ4|"/S:/T,+w_}dn|}~sU'"_ɹjstMr%(~1OǫϿz8G>zCzXGE)CS51_sEZ~=ׇ{}W?^␧*Gq_4KT=?Wssƃ/jnH{#>zاǿ;qx$>8E}qS[?^t੊-_}sE_|ʝ}֧|w^Hr;/S/q<>/f0H7;y*ͳvkw"_Txc>OE4< KUNEsG>U/O^[%z)o>ߒzCߟ^!OU|z3x{'V^(w_$N{M[/q/=W{8:6|p|Ɠ/s_r:az'wK/j.?FQHlA%Wѯ}~y;G}~t }8巯>5>Sr(w_$~{~=Rsw_$~廬{x}8w=3ǧ^稗|Q:_Q|Q§?^3^u_ߣ^wz@qwC^s|Qأ8g':G߾2Eqo_ͪn5{84~SsKƣ^(w_=]̟z59ݸ곞w]{8wEO|i>]sbru7Ouw1'z/q)^Ly*>3^K_S]{/q:橔7Vޫq4OTW]{zx%Ǘzij]{8ĩOʝӍtO%~yr/IkK}ӟ^fnYr >į[sǧGį^r{/q/{}M{=8g&*^qqO1=?OE#/{a*^| +T[/^_Ko^QHyyr|ze߹^]z\{=oxʽWs4E˒Vr_^78kTՋN<<>wJ[c8ޫ9[/q|ꇥz83>ޫ8gy* ROBI"q__8SnJ8#~S$y$/*zqw)#{*qCM>R^[}8w^Kq_}/E˓_?wH'ǧrUNE_oR'Sq_m{*]aʽ8w?*J|xh"[e_%9='w~^9O8)oo\'c{;G"qYK<_~z;HΟʽwJruz}?^('_4Fàq4|Q_u8ӍGEmsh7|A䋪\Kxn{|Q۟8u[iQh'{/qt?*wN7>G$GG9>#^(w_ԏwo6;1Ou(KE8_>֛&?{3拔=|z524TS~X(N㍣橔瞿js4E(sTSsu3O_8{'wD4^9re_3^n_~u\ʽ8Slr%N7' T8u_$~x{/q;y{3>KP}81ʱ|ƻ/R^8>)^~/wLr;!_}k㫳^/ٟg)uy%/Q/q|Ĺcs//z£^KE'loOq_$~}ӔSsn}{}Y|KE/%C/qiƏ~EO=QsLn4(;Ͽ;%Ts/uz_zSrǧqrr|Ճj7ʯjqOW7?6oʩ8i_Q/q_]wB%"=IB>R}8Ige2u [9{*qv}rOQ"?{}PsEͅ]O!_TWϯzH/߾{}?>$y{8ĩPs:yBTK~/_X}֧KEԋTNn_I z&|&3ž=Iy 4}Q׮x5n՟urESxr̡^(w_Wy~{w}Q77kOxuƻ TN}ֹ>/Qh?r%rEoQ_~QhgG%<ͷ'^('_oqa?{}֡xa'm%rEoiw1s%rEⷧ.?ʽ8s^(w_$Nᓓr%rE+^L"qK)Oy1{/q{8䋺O~ޚ&*wrE'ݜg^O/K4O^\;K.$|d[/q/\'OoQH7%ϣ{}8W$XK䋪{8i=v^}K˾SswO%~>;y/q/*;A_f_{wrEL׺S\gK)_ğsEwEa;O_G9^Y'SUqrE_oO(w${$KSUϿ[=ٮzRN/|O}={|z/q/<}تz^X/q:TT=?,+%rEד!qSMݿy6_6;7>KEga^ㅞ'7$/xwH<<'w֫q_է.8t?os1OuGNq4EʻxWh!{}ǿ~d@sHyO.Oq_$~WHgߕݟP/q=)}%"qOW9qzǿrURT|K6:^L|E_^'EzʮuO@sw_xh>ԫq_凎{g<ޫ95OyM^}m8bYH=랒[u}֡~cKw_4w<*y7KEW9w}Ɨ|[ѺN1;!_T_~Q/qS=5T7{;Gxs%^/ ߨO%y O~rz;i)/_u_Ew9+}uPo_Ep~g/q;I9xE8/%z[~&۹^L\i8oŹ:G'OUtazg9?5K}݃NM>_ISmޫus龨k>e:G=N^wh_|fxxr(wO%~y\^z7O%NwLJʽwJ~'8493Sr('_T_:G{E:W^//h/OOU=$}}z;>yU]|u֟|Q;~8'7q8Q'+X?~Ow=|>ޫ9x*CY[~lCt[Y/j?KE-:G9 5ߥu#OUq=5*'{*'ݣXf^Nt>19üVT=91S|Q}}ԫ9%Oտa3r|%ΔߴoKw/Nw/jɿ~Q/rS7͓z/qR={ǏGdi׽S9>/S~ٽJ>/~xyP/qio+/z6Hg8zݫOcʝS|Yksz3M-:Gڇ\_7kkoJyq|8O5|QSg4UREg{=8-'~zg9orrǿj~f{w/(彚'qSMoSn<ގխ2[e L +J,&QH”0cc."fPa P EEEJEDT{Y۽W~\^9]ڵk׮u~M9tΑ=q=8uW9'bA{S/]sAC?rrNN;fb~|9/Ut8S;aT^ԼpM@(ԹZþ"ݖ#_Łm8hy}b |>E_?(89/隸o#ew+_Qy$CesN6~f˜yQ}f|UN-3S |ʋwǯG;AWyQ=chϾs*YA'_i39U$ }_H|U^T)?p/qy೯ΩTWeNUy,o!8*/9jB_D_O!_.ʩ/qʩP4ʗ9U9UuԃgGE-Wq8/B=͇Қ?pN~uk:s*/~cGE3}W΋M7BWAq^X}^q8Bϊ/t]TVr:9/ݬ_*Ewc/& ]q*ʩo2ǧU΋POJBgtu~f>| + AS9*'.7:8 +z}2ECyd{i*75y}IHҗrC9UO'zwF+WTH^ "S _yq'l~H*TY/=s_A_'_W΋о}s3b>t8aݼÒ38WqT^Th:ss^λvnOԯ|>E/+Ež3:EQAM򫜯pN*o?jԵ:BW.=Qy8^׏~`_CyroЉtʩo;y}%Al꡼oCrE-r5uvzD^L74rtvΊ'88U^TtW]E_|Ͼ9ESG >s[pw6Y_wq^~(PBW΋о[M#)_́oWr(Lu⨼zꥃp^UONj|+̋hAWGE$OY/tQyQwݗYo.*j%~^29꟯U?=sPʋ?|9ǝt2Ag_Łybm{}ȾhɩWN?8}+_ᜪ#})wB8!韜/sr'pT|=8n4A=ݳR΋ih */jm>e4sG˜(J'О8hywwܿCw—9ૼլ|ў9Su?-p7\9fW iENt &_A=S^@WyQu8*Η?tUr^qGhϜPȩG7QqZé8_E/:WqP'TлO^?G2968kQrfթ;| ]qT^LQ9d ʋ)Ƹor*KϏ7 "/jٰ8UyQo(_ +rt}z:ss^4?es^[誸O_ɡ +?ɾ@y3/ry8k +z-|GEnUcº7—9-"g8RN;W//i98W_Kyïe|9/XN8AyQoyv?p^;3ʗ9/5w|,p^NL{Ue|9/rq=}ƾ"o? :*t΋)^xA?A'_Ł9U1$Uo98%)_́/EorK||@WyQ#Ͼ9Er*Ӡ/tWEݯ^!s*pozW lֹlB|R]Eŧ'ׯ|:EmkN9EkU΋/_Jȩ>;N΋?ʗ9s^lsAqWyQO"W'@uv|UyfIXe|9/BWzWSAww뫜⨼p^:oz?җ.̩<2㨼7X;E(Eh_3fW~q^~^f}'I91_*jt?P^ۋo5r'΋= *_ +zI,􍰹uM2?:E4ljξs*p:tUY7uV ;8 9AzY3np/]a\QNNQگ/NΝsUkt?yO?sgz芣 a:sr*Uo㨼>C+EeګŝGoΗ9s^NWqg_Łyt + /3 w΋оlg_Ł9U;KW.8AξzTNU\d|Ͼ9EhܸQ'Bgt΋/tnKy8Q_Tr%Rʋn'㨜M>|es*ɾhTs>?Q⨼uzG*T`~+}[eQeCxoga?Wq"K8"/q_EvW*/*Cr}DUS1kq}+Tο^63c:s9ϟ1_p^zvmYRʩʋcG{8*/r}+EV́/Eлg%WΗ9nS/|U^l/sWyQu[o9j.q/tPrk 9U^T} _9UXl8AA8^=})o4,w7$t_})8|SSWqr: | |jʋї̱՝˥ /ED[ _ ;d_2ի_?|+8**/t@WyQKM2|΋ͳ9*_{s⨜9s~WЙfrjn/pWq=>*|K9U7دǚCUy&߃|>EԨ#:*N/Sum㨼|ya_ԯ8*/*y87ߞ/so*/iT9T8z menʋo¿pT^|Y߯9S׮}s΋o͑6_*ʩOI^2'3g_q) +iVQsjqT^T7f;_/sܠ8/yeWq~QN~u_|su[|FΗ9/΋)/r(8WNS?ƛ[˹8SA/?̗홃~ +z /3eN|p/sz}{홃z8/BⰽAWqy@T~{೯Ao&E?oE{8/^QA{r_En {WqPO'r' _(_y"o홃z8/B{o@9ʋ*WAA:/sB{~'#/Q9/ +??[;xEz :E׫_*tpg*Թ[|ʜ#JWWqs^8Ū$_Ły '$^@)/ +Fգ}#l*8SSﺓ/syżZ|UwY|U*/<>?~@ +'tUSNՄylV@o)B>;$q8^GE{"/Oe}:Tu[N̗|J[~d99U;|"pne3yEK2~i}#lw|s*p˾3:EWć|>EoߠoC98w7$O]"pO}Q?ss^~V@缨\_ *t΋PgO|~)_Ł-á@uV8 ˾]E͕{h*N)/B?Ǫ}s΋zqЕ/syQ3̓3X|UL`U^{2+s*Sc:Bg_芣ny'p⨼=sR*/jiDWq8/jx]Ǖ/s{9C<@w+N7臊X~=s[6)U;S|U{ԣ5t+t/g_Ły8೯@(}e$ξ" kΏ|E{QyQW4|@WyQ9Bʗ9ૼk|sS}3ʋ +뎘|>E'&$ʃ}3ʋ;~<—9M'Ӝ +<9~WqP?EsOu|Wq`͔@W9{'oS8~y6b_d=|UNd9Sŗ:qyF\ߙ9^J/t9UqS]pT^ԾE 9+ʩPO)Əe_˾]EϾ]E䭓׿_-*tu߹)y}|Uvp} +fQI~}Tu8yڻ}ў9-Svయ⠽ʋyZ||>E4_o\?tU*/_VϾ _Tgd*N >T.4}5qdNE#*|9/ޜo?J(p^YPs|/aN;?~|#_Ły7vǾtWtQyQis: +ir*pT|=856J 2 ]q8/Bn7ZUFTX|"_(ytBTu6}s΋BH^|GTk$ ~q^NY{X}s΋ou5?+_QyQc(_}I?8*9N0:EA%SKEޕ|S+_怯r|UmS*̋VeWr|_2?U_b =q8ρ^qH:t8tI+_i^ߕ/t +u6ćξЙ_΋о+7+_)UNuu_/}PWZ8Wc}P|",]"\ϟЕp^N_2"݇د@r)ߠo#sCOWA{΋wLN8_\[}^q8/Bڏe"}^q8B5|WU/sE=hoWqT^T//QΩ;X }yFW:*t΋wkm؜Gcᰜ%m8#K2/g9~'ʑ:ګx}l"o{|~PyQu拳c~WrDNoGEtP_y Sy>~r*)_ ]r̋>|ϳo#w2R/r^ݗ$/7/2I+.΋ojr}3'ȩ8ow΋7ޚQes^oWqT^Txt ]qT^Թݾ(_Qy֛ߔ⨼-n~qϗ9'**a,GHNr'΋ͳ3fկ|GEg8*/k|"fe)E^~sy8-GÉyQ;xݶ[Y_wqNN/dtΩD8U^TeoWqs^~(V~⠽zԧoΗ9re_Ły^V89/ +/~U*k}??ś/E7~ ݑ@Wd4E*Eu 9U^Tm/syoyQ;/o/s&8(_+XqFr:9/B=_=)+_WN#Ͼp{SN{q;Wϋs*𫏯|~yE]VM/tÉ&8͖+O6ǿOLp^|U΋/>k,+EwkSr }̋ ||U&o4/Wȼ ߰Wq8/B/^fZ*ʋ&O2ɾ9E#||ENU~u?k~f_ŁyN[*t΋->*t3/X?yYE'þ3:Em~GL6+?'?_|ƞ|@6}'Sqvo9e|9/}>7 +vܪI> ]2TW7r(BArEЫSۋ rrw}s΋qwgu}T́/Ek<|q8z~d|ENծ6{?p^zhcWq8/`կ|fp|mߑ|>({T_~Ꜹ~89Uw*scUSS^}:*t΋<+_A{Ω/Z͢Q́o|\(ߑrо\:{sWp^`~x?hϾЙ]E_GUΝȑWyslC/Ǖ@_z񟳓;"k7|@z=}q|?/ܠQ "yk%9/^}bE{j3y7>8>Х/NhyV#9/YЕ/srv06\{೯@ g۔7|:Tzjx|*_Ł"I]Ee'}:E臦_UCU7=U@;|:ETϾ]ERi(_ŁyQ7"RyQ8 9ABW*w\5>u2:Qgy|:E>?Cg_Ł9@g_Ły|U?s*IA'_ŁyQַOtʩw~Ǘ|}Twϝ6⳯"𫛾Wqs^ o"3p:ss^~s;Sʗ9sNNWqQ9U{;L>*N;sNzoWq*/j|SwUՖ!ǔoSxW%oMXg\\o_oc?8l:=BgNr*~7ˡ +w}~Is' npJS}w&Hr! )Bu3g7~*+8y^^)—9_TV$U7 +jg +blz+_`8/:*t΋)zyʾsOoOq8/x[S +_GX+_洃STMѪ}< +w+^m?q8|yWq8/f|%r*oPrT8^?}a<~О}3_:aUDq?E^T\}| U*/*O78_WqsNU ^g"Oz̾QOg)D^4t6Ur^pg͜Q"+E{}-rvf|pPNNo+8*/2''_~_+82Z/ 9A}1~+NhOyڻwmO"s״@*t΋Ϯ|?E{8/ +/Z*ytq8/fT)_ʻzw̏|U΋ws~Aq^~{&}:T괝*t΋oj8A +vt}}PeN9>8*/j'| 8 ]q8/Bq/$es^~yߴgU^n#8~ws*/v\/sW;hWqsek Sޅ)(/^=ChϜNEh߼*t΋/68 Ͼ9EheNhobmY_zګ`]q`7"jc\8hyQǯOb>tU^^{:qTNUNsþOyQyQjzþǫ +bg{}Ⱦ9Exi/˜wG>Cg_~qNy:8]2**γW8`g_ŁNO9EuBrBgtS't'Z+_nuyڗ{?yE'_"px*ʋ_3@? ^PM([o$_BgtS}ͭeߝzCTC)|ʋJZ'`7Q9Ub_ u"Gb\{Ωtwg_\W%X:sIʩPOe| Q9U5a_ԣ8㿽*~/BǓx{~ȼeNyQ3{_@a\-wK2?*NhO9墉;C//qWyQ >*KEv]*tS~X\⠽ʋM6veګ\$_B=Wq8/ +`T> +}ΜvN+z3's*78OϾЙ"yʩq(_qq>ǺxdCD :79hyʏz^r:o--8t΋О9tU*/rק89/g_ŁS't.d=1Nyt΋Ww}F8NU΋BRG|SN~)u}s5koY2Aow7/3'ʩr'uo>0A;Eԕ.]}ϜVTɾa_Ł/o+q*_PM8SS_BWWGEyɆ::s"JzIqSzK`>88*/7_rr$ԯ|TTNNևйr΋)$ߏU΋q61/t@/)/ގy\(_QyQ  ]qT^~wV>|_΋Wۼ\_p\/s֥_*tk5>*GE6bN)/~P 9j%ߞes^?9_#|LBgQN0nl89B=?O)3R ΩPO1֞?Wq9b[Ͼh8S?8 9臚r*;Cg_Ły3Q)ǛDNՉ#r*Y/Wqy[h_"7F99/j?r+o-sX>Qy~wO"L_Ł98v]E?þ: Uv s* ]qT^̹ s}x.9"^*ʝT^TtϾ8.́yKžhΩWȼ?Ryӯ'? }3%"q~o93CZ?X:syq8B65/3U{~f_Ł[|/W94.ueH_O8o8~U#|_΋s{o=sо9T(; u.9S_c>|N=}y!,rP{6K?tyWh//q_*/*7Q9U&|GT屿GEWϵ'RyʋNGTϻŬ_2T37"Nq*ʩiFOm-_Sg}}a> +4_5ު}ssΗ9g΋)O8U^Tq{qD'oe_1?6s* ě.)?׬北 r^=Nj~pR΋xָbО./΋B{q`h}x[MnoiEN~uJΦ +t|U߾7@z6}d홃z8/BwA_T4?Ͼ9Eh'/"WN+V)s'_0mŜ׵Sʗף'Gq8^W }f{XS.!߉uoop^Sԩ|:e^'!˜zŔI~GhϹ't7p^o~SSr緲+9"*k-G$neƧR6_ +E8~~:Wq8=wدb?5*t]m>8O2+}E~' +|}:Tæta_Ł(Bk'|suWi?Wq~39^*_`x_׭U*/jz5}:T$߆ξܹVPN~m +_Νr+N_o98ZG2*_%DWqTy +oe]7uZ_Wq8/+AW[Ǐ] q"rQ/7ื"/*?$} s*p:_97j=+_Rw9U<㎌ыfYCA|6L//+ʋU'8C9TUrCsW~U?kʩwyK8 *t΋n>qLk|@wۏ;2>Wq˼&?|q8/B?wwA\?8Bgt΋SqNKEnqWBgt~%ԯ|GEԼh7).u<5:+.5ﺍRyxx-e"a쿯*|>EWϾ]EnKoq8/]-ϜS^}cʗs'p8/:BW+.΋q3Wqd^4I/sPl?@o/{AoesD^T?!t΋B?|̋8*"L?[8*/j.|K2*/H?—9s^ٻ|ګKEzϘXI܉s*[|*/*fJ*T?վ'_A/?e_(_o2r* 򕹓ȋܛ|u|7;u~%ty8ʋr|.5KGGE|s'C#j]E{$O@\BqT^]w82Y|8*/*&_2"g]qNyZ)yQ;ތ|3r:`tt*_Ł"C㨼9hw??/tΩws?/3UNU918SQ5ty8}ʋ7QyQ2s}+E}5G*TZ:|t8}Ǵ~Wq*ʩVeSE{_8"px?8r*;?-GkrWyQ[|qTNU}?FTkiయG 9U돏o+_}C9q|WqZʋ[:WA{?ʗ9s*G*ʋ$_RN{߻Ⱦ/@9z-| +F"Ag_w*jLRoyi]fN8 ]qz{6_]`/a7yWT:?~gs^7B:>sA{ΩN9RΗs'ԯzg>[]y8*/[.k|*_iE6|B*NI)8~8J/=0+_qKoWq08B4ߒ8um9=@gljWrDNUь틎޼|`8/^e_W΋|:?a_]E;ηGs*_j:GEnbsҗ.ԩr3X|EmnWqt ~5~Me_Ły8Ͼ>E}s΋P+9Z?`U-jk*_QyQu|>|"p܆ut5Voy庵o-|"ۍ+}EyQ_5?_N΋CzeS }ʋߋ8e^⳯@u[<|:94['tUS >*N)XUg_nZ$}ףRN#8r߬'N"pM|sG9Vʗ9n*ޗ^oy8n|e"<@oE^ԭ]#Wq7SΚ18doO6_6,89/. 5U,}:E4+Y}ʋܶuU9ʋʭq+_rj/s_g96taǿ̋׿}}W΋PGVm7syl>*tUrފHǷ|Ωc(_ʩw_H)y_; Gɽ%/tq~G +NM NsW?Еp_pE-|cΏ;tUu蟫.IWq.r1+GE:گ;/́^8kVWqJS/}+ʩߟ|U[o^b_Łdz6U7tBW|怯˾]EtZlWq(E;89/ގ"SPN{~Е/s/D^q}'"/>*t58**8 +|wbo9_Q9U#EQN:ݺ WyQ{ҘS|~C9o~}ʋ^68U^2~Ur*O->*tΩoV}%~T89/ŷ-w]`_Ł"7gaF b>8 9ΩK9_ʼn_pXG=%BW9/]7]_A_R|9/^ߓg_WU|՞u )Gyz;tÉڏk~`NkyQk~"kz8/B=%@? DUooā/EFz89߯7xCWy8խmyEt82 |8ӌ{8ߝ8|U΋/zx;kH_wq^4??|"#8ʩ?t"Oz*'/Ss+!7WGtUSS~7C/Wq7㔔SӊW|~|"pjϷ+9"V+_λqr$Ut#w?' 9{/.<]*/O@́"9>sWyH_E#+Tv:}Q⨜헎s ]q8/B=}ў9~?ȩꧮ ^þ +O%ξ߂"p~]|~"WqSCr೮8)GBrt|*,C|t@?-o__jC^$qwKi~\pH8 +VWq9US^~K'tUɳƾMG i7OPW<Oߨ7z?v9_ůi88n<"+t΋N@|w=9s^~M'O;gpTN?_rC9N=_88g8q8Ɇz;ϵ_Xz~Ǐ~83 /+J|/#78|:^qq FÎú@#3xq6}F)ms7[04<s- B?߷_1{7a~k_#O,~;>ks|>/Л牯8`ٌ6OK|a8C˰_q08g/-NO!ΐޭV~ҋ%AbYjti =2KGz;c54KXjݯ~.P{pxq{q|=ߒ^tnYBjt4~|RWztiG#/nNKon=8}pj?TgT@>nz=85 _5~=8y?9T:aۣ(G~G=1n~q{p:z59_?q=89c ;P{z=QgnzPq=8A׽=Zj#Y'}8}ܰ]h8P9y    LayerElementUVI f   VersionIe   NameS map1   MappingInformationTypeS ByPolygonVertex   ReferenceInformationTypeS IndexToDirect{M  ~ UVdf;  q xTyܕ^IiUh}ILd + B"-E)ڴJJJ&ZKY|~_;sfsscr*1& Ƕ 凁~&ln,l__q^}PIl a_-'}[91 / +e{ n vG}_]7.9kC8|-`]X&}&!kѮ5>f=\UYo\!zFz{2>(|]E)ǠΟ//\zg?_9s..Пyja_Ih}Zؠ0χc/<Χ ^|~ e>YbYϒ##/m]> /+.' +F! l^z/:. + ۏ r1/;j.W(No"l++>n >u/8!xn9N8k! sf/-/ںKw^)a=?r|'k~|~e>YdYoװ!K ۑS7@7O 6 +=nW?K8>U8O;QZ,O˿S/C8׫O6*Sh_דAԯ$ou8:{]O8Eyz{1uANYi= ]_tWlX_:s݅9Ya?3 ֫8;a?ϣ>*l~v-a?Z\Cz~xV?|G0?\|O9/Yoz1%{|yᝑ ːGk\| ^)'"a{ց?~K~S=u@oj >a<>p4P8=ZG;X8rm_awJWp_?za3pW0;wpufMc_%t6rzOkqU# ]b!򫯷w:~Z|C9||p=̏͗d5AFW|'_R.]$_#s?f.g_>\ayYǒ|AGh{¶_Oyyiп|J֣z/ɚ?73Zei=.<#S$naר_p^}ppS{an߭u'Xp[\oY /a[w^[.>;LhM؏{?a~TGzk6^᮵AO[)i2`ۣ䝯K/:8Yחjt\ܟ](#KZ +'fouKΏ|7:_|8]O8]/e_2z.<JUX3xp>|]Tfxī=~P8?TN:}/ 6s7v/e?w[ +H8 .a.|xf'NpYK{zYЮ.< n"/;Kŏg|+_3|Փ:ݟoy'NZsV#_T<7L%lZj|{=_g`|zJ5ad=_+j,kֻp=|eI*9m=׏mK^;n%+jih7Uv{{x=𬰭w3{x _EL?I^3ϐv +g]kuMFu?`ohp zA {tpzO Q9^W^w|Us/GAg%\ ݿQάGol> +fpCvz~y:|^c=zW$_FgKZ/j=]b.aj?\/~C\p{//J㈕aϹ1hwG*k67$E8$h'j vy$ר$ly=z|g~q'ɿ9V8|՝O cf {]oq)'tMZSؕCҡ {g(pzsvyEǏ->$? ?t}~˕-=<[<"K GoڸS|?y~Q|9??h~7i'i|j=ߠz2ZBB7ai!g_ ~|7]y^qW݄m%Tp a[ +m޸=F->h$JFE0TF;&S' vVK^'_=~է oԫ +W*yW^Mz&?F R~=(>h?+/?!Ä7 gݿ=m v_#w^$lƠЮC%:|̇0?y;|JO5QQ_֋zZ_GTatԓ'Wxx{O'~1 A76'~<}p9`7Y8݌;I9^Nj]BKfHݟXOx7XkvY~ u鹭o yzm3uQ8#F^?Jx^>p>:>l/"t$Ic>\8k«O#Z\mas o–r<^#zJuߨ 7_zͷz2N]|s1w%~xpd]}|| >W!Ϫ[/v]E8k8c|ףޠJ/~Kzh~+j3Bw!g{|o ~wvO(~x}܈ϯ˾⵭יxyhmL#UĞXSl@ԟSo!*܌: G%]}\/tyP'!ҍ tEW[cP8~+X<Z g}^ϰo>9=[|D]t%0i_m9ͯz1.3iֻ9׏GLjq`|?*"kWO>a{TxNkD8N,>6aPwsߒ羏}AI|4b|?>{pb nZ8h/aƋ] +ۄuXO8_x5Mbלk=̵wSJ?MWNZďQYOk\/o4BNO󫯈Q5'ݿ_1G8k}!eP???&D8y~V{qN;ΧpzKhL/A^Fm>.|~<ӏK.~p +ϻOw_9OX,xć4`u;ܧg#l>؞{]w)a[>U|JpP| vOJu?|2 +U(3v+ouzv#uz] !`^y]_8iߟ +;Xii<=}؟?rY7!Zp[Z?'wZ__7C99|yOt=FcuW0V|P/;j=?z>?|&_±x}cOFasHyI8t<~"o}N©ry#\u?]^ apދ%k-ͱVR~L  W'??axδcgoXOn~pڍ +ñxgw&Fu +upzJewjCV0=a𼡯w#t|O ֗O?=mjݿ;ypMp=x?Y?|AϏo񒞿p=ϞXoI~~ +I"|j=_oz2ZQ|Y<ݫ}̌2ho<9 ߝNEvUq}q[sVH[E-f<.ש ~J|]Lx0y1tmey>o| x/#{S[/4C/繒7C3"QuV9{qޒ|~?e>=uZ<vZOk cIqV#>P]8oo·v"]c!ﯿ2X/Ha35w,:QÇ5q`\,ɿΘQt)Z.rk1GR+N|X!,zx?x7|!|YwدY P3k{zg,ǣޅsaw\UUsS|vMΟZ7/PW|H~s쾸>ܪEY&7]Qwa _#?iX7]χJ|uot>1^+Eݯ|zY/wz:.|~.y/$Mo-*o?r<ᮚ)k/JO?©]->-Y->π_?W±fKyaoxaW-i[^f^9]VSؖATeja<3؆žGz5`7==1mq[~܅E=᳐^@[ۅֻ|vǓN^_on #E?_uaM6C%Qo@ر>/_9|9].ǁ' G=\r~ڟ8T# zeY؏ݯ|:G=P/3.|~|߫: ?/VX}>C8kU!"B1[ +vza_VGGnw]yCMa3X[;8_\7 ^F-:[0{V!w~j!k +GlGoPTz7 yozW +ըgL]x>zQ>aVŏi#7|t}񱸟[H??ta#$A㦽)~35nZ[=rctz-K~uVXͯz[i5F]\mǯ}̝_[r<_eą][OMǂ}cgvGepc;<~m],aJwWO>^8U)?CVNv2۞=3y`Za>S ֓_%7^Uc{|?Ƌ_vKa$/Y+'lE ρL?؞>sGקqC]ހ:8#~8]7Ao=4 ݿ>h}RwKǡCo~S8{=ߠ%=eGDO|;3_j="4.ܡH釽jn9!_fMn{Ѭ9׾+>5*>o4{U8ލq7jwy~^䕧‡˴&±DA~j_\?B2?Vap|yZct4֓{C]7>w +|ىز^Գ=G=rkKKWgnN8G oW%\_MK_#~d?๾o=^M|x^⃝qp1;| |`}:uECf5?©c<_t |ck=p=ߖ)= ._<Ou rW_u te*x]kvplXonvxqFK7 ?\ƨWo iXE (i*>m17z]x`cK5ɿ'_ >h}ݚO :^:|]z5_/iXoPZ<O޶T~H8}n{b"^#>2n.SdH9%9x ZV?R)|.>s>B[2'J1V85NV9\Cpu.Ƴ#G1_XVXw_v Wnaw#gJ~YauP9lU<~ʼ)]_j3T~75ҩq?zݳ~_Z_YY'k~2~(ィWbi sɯ,lV>1xJ}G)~tO1q>_F]!NV?Gn@aױ\/l3G=qtV\/ G>_;<$?|თŏL^s0E|`=n`[\/`>.~\ ~#~^؏A򧝄,*Իv^σ~ì:;ޯ~yH~ɏGj?p>y⃩sHכC<+|Iiku<u>z||5~3g߷W|T.|~.-.[?D&M;fKoE[[`<?? w|=i) {Nɿ^ׇ oďD>)z/;ŏηk+vam1Oa<4^ɷ=ovMQpv=OԻ|XǨ~mO> ~Ͽ9Əo8e\?"7 !q彛z7 7Ɠq7 [?Vχx~/;/k>B'j~hiv߻:ߴ ?t}nS+Fiuݳ~e +'|تb?V'|IϏjh~kj/[y^5i=##Ω+.,~^~G?חK-;L| 7\ +?#Srus2C|/\' jqZ\G] >0>>mRq>sGC4a"G5G@8m㙮|`!zo{ k_T8<ߊsnQ ++-Qxp;Ի|.xGO3u~3 +OJX#}_0?zabn +k}C3s+Kwj?V''=?xi>[XOI~>z~?M 24߁dޅ~l.b~1o&-~{* +ۃZ[$ȖpϯxzSߚ.׋4+Xu+4oaSm_?*~e}K{㹑ď S'޿5z/?a? {u7*%}^؍F=ͬMԻ|^G_?CǷ6AO?u獤3MINNf?w>湟|\~b_w C;G9nx%?\|x]X]/et?Vj>#j~|kQO7ZcZBk|mqb Nm$%~yí{>? ˈvM{-Gy%5:W|oC_+g^i,~CO? ? +&>/*|Z5}=0jbr¾'ꑺO7^_u;pnP8i8M0[ϽSm_ŏ닷+~pQ;^xݟ}u~T_!i}IA_g}?aCNz~^]8ߠ9=4F5?E2~O0̯hiը>y_?!g[?zb<?6TOq"SJF?0Y:_͇}!nyE|;yUspju$ _{{d-xU3~]1]T71`fV&دUKQ>{jz6za.z^CyG NW ?>?r~wP#KďOV~j'uݜ֯ +Go8' gpɽYk[2_˚)ɏ7a?F^~j=װ^ozj;Oy?_7^n?+WV0ÿV?G/ ?ZۻZ WMcCXYG*!NJt:_s?L  >1ߚ_\/lmuLCލ;a{ Ýy2g wޅsݖY!~X}Y8DB[# zA ~$ݟpyH]#ϢAgT8h}Ji ?(|YϏ/3]pWKh~ͺ^͗0F5V둘_|P }]88Fp݇ +!…@#= {Ι%ym?;%y_Aiw1=V*O6{prSk)C-!4Y?N| a3}Xpl??7C0OaUW*ΣK^LC~޿|}ɒgWV84|  _ቿqB]\?+ׇr6̡M;.Smϊaq0'xn$.<ݿS|Hqi7> ~n?"+\Vi$(=2FضE=ӜԻp[ʏnv񃯏#ďOA3~?O5؟;}g$>$Og"ַfo7vz>u:^|O>M׫2W #0w^Lj=?MA s]n?bEh_SM0Ga7?~kf1f}Gxo~,:)SznGWćtȻ8ekYYŏSo]/,G\->zG^ӹ=q?b^m#z2yzﻃ?z|.v=_ŏ,~Xac~C֗:߭$~_'| +Vh}WGt&b㔳_IxV|Nu=At^et?y&tAG=r+7Bk=#ȿz.ې?'y[|踏).4!>6\jk?_)&?UIV<03Xyjz]:¶?&E }Q p /w{Ǡޅ- +g{=v̟Řߞ7]p5>7u_s{nUn"y/'gqz>y\8߳ =ߟ 3Vד5?v짐 j=4y-e5e3}玨w 2^W"']us%[Vޕ~ W4Y\B;o]9|81Iki_'+\g.{A|H?3~g9pT}{s(8?zq3tϋnc^aŏxz|P؃DL VVޅp`{nXA}C_u8,{b~3bcď{OBɛۛk}M^d_Lǣi +ɬ' 'z/nz}|nXtV 9o`>C=hKwz.|~<-GY>VS#FÏFV:Kď@ra74y՜>;G%;b{v!ݽ>b.#w ?8p+(Gٮʘ/'~MXk9N|oc,>؏9p:|C=x2zM|Py)?!~D}}|D#錓; 볿|!~[~W5I[|p<09#v>h |_^Ϸ{O?|ߟ|D]O~OB|*Oş_t;QuZZϨePni?NU_~.~~ޘc.c>}oOxya&?TX7x?j!;Z^aӻ\y+k+j;^&߁SGO]G_G^&~{0_Õ{WGaA|V~C8zg^{kޅs=/?__aKm%~x]_y$tB#L(y3cs$Y*y½OOD{iC +!f8?zINjz^̇j~Xoft?I_ͧ;G ;^P .|~.>D~: +K׹4?L+m@?*?vWG_V/')ߛN4[&JLFJAÇWZdNs%}-Nq)8}~%Z;\?>|+c=qO|zݢCX;7Çد +]>Q^g@?v?©byU=Y\=?^sckÂD~XO\~oqc)>?_ϑڨ_:yr׵Nv%.lSŏ#~]^c/sw)ZY$A$l:w=8?<_//i>ǴG~/~h=7^z%w0 .|~^ʏi?¾Kۛ ??'k?[î"y*ÔyQ|yp?a7qJ\/ >v!2ޫS*^?Nz[89~xѡ)3T?#XSCTa~ȧć8uG#ֻy VǢ/^F –~h{,mϊI{/?~U⇫m3*˿ׁ'Xi+l@}s߲iINϧ{?vί{@fη>2^/|~GD]oft?I~5|;[wyG1>,+~.C⇙~pxx][M8%ϽqO[؇'J~'W_Q|˼0U[n=Ի|^ʏ܍rpzqt|cEy?7[=`m+IW+~JoG} +%~߫¿k}+. vz>d{6/WDku|_NS+Y/et?QtVvN^>F=ie~7yO8]ď)&??E&K~oӆK^+rz{Z~E·tуG}X |ݻ 'q.OMxi ޞ ?{A$ O f_VI_p7#G=JnV^igpz K>? #p#N?\w~$]kajcGŃJL߹ +OMvo6o?i"OT +?I{M{xUpi?0?|yOfد|GW|/P~C ~zҥ9Iw8!>Wm?|b|b.9DطA ;,G<sGo ?t}<í]w|4=4MHÉAS}8s~N\}u5|`S|-Ϗ| '0#zp^p{ItzoXz9[i4N]>/b·bN?bJyb(3[&ȣ7W*šRl79^0ח[WooS8Y _\;J4>t)™4)9Wbs8_i o"}~^ov7/MjQØ;JX/՞t|wGRl<9^0T{صߓoe{8RåPRʮ+Ņ(Ņq%^R 9/d ϟB/dB/dB6 y_L_ȅ|dB/ydB/dB/% PL_L_L_L_2}!2}!2}!/ K /dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/d#2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2})aL_L_L_L_L_L_L_L_L_L_L_L_L_B/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/% K/yL_L_L_L_L_L_L_L_L_L_L_L_2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}I 2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!ӗV_L_L_L_L_L_L_L_L_L_L_L_L_%L_L_L_L_L_L_L_L_L_L_L_L_2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}1/ K /dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dbߺD> K /dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/d6, K /dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/d+x}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2})aL_L_L_L_L_L_L_L_L_L_L_L_L_ {,L_L_L_L_L_L_L_L_L_L_L_L_2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}ϖ92}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!ӗV_L_L_L_L_L_L_L_L_L_L_L_L_%]2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!ӗV_L_L_L_L_L_L_L_L_L_L_L_L_%t|/2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!ӗV_L_L_L_L_L_L_L_L_L_L_L_L_9/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/% mp/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/% 鋻nB)_L_L_L_L_L_L_L_L_L_L_L_L_JX}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!?]!/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/dB/% KR)_L_L_L_L_L_L_L_L_L_L_L_L_JX}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!ӗsU;t^_L_L_L_L_L_L_L_L_L_L_L_L_JX}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!ӗTcX)_L_L_L_L_L_L_L_L_L_L_L_L_JX}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!2}!ӗ|QE^_L_L_L_L_L_L_L_L_L_L_L_L_JX}:lb}ۃ݁uI5&XX t،UlQQn[ǭbbw~ᆵsu\sϚ:] /hxA ^4 /hxA ^4 /hxA ^4 /w /hxA ^4 /hxA ^4 /hxA ^4 /hxA ^4 /hxA ^4 /hxA ^4 /iw /hxA ^4 /hxA ^4 /hxA ^4 /hxA ^4 /hxA ^4 /hxA ^4r-/-/C-[_ׇnya~e--/̯k}%߷D0~߲[~}[^4z%[^X~z%~ }K{}-/-[^[ⷼP߷oy^oB~z%~ {[wH >- =XƷ|x^/-_z% >P߷d|{o/"B=P߃m/` =XB~P߃o/` =XB~P߃o/`rCyny|x|//_^ >s|x/^ >Ez/-/ B=P/s^B=Pzx B=z/ _깿P/sa<~̋t > > > > > > > > > > > >Ezxny^zx^zx^zx^zx^zx^//a{JI^^^^^^^^^^^^"B=HP/ B=P/ B=P/ B=P/ B=P/ B=P/tE^^^^^^^^^^^^"B=HP/ B=P/ B=P/ B=P/ B=P/ B=P/.wuE^^^^^^^^^^^^"B=HP/ B=P/ B=P/ B=P/ B=P/ B=P/vE^^^^^^^^^^^^"B=HP/ B=P/ B=P/ B=P/ B=P/ B=P/+ۿx"////////////hxny^[^zx^zx^zx^zx^zx^˳vE^^^^^^^^^^^^"B=HP/ B=P/ B=P/ B=P/ B=P/ B=P/őt > > > > > > > > > > > >Ezxny^zx^zx^zx^zx^zx^/M̋t > > > > > > > > > > > >Ezxny^zx^zx^zx^zx^zx^/t6E^^^^^^^^^^^^"B=HP/ B=P/ B=P/ B=P/ B=P/ B=P/̋t > > > > > > > > > > > >Ezxny^zx^zx^zx^zx^zx^/^ՙ|x|x|x|x|x|x|x|x|x|x|x|xAËt "B=P/ B=P/ B=P/ B=P/ B=P/ B=0^v}3/-////////////Ezx^zx^zx^zx^zx^zxa<|'[^^^^^^^^^^^^ /-/Ët B=P/ B=P/ B=P/ B=P/ B=P/ xxKg^[^^^^^^^^^^^^ /-/Ët B=P/ B=P/ B=P/ B=P/ B=P/ xxY|Tg^[^^^^^^^^^^^^ /-/Ët B=P/ B=P/ B=P/ B=P/ B=P/ xxμH > > > > > > > > > > > ^[^zx^zx^zx^zx^zx^ܟtz"////////////hxny^[^zx^zx^zx^zx^zx^[wE^^^^^^^^^^^^"B=HP/ B=P/ B=P/ B=P/ B=P/ B=P/̋t > > > > > > > > > > > >Ezxny^zx^zx^zx^zx^zx^/ݞ̋t > > > > > > > > > > > >Ezxny^zx^zx^zx^zx^zx^/w8|x|x|x|x|x|x|x|x|x|x|x|xAËt "B=P/ B=P/ B=P/ B=P/ B=P/ B=POX3r/g0]㿮7'.zޮ\͙t=X~~tKn޹տg2_9y=,;g_e5>~;Pzawuu'hUz}7Og7ݰ鸛#=y$mM7gʼ C}|_ڼZ_p?|ַǷf̓??65???eL鰵7c2u>J׎FWZ메͗CJo~E;o T?xuc|v=3C\Z=rkm4u.^ʻ?7]_}}<֦}r־z΋[ #կ<ɦk3~^:_y5OMr>t·֗tX_`</r↑| j/utM+d:嫏\|t5ηfn~eZ]|h=ӮWOCJ,x?QV?+~AS,ߕH@zdSҘύ5Kc|4ZOx|0]k?u7;hԏGW\ +~r3>}PzР|ӌ1>KzyveޟM'w 3ߠ~~V_n&N=7y{`|Anzxwi:ukQr>5ZBӘRGĕiwǍB?.ȵz}bO{?㜇^ߏGOe|4Ok`7c7v>zM']xH_*qGwD{Q6Gp>u>j_Q|Q_znGd=*_Y_~#T?WZTC,?m7?he?tM۟q>gu?_I7noz-k|0_=9֯[ZOX_o^|+h?q̹G|x~4aVsgQĉ^h|UNw!k;]v 4t\,ng9/՟,ﱷ/q7<|.:,]W?WtQ׫S+?QmdbgW~+7|/P㣼~*ӗ;;'}}Z la:ՠ-;Ƶ5իJԇjA!jp㕮up·e:kai-aZ_پgtտ*s_suFuJHy}Qc +wYȿ GYtLCz_i? S59t>b Ga:_yv~{PzLy>M^LE7u^xl?_I!E;Ecv2>lXS4>yoGb:<.0Yx}މGϟֹv5j/|ku^ˡW;Oc_j~Poo-C/͖u>谩η|Ο5Gz*+k?Y3igoMJlFv>!wjQGL7|=y Y*f7#,jþA]l|>YpƦW/8We FU2>j]/?:(ZO|?_ibӌF~Er^.?G/9tu?PLg1z_4Oyߟ.-T΃_1~~YkΗtY y>֓~hՏ_FwիW'>B:xj~~7/W|V=ޟء~`6ÌBz\f|DIכtHp΋zxx?kJWs.g$y'>973Lo<4da_?^ _Xmk'}嵧fF㡨:52_ ~|h=zW~_~Yկr~=lwݭ3Sqnn|ԯ}`:Ttcw5]xCϰ6~{w_eG< t9d],ǗGdWӡot=4^?x?_IQk|^tfpQzhp!k +S?`eՏyUM7g\΋4H7m姣~Tc __xW|_R]ԟt|8Xߝj|>c|G^|KɇSȏKȋOMgVP~U)ߍRmJTowwh:l٘C7Om:Nq2e6IoX;|꾿-կt=n~+6jOcOwo:+O+0y?4q5_tsb||=eMZo񑴟rG~8{5>to<w#/qPTtC~G1]Konc|2_t|>^~}&~-<+)eKg9_{G\I<90ghL]Wp>t?\|xW)\J3y/_yyqy{ML3,t8Bh|zz#18yz8/GzC~2Boo3~?Q? X+;}y'>1͟d<]5w__=q㡺Lm:Nu=_v_yQ5s^U0|·>|tΏη|YuFZOP~/E7O#)UϠ]3-G]63]y}o[矏 ^7ǎw.<<+,m忉^=wuw^=t>N\t:-[m?_IW3\/c|W ~Ⱥ^:k' {7rF~iMW:y^tVpg3Fa wN|dC/M7C1b^z_{t?=_Iy8}7t4u>c|FW^FIZ/OP*Q>Q)U)Q T^wYѿOXxo~Bˇg<1~? m|\_b#LWGnL/O|gNtI͛p>K^.:?G/A:]!?|i?i T?ԯZJGh󜇷uV\ywCa{]5]}91n[j: ?f{?۷n>k5M7O|{?_IWƯ7HwU70>a|7񐴟f=R?'C鼨q9\!wkMÜ7o:j-F/nh}9?|<҈LF[g.rŏ8nyoX!cMYK˻ަ}^w=,\zXZLwpv9w+~2|\z:ֳGz4Oif1ݨ[=L'95>W{2 Q~\`!|ețG!yk\0y WnMg78oc'EϷgΏW?߬?s=#j=z|i?߬|6G WPJx=؉f͜EGF=MV2EY彙 c-t`Zˉ .s1n~_ݳ<~1Ӛn:}r9f:?gt?Q1CJ C'>ˇÂΚ??GZ_>[Zz#d_ihagM'' tt>8y=C}:*:rQសV*(/W$gg|wEMn˦qY!8.CGF_ŽÚ/:+{,_}B>~<|Ѧm1e bJ{?40͌JFY} qGӑ/j#}iWGa6EjNWy?כ.wn'>4nOduxÙ;CIϬjyc}qw_ӟu?w{gg7P=LW:t5p=G>Zo/RJo~%廢~wމ?F\n<[rڮ?]w/$!|L'od:>\,ߌ˝uyǷlg~כ7.2]r^k=L_>u>v@ף&;C||8}>E^k^9ﯾ4}f3^j~Qk??,Y LG#lZ +7my?mר~w>w~aݟ45XʿuCaYދWt[UoOꟷ_|{,票yAgk/q\/)(?Y%_"/,y?Un|G3N|P?|?eCiL7}>.W{=[x&{˜s? laS0j) +!/V466~:3]5Q=tQ҆s4w絝H/T_߷_|h}M?=8[>\?gFKo3y)uW:t1>|kW(?ȗ' Qȷ(~Ig>*5C3ϋ8/iy1x}ʿ\?7keO>ny1ݬpͿo6M<|LGtʦvk +~>Ya5EI}~X}?_i=~ntϓ[~û_xՏf^R_ͽl}GwtWl{8oy}n35J Ž&8/Z\O_q]Ͽ_vk'=|鰢7c[E雏^:οVO>ɏKOTo@T?*~%廡?-r=t:^%r}O7v=n4t屙7^c:a>P,q<۞|0J_c?_I=p&kWIK1Szk ~bM[}qWl~CLSO2fL=o_ʠmF k|l:ZW\͗lzZGu~}!]g~V;^w?]/Q#R]Ϫ6e2?jy-a3=}=/0]O׻Vwnt>4>|/]"ZOU(A%~( RYQ;Q ~8!ݡO8zϿbڴj<>N=y{ǚæ7\ta¦Gzo<~7V=?L3>_^Q+V7!Im.]tT3QC7>jYt]JplpWh;56 ?.~?C5O幺yWJ`:\ps\iKMW:ti||5_Ϻ^R>*$͗Cھߠ|fVW|T݌݉㱦㳷[Nk41yUMW/_.eNy{('-y9/;zeMǍOvKe|WJ ݷw^KC_0]jȏ64ԏV˚g^`ϫokoTt/]I돋haq}[{.bOXV⭵a棜??tFjOkoο#Q~J*_V[)I IjZ ~wBxަ{ރM !4͛ǜyeGLv3]]sRފoz,qXfz~^:t::ᢥM+t~?4>9Ț/NZOz[!ka>ZM=~ L/gh]㣨AF~^KM'_?CׯZèLg[ILgasLZտR| Q5_ZZ/b?hԯ|'Rw?z͝X~k`;>4]o7Nk:v}|5n5]}{0_ts:ûs.u䇻^r>v8tZ_'q wZ]d|$]/M3yz=6]kC~B>W{tCe:퇺V;^{=_/oaUiS4~u–Jk`:\s^hOfݿ3՟^,_ڭot>i||.?JC!?֛/S+IϠ~d~EϝGzleI푦8B߿[ڧ-OLWq=DAˬ[Od:'61g_qd챤Or>6xx5_u댇.=|'F+#RV?qטnO6\gfËi4V3q~t1{L_sw{A'Ώ:_K:W)I멕~ȟK>[_w{W>d߱~<߸i|O~'<yL[Fz(Yf~TS>7{C_}c?#C~֛/φ~(Q*w{Y+AnFwG=J#~-t՟66]ߒͯwtZUwWNp~Rٹa6޲kÜK^槹g6?,]/;~µƗ}EC_zawJ)i:ja|~?91?kM'?UWZgT;kFq~؇L\ƟM |}ž>ZQ[a~RҦ/ [R6_x:ߑhף^W_~߬~UwOQ;^wt93?y_̧;mw}]{oOa:ww>09Lgnۋ֯>o=tk,a:?o!njJ:ܻ?4>P4_5ioӑ=?i= Mg7?:ϿOZ +%W;^ ~y];?UO5ƘO}W3.>Ӎt~Yk/q~ZԏүDϤ=zOY{;q9̯[,ay(RY^϶1][Krg?[ǚ_92] <&.;NY-P6jYLsz宦7 ΋֛V]k?yM}7=x?¢7k)k`ӕuѵ,I#LW?_}~`}oayͬ0o}=xw׫q8O_^As~|o|$֫|%')A@?ү|7g.'Z^_=qxԳ?yڋL7 ;|L Ms[no:9t9N'鶏?yө¦ӍL]uOwƗ} :y+V2z*[+7Yɯm|hac{~&[޳rnxӍ1>5矹zW~~OJ +wV?K7_=GY~M-g^b}508쵕9//o\ߛ7~?IǼcnoU9'y`}LW M4AoOc|Ul[Y_ZoxfrO0߰U _qF;Q˯venh3LW~׳֗}ֿ~oVc~fF WayO_:/w|KMW:FuuJ!?KC/KwP?O;>?<͑74t:mM.&}\j%~Oy_?O,#O??ϺtsPH>äLE4]M^ԋt\0Eq]MG \/ߟjK_k?͸Ϟz5Ej*GY!~s;>4WWso}Wk?7R+Oun{{caᦛE|еΏoΟ<(?7)__K~_=s/WBҼZC|/^'hm~gP?/*Kz'wgΣZiKeɖFuou^Y<\׏#g:y1x<ۏ7]JA7>t|˽'A:9ŵt>N菝]piStzą\S?3]eɋ='x6Ǚ2@T r.=YotO9tK=Ұ+7yxp<'?_aw;e]vI·c]s|z~~I?/=:UMoy^t5.s]rbL|{O<\_彙xz/L4_8-9?ayLeGm:k|^r#ך5c:j=y]kw~ҷsn|B?'Lg:+M{N|7ii^5M]BӍ/` +_V~?)_sW>Z3y>ϿW-PcO8.i~AG+/qI#ޤ|Fl&+)'s;^޵7RR'ٴѦy~Ϗ:wyoFnj~ύKt;{ ަz޹mn)G]?64㫟3h5_d =O3zMG'~P~]G>}Ͽ٬i'N~jIo[?ߤoY_XEOd~o?>>՟f}IT˳:oܟµzn|М9_`=Jj_Ym~V?+~w|~ޙ?{!_|_1.y|j[N5iVG5?쯿#~WXqm_  jmLg_W1Ϙtg.$? lL3~foW}nH1[GgmU\bnZ^0OϾetyך/LZt3k=q-Q 1f}Boy.ޏp^Ͽot~/y/X}+//yb:~Rk>k/vx4)Gdbnf;~}|u~otIq\V>SKOV(~(|H=:XOS,ߍt]Wd:j<ڟi/s}R=짙~\<r8Wɑ Ex4ϻpK7#_jVG]?ߴZ+b=6?j$%i՗W?տC5?lNtQZ|6_OVoV>GүFNg]瑞Gg~yWM+7]뙮0]rݿej!߄0?}5jt?oa:kF/߶1^L?=}v|i`\iz fWie?q]k'M'ybWx:g8rw9XO֫|߬|&ү|7'o=4G|>usM磞6?>t|nj~zyIm~^͉A9?a(_l~o cga _yC2]4}k{`v3h=euMG;geģg4]k-Yk+9t~5_t_iqϛKO?Yk0L7)]_|>'пjg_߄{oGk|^|Dd7/5/C_QI{/>G,_< ×\/cl~ܟ{W^<Fmb~z_tٚo4s&'N3c_}33 }^jwZogSOxUIo^4~tۯYf2V~.:0\_W/f󢫘n׷?x泿c<ԟ#5/>t׫̓'xTpӜ1|!s6Ga/iEE_IgY[Ud5Ϙy\m/v]MW [>~Hޏt?;ruY}sUy`~4u<*}=|+o4#]k=Q~a~mx7tۏ64]ԯL^~w>7yYިHqRsC׬oU}?wMdz!|ͧ{.o5j?A=9Y|zm>6?Zo"_O!oQ>GR~+(?4zg㾞4şޱeq|y_?ă7rVyUO~tz'~ Ukq1>Ѭԯցt͇w筬MnY_}%ަ_Yq?MӟMM,0?HWƷ*?#C~Xo|_~U~*m':Gy,o%=<$끥_[㧞σO?4_y5|C|^6]2R޸O kLk~3e#|ˋz[1?jzkufg]k=wjY)k&׻~E^t~ǘwi?{̓~?a꭮stF\|^ǫ?ʦ_sUmݭO9?|/)(?͗?S |OO;>?|qe_yO z>/ |C|cvWoo)&f|F7~Hoy.#]g 㛕5|u|ǦYOzopdZUՏ8PU_0?UI~OhWF}yi?/\?j9=Ze/ߥy7gN:t4eZ^O`=zk?K>GM~կ~ߣΣ㿞^ߠC;Wbs"w  UVIndexit  ۅ x4Cim۶m۶m۶m۶m[3mSDT> 0  ͜T(w:n8vc2ݜq얌gf>$-8|xvg#2ݝq잌Ggf>&/8'>q'ed>9{4)cO8IvZƹgOfO}=]ʞggsu:|~b {)/^8|qjK2_q썌×gof[uvaqXf{u~a|qXfuQ3ߔ}qӌ÷de5&k:eZ\m<=T#uXam/{k:uXamZZuXoamYVuXam[^uXam|XQuXam|ZYuX_amkuX_am|Uu7uX6:|BOemRkouXamQg_uXamSo am 7_ +sX9A~94am 6sXC9!Pakc01L6am 6sX#9Hakc01J5am6FsXc91Xakc01N7am6sX9 Dakc01I:4Z\mL,ژ"amL6 +sXS9iƴakc01}ژ!amf83`:5f83`:383`:783`:X083`:X483`:X283`:X683`:X1J'V8;{k:6^xk:6^xk:6ިxk:6ީxk:6>k:6>k:6O#oƗuX_a|]ʴ66k:6~k:6~k:TWuXg6kc9akc01h,am 6sXC9akc01l.am 6FsX#9akc01j-am6sXc9akc01n/a -p6& OamL6&sX9Idakc01Eژ2amL6sXӄ9itakc01Cژ1a̔qX3g,u0ka̖qXgu0ga̕qXsg<u0oa̗qXgu`a,qX g"uha,qXguda,qXKg2ula,qXg +ubԹf+g+']OZ'VÓfɲ5ٚuxl:uXaWuuXaTqp j쐲68k:#68k:c68k:IuX'amR7N8k:36ά8k:s6έ8k: 6.ڸk:K6.ڸk:+6ڸk:k6ڸk:6nd7am\qKuxL ۳ڸk:6ڸk:6xk:6'eam5{k:'6xk:g6xk:6^xk:W6^xk:76ުxk:w6ޫxk:6>ߔ}RiguXC2-p6}Q6k:o6k:6~k:HKƯuXdz6k:6kcA6̛(am 6 sX9akc01d*am 6 sXÆ9akc01b)am6F sX9akc0u0f&;uX2-p6+k:68k:C68k:#68Sqlqux:6Nd'am\qJƩuXam^qFƙuXgam]qNƹuXam_qAƅuXam\\qIƥuXam\^qEƕuXWam\]ʮڸk:28W7f7qSuXamZq[uXwamYqWuXam[ڸk:||`CuXameZ\m|}T'uXam|VyuXoƗuX_am|]MƷu:6~f?amTs/uXamV{uXamUw?uXam 0ƀysX9 akc01Xƈakc0u0r&5am6f sX3eu0Ka̚qXeu0Ga̙qXseu0Oa̛qXeu@a,qX euHa,q߄8WguDuXKa,]uLuXa,_uBuxl:k:O6>k:/6S[eWu:o6k:@CƏuX?am\KƯuXam^GƟuXam]OƿuX ?1`@akc01H4am 6sXC9!~#*am 6 sXÆ9 -p6am6F sX#9(akc01Z=am6 sXc98akc01^?amL6& sX9$akc01Yژam6f sX3eu0Ka̚qXeu0Ga̙qXseu0Oa̛qXeMhsu`@Y a,\uHuXa,^uDuXKa,]uLuXa,_uBuX+a\uJuXa^uFuXka]uNuXa_uAuXal\uIuXal^uEuX[al]uMuXal_uCuxL إkc:6vأkc:6اkc:6eamXqP^1;k:C68k:#68k:c68k:6N8k:S6N8k:36Ϊ8k:s6Ϋ8o]PqaEuX2-p6..)k:6ڸk:6ڸk:6nڸTqs-u:6nڸk:6ڸk:6xk:6xk:6xk:6xk:6xk:|ARuXaZwʴx={76ެxk:w6ޭxk:6>k:|SIƧuX6k:o6k:@CƏuX?am\KƯuXam^GƟuXam]OƿuX ?1`@akc0_ sX9akB !CdXC90akc01\>am6F sX#9(akc01Z=am6 sXc98akc01^?amL6& sX9$akc01Yژam6f sxܙ2`:%f82`:#̸oB :`::X`::X`::X`::X`::X`:V:X`:V:X`:֮:X`:֯:ؠ`:6:ؤ`:6:آ`:CdZ\mlmSvuXamPcNuX;amRknuXamQg^"۫kc:}پuXam_q@ƁuXam\qHơuXam^qDƑuXGam]qLƱuXam_qBƉuX'am\qJƩuXam^qFƙux:6Ω:8ϕisq~v^Yam\XqQuXam\ZqYuXWam\YqUux:k6famPqcMuX7amRqkmuXamQqg]uXwamSqo}uXamuXaWuuXaTupp!uXaVupxuXGaUupt1uXaWup| uX'2-p6NN*k:S6N8k:36Ϊ8k:s6Ϋ8o]PqaEux:K6.ڸk:+6ڸk:k6ڸk:6nڸk:[6nڸk:;6ڸk:{6ڸk:|\@ƃuXa<\7ȴ==Z~,:xG~"?UOgg C@a  a `a a Pa 0a pa aHa(ahaaXaqo6n_f9U6~_g9M6af9]6qg9C6i?f9S6y?gS9K6eӄ6OamL6 sXӅ9 akc01S:9f8Y3`:=893`:;8y3`:?83`:X88E3`:X<8%3`:X:8e3`:X>V83`d+g0[%D٪'V8k:O6>k:amk:6k:6k:6~ӄ6~~)k:6k:6kc6̛(am 6 sX9akc01d*am 6 sXÆ9akc01b)am6F sX9akc01f+am6 sX9akc01aژ(amL6& sX9akc0 mL2ژ:amL6 sXӅ9 akc01S:9f8Y3`:=893`:;8y3`:?83`:X88E3`:X<8%3`:X:8e3`:X>V83`d+g0[%D٪'V8uXaWuuXaTupp!uXaVupxuXGaUupt1uXiBgǕqBƉuX'am\qJƩuXam^qFƙuXgam]qNƹuXam_qAƅuXam\\qIƥuXam\^qEƕuXWam\]qMƵuXam\_qCƍuX7am\qKƭuXam^qGƝuXwam]qOƽuXam_@&P`Yamam6F sX#9(akc01Z=am6 sXc98akc01^?amL6& sX9$akc01Yӄ6OamL6 +sXS9iƴakc01}ژ!am6f +sX3g,u0ka̖qXgu0ga̕qXsg<u0oa̗qXgu`a,qX g"uha,qXguda,qXKg2ula,qXg +ubaqxlfd([5j'V8k:O6>k:/6S:/6k:o6k:MhDz6~k:6~k:6k:0okc01p$am 6 sX9!Ɛakc01t&am 6 sXÇ9ƈakc01r%am6F sX91Ƙakc01v'am6 sX9 Ƅakc01qژ$amL4ÓeXS9)Takc01Mژ6amL6sX39La`:%f82`:#82`:'82`:X 82`:X$82`:X"82`:X&82`:X!V82Oqxlef8[-$'ȸɲɳ5S]֩Seᩳ4uxl:<]a>ۨϐm\g63eᙳ,uxl:<[e=۪`:m:خ`:v:ة`:]v:ح`:=:ث`:}:د`::8:C:8:#:8k:Mh옲68k:6N8k:S6N8kc=nVom۶m۶m۶m۶m۶=G}4i>IWmlgmƶqv9ml綱mƶq~mlm\ƶqq%mlm\ƶqymlWm\ƶqu5ml׶m\ƶq} ml7mƶqs-mlmƶq{mlwmƶqw=mlلmܟ6lcxm6G6mcxm6'6lcxm6g6mcxm66^lcxm6W6^mcxm676lcxm6w6mcxm66>lcm6O6>mcm6/6lcm6o6mcm6l6~Xmlmƶ[mlmƶWmlmƶ_ _c0@ƶ1pm kl5AcmcXc<65!cmcXc:65acmcXc>6F5cmcXc96F5QcmcXc=6ƈ51cmcXc;6Ɖ5qcmcXc?6&5 cmcXcۘ8l6&'ɱmLklƶ1emLklS4ƶ1mmLkl ƶ1c̔fil4Ysl9`v0GNc;3̕il4ys|9`v@Nc;X0,il4Esb9`vDNc;X2,il4esr9`vBNc;X1rO[9UrO[5Ĺr$IskV'˻fO[ȭSirԹxmlcm6O6>mcm6/6lcm6o6mcm6s?ԶSmlmƶ[mlmƶWmlmƶ_ _c0@ƶ1pm kl5AcmcXc<65!cmcXc:65acmcXc>6F5cmcXc96F5QcmcXc=6ƈ51cmcXc;6Ɖ5qcmcXc?6&5 cmcX ۘ$8Ƕ1imLklƶ1emLklS4ƶ1mmLkl ƶ1c̔fil4Ysl9`v0GNc;3̕il4ys|9`v@Nc;X0,il4Esb9`vDNc;X2,il4esr9`vBNc;X1rO[9UrO[5M$znzҼkd5xZmjcm6O6>kcm6/6jcm6o6k !}m?mƶs/mlmƶ{mlmƶw?mlmƶ16̷ƶ1Pm klbmcXc,65!bmcXc*65abmcXc.65bmcXc)6F5QbmcXc-6F51bmcXc+6Ǝ5qbmcXc/6Ə5 bmcX ۘ8(Ƕ1ImLklƶ1EmLklSԱƶ1MmLklƶ1Cmkl34s,9`֜v0[Nc;=̑ils4s<9`ޜv0_Nc;?,il 4s"9`ќvXNc;X<,ilK4s29`ٜv\Nc;X>Vi<~nVihcm66>icm66hcm66ic&lwmƶcOml?mƶkomlmƶg_mlmƶomlmc|klƶ1Hm/65bmcXc"65bmcXc&65bmcXc!6F5bmcXc%6F5bmcXc#6ƌ5bmcXc'6ƍ5bmcXcۘ l6&'̱mLklƤƶ1YmLklSƔƶ1UmLklƴƶ1]mLkl3ƌv0SNc;9̒fil4s9`Μv0WNc;;̓il4s9`vPNc;X8,il4s9`ɜvTNc;X:,il4s +9`ŜVincm66>ocm66nc&lܷm|ƶCƏml?mƶKƯmlmƶGƟmlmƶOƿmlm 05m kl ƶXc465cmcXc265cmcXc665cmcXc16F5cmcXc56F5cmcXc36Ɗ5cmcXc76Ƌ5c&lcxDƶ1qmLkldƶ1ymLklSTƶ1umLkltƶ1}mkl3L9`v0KNc;5̖fils49s\9`v0ONc;7̗il 4sB9`vHNc;X4,ilK4%sR9`vLNc;X6,il+4s[)M0JnzĹxmncm66>ocm66ng?ZnrXyi1sKxܒmmdn6^"W/۳ƋvoEr¹]x.m`n6^ Sϟ۱mc66kcضmc66jcزmc666kcشmc66jc;ذ`6kc;X`6jc;X`6Vkc;X`6xJm<~n6/Bmlyݘ/ ml׷m\ƶqm5mlWm\ƶqemlm\ƶqiw];.jsv 6oc8m6s6nc8m636Noc8m6SxmA6^?wb;m66mc8m66lc8m66mr񊹃xAml7M1qm|ƶAmlﵱmƶNmlomƶFm|I68j_{/̽^jcxm66kcxm66jcxm66ksIGm|B66hc۸m6{6ic۸m6;6hc۸nmrm|6IܨwC?ʍkl#Hƶ1bmklpƶ1lm klCPƶ1dm kl`/5 ƶ1pm kl ƶ_ƿmlmƶWƟmlmƏ~ksC_6~ncm66ocm6o6ncm6/6>ocmchly`,vh"ml ,v`mlv0o6wGic۸m6;6hc۸m6[6nic۸m6x mo6'w]mly ?j~7pƶ1lm klCPƶ1dm kl`ƶ1hm/65cmcXc0 ƶ_ƿmlmƶWƟmlmƶ[ƯmlmƶSƏm|_67}ߓm6o6ncm6/6>ocm6O6>ncm66F̟6a -Tmlv0o5x{m6G6ncxm66oc۸m6{6nc۸ƇhCs!6nmc۸m66nlc۸m66mc۸m6#wUw]ƶfmsC 9~-7Dm klƠƶXc$65bmcXc 6kcm66jsmƶ{omlmƶsOml?mƶ}wml߶m|wn;r_/mlm|ƶi'mlm|ƶamlﷱmƶn;mlomƶ1t&lAܹyj;`69foc;`6Yfnc;mc66Ϸƶ1]mLkWnX?sSTƶ1emLkldƶ1imLklDƶ1amLklxƶ1nmklcXƶ1fmklhƶ1jmkl#Hƶ1bmklpƶ1l kl [мmn6icغmc6-6hcؼmc6M66icظ`6 6hc;X`6uic;X`65hc;X`6UVic;X`6Vhc;X`6eic;X`6%hc;X`6Eic;X`6xm1p{=hc۸m6{6ic۸m6;6hc۸m6[6nis7x܍m_66kc۸m66jc۸m66.kc۸w];.nrm6R wC_ klƀƶ1@mƶo?mlmƶgmlmƶk/m`66~lcm66mcm66lcm6ܧm|S61qߐm66ocxm6w6ncxm676^ocxm6W6ۄ-h^;57[mv0Sƌml3mLomcXcۘ665cmcXcۘ265cMksnXcۘ$6&5bmcXcۘ 6Ə5bmcXc'6Ǝ5bmcXc#6F5bmcXc%6F5bmcXc!65bmcXc&65bmcXc"65b`7a 66mQfmlmlƶqFmllv~zml붱vvZmlkvzjmlvrJml+v|rml˶,vtRmlK,vxbml,vpBml ,v0|mlv0wƃhc;زmb63sg՛=m66Nmc8m66Nlc8m66mrǴ񚹣xQmz6^-wDmlmƶqHmlmƶq@mlmƶO/ۻƋlr{mƶ[ƮmlmƶSƎml;mlƶ]ƶm7_T6ncm6?6~ocm6_6~ncm6/Cߛ}wmrߴm|ƶUƗml_m|ƶYƧmlm|ƶQƇml{m|u6*NmlomƶFmlmƶJml/m^hssϷ96msϐrƴƶ1MmLklSƔƶ1EmLklƤƶ1ImLklƄƶ1AcmcXc76Ɖ5cmcXc36ƈ5cmcXc56F5cmcXc1憏5~'7\m klбƶ1Tm klCƶ1Xm kl5AbmcXc(65sۄ-h^;0Qmv^ml봱vVmlkvZmlvRml+,v\ml˴,vTmlK,vXml,vPml v0_mlv0Wmlsv0[m,wGnmrmƶqSƍml7m\ƶq]Ƶml״m\ƶqUƕmlWxems6)wImlm\ƶqAml統mƶqNmlgmƛhMs&6ocxh#ykcm66~jcm66kcm6Wm|{6-Eߚo}ƶi'mlm|ƶamlﷱmƶn;mlomƗlrokmlmƶrKml/mƶ|sml϶m<ƶt{O=Ƨhcm6)"6&5bmcXcۘ$6&5bmcXcۘ 6Ə5bmcXc'6Ǝ5b?ύklcƶ1Zmklȱƶ1Rmkl#ƶ1\m klбo䆊5~=7dk!bmcXc,65 ƶ1pm kl ƶ_ƿmlmƶW{kly`:vfmlvj*ml+vb +ml˷,vl2mlK,vdml,vh"ml ,v`mlv0o&}mlmƶqw]mlwmƶq{mmlmƶqsnh}s׷>6mc۸m66lc۸m66.mc۸m6x܅m]66w~o;m6s6nc8m636Noc8m6S6Nn r'x mlMhcxm6w6icxm676hcxm6W6^i s/܋m|^66kcxm66jcxm66kcxO='nrm6ScwCMklƶ1~mkl8ƶ1vmklcƶ1zmkl(F5bmcXc!65bmcXc&65bmcXc"65bmcXr5~17H cmcXc0656mcm66lcm66~mcmckly`jvrJml+v|rml˶,vtRmlK,vxbml,vpBml ,v0|mlv0w\mlsv0{lmlv0sLml3mƶ1}5bmcXcۘ&65bmcXcۘ"6&5b`6a 6Qamlmƶqp;hes2xܾmTn6^2w/۫ƋhEs"6vmcإmc66vlcءmc66mcئmc66lcآmc666mcؤmc66lc;ؠ`6mc;X`6"VO['˭vpdmsw{GmlmƶqKml7mƶqCml׵m\{i=sWxܕm[66.kc۸m66.jc۸m66kc8m62wvo;7ϝƛhc8m6S6Nic8m66Nhc8m6cx1mf6^#wTmlwy}#Wml_m|ƶygmlm|ƶqGmlm|ƶ~_{νWi+so6lcxm66^mcxm66^lcxm6ܳm|V6>3t{m6'6ocxm6G6ncxm66ocrѹ{=ml߶M|:7ݐrرƶ1Vmklcƶ1Zmklȱƶ1Rmkl#ƶ1\۹acmcXc:65!cmcXc<65Acmƶ1Hm klƀƶ1@3ܿmT66jcm66~kcm66~jcm66kc?n4 [мv|nkc;X`6jc;X`6kc;X`6jc;X`6kc;`6jc;`6fkc;`6fjcۘmc6mLkl4ƶ1umLklSƶ1ymLkl$ƶ1qmLklvbNcX!y;^.mln6^&_/۷Kn%r{=xmhn6^$[/۵ vnr;6vhcؾmc6m6icغmc6-6hcؼmc6M66icظ`6 6hc;X`6uic;X`65hc;X`6Ux*m Mml7mƶq}uml׶m\ƶquUmlWm\ƶqyw];.is6.lc۸m66mc8m66lc8m6xܩmQ60wro;m66oc8m6c6nc8m6#6oUrʹCx!mlM9im|ƶQƇmlmƶ^ƻmlﴱmƶVƛm|Y64z_{/ν^icxm66^hcxm6g6icxm6'6hSs)ܣm|R66jcxm66kc۸m66jc۸ƇnoCrm|6YwC?ɍkl(ƶ1rmkl#ƶ1|m kl0ƶ1tm klCƯ5bmcXc_m kl@ƶ1`m klmƶOmlmƏhGs#6~mcm66~lcm66mcm66lcmc&lAj;X`6mc;X`6lc;X`6mc;`6lc;`6fmc;`66flcۘmc|klƴƶ1MmLklSƔƶ1EmLklƤƶ1ImLklƄƶ1AmklƸƶ1Nmklc9ob6sKݫƋhEs"xܮmPn6^0s/۩ƶCml۵mlƶMmllZ,0à M@" H U0(XRi7ޝ>a qm؎Ɓv6㶱Ўvvvh`{;n#lk`k;n[q;܎v6v};nq;Xߎwk?n ֶqSdޛv6^qxю?mݎv6'mՎ???7ގ{؎ƃv6G;nqmҎmimv;3V;#oߞS;>7ڎv6nqkm\mmJ;nq۸Ԏv6.q;׎mimv&޶o>%|b(I? e>9PSCO e>=Gg2PBe>'P柄24y~ A1ŃH$翗 2?ed6doуg?e0PCPKB|i(C/ ert9D!G;No98VG7Outqt8o6G7{-qt8_ң  s   LayerElementMaterialI    VersionIe    NameS   MappingInformationTypeS AllSame?   ReferenceInformationTypeS IndexToDirectf   Materialsi     LayerI    VersionId  LayerElement   TypeS LayerElementNormal    +TypedIndexI y LayerElementP   TypeS LayerElementMateriall   +TypedIndexI  LayerElement   TypeS LayerElementUV   +TypedIndexI O  ! GeometryLД* S + GeometryS Mesh  m Verticesd ` ۲ C C @ ۲? C C @ ۲ C< C ۲? C< C    PolygonVertexIndexi       Edgesi     =   GeometryVersionI|    LayerElementNormalI z   VersionIe    NameS    MappingInformationTypeS ByVertice  ReferenceInformationTypeS Directr  m Normalsd ` ? < ? < ? < ? <    LayerElementUVI    VersionIe   NameS map1    MappingInformationTypeS ByPolygonVertex@   ReferenceInformationTypeS IndexToDirect  M UVd @ ,0? ? ,0? ?   UVIndexi        LayerElementMaterialI    VersionIe /   NameS ^  MappingInformationTypeS AllSame   ReferenceInformationTypeS IndexToDirect   Materialsi  B   LayerI    VersionId c LayerElement:   TypeS LayerElementNormalV   +TypedIndexI  LayerElement   TypeS LayerElementMaterial   +TypedIndexI 5 LayerElement    TypeS LayerElementUV(   +TypedIndexI   ! GeometryLЪ* S + GeometryS Mesh  m Verticesd ` ۲ C C @ ۲? C C @ ۲ C< C ۲? C< C C   PolygonVertexIndexi    r   Edgesi        GeometryVersionI|    LayerElementNormalI    VersionIe    NameS    MappingInformationTypeS ByVerticeG  ReferenceInformationTypeS Direct  m Normalsd ` ? < ? < ? < ? < 0   LayerElementUVI    VersionIe (  NameS map1_   MappingInformationTypeS ByPolygonVertex   ReferenceInformationTypeS IndexToDirect  M UVd @ ,0? ? ,0? ?#   UVIndexi        LayerElementMaterialI o   VersionIe    NameS   MappingInformationTypeS AllSame   ReferenceInformationTypeS IndexToDirect   Materialsi     LayerI O   VersionId  LayerElement   TypeS LayerElementNormal   +TypedIndexI % LayerElement   TypeS LayerElementMaterial   +TypedIndexI  LayerElementb   TypeS LayerElementUV~   +TypedIndexI   ! GeometryLЬ* S + GeometryS Mesh]  m Verticesd ` ۲ C C @ ۲? C C @ ۲ C< C ۲? C< C    PolygonVertexIndexi       Edgesi        GeometryVersionI| +   LayerElementNormalI &   VersionIe <   NameS m   MappingInformationTypeS ByVertice  ReferenceInformationTypeS Direct  m Normalsd ` ? < ? < ? < ? <    LayerElementUVI d   VersionIe ~  NameS map1   MappingInformationTypeS ByPolygonVertex   ReferenceInformationTypeS IndexToDirectH  M UVd @ ,0? ? ,0? ?y   UVIndexi     u   LayerElementMaterialI    VersionIe    NameS +  MappingInformationTypeS AllSameA   ReferenceInformationTypeS IndexToDirecth   Materialsi     LayerI    VersionId  LayerElement   TypeS LayerElementNormal   +TypedIndexI { LayerElementR   TypeS LayerElementMaterialn   +TypedIndexI  LayerElement   TypeS LayerElementUV   +TypedIndexI Q  ! GeometryLИ* S + GeometryS Mesh  m Verticesd ` ۲ C C @ ۲? C C @ ۲ C< C ۲? C< C    PolygonVertexIndexi       Edgesi     ?   GeometryVersionI|    LayerElementNormalI |   VersionIe    NameS    MappingInformationTypeS ByVertice  ReferenceInformationTypeS Directt  m Normalsd ` ? < ? < ? < ? <    LayerElementUVI    VersionIe   NameS map1    MappingInformationTypeS ByPolygonVertexB   ReferenceInformationTypeS IndexToDirect  M UVd @ ,0? ? ,0? ?   UVIndexi        LayerElementMaterialI    VersionIe 1   NameS `  MappingInformationTypeS AllSame   ReferenceInformationTypeS IndexToDirect   Materialsi  D   LayerI    VersionId e LayerElement<   TypeS LayerElementNormalX   +TypedIndexI  LayerElement   TypeS LayerElementMaterial   +TypedIndexI 7 LayerElement   TypeS LayerElementUV*   +TypedIndexI   ! GeometryLТ* S + GeometryS Mesh   m Verticesd ` ۲ C C @ ۲? C C @ ۲ C< C ۲? C< C E   PolygonVertexIndexi    t   Edgesi        GeometryVersionI|    LayerElementNormalI    VersionIe    NameS    MappingInformationTypeS ByVerticeI  ReferenceInformationTypeS Direct  m Normalsd ` ? < ? < ? < ? < 2   LayerElementUVI    VersionIe *  NameS map1a   MappingInformationTypeS ByPolygonVertex   ReferenceInformationTypeS IndexToDirect  M UVd @ ,0? ? ,0? ?%   UVIndexi     !   LayerElementMaterialI q   VersionIe    NameS   MappingInformationTypeS AllSame   ReferenceInformationTypeS IndexToDirect   Materialsi     LayerI Q   VersionId  LayerElement   TypeS LayerElementNormal   +TypedIndexI ' LayerElement   TypeS LayerElementMaterial   +TypedIndexI  LayerElementd   TypeS LayerElementUV   +TypedIndexI   . NodeAttributeLp_ S persp1 NodeAttributeS Camera8 Properties70T  > PS PositionS VectorS S AD D @D O}hb*@  > PS UpVectorS VectorS S AD\3&D D   F PS InterestPositionS VectorS S AD*icQF^ei^|{OG_9N5d<|g}~Wv]p}# +?G|w y>;N]ufmܾ_ɐ7_D,O\z /-x2aV.pLQ>;~moy>+c<">q"y9>/u +›~:ܟL~c?3u}_?~?wo#=s_osx5]j/?7wB~fK'0qn<">Nq6N5G>s~[?]|HG>;ygꚯxG߅|}g[x_ \M# +?g/nQx"G}+]y6N5b<#n_`uzGG:Vn#;~>{~/>ܫ߽W8oŇWNS#_#q"b^L}›/uϙqoҙ䩟f_8'DΧ_|NW0MϧzD;:ި_w|>{>sԯDy_#bб;ny Og}vIQt?5OG:Hm}|&^ x[f0?8y䏯n>3N^f?፺>•GG8į93\>l+^o<χ3O!KQ~;N]̻/u49^ZOswϝ7]j;NׅOlN7 m\ W9Wqͳ#jy@'_s<)GB̸ek#hO;>қo}ϯ_{ +O79Kx5]jǹ9|GBڦ(.gǩ[q11F4qͳ~ȿ.p{tO#GB(WqUske9e>gycCٛN`ӝk'G~h| <թCU>>_W8N5yN>{n}_^ȖQWj>s? 3dJ]uu~O}/~G }v\,xPG[CʺZuTWKu^OTG \m_yOC`u}Σ>wF:B5Gq"|+Ee|To <~f~a> cY?s]?{` &tS&|GM:Kϥq֛͛/t5Ͽ3>zU/y{?6~>_Gh}g>">c}"x_ɯ~h}S?3+ׁq}ݿr>ǹ_qٿb6} +a)__g6 =`/;-xu# +vOc?(|VpOu*<v}֑|/V3Yu?󩯺~ ~b{xt~<:?99Ώ9}Ж&r~pGyr~~bZΏ9?.8&5_e]L~_օu|v~p;}O7*}Gx#>׃~8Ov]ws}/r <#D}^}ⱟ/>o zayq7Wp%zN~7*^p\9Nē}~q}xO~ol8A |7S2   PolygonVertexIndexi   xsG"Uj۶mvj]m۶R۶yk׮}9Ͻ'y$ 30!Cp1A|Wcb#uGI⫚Ɨ59&/j>JL|`L|Ԙ2>h1U|Rcc4QnjiÚ5;k>N|ܘ1ޭڵvm7KUsov5zm7GVsvs+5rm7OTs/|/^kn⹚nx[(knE⩚nx6)]-vKc5ݒhmT 햎Gjnenx[.햏jnnŸ[)V{jnUnո[-V;jn5n͸6[,n]ۭv5ݺqS͗ƚn 6jnښn㸦$6jnʚn".jnAqimU\RsmvE5Zqan>ίvjnܚn89ήvjn]̚n8=Njn=Ԛn8;Nj>(knℚq|mWsm֮cjn8cYs)va5ݡCjn5cmwT\svā5ݱq@mw\_۹5 _mv@[;)NknSbس;-Nkn3bgƮ5YKmwv\s۝;vƎ5yCmw~l_s]vƶ5EM͏cت6;1ծ.-kncؼ涻26Mkncظ涻66 kncX涻1֫nukncX涻5֪n5kncX64V]vw*5=rmwoTs+v +5|m`,Ws=v25#tm78%knbX{"힌EknbXƝPX{.힏knbŘ{)橹^knWb՘{-樹^k>8ވjn7c֚{;fމjnwcƚ{?͞jvƴ5G1MmqL]s}SvƔ5g1EmyL^s}v_Ƥ5W1ImuL\s}vƄ5w1Am}_sv?Ƹ5O1Nm6 ƪ]-ƬcvkI3Fv5b%F#v5o1_ _svCbؚn?Zkna>dp5jnok_knv#տv#?vvFYsۍ^{m7fn5_jnqjnqkn⇚n涛 &oknojv5$Um7i|YsM_v5Sg5ݔim7U|RsMvG5ݴam7]|PsMv3{5݌nm7SSsov[5ݬfm6QQ=^jn9՚nx涛;^扗jnyŚnx涛?jnٚnx[8jnEɚnx[<했jn%њl\[:행kne㡚nx[>Vkn㾚n[9VknU㮚n[=ֈkn5㶚n[;n։knu㦚lvm~Psmvu5Fqmmq\SsmWvU5fqemy\Qsmv[e5ݠ*.knm⢚n۸..kn⼚l8vmSSsgvY5ݮqfm[Qsv{i5ݞqjmWRs'vI5ݾqbm_Ps!jnؚ8;(jnȺ3|LỚj>BW|t5|Ԯu5]j>VW󱻚|ܮ条p]ow zp]ߞ;w}s;s7ߓpc6  e Edgesi   X x q( qs^h46FhSQŢ(EQ(Ebv)uE;ENsohwEcS==O: ɷ6{|#~O)? J8DK<$J:dK>SJTQC 4BtC 1L1,p-<P‰$XI$T$\)Rʨh蠋`Ƙ`f),KVYgmv#N8-\kn灠 N$O"ɤN&O!ŔRFUPGMF] C0S03q;>ˆ H  ~FEr*ziv:馗~淌34Ocgh !;$D +idEyPD )jjf~M;tK? 38L9xsy+`-vx>3q{ᎏSDC $BdCQcʩ_QO#ʹN' Ì2$7   NameS o7   MappingInformationTypeS ByVertice7  ReferenceInformationTypeS Direct$D  q Normalsdz  d xmZm]E\"Q-)Dd)!vHbibtc"4a]*"fb SJ TF|fwzzy?;wvf=t]?Usa㼝#k),odžx:!G /7r%.Y+J;l#vO~W˃~ӑC~vG9Ooo&s)[n+y">.):agg̷z糺ݙ>nw;ozo6޾3VcdOF[n<<]̮#ͧ<~nK.`ځΙB/>үW~l~cOqaϡ\:Q̷'D~0^í^!~^DZ,v"uy^m 7=H} \yq{z坣+w#Ӌe٣ho/~_Gk`|٣Cܙnwp(|uS~p˻'?Q?Olz_zd{=!3llݷmGCOuWOOߴx~Z^|O{!6/nt뇋<|>GnIq'G+~>&׿y%*xsGc6r>Oo>ϖx\h3qOQ-q?Cnt>G=_edm>Ц5״^[ݺL?봧+N9^ W:MgNI'q_yOxO~[#Gr&ˁ?X^pdtq18'>c;; +~8ƗZ3B]IZ ~"aAkGGB'ƕw +o>c_G|/t|{~}wSs/)sxg>N:VlGx1݆xp5V/h_33QM^ʟ蓈{=ȑ:5r}m-ek|kgg<^|_tLw5WOusIWmݧϖ'G~N|>xY ."r G?<>| n#|S>!{rٷ+V:-ui72Fݱtܽ}ݷHK߅~~am~˳zO"-pO>+GܫN0OָX?tic~W[}]DN{5 O8  +Ҹ߱w[w6=Wu{n~ܿw'Q{t?޿MZ'֕Y'od]=q 9/'ix$o| ݿMo̿5οo|wO56>Sۼ \_ab~8:h^L'EG#$_?)/qοm<$?Gk<~Qy ϓ-=ϫxH|g|Z8~~Oׯ2O +u`|{~;(㸏}Jm&G/3_'_ S5G?Qh_'|/ܯ?N ?䨏v}#gx 3gx><{|jWO'/W~|cO\ħޟO'eZ +/U}]ٟ<۟a>n3q>ŗm&^gw0[m;v|''+U}sx_W~|j_'/W~|b>qO'^'O'NWv`U)O'^q?n=q>vޓϧi;)'b|O\|ǕOs.?磝OO\ٟ>1wO|O|5ĵO\k|b5ڗO'_'^'O'_''{u]{W<>q O'~Gvxf>W}?}ώܟ|_'Swy|gb>q'>O'wl^x_W~|jWėoU'>t1v3b=wf;_=GԿ|bO|b>ﶝ0|⚟n1j^98>#\kw\5|o~obO|}[ħ~a>1/}NOλy'^'x|,>q'_':ݟ9Ǖ=ߗul35[3vOSݟ2=H3'>'W~|b>1}+^{|b>u}ͧv>1O|>d_8/>'?1>qy|'։5|b>>'W~'r1O|mOO\#|kw'?q?898>Q35X|Kĕ9|hwO|_|Z~'kĕO\'Kĕ>Oskk|io82돼+?>q O|ć+?>1z_<9O<}]^>~T=ɇ:rr??\*ÌW)3^:wdxx57k=x/^+_/<'_/qXgda>qXOg3ula>qXg ubaqX/g+ji׳uF}3N8ӷ3:}7N83fa82Ì:8ӏ9e~qXduYa~qie~Uuu7uXa~Wu}uX?aTus/uX9uz'N83:3v8ʹOvڽ[ ʛ sX!ak74a톅90v#iT:sX1a46aƅ90uvÜfMOʰvSnj:͕qX34Oa8|u?N di:-qX3Haeiь:-qX3Da8tZ:[2uXeNa:PuZRi:*uXUNaV:QuZVi::uXuvie6mT۸k7kIn:fuXv[amUۺkMn:vuXv;avTvd;۵k[n:uX=v{aS۷k_n:uXvafaR;kwXf^:QuXva펭W;kwB:IuXvaNڝV;kwF:YuX4윲vaί]Pk7kwQ:%uXKva.]QkwU:5uXkva ;1<$}Sxha޷z}[xdQwGz}Wxlqz}_xbI'z +O~0<5oķp Ɂ Ɂ Ɂ Ɂ ` ` Ɂ Ɂ @ Ɂ @ Ɂ Ɂ? +V  M PolygonVertexIndexi @         jV  - Edgesi       V   GeometryVersionI| X   LayerElementNormalI V   VersionIe V   NameS W   MappingInformationTypeS ByPolygonVertexEW  ReferenceInformationTypeS DirectX   Normalsd0  @.Y> @.Y> .Y> .Y> y @y? `y y @y? `y y @y? `y @y @y? y? @y @y? y? @y @y? y? @y? y? @y? @y? y? @y? @y? y? @y? @y? `y? y @y? `y? y @y? y? y Z   LayerElementUVI ,Y   VersionIe FY  NameS map1}Y   MappingInformationTypeS ByPolygonVertexY   ReferenceInformationTypeS IndexToDirectpZ  UVd ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Z  M UVIndexi @            [   LayerElementMaterialI [   VersionIe 3[   NameS b[  MappingInformationTypeS AllSame[   ReferenceInformationTypeS IndexToDirect[   Materialsi  F]   LayerI [   VersionId g\ LayerElement>\   TypeS LayerElementNormalZ\   +TypedIndexI \ LayerElement\   TypeS LayerElementMaterial\   +TypedIndexI 9] LayerElement]   TypeS LayerElementUV,]   +TypedIndexI ^  & NodeAttributeL$ S NodeAttributeS Null] Properties70]  ! PS LookS enumS S I ^  TypeFlagsS Null u`  $ ModelL0q* S pCube1 ModelS Meshc^   VersionI /` Properties70^  + PS RotationActiveS boolS S I ^  ( PS InheritTypeS enumS S I @_  G PS + ScalingMaxS Vector3DS VectorS D D D _  8 PS DefaultAttributeIndexS intS IntegerS I _  O PS Lcl TranslationS Lcl TranslationS S A+DF e:׿D +t%@D`dM?"`  1 PS currentUVSetS KStringS S US map1 E`   ShadingCTh`   CullingS + CullingOff b  ( ModelL@q* S pCylinder1 ModelS Mesh`   VersionI b Properties70a  + PS RotationActiveS boolS S I Pa  ( PS InheritTypeS enumS S I a  G PS + ScalingMaxS Vector3DS VectorS D D D a  8 PS DefaultAttributeIndexS intS IntegerS I Gb  N PS Lcl TranslationS Lcl TranslationS S AD}@Dm\& @DE@b  1 PS currentUVSetS KStringS S US map1 b   ShadingCTb   CullingS + CullingOff e  % ModelLPr* S pPlane1 ModelS Mesh)c   VersionI e Properties70{c  + PS RotationActiveS boolS S I c  ( PS InheritTypeS enumS S I d  G PS + ScalingMaxS Vector3DS VectorS D D D Ld  8 PS DefaultAttributeIndexS intS IntegerS I d  N PS Lcl TranslationS Lcl TranslationS S AD D DF1!@d  H PS Lcl RotationS Lcl RotationS S AD V@D D Re  F PS Lcl ScalingS Lcl ScalingS S ADO^z@DO^z@DO^z@e  1 PS currentUVSetS KStringS S US map1 e   ShadingCTe   CullingS + CullingOff h  % ModelL`r* S pPlane2 ModelS Mesh4f   VersionI h Properties70f  + PS RotationActiveS boolS S I f  ( PS InheritTypeS enumS S I g  G PS + ScalingMaxS Vector3DS VectorS D D D Wg  8 PS DefaultAttributeIndexS intS IntegerS I g  N PS Lcl TranslationS Lcl TranslationS S AD D D3؈"@ h  H PS Lcl RotationS Lcl RotationS S AD V@D D ]h  F PS Lcl ScalingS Lcl ScalingS S ADO^z@DO^z@DO^z@h  1 PS currentUVSetS KStringS S US map1 h   ShadingCTh   CullingS + CullingOff k  % ModelLpr* S pPlane3 ModelS Mesh?i   VersionI k Properties70i  + PS RotationActiveS boolS S I i  ( PS InheritTypeS enumS S I j  G PS + ScalingMaxS Vector3DS VectorS D D D bj  8 PS DefaultAttributeIndexS intS IntegerS I j  N PS Lcl TranslationS Lcl TranslationS S AD D D` $@k  H PS Lcl RotationS Lcl RotationS S AD V@D D hk  F PS Lcl ScalingS Lcl ScalingS S ADO^z@DO^z@DO^z@k  1 PS currentUVSetS KStringS S US map1 k   ShadingCTk   CullingS + CullingOff o  % ModelL#r* S pPlane4 ModelS MeshJl   VersionI n Properties70l  + PS RotationActiveS boolS S I l  ( PS InheritTypeS enumS S I 'm  G PS + ScalingMaxS Vector3DS VectorS D D D mm  8 PS DefaultAttributeIndexS intS IntegerS I m  N PS Lcl TranslationS Lcl TranslationS S AD D D0&yP&@n  H PS Lcl RotationS Lcl RotationS S AD V@D D sn  F PS Lcl ScalingS Lcl ScalingS S ADO^z@DO^z@DO^z@n  1 PS currentUVSetS KStringS S US map1 n   ShadingCTn   CullingS + CullingOff r  % ModelL,r* S pPlane5 ModelS MeshUo   VersionI q Properties70o  + PS RotationActiveS boolS S I o  ( PS InheritTypeS enumS S I 2p  G PS + ScalingMaxS Vector3DS VectorS D D D xp  8 PS DefaultAttributeIndexS intS IntegerS I p  N PS Lcl TranslationS Lcl TranslationS S AD D Dhܲ %@*q  H PS Lcl RotationS Lcl RotationS S AD V@D D ~q  F PS Lcl ScalingS Lcl ScalingS S ADO^z@DO^z@DO^z@q  1 PS currentUVSetS KStringS S US map1 q   ShadingCTr   CullingS + CullingOff t  & ModelL5r* S persp1 ModelS Cameraar   VersionI t Properties70r  I PS PostRotationS Vector3DS VectorS D D VD +s  + PS RotationActiveS boolS S I @s  ( PS InheritTypeS enumS S I s  G PS + ScalingMaxS Vector3DS VectorS D D D s  8 PS DefaultAttributeIndexS intS IntegerS I 7t  N PS Lcl TranslationS Lcl TranslationS S AD D @D O}hb*@t  H PS Lcl RotationS Lcl RotationS S AD D f@D f@ t   ShadingCYt   CullingS + CullingOff kx  0 ModelL>r* S directionalLight1 ModelS Light;u   VersionI %x Properties70u  K PS RotationOffsetS Vector3DS VectorS Dh 3ZD!R/@D _J?  1 PS currentUVSetS KStringS S US map1 ։   ShadingCT   CullingS + CullingOff i  ' ModelLp* S pPyramid1 ModelS MeshX   VersionI # Properties70  + PS RotationActiveS boolS S I   ( PS InheritTypeS enumS S I 5  G PS + ScalingMaxS Vector3DS VectorS D D D {  8 PS DefaultAttributeIndexS intS IntegerS I ׋  N PS Lcl TranslationS Lcl TranslationS S AD4xK)D?f@D" +@  1 PS currentUVSetS KStringS S US map1 9   ShadingCT\   CullingS + CullingOff 2  ( ModelL"* S transform1 ModelS Null   VersionI  Properties70  + PS RotationActiveS boolS S I D  ( PS InheritTypeS enumS S I   G PS + ScalingMaxS Vector3DS VectorS D D D ߍ  8 PS DefaultAttributeIndexS intS IntegerS I    ShadingCY%   CullingS + CullingOff   * MaterialLH S normalMapTest MaterialS    VersionIf   + ShadingModelS phongɎ   +MultiLayerI  Properties701  A PS AmbientColorS ColorS S AD D D   A PS DiffuseColorS ColorS S AD ?D ?D ?  1 PS DiffuseFactorS NumberS S AD ?  6 PS TransparencyFactorS NumberS S AD ?S  B PS SpecularColorS ColorS S AD ?D ?D ?  4 PS ReflectionFactorS NumberS S AD ?  E PS EmissiveS Vector3DS VectorS D D D :  D PS AmbientS Vector3DS VectorS D D D   D PS DiffuseS Vector3DS VectorS D ?D ?D ?ߑ  E PS SpecularS Vector3DS VectorS D ?D ?D ?  2 PS ShininessS doubleS NumberS D 4@]  0 PS OpacityS doubleS NumberS D ?  5 PS ReflectivityS doubleS NumberS D ו  % MaterialL{ S lambert1 MaterialS    VersionIf 2  ShadingModelS lambertN   +MultiLayerI ʕ Properties70  A PS AmbientColorS ColorS S AD D D   A PS DiffuseColorS ColorS S AD ?D ?D ?D  1 PS DiffuseFactorS NumberS S AD ?  6 PS TransparencyFactorS NumberS S AD ?۔  E PS EmissiveS Vector3DS VectorS D D D -  D PS AmbientS Vector3DS VectorS D D D   D PS DiffuseS Vector3DS VectorS D ?D ?D ?  0 PS OpacityS doubleS NumberS D ?   ( MaterialLp S transparent MaterialS -   VersionIf P  + ShadingModelS phongl   +MultiLayerI  Properties70Ԗ  A PS AmbientColorS ColorS S AD D D #  A PS DiffuseColorS ColorS S AD ?D ?D ?b  1 PS DiffuseFactorS NumberS S AD ?  E PS TransparentColorS ColorS S AD *?D *?D *?  6 PS TransparencyFactorS NumberS S AD ?I  B PS SpecularColorS ColorS S AD ?D ?D ?  5 PS ShininessExponentS NumberS S AD|F?@Θ  4 PS ReflectionFactorS NumberS S AD ?!  E PS EmissiveS Vector3DS VectorS D D D s  D PS AmbientS Vector3DS VectorS D D D ř  D PS DiffuseS Vector3DS VectorS D ?D ?D ?  E PS SpecularS Vector3DS VectorS D ?D ?D ?X  2 PS ShininessS doubleS NumberS D|F?@  0 PS OpacityS doubleS NumberS D $T?ٚ  5 PS ReflectivityS doubleS NumberS D   ' MaterialLD S glasGreen1 MaterialS H   VersionIf k  + ShadingModelS phong   +MultiLayerI  Properties70  A PS AmbientColorS ColorS S AD D D >  A PS DiffuseColorS ColorS S AD D D }  1 PS DiffuseFactorS NumberS S AD ?М  E PS TransparentColorS ColorS S AD D ?D   6 PS TransparencyFactorS NumberS S AD ?d  B PS SpecularColorS ColorS S AD ?D ?D ?  5 PS ShininessExponentS NumberS S AD|F?@  4 PS ReflectionFactorS NumberS S AD ?<  E PS EmissiveS Vector3DS VectorS D D D   D PS AmbientS Vector3DS VectorS D D D   D PS DiffuseS Vector3DS VectorS D D D 3  E PS SpecularS Vector3DS VectorS D ?D ?D ?s  2 PS ShininessS doubleS NumberS D|F?@  0 PS OpacityS doubleS NumberS DVUUUUU?  5 PS ReflectivityS doubleS NumberS D &  $ MaterialLPL S glasRed MaterialS `   VersionIf   + ShadingModelS phong   +MultiLayerI  Properties70  A PS AmbientColorS ColorS S AD D D V  A PS DiffuseColorS ColorS S AD D D   1 PS DiffuseFactorS NumberS S AD ?  E PS TransparentColorS ColorS S AD ?D D ,  6 PS TransparencyFactorS NumberS S AD ?|  B PS SpecularColorS ColorS S AD ?D ?D ?  5 PS ShininessExponentS NumberS S AD|F?@  4 PS ReflectionFactorS NumberS S AD ?T  E PS EmissiveS Vector3DS VectorS D D D   D PS AmbientS Vector3DS VectorS D D D   D PS DiffuseS Vector3DS VectorS D D D K  E PS SpecularS Vector3DS VectorS D ?D ?D ?  2 PS ShininessS doubleS NumberS D|F?@ɤ  0 PS OpacityS doubleS NumberS DVUUUUU?   5 PS ReflectivityS doubleS NumberS D   ' MaterialL0N S glaseBlack MaterialS {   VersionIf   + ShadingModelS phong   +MultiLayerI  Properties70"  A PS AmbientColorS ColorS S AD D D q  A PS DiffuseColorS ColorS S AD D D   1 PS DiffuseFactorS NumberS S AD ?  6 PS TransparencyFactorS NumberS S AD ?D  B PS SpecularColorS ColorS S AD ?D ?D ?  5 PS ShininessExponentS NumberS S AD|F?@ɧ  4 PS ReflectionFactorS NumberS S AD ?  E PS EmissiveS Vector3DS VectorS D D D n  D PS AmbientS Vector3DS VectorS D D D   D PS DiffuseS Vector3DS VectorS D D D   E PS SpecularS Vector3DS VectorS D ?D ?D ?S  2 PS ShininessS doubleS NumberS D|F?@  0 PS OpacityS doubleS NumberS D ?ԩ  5 PS ReflectivityS doubleS NumberS D   % MaterialLP S glasBlue MaterialS A   VersionIf d  + ShadingModelS phong   +MultiLayerI  Properties70  A PS AmbientColorS ColorS S AD D D 7  A PS DiffuseColorS ColorS S AD D D v  1 PS DiffuseFactorS NumberS S AD ?ɫ  E PS TransparentColorS ColorS S AD D D ?   6 PS TransparencyFactorS NumberS S AD ?]  B PS SpecularColorS ColorS S AD ?D ?D ?  5 PS ShininessExponentS NumberS S AD|F?@  4 PS ReflectionFactorS NumberS S AD ?5  E PS EmissiveS Vector3DS VectorS D D D   D PS AmbientS Vector3DS VectorS D D D ٭  D PS DiffuseS Vector3DS VectorS D D D ,  E PS SpecularS Vector3DS VectorS D ?D ?D ?l  2 PS ShininessS doubleS NumberS D|F?@  0 PS OpacityS doubleS NumberS DVUUUUU?  5 PS ReflectivityS doubleS NumberS D <  ) MaterialL` S emissiveTest MaterialS ^   VersionIf   ShadingModelS unknown   +MultiLayerI / Properties70  PS MayaS CompoundS S "  . PS Maya|TypeIdS intS IntegerS I   ) MaterialLQ S specularTest MaterialS    VersionIf   + ShadingModelS phongҰ   +MultiLayerI  Properties70:  A PS AmbientColorS ColorS S AD D D   A PS DiffuseColorS ColorS S AD D D ȱ  1 PS DiffuseFactorS NumberS S AD ?   6 PS TransparencyFactorS NumberS S AD ?\  B PS SpecularColorS ColorS S AD "?D "?D "?  5 PS ShininessExponentS NumberS S AD|F?@  4 PS ReflectionFactorS NumberS S AD ?4  E PS EmissiveS Vector3DS VectorS D D D   D PS AmbientS Vector3DS VectorS D D D س  D PS DiffuseS Vector3DS VectorS D D D +  E PS SpecularS Vector3DS VectorS D "?D "?D "?k  2 PS ShininessS doubleS NumberS D|F?@  0 PS OpacityS doubleS NumberS D ?  5 PS ReflectivityS doubleS NumberS D   # VideoL S file2 VideoS ClipU  TypeS Clip Properties70  PS PathS KStringS XRefUrlS S\ C:/Users/Destranix/Documents/maya/projects/Test/images/Tardis_Displacement/Wood_007_ROUGH.tx +   UseMipMapI   a FilenameS\ C:/Users/Destranix/Documents/maya/projects/Test/images/Tardis_Displacement/Wood_007_ROUGH.tx  1 RelativeFilenameS, resources\textures\Tardis_Displacement\Wood_007_ROUGH.tx   ! TextureL# S file2 TextureS W   TypeS TextureVideoClipp   VersionI    TextureNameS file2 Texturep Properties70  4 PS CurrentTextureBlendModeS enumS S I -  ) PS UVSetS KStringS S S map1c  ( PS UseMaterialS boolS S I    MediaS file2 Video   a FileNameS\ C:/Users/Destranix/Documents/maya/projects/Test/images/Tardis_Displacement/Wood_007_ROUGH.txW  1 RelativeFilenameS, resources\textures\Tardis_Displacement\Wood_007_ROUGH.tx   ModelUVTranslationD D    ModelUVScalingD ?D ?߹  Texture_Alpha_SourceS None   CroppingI I I I   > ImplementationLv* S+ emissiveTest_Implementation ImplementationS    VersionId  Properties70ߺ  1 PS ShaderLanguageS KStringS S S SFX$  7 PS ShaderLanguageVersionS KStringS S S 28[  ) PS RenderAPIS KStringS S S   3 PS RootBindingNameS KStringS S S root  ( PS ShaderGraphS BlobS S I    +BinaryDataR SFX_WIN +Version=28 +GroupVersion=-1.000000 +Advanced=0 +HelpID=0 +ParentMaterial=0 +NumberOfNodes=5 +#NT=10100 1 Hw Material Base-Hw Shader Nodes-Core + PC=35 + name=1 v=5000 _Material + version=1 v=2003 1.842000 + posx=1 v=2003 10.000000 + posy=1 v=2003 10.000000 + classname=1 v=5000 Hw Material Base + submenuname=1 v=5000 Core + bitmapnodeindex=1 v=2002 10 + isadvanced=1 v=2001 1 + advanceddelete=1 v=2001 1 + helpid=1 v=2002 73 + grpnodecolor=1 v=5012 4 + grpPosX=1 v=2003 -1129.380005 + grpPosY=1 v=2003 -143.923004 + disableconsolidation_HwShader=2 e=1 v=2001 0 + value_ClampDynamicLights=2 e=1 v=2002 99 + value_MaxNumberLights=2 e=1 v=2002 3 + value_Gamma=2 e=2 v=2001 0 + value_Wireframe=2 e=3 v=2001 0 + value_DepthTest=2 e=4 v=2001 1 + value_DepthWrite=2 e=4 v=2001 1 + value_CastShadow=2 e=5 v=2001 1 + value_SurfaceMaskCutoff=2 e=6 v=2003 0.000000 + value_SSAO=2 e=7 v=2001 1 + options_Tessellation=2 e=900 v=5012 0 + value_FlatTessellationBlend=2 e=901 v=2003 0.000000 + value_BoundingBoxMultiplier=2 e=902 v=2003 1.000000 + value_ClippingBiasAdd=2 e=902 v=2003 5.000000 + options_Displacement=2 e=1000 v=5012 1 + options_VDM_CoordSys=2 e=1001 v=5012 1 + value_DisplacementMultiplier=2 e=1002 v=2003 1.000000 + value_DisplacementOffset=2 e=1003 v=2003 0.000000 + cgfxprofile_HwShader=2 e=1999 v=5012 0 + config_HwShader=2 e=2000 v=5012 1 + shadername_HwShader=2 e=2001 v=5000 + saveshadertodisk_HwShader=2 e=2002 v=5015 + group=-1 + ISC=9 + SVT=2002 2002 0 0 0 _NumberOfLights + SVT=5001 3002 0 0 0 _ObjectVertexPosition + SVT=5001 2003 0 0 0 + SVT=5001 3002 0 0 0 _Displacement + SVT=5001 5018 0 0 0 _SurfaceShader + SVT=5001 2003 0 0 0 _SurfaceMask + SVT=5001 2003 0 0 0 _SurfaceMaskCutoff + SVT=2001 2001 0 0 0 _Gamma + SVT=1001 1002 0 0 0 + OSC=0 +#NT=10100 1 Traditional Game Surface Shader-Hw Shader Nodes-Surface Shaders + PC=26 + name=1 v=5000 TraditionalGameSurfaceShader + version=1 v=2003 1.481000 + posx=1 v=2003 -200.000000 + posy=1 v=2003 10.000000 + previewswatch=1 v=2002 2 + classname=1 v=5000 Traditional Game Surface Shader + submenuname=1 v=5000 Surface Shaders + bitmapnodeindex=1 v=2002 10 + isadvanced=1 v=2001 1 + advanceddelete=1 v=2001 1 + helpid=1 v=2002 74 + grpnodecolor=1 v=5012 4 + grpPosX=1 v=2003 -990.607971 + grpPosY=1 v=2003 169.649994 + options_Diffuse=2 e=1 v=5012 0 + options_Specular=2 e=1 v=5012 0 + value_FlipBackFaces=2 e=1 v=2001 1 + value_TranslucencyDistortion=2 e=1100 v=2003 0.200000 + value_TranslucencyPower=2 e=1101 v=2003 3.000000 + value_TranslucencyMinimum=2 e=1102 v=2003 0.000000 + color_TranslucencyOuter=2 e=1104 v=3003 1.000000,0.640000,0.250000,1.000000 + color_TranslucencyMedium=2 e=1105 v=3003 1.000000,0.210000,0.140000,1.000000 + color_TranslucencyInner=2 e=1106 v=3003 0.250000,0.050000,0.020000,1.000000 + value_UseStreamLightData=2 e=1500 v=2001 0 + value_BakedLightColorSet=2 e=1502 v=5000 BakedLightColorSet + value_BakedLightColorSetUnshared=2 e=1503 v=2001 1 + group=-1 + ISC=17 + SVT=5001 2003 0 0 0 _Opacity + SVT=5001 3002 0 0 0 _Emissive + SVT=5001 2003 0 0 0 _AmbientOcclusion + SVT=5001 3002 0 0 0 _DiffuseColor + SVT=5001 2003 0 0 0 _SpecularPower + SVT=5001 3002 0 0 0 _SpecularColor + SVT=5001 3002 0 0 0 _Reflection + SVT=5001 2003 0 0 0 _ReflectionIntensity + SVT=5001 3002 0 0 0 _Normal + SVT=5001 3002 0 0 0 _ObjectThickness + SVT=5001 2003 0 0 0 _BlendedNormal + SVT=5001 2003 0 0 0 _BlendedNormalMask + SVT=5001 3002 0 0 0 _AnisotropicDirection + SVT=5001 3001 0 0 0 _AnisotropicSpread + SVT=5001 3002 0 0 0 _IBL + SVT=5001 2003 0 0 0 _Weight + SVT=1001 1002 0 0 0 + OSC=2 + SVT=5001 5018 0 _SurfaceShader + CC=1 + C=1 0 0 0 4 0 0 + CPC=0 + SVT=1001 1002 0 + CC=0 +#NT=20011 0 + PC=3 + posx=1 v=2003 -567.500000 + posy=1 v=2003 135.000000 + color=2 e=0 v=3003 0.159091,0.159091,0.159091,1.000000 + group=-1 + ISC=0 + OSC=6 + SVT=5001 3003 1 + CC=0 + SVT=5001 3002 2 + CC=1 + C=2 1 2 1 3 0 0 + CPC=0 + SVT=5001 2003 3 + CC=0 + SVT=5001 2003 4 + CC=0 + SVT=5001 2003 5 + CC=0 + SVT=5001 2003 6 + CC=0 +#NT=20011 0 + PC=3 + posx=1 v=2003 -490.500000 + posy=1 v=2003 -45.000000 + color=2 e=0 v=3003 0.462700,0.000000,0.839200,1.000000 + group=-1 + ISC=0 + OSC=6 + SVT=5001 3003 1 + CC=0 + SVT=5001 3002 2 + CC=1 + C=3 1 2 1 1 0 0 + CPC=0 + SVT=5001 2003 3 + CC=0 + SVT=5001 2003 4 + CC=0 + SVT=5001 2003 5 + CC=0 + SVT=5001 2003 6 + CC=0 +#NT=20011 0 + PC=3 + posx=1 v=2003 -537.305542 + posy=1 v=2003 315.972229 + color=2 e=0 v=3003 0.164773,0.164773,0.164773,1.000000 + group=-1 + ISC=0 + OSC=6 + SVT=5001 3003 1 + CC=0 + SVT=5001 3002 2 + CC=1 + C=4 1 2 1 5 0 0 + CPC=0 + SVT=5001 2003 3 + CC=0 + SVT=5001 2003 4 + CC=0 + SVT=5001 2003 5 + CC=0 + SVT=5001 2003 6 + CC=0 +   ' BindingTableL0 S root 1 BindingTableS k   VersionId  Properties70  . PS + TargetNameS KStringS S S root  0 PS + TargetTypeS KStringS S S shader   & AnimationStackLB S Take 001 AnimStackS } Properties70  0 PS + LocalStartS KTimeS TimeS LR^r   / PS LocalStopS KTimeS TimeS Lp65 /  4 PS ReferenceStartS KTimeS TimeS LR^r p  3 PS ReferenceStopS KTimeS TimeS Lp65   ' AnimationLayerLp k* S BaseLayer AnimLayerS   , AnimationCurveNodeL0C S Visibility AnimCurveNodeS  Properties70  4 PS d|VisibilityS + VisibilityS S AD ?   # AnimationCurveNodeL B S T AnimCurveNodeS  Properties70)  ' PS d|XS NumberS S ADF e:׿^  ' PS d|YS NumberS S AD +t%@  ' PS d|ZS NumberS S AD`dM?   # AnimationCurveNodeLB S S AnimCurveNodeS  Properties70=  ' PS d|XS NumberS S AD ?r  ' PS d|YS NumberS S AD ?  ' PS d|ZS NumberS S AD ?   # AnimationCurveNodeL`B S R AnimCurveNodeS  Properties70Q  ' PS d|XS NumberS S AD   ' PS d|YS NumberS S AD   ' PS d|ZS NumberS S AD 5   AnimationCurveL` S AnimCurveS +  DefaultD C   KeyVerI l   KeyTimel  R^r    KeyValueFloatf  ?   KeyAttrFlagsi      KeyAttrDataFloatf    (   KeyAttrRefCounti      AnimationCurveL` S AnimCurveS   DefaultD    KeyVerI    KeyTimel  R^r    KeyValueFloatf  -ӹ!   KeyAttrFlagsi  a [   KeyAttrDataFloatf       KeyAttrRefCounti      AnimationCurveL ` S AnimCurveS   DefaultD    KeyVerI ,   KeyTimel  R^r W   KeyValueFloatf  }@   KeyAttrFlagsi  a    KeyAttrDataFloatf       KeyAttrRefCounti   U   AnimationCurveL` S AnimCurveS K  DefaultD c   KeyVerI    KeyTimel  R^r    KeyValueFloatf  {m?   KeyAttrFlagsi  a    KeyAttrDataFloatf    H   KeyAttrRefCounti      AnimationCurveL` S AnimCurveS   DefaultD    KeyVerI    KeyTimel  R^r    KeyValueFloatf  ?A   KeyAttrFlagsi  a {   KeyAttrDataFloatf       KeyAttrRefCounti      AnimationCurveL ` S AnimCurveS   DefaultD #   KeyVerI L   KeyTimel  R^r w   KeyValueFloatf  ?   KeyAttrFlagsi  a    KeyAttrDataFloatf       KeyAttrRefCounti   u   AnimationCurveL~` S AnimCurveS k  DefaultD    KeyVerI    KeyTimel  R^r    KeyValueFloatf  ?   KeyAttrFlagsi  a ;   KeyAttrDataFloatf    h   KeyAttrRefCounti      AnimationCurveL` S AnimCurveS   DefaultD    KeyVerI    KeyTimel  R^r 7   KeyValueFloatf  a   KeyAttrFlagsi  a    KeyAttrDataFloatf       KeyAttrRefCounti   5   AnimationCurveL` S AnimCurveS +  DefaultD C   KeyVerI l   KeyTimel  R^r    KeyValueFloatf     KeyAttrFlagsi  a    KeyAttrDataFloatf    (   KeyAttrRefCounti      AnimationCurveL`` S AnimCurveS   DefaultD    KeyVerI    KeyTimel  R^r    KeyValueFloatf  !   KeyAttrFlagsi  a [   KeyAttrDataFloatf       KeyAttrRefCounti    Connections   CS OOL0q* L    CS OOL@q* L /   CS OOLPr* L V   CS OOL`r* L }   CS OOLpr* L    CS OOL#r* L    CS OOL,r* L    CS OOL5r* L    CS OOL>r* L @   CS OOLGr* L g   CS OOL * L    CS OOL0* L    CS OOL@* L    CS OOLP* L    CS OOL`* L *   CS OOLp* L Q   CS OOL"* L x   CS OOLp k* LB    CS OOL0C Lp k*    CS OOL B Lp k*    CS OOLB Lp k*    CS OOL`B Lp k* I  ' CS OPL# LH S NormalMapp   CS OOL L#    CS OOL* L0q*    CS OOLH L0q*   - CS OPL B L0q* S Lcl Translation1  * CS OPL`B L0q* S Lcl Rotationh  ) CS OPLB L0q* S Lcl Scaling  ( CS OPL0C L0q* S + Visibility   CS OOLҍ* L@q*    CS OOL{ L@q*    CS OOLД* LPr* :   CS OOLp LPr* a   CS OOLЪ* L`r*    CS OOLp L`r*    CS OOLЬ* Lpr*    CS OOLp Lpr*    CS OOLИ* L#r* $   CS OOLp L#r* K   CS OOLТ* L,r* r   CS OOLp L,r*    CS OOLp_ L5r*    CS OOLȱ L>r*    CS OOLލ* LGr*    CS OOLD LGr* 5   CS OOLPL LGr* \   CS OOL0N LGr*    CS OOLP LGr*    CS OOLč* L *    CS OOLD L *    CS OOLPL L *    CS OOL0N L * F   CS OOLP L * m   CS OOLМ* L0*    CS OOLD L0*    CS OOLPL L0*    CS OOL0N L0*    CS OOLP L0* 0   CS OOLЍ* L@* W   CS OOLD L@* ~   CS OOLPL L@*    CS OOL0N L@*    CS OOLP L@*    CS OOLм* LP*    CS OOLD LP* A   CS OOLPL LP* h   CS OOL0N LP*    CS OOLP LP*    CS OOL` Lv*    CS OOL0 Lv*    CS OOL΍* L`* +   CS OOL` L`* R   CS OOL* Lp* y   CS OOLQ Lp*    CS OOL$ L"*   * CS OPL` L0C S d|Visibility  ! CS OPL` L B S d|X6  ! CS OPL ` L B S d|Ye  ! CS OPL` L B S d|Z  ! CS OPL` LB S d|X  ! CS OPL ` LB S d|Y  ! CS OPL~` LB S d|Z!  ! CS OPL` L`B S d|XP  ! CS OPL` L`B S d|Y  ! CS OPL`` L`B S d|Z q Takes  CurrentS Take 001d  TakeS Take 001   FileNameS Take_001.tak+   LocalTimeLR^r Lp65 W   ReferenceTimeLR^r Lp65 e"s  Zj~ u) \ No newline at end of file diff --git a/test/models/M3D/WusonBlitz0.m3d b/test/models/M3D/WusonBlitz0.m3d new file mode 100644 index 000000000..29a397b79 Binary files /dev/null and b/test/models/M3D/WusonBlitz0.m3d differ diff --git a/test/models/M3D/WusonBlitz1.m3d b/test/models/M3D/WusonBlitz1.m3d new file mode 100644 index 000000000..366125257 Binary files /dev/null and b/test/models/M3D/WusonBlitz1.m3d differ diff --git a/test/models/M3D/WusonBlitz2.m3d b/test/models/M3D/WusonBlitz2.m3d new file mode 100644 index 000000000..682876816 Binary files /dev/null and b/test/models/M3D/WusonBlitz2.m3d differ diff --git a/test/models/M3D/cube_normals.m3d b/test/models/M3D/cube_normals.m3d new file mode 100644 index 000000000..ec4e4ae99 Binary files /dev/null and b/test/models/M3D/cube_normals.m3d differ diff --git a/test/models/M3D/cube_usemtl.m3d b/test/models/M3D/cube_usemtl.m3d new file mode 100644 index 000000000..e3f0b79e5 Binary files /dev/null and b/test/models/M3D/cube_usemtl.m3d differ diff --git a/test/models/M3D/cube_with_vertexcolors.a3d b/test/models/M3D/cube_with_vertexcolors.a3d new file mode 100644 index 000000000..55fe5a353 --- /dev/null +++ b/test/models/M3D/cube_with_vertexcolors.a3d @@ -0,0 +1,33 @@ +3dmodel 1 +cube_with_vertexcolors.obj +MIT +bzt +comment + +Vertex +0 0 0 1 #ff786d7b +0 0 -1 1 +1 1 0 1 #ff320a4d +1 0 0 1 #ff19c718 +0 1 0 1 #ff2c0004 +-1 0 0 1 +0 1 1 1 #ff0a00df +0 0 1 1 #ff790018 +1 1 1 1 #ffc70017 +1 0 1 1 #ff380a7b +0 -1 0 1 + +Mesh +0//1 2//1 3//1 +0//1 4//1 2//1 +0//5 6//5 4//5 +0//5 7//5 6//5 +4//4 8//4 2//4 +4//4 6//4 8//4 +3//3 2//3 8//3 +3//3 8//3 9//3 +0//10 3//10 9//10 +0//10 9//10 7//10 +7//7 9//7 8//7 +7//7 8//7 6//7 + diff --git a/test/models/M3D/cube_with_vertexcolors.m3d b/test/models/M3D/cube_with_vertexcolors.m3d new file mode 100644 index 000000000..598de4ee9 Binary files /dev/null and b/test/models/M3D/cube_with_vertexcolors.m3d differ diff --git a/test/models/M3D/suzanne.m3d b/test/models/M3D/suzanne.m3d new file mode 100644 index 000000000..9bc64d7d7 Binary files /dev/null and b/test/models/M3D/suzanne.m3d differ diff --git a/test/models/PLY/cube_test.ply b/test/models/PLY/cube_test.ply deleted file mode 100644 index f2ac612e8..000000000 --- a/test/models/PLY/cube_test.ply +++ /dev/null @@ -1,24 +0,0 @@ -ply -format ascii 1.0 -comment Created by Open Asset Import Library - http://assimp.sf.net (v4.1.993695325) -element vertex 8 -property float x -property float y -property float z -element face 6 -property list uchar int vertex_index -end_header -0 0 0 -0 0 1 -0 1 1 -0 1 0 -1 0 0 -1 0 1 -1 1 1 -1 1 0 -4 0 1 2 3 -4 7 6 5 4 -4 0 4 5 1 -4 1 5 6 2 -4 2 6 7 3 -4 3 7 4 0 diff --git a/test/models/glTF/IncorrectVertexArrays/Cube.bin b/test/models/glTF/IncorrectVertexArrays/Cube.bin new file mode 100644 index 000000000..fa55b363a Binary files /dev/null and b/test/models/glTF/IncorrectVertexArrays/Cube.bin differ diff --git a/test/models/glTF/IncorrectVertexArrays/Cube_v1.gltf b/test/models/glTF/IncorrectVertexArrays/Cube_v1.gltf new file mode 100644 index 000000000..c076c5bcb --- /dev/null +++ b/test/models/glTF/IncorrectVertexArrays/Cube_v1.gltf @@ -0,0 +1,283 @@ +{ + "accessors" : { + "accessor_0" : { + "bufferView" : "bufferView_0", + "byteOffset" : 0, + "componentType" : 5123, + "count" : 36, + "max" : [ + 35 + ], + "min" : [ + 0 + ], + "type" : "SCALAR" + }, + "accessor_1" : { + "bufferView" : "bufferView_1", + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + 1.000000, + 1.000001 + ], + "min" : [ + -1.000000, + -1.000000, + -1.000000 + ], + "type" : "VEC3" + }, + "accessor_2" : { + "bufferView" : "bufferView_2", + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + 1.000000, + 1.000000 + ], + "min" : [ + -1.000000, + -1.000000, + -1.000000 + ], + "type" : "VEC3" + }, + "accessor_3" : { + "bufferView" : "bufferView_3", + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + -0.000000, + -0.000000, + 1.000000 + ], + "min" : [ + 0.000000, + -0.000000, + -1.000000, + -1.000000 + ], + "type" : "VEC4" + }, + "accessor_4" : { + "bufferView" : "bufferView_4", + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + 1.000000 + ], + "min" : [ + -1.000000, + -1.000000 + ], + "type" : "VEC2" + }, + "accessor_5" : { + "bufferView" : "bufferView_1", + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "type" : "VEC3" + }, + "accessor_6" : { + "bufferView" : "bufferView_1", + "byteOffset" : 0, + "componentType" : 5126, + "count" : 35, + "type" : "VEC3" + }, + "accessor_7" : { + "bufferView" : "bufferView_0", + "byteOffset" : 0, + "componentType" : 5123, + "count" : 35, + "max" : [ + 35 + ], + "min" : [ + 0 + ], + "type" : "SCALAR" + } + }, + "asset" : { + "generator" : "VKTS glTF 2.0 exporter", + "version" : "1.0" + }, + "bufferViews" : { + "bufferView_0" : { + "buffer" : "buffer_0", + "byteLength" : 72, + "byteOffset" : 0, + "target" : 34963 + }, + "bufferView_1" : { + "buffer" : "buffer_0", + "byteLength" : 432, + "byteOffset" : 72, + "target" : 34962 + }, + "bufferView_2" : { + "buffer" : "buffer_0", + "byteLength" : 432, + "byteOffset" : 504, + "target" : 34962 + }, + "bufferView_3" : { + "buffer" : "buffer_0", + "byteLength" : 576, + "byteOffset" : 936, + "target" : 34962 + }, + "bufferView_4" : { + "buffer" : "buffer_0", + "byteLength" : 288, + "byteOffset" : 1512, + "target" : 34962 + } + }, + "buffers" : { + "buffer_0" : { + "byteLength" : 514, + "uri" : "Cube.bin" + } + }, + "meshes" : { + "mesh_0" : { + "name" : "Cube", + "primitives" : [ + { + "attributes" : { + "POSITION" : "accessor_1" + }, + "mode" : 4 + } + ] + }, + "mesh_1" : { + "name" : "TruncatedCube", + "primitives" : [ { + "attributes" : { + "POSITION" : "accessor_6" + }, + "mode" : 4 + } ] + }, + "mesh_2" : { + "name" : "Lines", + "primitives" : [ { + "attributes" : { + "POSITION" : "accessor_5" + }, + "mode" : 1 + } ] + }, + "mesh_3" : { + "name" : "TruncatedLines", + "primitives" : [ { + "attributes" : { + "POSITION" : "accessor_6" + }, + "mode" : 1 + } ] + }, + "mesh_4" : { + "name" : "IndexedCube", + "primitives" : [ { + "attributes" : { + "POSITION" : "accessor_1" + }, + "mode" : 4, + "indices" : "accessor_0" + } ] + }, + "mesh_5" : { + "name" : "TruncatedIndexedCube", + "primitives" : [ { + "attributes" : { + "POSITION" : "accessor_6" + }, + "mode" : 4, + "indices" : "accessor_7" + } ] + }, + "mesh_6" : { + "name" : "IndexedLines", + "primitives" : [ { + "attributes" : { + "POSITION" : "accessor_5" + }, + "mode" : 1, + "indices" : "accessor_0" + } ] + }, + "mesh_7" : { + "name" : "TruncatedIndexedLines", + "primitives" : [ { + "attributes" : { + "POSITION" : "accessor_6" + }, + "mode" : 1, + "indices" : "accessor_7" + } ] + } + }, + "nodes" : { + "node_0" : { + "meshes" : [ "mesh_0" ], + "name" : "Cube" + }, + "node_1" : { + "meshes" : [ "mesh_1" ], + "name" : "TruncatedCube", + "translation": [ 2.5, 0.0, 2.5 ] + }, + "node_2" : { + "meshes" : [ "mesh_2" ], + "name" : "Lines", + "translation": [ 2.5, 0.0, 0.0 ] + }, + "node_3" : { + "meshes" : [ "mesh_3" ], + "name" : "TruncatedLines", + "translation": [ 2.5, 0.0, -2.5 ] + }, + "node_4" : { + "meshes" : [ "mesh_4" ], + "name" : "IndexedCube", + "translation": [ -2.5, 0.0, 2.5 ] + }, + "node_5" : { + "meshes" : [ "mesh_5" ], + "name" : "TruncatedIndexedCube", + "translation": [ -2.5, 0.0, 0.0 ] + }, + "node_6" : { + "meshes" : [ "mesh_6" ], + "name" : "IndexedLines", + "translation": [ -2.5, 0.0, -2.5 ] + }, + "node_7" : { + "meshes" : [ "mesh_7" ], + "name" : "TruncatedIndexedLines", + "translation": [ 0.0, 0.0, -2.5 ] + } + }, + "scene" : "defaultScene", + "scenes" : { + "defaultScene" : { + "nodes" : [ + "node_0", "node_1", "node_2", "node_3", "node_4", "node_5", "node_6", "node_7" + ] + } + } +} diff --git a/test/models/glTF2/IncorrectVertexArrays/Cube.bin b/test/models/glTF2/IncorrectVertexArrays/Cube.bin new file mode 100644 index 000000000..fa55b363a Binary files /dev/null and b/test/models/glTF2/IncorrectVertexArrays/Cube.bin differ diff --git a/test/models/glTF2/IncorrectVertexArrays/Cube.gltf b/test/models/glTF2/IncorrectVertexArrays/Cube.gltf new file mode 100644 index 000000000..0ca17e1e5 --- /dev/null +++ b/test/models/glTF2/IncorrectVertexArrays/Cube.gltf @@ -0,0 +1,286 @@ +{ + "accessors" : [ + { + "bufferView" : 0, + "byteOffset" : 0, + "componentType" : 5123, + "count" : 36, + "max" : [ + 35 + ], + "min" : [ + 0 + ], + "type" : "SCALAR" + }, + { + "bufferView" : 1, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + 1.000000, + 1.000001 + ], + "min" : [ + -1.000000, + -1.000000, + -1.000000 + ], + "type" : "VEC3" + }, + { + "bufferView" : 2, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + 1.000000, + 1.000000 + ], + "min" : [ + -1.000000, + -1.000000, + -1.000000 + ], + "type" : "VEC3" + }, + { + "bufferView" : 3, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + -0.000000, + -0.000000, + 1.000000 + ], + "min" : [ + 0.000000, + -0.000000, + -1.000000, + -1.000000 + ], + "type" : "VEC4" + }, + { + "bufferView" : 4, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + 1.000000 + ], + "min" : [ + -1.000000, + -1.000000 + ], + "type" : "VEC2" + }, + { + "bufferView" : 1, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "type" : "VEC3" + }, + { + "bufferView" : 1, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 35, + "type" : "VEC3" + }, + { + "bufferView" : 0, + "byteOffset" : 0, + "componentType" : 5123, + "count" : 35, + "max" : [ + 35 + ], + "min" : [ + 0 + ], + "type" : "SCALAR" + } + ], + "asset" : { + "generator" : "VKTS glTF 2.0 exporter", + "version" : "2.0" + }, + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 72, + "byteOffset" : 0, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 432, + "byteOffset" : 72, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 432, + "byteOffset" : 504, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 576, + "byteOffset" : 936, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 288, + "byteOffset" : 1512, + "target" : 34962 + } + ], + "buffers" : [ + { + "byteLength" : 514, + "uri" : "Cube.bin" + } + ], + "meshes" : [ + { + "name" : "Cube", + "primitives" : [ + { + "attributes" : { + "POSITION" : 1 + }, + "mode" : 4 + } + ] + }, + { + "name" : "TruncatedCube", + "primitives" : [ { + "attributes" : { + "POSITION" : 6 + }, + "mode" : 4 + } ] + }, + { + "name" : "Lines", + "primitives" : [ { + "attributes" : { + "POSITION" : 5 + }, + "mode" : 1 + } ] + }, + { + "name" : "TruncatedLines", + "primitives" : [ { + "attributes" : { + "POSITION" : 6 + }, + "mode" : 1 + } ] + }, + { + "name" : "IndexedCube", + "primitives" : [ { + "attributes" : { + "POSITION" : 1 + }, + "mode" : 4, + "indices" : 0 + } ] + }, + { + "name" : "TruncatedIndexedCube", + "primitives" : [ { + "attributes" : { + "POSITION" : 6 + }, + "mode" : 4, + "indices" : 7 + } ] + }, + { + "name" : "IndexedLines", + "primitives" : [ { + "attributes" : { + "POSITION" : 5 + }, + "mode" : 1, + "indices" : 0 + } ] + }, + { + "name" : "TruncatedIndexedLines", + "primitives" : [ { + "attributes" : { + "POSITION" : 6 + }, + "mode" : 1, + "indices" : 7 + } ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "Cube" + }, + { + "mesh" : 1, + "name" : "TruncatedCube", + "translation": [ 2.5, 0.0, 2.5 ] + }, + { + "mesh" : 2, + "name" : "Lines", + "translation": [ 2.5, 0.0, 0.0 ] + }, + { + "mesh" : 3, + "name" : "TruncatedLines", + "translation": [ 2.5, 0.0, -2.5 ] + }, + { + "mesh" : 4, + "name" : "IndexedCube", + "translation": [ -2.5, 0.0, 2.5 ] + }, + { + "mesh" : 5, + "name" : "TruncatedIndexedCube", + "translation": [ -2.5, 0.0, 0.0 ] + }, + { + "mesh" : 6, + "name" : "IndexedLines", + "translation": [ -2.5, 0.0, -2.5 ] + }, + { + "mesh" : 7, + "name" : "TruncatedIndexedLines", + "translation": [ 0.0, 0.0, -2.5 ] + } + ], + "samplers" : [ + {} + ], + "scene" : 0, + "scenes" : [ + { + "nodes" : [ + 0, 1, 2, 3, 4, 5, 6, 7 + ] + } + ] +} diff --git a/test/models/glTF2/cameras/Cameras.gltf b/test/models/glTF2/cameras/Cameras.gltf new file mode 100644 index 000000000..25e9e24a3 --- /dev/null +++ b/test/models/glTF2/cameras/Cameras.gltf @@ -0,0 +1,98 @@ +{ + "scenes" : [ + { + "nodes" : [ 0, 1, 2 ] + } + ], + "nodes" : [ + { + "rotation" : [ -0.383, 0.0, 0.0, 0.92375 ], + "mesh" : 0 + }, + { + "translation" : [ 0.5, 0.5, 3.0 ], + "camera" : 0 + }, + { + "translation" : [ 0.5, 0.5, 3.0 ], + "camera" : 1 + } + ], + + "cameras" : [ + { + "type": "perspective", + "perspective": { + "aspectRatio": 1.0, + "yfov": 0.7, + "zfar": 100, + "znear": 0.01 + } + }, + { + "type": "orthographic", + "orthographic": { + "xmag": 1.0, + "ymag": 1.0, + "zfar": 100, + "znear": 0.01 + } + } + ], + + "meshes" : [ + { + "primitives" : [ { + "attributes" : { + "POSITION" : 1 + }, + "indices" : 0 + } ] + } + ], + + "buffers" : [ + { + "uri" : "simpleSquare.bin", + "byteLength" : 60 + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteOffset" : 0, + "byteLength" : 12, + "target" : 34963 + }, + { + "buffer" : 0, + "byteOffset" : 12, + "byteLength" : 48, + "target" : 34962 + } + ], + "accessors" : [ + { + "bufferView" : 0, + "byteOffset" : 0, + "componentType" : 5123, + "count" : 6, + "type" : "SCALAR", + "max" : [ 3 ], + "min" : [ 0 ] + }, + { + "bufferView" : 1, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 4, + "type" : "VEC3", + "max" : [ 1.0, 1.0, 0.0 ], + "min" : [ 0.0, 0.0, 0.0 ] + } + ], + + "asset" : { + "version" : "2.0" + } +} diff --git a/test/models/glTF2/cameras/simpleSquare.bin b/test/models/glTF2/cameras/simpleSquare.bin new file mode 100644 index 000000000..a6edb3b0d Binary files /dev/null and b/test/models/glTF2/cameras/simpleSquare.bin differ diff --git a/test/models/glTF2/glTF-Sample-Models/AnimatedMorphCube-glTF/AnimatedMorphCube.bin b/test/models/glTF2/glTF-Sample-Models/AnimatedMorphCube-glTF/AnimatedMorphCube.bin new file mode 100644 index 000000000..7b14a1793 Binary files /dev/null and b/test/models/glTF2/glTF-Sample-Models/AnimatedMorphCube-glTF/AnimatedMorphCube.bin differ diff --git a/test/models/glTF2/glTF-Sample-Models/AnimatedMorphCube-glTF/AnimatedMorphCube.gltf b/test/models/glTF2/glTF-Sample-Models/AnimatedMorphCube-glTF/AnimatedMorphCube.gltf new file mode 100644 index 000000000..b1b720147 --- /dev/null +++ b/test/models/glTF2/glTF-Sample-Models/AnimatedMorphCube-glTF/AnimatedMorphCube.gltf @@ -0,0 +1,282 @@ +{ + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 24, + "type": "VEC4" + }, + { + "bufferView": 2, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "max": [ + 0.0100000035, + 0.0100000035, + 0.01 + ], + "min": [ + -0.0100000044, + -0.0100000054, + -0.01 + ] + }, + { + "bufferView": 3, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "name": "thin" + }, + { + "bufferView": 4, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "max": [ + 0.0, + 0.01893253, + 0.0 + ], + "min": [ + 0.0, + 0.0, + 0.0 + ], + "name": "thin" + }, + { + "bufferView": 5, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "name": "thin" + }, + { + "bufferView": 6, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "name": "angle" + }, + { + "bufferView": 7, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "max": [ + 0.0, + 0.0198908355, + 0.0 + ], + "min": [ + 0.0, + 0.0, + 0.0 + ], + "name": "angle" + }, + { + "bufferView": 8, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "name": "angle" + }, + { + "bufferView": 9, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 10, + "componentType": 5126, + "count": 127, + "type": "SCALAR", + "max": [ + 4.19999743 + ], + "min": [ + 0.0 + ] + }, + { + "bufferView": 11, + "componentType": 5126, + "count": 254, + "type": "SCALAR" + } + ], + "animations": [ + { + "channels": [ + { + "sampler": 0, + "target": { + "node": 0, + "path": "weights" + } + } + ], + "samplers": [ + { + "input": 10, + "interpolation": "LINEAR", + "output": 11 + } + ], + "name": "Square" + } + ], + "asset": { + "generator": "glTF Tools for Unity", + "version": "2.0" + }, + "bufferViews": [ + { + "buffer": 0, + "byteLength": 288 + }, + { + "buffer": 0, + "byteOffset": 288, + "byteLength": 384 + }, + { + "buffer": 0, + "byteOffset": 672, + "byteLength": 288 + }, + { + "buffer": 0, + "byteOffset": 960, + "byteLength": 288 + }, + { + "buffer": 0, + "byteOffset": 1248, + "byteLength": 288 + }, + { + "buffer": 0, + "byteOffset": 1536, + "byteLength": 288 + }, + { + "buffer": 0, + "byteOffset": 1824, + "byteLength": 288 + }, + { + "buffer": 0, + "byteOffset": 2112, + "byteLength": 288 + }, + { + "buffer": 0, + "byteOffset": 2400, + "byteLength": 288 + }, + { + "buffer": 0, + "byteOffset": 2688, + "byteLength": 72 + }, + { + "buffer": 0, + "byteOffset": 2760, + "byteLength": 508 + }, + { + "buffer": 0, + "byteOffset": 3268, + "byteLength": 1016 + } + ], + "buffers": [ + { + "uri": "AnimatedMorphCube.bin", + "byteLength": 4284 + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 0, + "TANGENT": 1, + "POSITION": 2 + }, + "indices": 9, + "material": 0, + "targets": [ + { + "NORMAL": 3, + "POSITION": 4, + "TANGENT": 5 + }, + { + "NORMAL": 6, + "POSITION": 7, + "TANGENT": 8 + } + ] + } + ], + "weights": [ + 0.0, + 0.0 + ], + "name": "Cube" + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.6038274, + 0.6038274, + 0.6038274, + 1.0 + ], + "metallicFactor": 0.0, + "roughnessFactor": 0.5 + }, + "name": "Material" + } + ], + "nodes": [ + { + "mesh": 0, + "rotation": [ + 0.0, + 0.7071067, + -0.7071068, + 0.0 + ], + "scale": [ + 100.0, + 100.0, + 100.0 + ], + "name": "AnimatedMorphCube" + } + ], + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ] +} \ No newline at end of file diff --git a/test/models/glTF2/textureTransform/Arrow.png b/test/models/glTF2/textureTransform/Arrow.png new file mode 100644 index 000000000..fe3405b3f Binary files /dev/null and b/test/models/glTF2/textureTransform/Arrow.png differ diff --git a/test/models/glTF2/textureTransform/Correct.png b/test/models/glTF2/textureTransform/Correct.png new file mode 100644 index 000000000..e332824fb Binary files /dev/null and b/test/models/glTF2/textureTransform/Correct.png differ diff --git a/test/models/glTF2/textureTransform/Error.png b/test/models/glTF2/textureTransform/Error.png new file mode 100644 index 000000000..35acf6e94 Binary files /dev/null and b/test/models/glTF2/textureTransform/Error.png differ diff --git a/test/models/glTF2/textureTransform/License.txt b/test/models/glTF2/textureTransform/License.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test/models/glTF2/textureTransform/NotSupported.png b/test/models/glTF2/textureTransform/NotSupported.png new file mode 100644 index 000000000..721e7fd76 Binary files /dev/null and b/test/models/glTF2/textureTransform/NotSupported.png differ diff --git a/test/models/glTF2/textureTransform/TextureTransformTest.bin b/test/models/glTF2/textureTransform/TextureTransformTest.bin new file mode 100644 index 000000000..6765a1300 Binary files /dev/null and b/test/models/glTF2/textureTransform/TextureTransformTest.bin differ diff --git a/test/models/glTF2/textureTransform/TextureTransformTest.gltf b/test/models/glTF2/textureTransform/TextureTransformTest.gltf new file mode 100644 index 000000000..6dde51d9a --- /dev/null +++ b/test/models/glTF2/textureTransform/TextureTransformTest.gltf @@ -0,0 +1,540 @@ +{ + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "count": 4, + "type": "VEC3", + "max": [ + 0.5, + 0.5, + 0.0 + ], + "min": [ + -0.5, + -0.5, + 0.0 + ], + "name": "Positions" + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 4, + "type": "VEC2", + "name": "UV0" + }, + { + "bufferView": 2, + "componentType": 5126, + "count": 4, + "type": "VEC2", + "name": "UV1" + }, + { + "bufferView": 3, + "componentType": 5125, + "count": 6, + "type": "SCALAR", + "name": "Indices" + } + ], + "asset": { + "version": "2.0" + }, + "buffers": [ + { + "uri": "TextureTransformTest.bin", + "byteLength": 136 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 48, + "name": "Positions" + }, + { + "buffer": 0, + "byteOffset": 48, + "byteLength": 32, + "name": "UV0" + }, + { + "buffer": 0, + "byteOffset": 80, + "byteLength": 32, + "name": "UV1" + }, + { + "buffer": 0, + "byteOffset": 112, + "byteLength": 24, + "name": "Indices" + } + ], + "extensionsUsed": [ + "KHR_texture_transform" + ], + "images": [ + { + "uri": "UV.png" + }, + { + "uri": "Arrow.png" + }, + { + "uri": "Correct.png" + }, + { + "uri": "NotSupported.png" + }, + { + "uri": "Error.png" + } + ], + "materials": [ + { + "name": "Offset U", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 0, + "extensions": { + "KHR_texture_transform": { + "offset": [ + 0.5, + 0.0 + ] + } + } + }, + "metallicFactor": 0 + } + }, + { + "name": "Offset V", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 0, + "extensions": { + "KHR_texture_transform": { + "offset": [ + 0.0, + 0.5 + ] + } + } + }, + "metallicFactor": 0 + } + }, + { + "name": "Offset UV", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 0, + "extensions": { + "KHR_texture_transform": { + "offset": [ + 0.5, + 0.5 + ] + } + } + }, + "metallicFactor": 0 + } + }, + { + "name": "Rotation", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 1, + "extensions": { + "KHR_texture_transform": { + "rotation": 0.39269908169872415480783042290994 + } + } + }, + "metallicFactor": 0 + } + }, + { + "name": "Scale", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 1, + "extensions": { + "KHR_texture_transform": { + "scale": [ + 1.5, + 1.5 + ] + } + } + }, + "metallicFactor": 0 + } + }, + { + "name": "All", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 1, + "extensions": { + "KHR_texture_transform": { + "offset": [ + -0.2, + -0.1 + ], + "rotation": 0.3, + "scale": [ + 1.5, + 1.5 + ] + } + } + }, + "metallicFactor": 0 + } + }, + { + "name": "Correct", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 2 + }, + "metallicFactor": 0 + } + }, + { + "name": "NotSupported", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 3 + }, + "metallicFactor": 0 + } + }, + { + "name": "Error", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 4 + }, + "metallicFactor": 0 + } + } + ], + "meshes": [ + { + "name": "Offset U", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 2 + }, + "indices": 3, + "material": 0 + } + ] + }, + { + "name": "Offset V", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 2 + }, + "indices": 3, + "material": 1 + } + ] + }, + { + "name": "Offset UV", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 2 + }, + "indices": 3, + "material": 2 + } + ] + }, + { + "name": "Rotation", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "indices": 3, + "material": 3 + } + ] + }, + { + "name": "Scale", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "indices": 3, + "material": 4 + } + ] + }, + { + "name": "All", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "indices": 3, + "material": 5 + } + ] + }, + { + "name": "Correct Marker", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "indices": 3, + "material": 6 + } + ] + }, + { + "name": "Not Supported Marker", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "indices": 3, + "material": 7 + } + ] + }, + { + "name": "Error Marker", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "indices": 3, + "material": 8 + } + ] + } + ], + "nodes": [ + { + "name": "Offset U", + "mesh": 0, + "translation": [ + -1.1, + 0.55, + 0 + ] + }, + { + "name": "Offset V", + "mesh": 1, + "translation": [ + 0, + 0.55, + 0 + ] + }, + { + "name": "Offset UV", + "mesh": 2, + "translation": [ + 1.1, + 0.55, + 0 + ] + }, + { + "name": "Rotation", + "mesh": 3, + "translation": [ + -1.1, + -0.55, + 0 + ], + "children": [ + 4, + 5, + 6 + ] + }, + { + "name": "Rotation - Correct", + "mesh": 6, + "translation": [ + -0.07904822439840125109869401756656, + -0.51626748576241543174100150833647, + 0.01 + ], + "scale": [ + 0.15, + 0.15, + 0.15 + ] + }, + { + "name": "Rotation - Not Supported", + "mesh": 7, + "translation": [ + 0.27781745930520227684092879831533, + -0.27781745930520227684092879831533, + 0.01 + ], + "scale": [ + 0.15, + 0.15, + 0.15 + ] + }, + { + "name": "Rotation - Error", + "mesh": 8, + "translation": [ + 0.51626748576241543174100150833647, + 0.07904822439840125109869401756656, + 0.01 + ], + "scale": [ + 0.15, + 0.15, + 0.15 + ] + }, + { + "name": "Scale", + "mesh": 4, + "translation": [ + 0, + -0.55, + 0 + ], + "children": [ + 8, + 9 + ] + }, + { + "name": "Scale - Correct", + "mesh": 6, + "translation": [ + 0.01854497287013485122728586554355, + -0.01854497287013485122728586554355, + 0.01 + ], + "scale": [ + 0.1, + 0.1, + 0.1 + ] + }, + { + "name": "Scale - Not Supported", + "mesh": 7, + "translation": [ + 0.27781745930520227684092879831533, + -0.27781745930520227684092879831533, + 0.01 + ], + "scale": [ + 0.15, + 0.15, + 0.15 + ] + }, + { + "name": "All", + "mesh": 5, + "translation": [ + 1.1, + -0.55, + 0 + ], + "children": [ + 11 + ] + }, + { + "name": "All - Correct", + "mesh": 6, + "translation": [ + -0.07, + -0.25, + 0.01 + ], + "scale": [ + 0.1, + 0.1, + 0.1 + ] + } + ], + "scene": 0, + "scenes": [ + { + "nodes": [ + 0, + 1, + 2, + 3, + 7, + 10 + ] + } + ], + "textures": [ + { + "source": 0, + "sampler": 0 + }, + { + "source": 1, + "sampler": 0 + }, + { + "source": 2 + }, + { + "source": 3 + }, + { + "source": 4 + } + ], + "samplers": [ + { + "wrapS": 33071, + "wrapT": 33071, + "magFilter": 9729, + "minFilter": 9729 + } + ] +} \ No newline at end of file diff --git a/test/models/glTF2/textureTransform/UV.png b/test/models/glTF2/textureTransform/UV.png new file mode 100644 index 000000000..c1a6d4d37 Binary files /dev/null and b/test/models/glTF2/textureTransform/UV.png differ diff --git a/test/unit/ImportExport/utAssjsonImportExport.cpp b/test/unit/ImportExport/utAssjsonImportExport.cpp index 20a2a9e24..82dbf8b57 100644 --- a/test/unit/ImportExport/utAssjsonImportExport.cpp +++ b/test/unit/ImportExport/utAssjsonImportExport.cpp @@ -57,7 +57,7 @@ public: const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/OBJ/spider.obj", aiProcess_ValidateDataStructure); Exporter exporter; - aiReturn res = exporter.Export(scene, "json", "./spider_test.json"); + aiReturn res = exporter.Export(scene, "assjson", "./spider_test.json"); return aiReturn_SUCCESS == res; } }; diff --git a/test/unit/ImportExport/utExporter.cpp b/test/unit/ImportExport/utExporter.cpp index c46a55b00..43e711cb3 100644 --- a/test/unit/ImportExport/utExporter.cpp +++ b/test/unit/ImportExport/utExporter.cpp @@ -71,3 +71,32 @@ TEST_F(ExporterTest, ProgressHandlerTest) { TestProgressHandler *ph(new TestProgressHandler); exporter.SetProgressHandler(ph); } + +// Make sure all the registered exporters have useful descriptions +TEST_F(ExporterTest, ExporterIdTest) { + Exporter exporter; + size_t exportFormatCount = exporter.GetExportFormatCount(); + EXPECT_NE(0u, exportFormatCount) << "No registered exporters"; + typedef std::map ExportIdMap; + ExportIdMap exporterMap; + for (size_t i = 0; i < exportFormatCount; ++i) + { + // Check that the exporter description exists and makes sense + const aiExportFormatDesc* desc = exporter.GetExportFormatDescription(i); + ASSERT_NE(nullptr, desc) << "Missing aiExportFormatDesc at index " << i; + EXPECT_NE(nullptr, desc->id) << "Null exporter ID at index " << i; + EXPECT_STRNE("", desc->id) << "Empty exporter ID at index " << i; + EXPECT_NE(nullptr, desc->description) << "Null exporter description at index " << i; + EXPECT_STRNE("", desc->description) << "Empty exporter description at index " << i; + EXPECT_NE(nullptr, desc->fileExtension) << "Null exporter file extension at index " << i; + EXPECT_STRNE("", desc->fileExtension) << "Empty exporter file extension at index " << i; + + // Check the ID is unique + std::string key(desc->id); + std::pair result = exporterMap.emplace(key, desc); + EXPECT_TRUE(result.second) << "Duplicate exported id: '" << key << "' " << desc->description << " *." << desc->fileExtension << " at index " << i; + } + + const aiExportFormatDesc* desc = exporter.GetExportFormatDescription(exportFormatCount); + EXPECT_EQ(nullptr, desc) << "More exporters than claimed"; +} diff --git a/test/unit/Main.cpp b/test/unit/Main.cpp index 5ba5c487d..333fd655d 100644 --- a/test/unit/Main.cpp +++ b/test/unit/Main.cpp @@ -16,7 +16,7 @@ int main(int argc, char* argv[]) // create a logger from both CPP Assimp::DefaultLogger::create("AssimpLog_Cpp.txt",Assimp::Logger::VERBOSE, - aiDefaultLogStream_DEBUGGER | aiDefaultLogStream_FILE); + aiDefaultLogStream_STDOUT | aiDefaultLogStream_DEBUGGER | aiDefaultLogStream_FILE); // .. and C. They should smoothly work together aiEnableVerboseLogging(AI_TRUE); diff --git a/test/unit/SceneDiffer.cpp b/test/unit/SceneDiffer.cpp index fb000db29..684d9fee6 100644 --- a/test/unit/SceneDiffer.cpp +++ b/test/unit/SceneDiffer.cpp @@ -123,7 +123,7 @@ void SceneDiffer::showReport() { return; } - for ( std::vector::iterator it = m_diffs.begin(); it != m_diffs.end(); it++ ) { + for ( std::vector::iterator it = m_diffs.begin(); it != m_diffs.end(); ++it ) { std::cout << *it << "\n"; } diff --git a/test/unit/TestModelFactory.h b/test/unit/TestModelFactory.h index f1ef1701f..0e48fb744 100644 --- a/test/unit/TestModelFactory.h +++ b/test/unit/TestModelFactory.h @@ -89,7 +89,7 @@ public: scene->mMeshes[ 0 ]->mFaces[ 0 ].mIndices[ 1 ] = 1; scene->mMeshes[ 0 ]->mFaces[ 0 ].mIndices[ 2 ] = 2; - scene->mRootNode = new aiNode; + scene->mRootNode = new aiNode(); scene->mRootNode->mNumMeshes = 1; scene->mRootNode->mMeshes = new unsigned int[1]{ 0 }; diff --git a/tools/assimp_qt_viewer/main.cpp b/test/unit/utArmaturePopulate.cpp similarity index 59% rename from tools/assimp_qt_viewer/main.cpp rename to test/unit/utArmaturePopulate.cpp index 6dfdd4981..8eb577d61 100644 --- a/tools/assimp_qt_viewer/main.cpp +++ b/test/unit/utArmaturePopulate.cpp @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2018, assimp team +Copyright (c) 2006-2019, assimp team @@ -40,19 +40,44 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ +#include "UnitTestPCH.h" +#include "TestModelFactory.h" -// Thanks to acorn89 for support. -// Header files, project. -#include "mainwindow.hpp" +#include "SceneDiffer.h" +#include "AbstractImportExportBase.h" -// Header files, Qt. -#include +#include +#include +#include +#include +#include -int main(int argc, char *argv[]) { - QApplication app(argc, argv); - MainWindow win; - win.show(); +#include "PostProcessing/ArmaturePopulate.h" - return app.exec(); +namespace Assimp { +namespace UnitTest { + +class utArmaturePopulate : public ::testing::Test { + // empty +}; + +TEST_F( utArmaturePopulate, importCheckForArmatureTest) { + Assimp::Importer importer; + unsigned int mask = aiProcess_PopulateArmatureData | aiProcess_ValidateDataStructure; + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/FBX/huesitos.fbx", mask); + EXPECT_NE( nullptr, scene ); + EXPECT_EQ(scene->mNumMeshes, 1u); + aiMesh* mesh = scene->mMeshes[0]; + EXPECT_EQ(mesh->mNumFaces, 68u); + EXPECT_EQ(mesh->mNumVertices, 256u); + EXPECT_GT(mesh->mNumBones, 0u); + + aiBone* exampleBone = mesh->mBones[0]; + EXPECT_NE(exampleBone, nullptr); + EXPECT_NE(exampleBone->mArmature, nullptr); + EXPECT_NE(exampleBone->mNode, nullptr); } + +} // Namespace UnitTest +} // Namespace Assimp diff --git a/test/unit/utAssbinImportExport.cpp b/test/unit/utAssbinImportExport.cpp index 011aaa649..42504df56 100644 --- a/test/unit/utAssbinImportExport.cpp +++ b/test/unit/utAssbinImportExport.cpp @@ -56,8 +56,8 @@ public: const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OBJ/spider.obj", aiProcess_ValidateDataStructure ); Exporter exporter; - EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "assbin", ASSIMP_TEST_MODELS_DIR "/OBJ/spider_test.assbin" ) ); - const aiScene *newScene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OBJ/spider_test.assbin", aiProcess_ValidateDataStructure ); + EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "assbin", ASSIMP_TEST_MODELS_DIR "/OBJ/spider_out.assbin" ) ); + const aiScene *newScene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OBJ/spider_out.assbin", aiProcess_ValidateDataStructure ); return newScene != nullptr; } diff --git a/test/unit/utBlenderWork.cpp b/test/unit/utBlenderWork.cpp index 65fefde28..c0e3347ab 100644 --- a/test/unit/utBlenderWork.cpp +++ b/test/unit/utBlenderWork.cpp @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -73,11 +71,6 @@ TEST_F(BlenderWorkTest,work_279) { ASSERT_TRUE(pTest->HasMaterials()); ASSERT_TRUE(pTest->HasMeshes()); ASSERT_TRUE(pTest->mMeshes[0]->mNumVertices > 0); - ASSERT_EQ(44, pTest->mMeshes[0]->mNumFaces); - EXPECT_EQ(1, pTest->mNumMaterials); + ASSERT_EQ(44u, pTest->mMeshes[0]->mNumFaces); + EXPECT_EQ(1u, pTest->mNumMaterials); } - - - - - diff --git a/test/unit/utColladaImportExport.cpp b/test/unit/utColladaImportExport.cpp index 78a74d973..8bce3a3dd 100644 --- a/test/unit/utColladaImportExport.cpp +++ b/test/unit/utColladaImportExport.cpp @@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "AbstractImportExportBase.h" #include +#include #include using namespace Assimp; @@ -52,11 +53,63 @@ class utColladaImportExport : public AbstractImportExportBase { public: virtual bool importerTest() { Assimp::Importer importer; - const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure ); - return nullptr != scene; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure); + if (scene == nullptr) + return false; + + // Expected number of items + EXPECT_EQ(scene->mNumMeshes, 1u); + EXPECT_EQ(scene->mNumMaterials, 1u); + EXPECT_EQ(scene->mNumAnimations, 0u); + EXPECT_EQ(scene->mNumTextures, 0u); + EXPECT_EQ(scene->mNumLights, 1u); + EXPECT_EQ(scene->mNumCameras, 1u); + + return true; } }; -TEST_F( utColladaImportExport, importBlenFromFileTest ) { - EXPECT_TRUE( importerTest() ); +TEST_F(utColladaImportExport, importBlenFromFileTest) { + EXPECT_TRUE(importerTest()); +} + +class utColladaZaeImportExport : public AbstractImportExportBase { +public: + virtual bool importerTest() { + { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.zae", aiProcess_ValidateDataStructure); + if (scene == nullptr) + return false; + + // Expected number of items + EXPECT_EQ(scene->mNumMeshes, 1u); + EXPECT_EQ(scene->mNumMaterials, 1u); + EXPECT_EQ(scene->mNumAnimations, 0u); + EXPECT_EQ(scene->mNumTextures, 1u); + EXPECT_EQ(scene->mNumLights, 1u); + EXPECT_EQ(scene->mNumCameras, 1u); + } + + { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck_nomanifest.zae", aiProcess_ValidateDataStructure); + if (scene == nullptr) + return false; + + // Expected number of items + EXPECT_EQ(scene->mNumMeshes, 1u); + EXPECT_EQ(scene->mNumMaterials, 1u); + EXPECT_EQ(scene->mNumAnimations, 0u); + EXPECT_EQ(scene->mNumTextures, 1u); + EXPECT_EQ(scene->mNumLights, 1u); + EXPECT_EQ(scene->mNumCameras, 1u); + } + + return true; + } +}; + +TEST_F(utColladaZaeImportExport, importBlenFromFileTest) { + EXPECT_TRUE(importerTest()); } diff --git a/test/unit/utFBXImporterExporter.cpp b/test/unit/utFBXImporterExporter.cpp index 3180d96a2..43dc40e88 100644 --- a/test/unit/utFBXImporterExporter.cpp +++ b/test/unit/utFBXImporterExporter.cpp @@ -70,12 +70,13 @@ TEST_F( utFBXImporterExporter, importBareBoxWithoutColorsAndTextureCoords ) { Assimp::Importer importer; const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/FBX/box.fbx", aiProcess_ValidateDataStructure ); EXPECT_NE( nullptr, scene ); - EXPECT_EQ(scene->mNumMeshes, 1); + EXPECT_EQ(scene->mNumMeshes, 1u); aiMesh* mesh = scene->mMeshes[0]; - EXPECT_EQ(mesh->mNumFaces, 12); - EXPECT_EQ(mesh->mNumVertices, 36); + EXPECT_EQ(mesh->mNumFaces, 12u); + EXPECT_EQ(mesh->mNumVertices, 36u); } + TEST_F(utFBXImporterExporter, importCubesWithNoNames) { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/cubes_nonames.fbx", aiProcess_ValidateDataStructure); @@ -85,27 +86,7 @@ TEST_F(utFBXImporterExporter, importCubesWithNoNames) { const auto root = scene->mRootNode; ASSERT_STREQ(root->mName.C_Str(), "RootNode"); ASSERT_TRUE(root->mChildren); - ASSERT_EQ(root->mNumChildren, 2); - - const auto child0 = root->mChildren[0]; - ASSERT_TRUE(child0); - ASSERT_STREQ(child0->mName.C_Str(), "RootNode001"); - ASSERT_TRUE(child0->mChildren); - ASSERT_EQ(child0->mNumChildren, 1); - - const auto child00 = child0->mChildren[0]; - ASSERT_TRUE(child00); - ASSERT_STREQ(child00->mName.C_Str(), "RootNode001001"); - - const auto child1 = root->mChildren[1]; - ASSERT_TRUE(child1); - ASSERT_STREQ(child1->mName.C_Str(), "RootNode002"); - ASSERT_TRUE(child1->mChildren); - ASSERT_EQ(child1->mNumChildren, 1); - - const auto child10 = child1->mChildren[0]; - ASSERT_TRUE(child10); - ASSERT_STREQ(child10->mName.C_Str(), "RootNode002001"); + ASSERT_EQ(root->mNumChildren, 2u); } TEST_F(utFBXImporterExporter, importCubesWithUnicodeDuplicatedNames) { @@ -117,13 +98,13 @@ TEST_F(utFBXImporterExporter, importCubesWithUnicodeDuplicatedNames) { const auto root = scene->mRootNode; ASSERT_STREQ(root->mName.C_Str(), "RootNode"); ASSERT_TRUE(root->mChildren); - ASSERT_EQ(root->mNumChildren, 2); + ASSERT_EQ(root->mNumChildren, 2u); const auto child0 = root->mChildren[0]; ASSERT_TRUE(child0); ASSERT_STREQ(child0->mName.C_Str(), "Cube2"); ASSERT_TRUE(child0->mChildren); - ASSERT_EQ(child0->mNumChildren, 1); + ASSERT_EQ(child0->mNumChildren, 1u); const auto child00 = child0->mChildren[0]; ASSERT_TRUE(child00); @@ -133,11 +114,11 @@ TEST_F(utFBXImporterExporter, importCubesWithUnicodeDuplicatedNames) { ASSERT_TRUE(child1); ASSERT_STREQ(child1->mName.C_Str(), "Cube3"); ASSERT_TRUE(child1->mChildren); - ASSERT_EQ(child1->mNumChildren, 1); + ASSERT_EQ(child1->mNumChildren, 1u); const auto child10 = child1->mChildren[0]; ASSERT_TRUE(child10); - ASSERT_STREQ(child10->mName.C_Str(), "\xd0\x9a\xd1\x83\xd0\xb1\x31""001"); + ASSERT_STREQ(child10->mName.C_Str(), "\xd0\x9a\xd1\x83\xd0\xb1\x31"); } TEST_F(utFBXImporterExporter, importCubesComplexTransform) { @@ -149,13 +130,13 @@ TEST_F(utFBXImporterExporter, importCubesComplexTransform) { const auto root = scene->mRootNode; ASSERT_STREQ(root->mName.C_Str(), "RootNode"); ASSERT_TRUE(root->mChildren); - ASSERT_EQ(root->mNumChildren, 2); + ASSERT_EQ(root->mNumChildren, 2u); const auto child0 = root->mChildren[0]; ASSERT_TRUE(child0); ASSERT_STREQ(child0->mName.C_Str(), "Cube2"); ASSERT_TRUE(child0->mChildren); - ASSERT_EQ(child0->mNumChildren, 1); + ASSERT_EQ(child0->mNumChildren, 1u); const auto child00 = child0->mChildren[0]; ASSERT_TRUE(child00); @@ -168,44 +149,53 @@ TEST_F(utFBXImporterExporter, importCubesComplexTransform) { auto parent = child1; const size_t chain_length = 8u; const char* chainStr[chain_length] = { - "Cube1001_$AssimpFbx$_Translation", - "Cube1001_$AssimpFbx$_RotationPivot", - "Cube1001_$AssimpFbx$_RotationPivotInverse", - "Cube1001_$AssimpFbx$_ScalingOffset", - "Cube1001_$AssimpFbx$_ScalingPivot", - "Cube1001_$AssimpFbx$_Scaling", - "Cube1001_$AssimpFbx$_ScalingPivotInverse", - "Cube1001" + "Cube1_$AssimpFbx$_Translation", + "Cube1_$AssimpFbx$_RotationPivot", + "Cube1_$AssimpFbx$_RotationPivotInverse", + "Cube1_$AssimpFbx$_ScalingOffset", + "Cube1_$AssimpFbx$_ScalingPivot", + "Cube1_$AssimpFbx$_Scaling", + "Cube1_$AssimpFbx$_ScalingPivotInverse", + "Cube1" }; - for (size_t i = 0; i < chain_length; ++i) - { + for (size_t i = 0; i < chain_length; ++i) { ASSERT_TRUE(parent->mChildren); - ASSERT_EQ(parent->mNumChildren, 1); + ASSERT_EQ(parent->mNumChildren, 1u); auto node = parent->mChildren[0]; ASSERT_TRUE(node); ASSERT_STREQ(node->mName.C_Str(), chainStr[i]); parent = node; } - ASSERT_EQ(0, parent->mNumChildren) << "Leaf node"; + ASSERT_EQ(0u, parent->mNumChildren) << "Leaf node"; +} + +TEST_F(utFBXImporterExporter, importCloseToIdentityTransforms) { + Assimp::Importer importer; + // This was asserting in FBXConverter.cpp because the transforms appeared to be the identity by one test, but not by another. + // This asset should now load successfully. + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/close_to_identity_transforms.fbx", aiProcess_ValidateDataStructure); + ASSERT_TRUE(scene); } TEST_F( utFBXImporterExporter, importPhongMaterial ) { Assimp::Importer importer; const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/FBX/phong_cube.fbx", aiProcess_ValidateDataStructure ); EXPECT_NE( nullptr, scene ); - EXPECT_EQ( (unsigned int)1, scene->mNumMaterials ); + EXPECT_EQ( 1u, scene->mNumMaterials ); const aiMaterial *mat = scene->mMaterials[0]; EXPECT_NE( nullptr, mat ); - float f; aiColor3D c; + float f; + aiColor3D c; + // phong_cube.fbx has all properties defined EXPECT_EQ( mat->Get(AI_MATKEY_COLOR_DIFFUSE, c), aiReturn_SUCCESS ); EXPECT_EQ( c, aiColor3D(0.5, 0.25, 0.25) ); EXPECT_EQ( mat->Get(AI_MATKEY_COLOR_SPECULAR, c), aiReturn_SUCCESS ); EXPECT_EQ( c, aiColor3D(0.25, 0.25, 0.5) ); EXPECT_EQ( mat->Get(AI_MATKEY_SHININESS_STRENGTH, f), aiReturn_SUCCESS ); - EXPECT_EQ( f, 0.5 ); + EXPECT_EQ( f, 0.5f ); EXPECT_EQ( mat->Get(AI_MATKEY_SHININESS, f), aiReturn_SUCCESS ); - EXPECT_EQ( f, 10.0 ); + EXPECT_EQ( f, 10.0f ); EXPECT_EQ( mat->Get(AI_MATKEY_COLOR_AMBIENT, c), aiReturn_SUCCESS ); EXPECT_EQ( c, aiColor3D(0.125, 0.25, 0.25) ); EXPECT_EQ( mat->Get(AI_MATKEY_COLOR_EMISSIVE, c), aiReturn_SUCCESS ); @@ -213,7 +203,7 @@ TEST_F( utFBXImporterExporter, importPhongMaterial ) { EXPECT_EQ( mat->Get(AI_MATKEY_COLOR_TRANSPARENT, c), aiReturn_SUCCESS ); EXPECT_EQ( c, aiColor3D(0.75, 0.5, 0.25) ); EXPECT_EQ( mat->Get(AI_MATKEY_OPACITY, f), aiReturn_SUCCESS ); - EXPECT_EQ( f, 0.5 ); + EXPECT_EQ( f, 0.5f ); } TEST_F(utFBXImporterExporter, importUnitScaleFactor) { @@ -234,7 +224,7 @@ TEST_F(utFBXImporterExporter, importEmbeddedAsciiTest) { const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/embedded_ascii/box.FBX", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(1, scene->mNumMaterials); + EXPECT_EQ(1u, scene->mNumMaterials); aiMaterial *mat = scene->mMaterials[0]; ASSERT_NE(nullptr, mat); @@ -243,7 +233,7 @@ TEST_F(utFBXImporterExporter, importEmbeddedAsciiTest) { EXPECT_EQ(aiReturn_SUCCESS, mat->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, nullptr, nullptr, modes)); ASSERT_STREQ(path.C_Str(), "..\\..\\..\\Desktop\\uv_test.png"); - ASSERT_EQ(1, scene->mNumTextures); + ASSERT_EQ(1u, scene->mNumTextures); ASSERT_TRUE(scene->mTextures[0]->pcData); ASSERT_EQ(439176u, scene->mTextures[0]->mWidth) << "FBX ASCII base64 compression splits data by 512Kb, it should be two parts for this texture"; } @@ -254,7 +244,7 @@ TEST_F(utFBXImporterExporter, importEmbeddedFragmentedAsciiTest) { const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/embedded_ascii/box_embedded_texture_fragmented.fbx", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(1, scene->mNumMaterials); + EXPECT_EQ(1u, scene->mNumMaterials); aiMaterial *mat = scene->mMaterials[0]; ASSERT_NE(nullptr, mat); @@ -263,7 +253,33 @@ TEST_F(utFBXImporterExporter, importEmbeddedFragmentedAsciiTest) { ASSERT_EQ(aiReturn_SUCCESS, mat->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, nullptr, nullptr, modes)); ASSERT_STREQ(path.C_Str(), "paper.png"); - ASSERT_EQ(1, scene->mNumTextures); + ASSERT_EQ(1u, scene->mNumTextures); ASSERT_TRUE(scene->mTextures[0]->pcData); ASSERT_EQ(968029u, scene->mTextures[0]->mWidth) << "FBX ASCII base64 compression splits data by 512Kb, it should be two parts for this texture"; } + +TEST_F(utFBXImporterExporter, fbxTokenizeTestTest) { + //Assimp::Importer importer; + //const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/transparentTest2.fbx", aiProcess_ValidateDataStructure); + //EXPECT_NE(nullptr, scene); +} + +TEST_F(utFBXImporterExporter, importOrphantEmbeddedTextureTest) { + // see https://github.com/assimp/assimp/issues/1957 + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/box_orphant_embedded_texture.fbx", aiProcess_ValidateDataStructure); + EXPECT_NE(nullptr, scene); + + EXPECT_EQ(1u, scene->mNumMaterials); + aiMaterial *mat = scene->mMaterials[0]; + ASSERT_NE(nullptr, mat); + + aiString path; + aiTextureMapMode modes[2]; + ASSERT_EQ(aiReturn_SUCCESS, mat->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, nullptr, nullptr, modes)); + ASSERT_STREQ(path.C_Str(), "..\\Primitives\\GridGrey.tga"); + + ASSERT_EQ(1u, scene->mNumTextures); + ASSERT_TRUE(scene->mTextures[0]->pcData); + ASSERT_EQ(9026u, scene->mTextures[0]->mWidth) << "FBX ASCII base64 compression used for a texture."; +} diff --git a/test/unit/utIssues.cpp b/test/unit/utIssues.cpp index 6a974d7b9..0e30fa182 100644 --- a/test/unit/utIssues.cpp +++ b/test/unit/utIssues.cpp @@ -74,7 +74,7 @@ TEST_F( utIssues, OpacityBugWhenExporting_727 ) { if ( newScene->mNumMaterials > 0 ) { std::cout << "Desc = " << desc->description << "\n"; EXPECT_EQ( AI_SUCCESS, newScene->mMaterials[ 0 ]->Get( AI_MATKEY_OPACITY, newOpacity ) ); - EXPECT_EQ( opacity, newOpacity ); + EXPECT_FLOAT_EQ( opacity, newOpacity ); } delete scene; } diff --git a/tools/assimp_qt_viewer/loggerview.cpp b/test/unit/utM3DImportExport.cpp similarity index 70% rename from tools/assimp_qt_viewer/loggerview.cpp rename to test/unit/utM3DImportExport.cpp index 2e0d15547..31028235d 100644 --- a/tools/assimp_qt_viewer/loggerview.cpp +++ b/test/unit/utM3DImportExport.cpp @@ -1,9 +1,9 @@ -/* +/* --------------------------------------------------------------------------- Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2018, assimp team +Copyright (c) 2006-2019, assimp team @@ -40,25 +40,29 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ -#include "loggerview.hpp" -// Header files, Qt. -#include -#include +#include "UnitTestPCH.h" +#include "SceneDiffer.h" +#include "AbstractImportExportBase.h" -CLoggerView::CLoggerView(QTextBrowser* pOutputWidget) -: mOutputWidget(pOutputWidget) { - // empty -} +#include +#include -CLoggerView::~CLoggerView() { - mOutputWidget = nullptr; -} +using namespace Assimp; -void CLoggerView::write(const char *pMessage) { - if (nullptr == mOutputWidget) { - return; +class utM3DImportExport : public AbstractImportExportBase { +public: + virtual bool importerTest() { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/M3D/cube_normals.m3d", aiProcess_ValidateDataStructure ); +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER + return nullptr != scene; +#else + return nullptr == scene; +#endif // ASSIMP_BUILD_NO_M3D_IMPORTER } +}; - mOutputWidget->insertPlainText(QString("[%1] %2").arg(QTime::currentTime().toString()).arg(pMessage)); +TEST_F( utM3DImportExport, importM3DFromFileTest ) { + EXPECT_TRUE( importerTest() ); } diff --git a/test/unit/utObjImportExport.cpp b/test/unit/utObjImportExport.cpp index bb0d36b13..b6cb72623 100644 --- a/test/unit/utObjImportExport.cpp +++ b/test/unit/utObjImportExport.cpp @@ -205,8 +205,8 @@ protected: ::Assimp::Exporter exporter; const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OBJ/spider.obj", aiProcess_ValidateDataStructure ); EXPECT_NE( nullptr, scene ); - EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "obj", ASSIMP_TEST_MODELS_DIR "/OBJ/spider_test.obj" ) ); - EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "objnomtl", ASSIMP_TEST_MODELS_DIR "/OBJ/spider_nomtl_test.obj" ) ); + EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "obj", ASSIMP_TEST_MODELS_DIR "/OBJ/spider_out.obj" ) ); + EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "objnomtl", ASSIMP_TEST_MODELS_DIR "/OBJ/spider_nomtl_out.obj" ) ); return true; } @@ -263,7 +263,7 @@ TEST_F( utObjImportExport, issue809_vertex_color_Test ) { #ifndef ASSIMP_BUILD_NO_EXPORT ::Assimp::Exporter exporter; - EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "obj", ASSIMP_TEST_MODELS_DIR "/OBJ/test.obj" ) ); + EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "obj", ASSIMP_TEST_MODELS_DIR "/OBJ/test_out.obj" ) ); #endif // ASSIMP_BUILD_NO_EXPORT } @@ -448,3 +448,37 @@ TEST_F(utObjImportExport, import_without_linend) { const aiScene *scene = myImporter.ReadFile(ASSIMP_TEST_MODELS_DIR "/OBJ/box_without_lineending.obj", 0); ASSERT_NE(nullptr, scene); } + +TEST_F(utObjImportExport, import_with_line_continuations) { + static const char *ObjModel = + "v -0.5 -0.5 0.5\n" + "v -0.5 \\\n" + " -0.5 -0.5\n" + "v -0.5 \\\n" + " 0.5 \\\n" + " -0.5\n" + "f 1 2 3\n"; + + Assimp::Importer myImporter; + const aiScene *scene = myImporter.ReadFileFromMemory(ObjModel, strlen(ObjModel), 0); + EXPECT_NE(nullptr, scene); + + EXPECT_EQ(scene->mNumMeshes, 1U); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 3U); + EXPECT_EQ(scene->mMeshes[0]->mNumFaces, 1U); + + auto vertices = scene->mMeshes[0]->mVertices; + const float threshold = 0.0001f; + + EXPECT_NEAR(vertices[0].x, -0.5f, threshold); + EXPECT_NEAR(vertices[0].y, -0.5f, threshold); + EXPECT_NEAR(vertices[0].z, 0.5f, threshold); + + EXPECT_NEAR(vertices[1].x, -0.5f, threshold); + EXPECT_NEAR(vertices[1].y, -0.5f, threshold); + EXPECT_NEAR(vertices[1].z, -0.5f, threshold); + + EXPECT_NEAR(vertices[2].x, -0.5f, threshold); + EXPECT_NEAR(vertices[2].y, 0.5f, threshold); + EXPECT_NEAR(vertices[2].z, -0.5f, threshold); +} diff --git a/test/unit/utPLYImportExport.cpp b/test/unit/utPLYImportExport.cpp index bb8ba64d9..17d1b28de 100644 --- a/test/unit/utPLYImportExport.cpp +++ b/test/unit/utPLYImportExport.cpp @@ -71,7 +71,7 @@ public: Exporter exporter; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/PLY/cube.ply", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "ply", ASSIMP_TEST_MODELS_DIR "/PLY/cube_test.ply")); + EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "ply", ASSIMP_TEST_MODELS_DIR "/PLY/cube_out.ply")); return true; } diff --git a/test/unit/utScaleProcess.cpp b/test/unit/utScaleProcess.cpp index fe87daa8d..fd2773c24 100644 --- a/test/unit/utScaleProcess.cpp +++ b/test/unit/utScaleProcess.cpp @@ -69,18 +69,5 @@ TEST_F( utScaleProcess, accessScaleTest ) { EXPECT_FLOAT_EQ( 2.0f, process.getScale() ); } -TEST_F( utScaleProcess, rescaleModelTest ) { - float opacity; - aiScene *testScene = TestModelFacttory::createDefaultTestModel( opacity ); - ai_real v1 = testScene->mRootNode->mTransformation.a1; - ScaleProcess process; - process.setScale( 10.0f ); - process.Execute( testScene ); - ai_real v2 = testScene->mRootNode->mTransformation.a1; - const ai_real scale = v2 / v1; - EXPECT_FLOAT_EQ( scale, 10.0f ); - TestModelFacttory::releaseDefaultTestModel( &testScene ); -} - } // Namespace UnitTest } // Namespace Assimp diff --git a/test/unit/utSortByPType.cpp b/test/unit/utSortByPType.cpp index 749e618da..fb637b004 100644 --- a/test/unit/utSortByPType.cpp +++ b/test/unit/utSortByPType.cpp @@ -183,8 +183,7 @@ TEST_F(SortByPTypeProcessTest, SortByPTypeStep) { unsigned int idx = 0; for (unsigned int m = 0,real = 0; m< 10;++m) { for (unsigned int n = 0; n < 4;++n) { - if ((idx = num[m][n])) - { + if ((idx = num[m][n])) { EXPECT_TRUE(real < mScene->mNumMeshes); aiMesh* mesh = mScene->mMeshes[real]; diff --git a/test/unit/utVersion.cpp b/test/unit/utVersion.cpp index 5cfc91ccd..66e832baa 100644 --- a/test/unit/utVersion.cpp +++ b/test/unit/utVersion.cpp @@ -4,8 +4,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -50,16 +48,16 @@ TEST_F( utVersion, aiGetLegalStringTest ) { EXPECT_NE( lv, nullptr ); std::string text( lv ); - size_t pos( text.find( std::string( "2017" ) ) ); + size_t pos( text.find( std::string( "2019" ) ) ); EXPECT_NE( pos, std::string::npos ); } TEST_F( utVersion, aiGetVersionMinorTest ) { - EXPECT_EQ( aiGetVersionMinor(), 1U ); + EXPECT_EQ( aiGetVersionMinor(), 0U ); } TEST_F( utVersion, aiGetVersionMajorTest ) { - EXPECT_EQ( aiGetVersionMajor(), 4U ); + EXPECT_EQ( aiGetVersionMajor(), 5U ); } TEST_F( utVersion, aiGetCompileFlagsTest ) { diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index bac76473d..0df692f0b 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -55,17 +53,23 @@ class utglTF2ImportExport : public AbstractImportExportBase { public: virtual bool importerTest() { Assimp::Importer importer; - const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", aiProcess_ValidateDataStructure); + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", + aiProcess_ValidateDataStructure); EXPECT_NE( scene, nullptr ); - if ( !scene ) return false; + if (!scene) { + return false; + } EXPECT_TRUE( scene->HasMaterials() ); - if ( !scene->HasMaterials() ) return false; + if (!scene->HasMaterials()) { + return false; + } const aiMaterial *material = scene->mMaterials[0]; aiString path; aiTextureMapMode modes[2]; - EXPECT_EQ( aiReturn_SUCCESS, material->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, nullptr, nullptr, modes) ); + EXPECT_EQ( aiReturn_SUCCESS, material->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, + nullptr, nullptr, modes) ); EXPECT_STREQ( path.C_Str(), "CesiumLogoFlat.png" ); EXPECT_EQ( modes[0], aiTextureMapMode_Mirror ); EXPECT_EQ( modes[1], aiTextureMapMode_Clamp ); @@ -75,7 +79,8 @@ public: virtual bool binaryImporterTest() { Assimp::Importer importer; - const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/glTF2/2CylinderEngine-glTF-Binary/2CylinderEngine.glb", aiProcess_ValidateDataStructure); + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/glTF2/2CylinderEngine-glTF-Binary/2CylinderEngine.glb", + aiProcess_ValidateDataStructure); return nullptr != scene; } @@ -83,7 +88,8 @@ public: virtual bool exporterTest() { Assimp::Importer importer; Assimp::Exporter exporter; - const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", aiProcess_ValidateDataStructure ); + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", + aiProcess_ValidateDataStructure ); EXPECT_NE( nullptr, scene ); EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "gltf2", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured_out.gltf" ) ); @@ -105,7 +111,8 @@ TEST_F( utglTF2ImportExport, importBinaryglTF2FromFileTest ) { TEST_F(utglTF2ImportExport, importglTF2AndExportToOBJ) { Assimp::Importer importer; Assimp::Exporter exporter; - const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", aiProcess_ValidateDataStructure); + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", + aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "obj", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured_out.obj")); } @@ -113,7 +120,8 @@ TEST_F(utglTF2ImportExport, importglTF2AndExportToOBJ) { TEST_F(utglTF2ImportExport, importglTF2EmbeddedAndExportToOBJ) { Assimp::Importer importer; Assimp::Exporter exporter; - const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-Embedded/BoxTextured.gltf", aiProcess_ValidateDataStructure); + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-Embedded/BoxTextured.gltf", + aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "obj", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-Embedded/BoxTextured_out.obj")); } @@ -124,10 +132,9 @@ TEST_F(utglTF2ImportExport, importglTF2PrimitiveModePointsWithoutIndices) { //Points without indices const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Asset-Generator/Mesh_PrimitiveMode/Mesh_PrimitiveMode_00.gltf", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 1024); - for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) - { - EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mNumIndices, 1); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 1024u); + for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) { + EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mNumIndices, 1u); EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[0], i); } } @@ -137,12 +144,11 @@ TEST_F(utglTF2ImportExport, importglTF2PrimitiveModeLinesWithoutIndices) { //Lines without indices const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Asset-Generator/Mesh_PrimitiveMode/Mesh_PrimitiveMode_01.gltf", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 8); - for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) - { - EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mNumIndices, 2); - EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[0], i*2); - EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[1], i*2 + 1); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 8u); + for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) { + EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mNumIndices, 2u); + EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[0], i*2u); + EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[1], i*2u + 1u); } } @@ -151,15 +157,14 @@ TEST_F(utglTF2ImportExport, importglTF2PrimitiveModeLinesLoopWithoutIndices) { //Lines loop without indices const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Asset-Generator/Mesh_PrimitiveMode/Mesh_PrimitiveMode_02.gltf", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4u); - std::array l1 = {{ 0, 1, 2, 3, 0 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 2); - for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) - { - EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mNumIndices, 2); + std::array l1 = {{ 0u, 1u, 2u, 3u, 0u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 2u); + for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) { + EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mNumIndices, 2u); EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[0], l1[i]); - EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[1], l1[i + 1]); + EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[1], l1[i + 1u]); } } @@ -168,14 +173,13 @@ TEST_F(utglTF2ImportExport, importglTF2PrimitiveModeLinesStripWithoutIndices) { //Lines strip without indices const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Asset-Generator/Mesh_PrimitiveMode/Mesh_PrimitiveMode_03.gltf", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 5); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 5u); - EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 2); - for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) - { - EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mNumIndices, 2); + EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 2u); + for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) { + EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mNumIndices, 2u); EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[0], i); - EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[1], i + 1); + EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[1], i + 1u); } } @@ -184,19 +188,17 @@ TEST_F(utglTF2ImportExport, importglTF2PrimitiveModeTrianglesStripWithoutIndices //Triangles strip without indices const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Asset-Generator/Mesh_PrimitiveMode/Mesh_PrimitiveMode_04.gltf", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(scene->mMeshes[0]->mNumFaces, 2); - EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4); - std::array f1 = {{ 0, 1, 2 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 3); - for (int i = 0; i < 3; ++i) - { + EXPECT_EQ(scene->mMeshes[0]->mNumFaces, 2u); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4u); + std::array f1 = {{ 0u, 1u, 2u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 3u); + for (unsigned int i = 0; i < 3; ++i) { EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mIndices[i], f1[i]); } - std::array f2 = {{ 2, 1, 3 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mNumIndices, 3); - for (int i = 0; i < 3; ++i) - { + std::array f2 = {{ 2u, 1u, 3u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mNumIndices, 3u); + for (size_t i = 0; i < 3; ++i) { EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mIndices[i], f2[i]); } } @@ -206,19 +208,17 @@ TEST_F(utglTF2ImportExport, importglTF2PrimitiveModeTrianglesFanWithoutIndices) //Triangles fan without indices const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Asset-Generator/Mesh_PrimitiveMode/Mesh_PrimitiveMode_05.gltf", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(scene->mMeshes[0]->mNumFaces, 2); - EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4); - std::array f1 = {{ 0, 1, 2 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 3); - for (int i = 0; i < 3; ++i) - { + EXPECT_EQ(scene->mMeshes[0]->mNumFaces, 2u); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4u); + std::array f1 = {{ 0u, 1u, 2u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 3u); + for (size_t i = 0; i < 3; ++i) { EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mIndices[i], f1[i]); } - std::array f2 = {{ 0, 2, 3 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mNumIndices, 3); - for (int i = 0; i < 3; ++i) - { + std::array f2 = {{ 0u, 2u, 3u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mNumIndices, 3u); + for (size_t i = 0; i < 3; ++i) { EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mIndices[i], f2[i]); } } @@ -228,19 +228,17 @@ TEST_F(utglTF2ImportExport, importglTF2PrimitiveModeTrianglesWithoutIndices) { //Triangles without indices const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Asset-Generator/Mesh_PrimitiveMode/Mesh_PrimitiveMode_06.gltf", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(scene->mMeshes[0]->mNumFaces, 2); - EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 6); - std::array f1 = {{ 0, 1, 2 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 3); - for (int i = 0; i < 3; ++i) - { + EXPECT_EQ(scene->mMeshes[0]->mNumFaces, 2u); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 6u); + std::array f1 = {{ 0u, 1u, 2u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 3u); + for (size_t i = 0; i < 3; ++i) { EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mIndices[i], f1[i]); } - std::array f2 = {{ 3, 4, 5 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mNumIndices, 3); - for (int i = 0; i < 3; ++i) - { + std::array f2 = {{ 3u, 4u, 5u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mNumIndices, 3u); + for (size_t i = 0; i < 3; ++i) { EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mIndices[i], f2[i]); } } @@ -250,10 +248,9 @@ TEST_F(utglTF2ImportExport, importglTF2PrimitiveModePoints) { //Line loop const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Asset-Generator/Mesh_PrimitiveMode/Mesh_PrimitiveMode_07.gltf", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 1024); - for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) - { - EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mNumIndices, 1); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 1024u); + for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) { + EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mNumIndices, 1u); EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[0], i); } } @@ -263,9 +260,9 @@ TEST_F(utglTF2ImportExport, importglTF2PrimitiveModeLines) { //Lines const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Asset-Generator/Mesh_PrimitiveMode/Mesh_PrimitiveMode_08.gltf", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4); - std::array l1 = {{ 0, 3, 2, 1, 0 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 2); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4u); + std::array l1 = {{ 0u, 3u, 2u, 1u, 0u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 2u); for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) { EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[0], l1[i]); @@ -278,9 +275,9 @@ TEST_F(utglTF2ImportExport, importglTF2PrimitiveModeLineLoop) { //Line loop const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Asset-Generator/Mesh_PrimitiveMode/Mesh_PrimitiveMode_09.gltf", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4); - std::array l1 = {{ 0, 3, 2, 1, 0 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 2); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4u); + std::array l1 = {{ 0, 3u, 2u, 1u, 0u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 2u); for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) { EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[0], l1[i]); @@ -293,11 +290,10 @@ TEST_F(utglTF2ImportExport, importglTF2PrimitiveModeLineStrip) { //Lines Strip const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Asset-Generator/Mesh_PrimitiveMode/Mesh_PrimitiveMode_10.gltf", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4); - std::array l1 = {{ 0, 3, 2, 1, 0 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 2); - for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) - { + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4u); + std::array l1 = {{ 0u, 3u, 2u, 1u, 0u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 2u); + for (unsigned int i = 0; i < scene->mMeshes[0]->mNumFaces; ++i) { EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[0], l1[i]); EXPECT_EQ(scene->mMeshes[0]->mFaces[i].mIndices[1], l1[i + 1]); } @@ -308,19 +304,17 @@ TEST_F(utglTF2ImportExport, importglTF2PrimitiveModeTrianglesStrip) { //Triangles strip const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Asset-Generator/Mesh_PrimitiveMode/Mesh_PrimitiveMode_11.gltf", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(scene->mMeshes[0]->mNumFaces, 2); - EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4); - std::array f1 = {{ 0, 3, 1 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 3); - for (int i = 0; i < 3; ++i) - { + EXPECT_EQ(scene->mMeshes[0]->mNumFaces, 2u); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4u); + std::array f1 = {{ 0u, 3u, 1u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 3u); + for (size_t i = 0; i < 3; ++i) { EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mIndices[i], f1[i]); } - std::array f2 = {{ 1, 3, 2 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mNumIndices, 3); - for (int i = 0; i < 3; ++i) - { + std::array f2 = {{ 1u, 3u, 2u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mNumIndices, 3u); + for (size_t i = 0; i < 3; ++i) { EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mIndices[i], f2[i]); } } @@ -330,19 +324,17 @@ TEST_F(utglTF2ImportExport, importglTF2PrimitiveModeTrianglesFan) { //Triangles fan const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Asset-Generator/Mesh_PrimitiveMode/Mesh_PrimitiveMode_12.gltf", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4); - EXPECT_EQ(scene->mMeshes[0]->mNumFaces, 2); - std::array f1 = {{ 0, 3, 2 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 3); - for (int i = 0; i < 3; ++i) - { + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 4u); + EXPECT_EQ(scene->mMeshes[0]->mNumFaces, 2u); + std::array f1 = {{ 0u, 3u, 2u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mNumIndices, 3u ); + for (size_t i = 0; i < 3; ++i) { EXPECT_EQ(scene->mMeshes[0]->mFaces[0].mIndices[i], f1[i]); } - std::array f2 = {{ 0, 2, 1 }}; - EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mNumIndices, 3); - for (int i = 0; i < 3; ++i) - { + std::array f2 = {{ 0u, 2u, 1u }}; + EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mNumIndices, 3u ); + for (size_t i = 0; i < 3; ++i) { EXPECT_EQ(scene->mMeshes[0]->mFaces[1].mIndices[i], f2[i]); } } @@ -378,13 +370,60 @@ TEST_F(utglTF2ImportExport, importglTF2FromMemory) { TEST_F( utglTF2ImportExport, bug_import_simple_skin ) { Assimp::Importer importer; - const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/glTF2/simple_skin/simple_skin.gltf", aiProcess_ValidateDataStructure ); + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/glTF2/simple_skin/simple_skin.gltf", + aiProcess_ValidateDataStructure ); EXPECT_NE( nullptr, scene ); } +TEST_F(utglTF2ImportExport, import_cameras) { + Assimp::Importer importer; + const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/cameras/Cameras.gltf", + aiProcess_ValidateDataStructure); + EXPECT_NE(nullptr, scene); +} + +TEST_F(utglTF2ImportExport, incorrect_vertex_arrays) { + Assimp::Importer importer; + const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/IncorrectVertexArrays/Cube.gltf", + aiProcess_ValidateDataStructure); + EXPECT_NE(nullptr, scene); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 36u); + EXPECT_EQ(scene->mMeshes[0]->mNumFaces, 12u); + EXPECT_EQ(scene->mMeshes[1]->mNumVertices, 35u); + EXPECT_EQ(scene->mMeshes[1]->mNumFaces, 11u); + EXPECT_EQ(scene->mMeshes[2]->mNumVertices, 36u); + EXPECT_EQ(scene->mMeshes[2]->mNumFaces, 18u); + EXPECT_EQ(scene->mMeshes[3]->mNumVertices, 35u); + EXPECT_EQ(scene->mMeshes[3]->mNumFaces, 17u); + EXPECT_EQ(scene->mMeshes[4]->mNumVertices, 36u); + EXPECT_EQ(scene->mMeshes[4]->mNumFaces, 12u); + EXPECT_EQ(scene->mMeshes[5]->mNumVertices, 35u); + EXPECT_EQ(scene->mMeshes[5]->mNumFaces, 11u); + EXPECT_EQ(scene->mMeshes[6]->mNumVertices, 36u); + EXPECT_EQ(scene->mMeshes[6]->mNumFaces, 18u); + EXPECT_EQ(scene->mMeshes[7]->mNumVertices, 35u); + EXPECT_EQ(scene->mMeshes[7]->mNumFaces, 17u); +} + +TEST_F( utglTF2ImportExport, texture_transform_test ) { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/textureTransform/TextureTransformTest.gltf", + aiProcess_ValidateDataStructure); + EXPECT_NE(nullptr, scene); +} + #ifndef ASSIMP_BUILD_NO_EXPORT TEST_F( utglTF2ImportExport, exportglTF2FromFileTest ) { EXPECT_TRUE( exporterTest() ); } +TEST_F( utglTF2ImportExport, crash_in_anim_mesh_destructor ) { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Sample-Models/AnimatedMorphCube-glTF/AnimatedMorphCube.gltf", + aiProcess_ValidateDataStructure); + ASSERT_NE( nullptr, scene ); + Assimp::Exporter exporter; + ASSERT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/glTF-Sample-Models/AnimatedMorphCube-glTF/AnimatedMorphCube_out.glTF")); +} + #endif // ASSIMP_BUILD_NO_EXPORT diff --git a/test/unit/utglTFImportExport.cpp b/test/unit/utglTFImportExport.cpp index 562cb05b9..90f0758bb 100644 --- a/test/unit/utglTFImportExport.cpp +++ b/test/unit/utglTFImportExport.cpp @@ -46,6 +46,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + using namespace Assimp; class utglTFImportExport : public AbstractImportExportBase { @@ -60,3 +62,26 @@ public: TEST_F( utglTFImportExport, importglTFFromFileTest ) { EXPECT_TRUE( importerTest() ); } + +TEST_F(utglTFImportExport, incorrect_vertex_arrays) { + Assimp::Importer importer; + const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF/IncorrectVertexArrays/Cube_v1.gltf", + aiProcess_ValidateDataStructure); + EXPECT_NE(nullptr, scene); + EXPECT_EQ(scene->mMeshes[0]->mNumVertices, 36u); + EXPECT_EQ(scene->mMeshes[0]->mNumFaces, 12u); + EXPECT_EQ(scene->mMeshes[1]->mNumVertices, 35u); + EXPECT_EQ(scene->mMeshes[1]->mNumFaces, 11u); + EXPECT_EQ(scene->mMeshes[2]->mNumVertices, 36u); + EXPECT_EQ(scene->mMeshes[2]->mNumFaces, 18u); + EXPECT_EQ(scene->mMeshes[3]->mNumVertices, 35u); + EXPECT_EQ(scene->mMeshes[3]->mNumFaces, 17u); + EXPECT_EQ(scene->mMeshes[4]->mNumVertices, 36u); + EXPECT_EQ(scene->mMeshes[4]->mNumFaces, 12u); + EXPECT_EQ(scene->mMeshes[5]->mNumVertices, 35u); + EXPECT_EQ(scene->mMeshes[5]->mNumFaces, 11u); + EXPECT_EQ(scene->mMeshes[6]->mNumVertices, 36u); + EXPECT_EQ(scene->mMeshes[6]->mNumFaces, 18u); + EXPECT_EQ(scene->mMeshes[7]->mNumVertices, 35u); + EXPECT_EQ(scene->mMeshes[7]->mNumFaces, 17u); +} diff --git a/tools/assimp_cmd/Main.cpp b/tools/assimp_cmd/Main.cpp index af372582c..9173a756b 100644 --- a/tools/assimp_cmd/Main.cpp +++ b/tools/assimp_cmd/Main.cpp @@ -384,6 +384,7 @@ int ProcessStandardArguments( // -om --optimize-meshes // -db --debone // -sbc --split-by-bone-count + // -gs --global-scale // // -c --config-file= @@ -472,6 +473,9 @@ int ProcessStandardArguments( else if (!strcmp(param, "-embtex") || ! strcmp(param, "--embed-textures")) { fill.ppFlags |= aiProcess_EmbedTextures; } + else if (!strcmp(param, "-gs") || ! strcmp(param, "--global-scale")) { + fill.ppFlags |= aiProcess_GlobalScale; + } else if (! strncmp( param, "-c",2) || ! strncmp( param, "--config=",9)) { const unsigned int ofs = (params[i][1] == '-' ? 9 : 2); diff --git a/tools/assimp_qt_viewer/CMakeLists.txt b/tools/assimp_qt_viewer/CMakeLists.txt deleted file mode 100644 index d8d8e27a9..000000000 --- a/tools/assimp_qt_viewer/CMakeLists.txt +++ /dev/null @@ -1,76 +0,0 @@ -set(PROJECT_VERSION "") -project(assimp_qt_viewer) - -# Qt5 requires cmake 3.1 or newer -cmake_minimum_required(VERSION 3.1) - -FIND_PACKAGE(OpenGL QUIET) - -# Qt5 version -FIND_PACKAGE(Qt5 COMPONENTS Gui Widgets OpenGL QUIET) - -SET(VIEWER_BUILD:BOOL FALSE) - -IF( Qt5Widgets_FOUND AND OPENGL_FOUND) - SET(VIEWER_BUILD TRUE) -ELSE( Qt5Widgets_FOUND AND OPENGL_FOUND) - SET ( ASSIMP_QT_VIEWER_DEPENDENCIES "") - - IF (NOT Qt5_FOUND) - SET ( ASSIMP_QT_VIEWER_DEPENDENCIES "${ASSIMP_QT_VIEWER_DEPENDENCIES} Qt5") - ENDIF (NOT Qt5_FOUND) - - IF (NOT OPENGL_FOUND) - SET ( ASSIMP_QT_VIEWER_DEPENDENCIES "${ASSIMP_QT_VIEWER_DEPENDENCIES} OpengGL") - ENDIF (NOT OPENGL_FOUND) - - MESSAGE (WARNING "Build of assimp_qt_viewer is disabled. Unsatisfied dendencies: ${ASSIMP_QT_VIEWER_DEPENDENCIES}") -ENDIF( Qt5Widgets_FOUND AND OPENGL_FOUND) - -IF(VIEWER_BUILD) - INCLUDE_DIRECTORIES( - ${Assimp_SOURCE_DIR}/include - ${Assimp_SOURCE_DIR}/code - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_SOURCE_DIR} - ${OPENGL_INCLUDE_DIR} - ) - - LINK_DIRECTORIES(${Assimp_BINARY_DIR}) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pedantic -Wall") - - SET(assimp_qt_viewer_SRCS - main.cpp - loggerview.hpp - loggerview.cpp - glview.hpp - glview.cpp - mainwindow.hpp - mainwindow.cpp - ) - - MESSAGE("assimp_qt_viewer use Qt5") - INCLUDE_DIRECTORIES(${Qt5Widgets_INCLUDES}) - qt5_wrap_ui(UISrcs mainwindow.ui) - qt5_wrap_cpp(MOCrcs mainwindow.hpp glview.hpp) - - add_executable(${PROJECT_NAME} ${assimp_qt_viewer_SRCS} ${UISrcs} ${MOCrcs}) - target_link_libraries(${PROJECT_NAME} Qt5::Gui Qt5::Widgets Qt5::OpenGL ${OPENGL_LIBRARIES} assimp) - - IF(WIN32) # Check if we are on Windows - IF(MSVC) # Check if we are using the Visual Studio compiler - #set_target_properties(TestProject PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") - ELSEIF(CMAKE_COMPILER_IS_GNUCXX) - # SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mwindows") # Not tested - ELSE() - MESSAGE(SEND_ERROR "You are using an unsupported Windows compiler! (Not MSVC or GCC)") - ENDIF() - ELSEIF(UNIX) - # Nothing special required - ELSE() - MESSAGE(SEND_ERROR "You are on an unsupported platform! (Not Win32 or Unix)") - ENDIF() - - SET_PROPERTY(TARGET ${PROJECT_NAME} PROPERTY DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) - INSTALL(TARGETS assimp_qt_viewer DESTINATION "${ASSIMP_BIN_INSTALL_DIR}") -ENDIF(VIEWER_BUILD) diff --git a/tools/assimp_qt_viewer/doc/Assimp_qt_viewer. Manual (en).odt b/tools/assimp_qt_viewer/doc/Assimp_qt_viewer. Manual (en).odt deleted file mode 100644 index 6e4e8c193..000000000 Binary files a/tools/assimp_qt_viewer/doc/Assimp_qt_viewer. Manual (en).odt and /dev/null differ diff --git a/tools/assimp_qt_viewer/doc/Assimp_qt_viewer. Manual (ru).odt b/tools/assimp_qt_viewer/doc/Assimp_qt_viewer. Manual (ru).odt deleted file mode 100644 index 6bd8f8dbb..000000000 Binary files a/tools/assimp_qt_viewer/doc/Assimp_qt_viewer. Manual (ru).odt and /dev/null differ diff --git a/tools/assimp_qt_viewer/glview.cpp b/tools/assimp_qt_viewer/glview.cpp deleted file mode 100644 index 97eba83a1..000000000 --- a/tools/assimp_qt_viewer/glview.cpp +++ /dev/null @@ -1,1161 +0,0 @@ -/* ---------------------------------------------------------------------------- -Open Asset Import Library (assimp) ---------------------------------------------------------------------------- - -Copyright (c) 2006-2018, assimp team - - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following -conditions are met: - -* Redistributions of source code must retain the above -copyright notice, this list of conditions and the -following disclaimer. - -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the -following disclaimer in the documentation and/or other -materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its -contributors may be used to endorse or promote products -derived from this software without specific prior -written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------- -*/ -#include "glview.hpp" - -// Header files, Qt. -#include - -// Header files, OpenGL. -#if defined(__APPLE__) -# include -#else -# include -#endif - -// Header files, DevIL. - -// Header files, Assimp. -#include - -#define STB_IMAGE_IMPLEMENTATION -#include "contrib/stb_image/stb_image.h" - -CGLView::SHelper_Mesh::SHelper_Mesh(const size_t pQuantity_Point, const size_t pQuantity_Line, const size_t pQuantity_Triangle, const SBBox& pBBox) -: Quantity_Point(pQuantity_Point) -, Quantity_Line(pQuantity_Line) -, Quantity_Triangle(pQuantity_Triangle) -, BBox(pBBox) { - Index_Point = pQuantity_Point ? new GLuint[pQuantity_Point * 1] : nullptr; - Index_Line = pQuantity_Line ? new GLuint[pQuantity_Line * 2] : nullptr; - Index_Triangle = pQuantity_Triangle ? new GLuint[pQuantity_Triangle * 3] : nullptr; -} - -CGLView::SHelper_Mesh::~SHelper_Mesh() { - delete [] Index_Point; - delete [] Index_Line; - delete [] Index_Triangle; -} - -void CGLView::SHelper_Camera::SetDefault() { - Position.Set(0, 0, 0); - Target.Set(0, 0, -1); - Rotation_AroundCamera = aiMatrix4x4(); - Rotation_Scene = aiMatrix4x4(); - Translation_ToScene.Set(0, 0, 2); -} - -static void set_float4(float f[4], float a, float b, float c, float d) { - f[0] = a; - f[1] = b; - f[2] = c; - f[3] = d; -} - -static void color4_to_float4(const aiColor4D *c, float f[4]) { - f[0] = c->r; - f[1] = c->g; - f[2] = c->b; - f[3] = c->a; -} - -void CGLView::Material_Apply(const aiMaterial* pMaterial) { - GLfloat tcol[4]; - aiColor4D taicol; - unsigned int max; - int ret1, ret2; - int texture_index = 0; - aiString texture_path; - - ///TODO: cache materials - // Disable color material because glMaterial is used. - glDisable(GL_COLOR_MATERIAL);///TODO: cache - - // Set texture. If assigned. - if(AI_SUCCESS == pMaterial->GetTexture(aiTextureType_DIFFUSE, texture_index, &texture_path)) { - //bind texture - unsigned int texture_ID = mTexture_IDMap.value(texture_path.data, 0); - - glBindTexture(GL_TEXTURE_2D, texture_ID); - } - // - // Set material parameters from scene or default values. - // - // Diffuse - set_float4(tcol, 0.8f, 0.8f, 0.8f, 1.0f); - if ( AI_SUCCESS == aiGetMaterialColor( pMaterial, AI_MATKEY_COLOR_DIFFUSE, &taicol )) { - color4_to_float4( &taicol, tcol ); - } - - glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, tcol); - - // Specular - set_float4(tcol, 0.0f, 0.0f, 0.0f, 1.0f); - if ( AI_SUCCESS == aiGetMaterialColor( pMaterial, AI_MATKEY_COLOR_SPECULAR, &taicol )) { - color4_to_float4( &taicol, tcol ); - } - - glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, tcol); - // Ambient - set_float4(tcol, 0.2f, 0.2f, 0.2f, 1.0f); - if ( AI_SUCCESS == aiGetMaterialColor( pMaterial, AI_MATKEY_COLOR_AMBIENT, &taicol )) { - color4_to_float4( &taicol, tcol ); - } - glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, tcol); - - // Emission - set_float4(tcol, 0.0f, 0.0f, 0.0f, 1.0f); - if(AI_SUCCESS == aiGetMaterialColor(pMaterial, AI_MATKEY_COLOR_EMISSIVE, &taicol)) color4_to_float4(&taicol, tcol); - - glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, tcol); - // Shininess - ai_real shininess, strength; - - max = 1; - ret1 = aiGetMaterialFloatArray(pMaterial, AI_MATKEY_SHININESS, &shininess, &max); - // Shininess strength - max = 1; - ret2 = aiGetMaterialFloatArray(pMaterial, AI_MATKEY_SHININESS_STRENGTH, &strength, &max); - if((ret1 == AI_SUCCESS) && (ret2 == AI_SUCCESS)) { - glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess * strength);///TODO: cache - } else { - glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 0.0f);///TODO: cache - set_float4(tcol, 0.0f, 0.0f, 0.0f, 0.0f); - glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, tcol); - } - - // Fill mode - GLenum fill_mode; - int wireframe; - - max = 1; - if(AI_SUCCESS == aiGetMaterialIntegerArray(pMaterial, AI_MATKEY_ENABLE_WIREFRAME, &wireframe, &max)) - fill_mode = wireframe ? GL_LINE : GL_FILL; - else - fill_mode = GL_FILL; - - glPolygonMode(GL_FRONT_AND_BACK, fill_mode);///TODO: cache - // Fill side - int two_sided; - - max = 1; - if((AI_SUCCESS == aiGetMaterialIntegerArray(pMaterial, AI_MATKEY_TWOSIDED, &two_sided, &max)) && two_sided)///TODO: cache - glDisable(GL_CULL_FACE); - else - glEnable(GL_CULL_FACE); -} - -void CGLView::Matrix_NodeToRoot(const aiNode* pNode, aiMatrix4x4& pOutMatrix) -{ - const aiNode* node_cur; - std::list mat_list; - - pOutMatrix = aiMatrix4x4(); - // starting walk from current element to root - node_cur = pNode; - if(node_cur != nullptr) - { - do - { - // if cur_node is group then store group transformation matrix in list. - mat_list.push_back(node_cur->mTransformation); - node_cur = node_cur->mParent; - } while(node_cur != nullptr); - } - - // multiply all matrices in reverse order - for ( std::list::reverse_iterator rit = mat_list.rbegin(); rit != mat_list.rend(); rit++) - { - pOutMatrix = pOutMatrix * (*rit); - } -} - -void CGLView::ImportTextures(const QString& scenePath) { - auto LoadTexture = [&](const QString& pFileName) -> bool ///TODO: IME texture mode, operation. - { - GLuint id_ogl_texture;// OpenGL texture ID. - - if(!pFileName.startsWith(AI_EMBEDDED_TEXNAME_PREFIX)) - { - QString basepath = scenePath.left(scenePath.lastIndexOf('/') + 1);// path with '/' at the end. - QString fileloc = (basepath + pFileName); - - fileloc.replace('\\', "/"); - int x, y, n; - unsigned char *data = stbi_load(fileloc.toLocal8Bit(), &x, &y, &n, STBI_rgb_alpha ); - if ( nullptr == data ) { - LogError(QString("Couldn't load Image: %1").arg(fileloc)); - - return false; - } - - // Convert every colour component into unsigned byte. If your image contains alpha channel you can replace IL_RGB with IL_RGBA. - - glGenTextures(1, &id_ogl_texture);// Texture ID generation. - mTexture_IDMap[pFileName] = id_ogl_texture;// save texture ID for filename in map - glBindTexture(GL_TEXTURE_2D, id_ogl_texture);// Binding of texture ID. - // Redefine standard texture values - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// We will use linear interpolation for magnification filter. - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);// We will use linear interpolation for minifying filter. - glTexImage2D(GL_TEXTURE_2D, 0, n, x, y, 0, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, data );// Texture specification. - stbi_image_free(data); - // Cleanup - } - else - { - struct SPixel_Description - { - const char* FormatHint; - const GLint Image_InternalFormat; - const GLint Pixel_Format; - }; - - constexpr SPixel_Description Pixel_Description[] = { - {"rgba8880", GL_RGB, GL_RGB}, - {"rgba8888", GL_RGBA, GL_RGBA} - }; - - constexpr size_t Pixel_Description_Count = sizeof(Pixel_Description) / sizeof(SPixel_Description); - - size_t idx_description; - // Get texture index. - bool ok; - size_t idx_texture = pFileName.right(strlen(AI_EMBEDDED_TEXNAME_PREFIX)).toULong(&ok); - - if(!ok) - { - LogError("Can not get index of the embedded texture from path in material."); - - return false; - } - - // Create alias for conveniance. - const aiTexture& als = *mScene->mTextures[idx_texture]; - - if(als.mHeight == 0)// Compressed texture. - { - LogError("IME: compressed embedded textures are not implemented."); - } - else - { - ok = false; - for(size_t idx = 0; idx < Pixel_Description_Count; idx++) - { - if(als.CheckFormat(Pixel_Description[idx].FormatHint)) - { - idx_description = idx; - ok = true; - break; - } - } - - if(!ok) - { - LogError(QString("Unsupported format hint for embedded texture: [%1]").arg(als.achFormatHint)); - - return false; - } - - glGenTextures(1, &id_ogl_texture);// Texture ID generation. - mTexture_IDMap[pFileName] = id_ogl_texture;// save texture ID for filename in map - glBindTexture(GL_TEXTURE_2D, id_ogl_texture);// Binding of texture ID. - // Redefine standard texture values - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// We will use linear interpolation for magnification filter. - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);// We will use linear interpolation for minifying filter. - // Texture specification. - glTexImage2D(GL_TEXTURE_2D, 0, Pixel_Description[idx_description].Image_InternalFormat, als.mWidth, als.mHeight, 0, - Pixel_Description[idx_description].Pixel_Format, GL_UNSIGNED_BYTE, (uint8_t*)als.pcData); - }// if(als.mHeight == 0) else - }// if(!filename.startsWith(AI_EMBEDDED_TEXNAME_PREFIX)) else - - return true; - };// auto LoadTexture = [&](const aiString& pPath) - - if(mScene == nullptr) - { - LogError("Trying to load textures for empty scene."); - - return; - } - - // - // Load textures. - // - // Get textures file names and number of textures. - for(size_t idx_material = 0; idx_material < mScene->mNumMaterials; idx_material++) { - int idx_texture = 0; - aiString path; - - do { - if (mScene->mMaterials[ idx_material ]->GetTexture( aiTextureType_DIFFUSE, idx_texture, &path ) != AI_SUCCESS) { - break; - } - - LoadTexture(QString(path.C_Str())); - idx_texture++; - } while(true); - }// for(size_t idx_material = 0; idx_material < mScene->mNumMaterials; idx_material++) - - // Textures list is empty, exit. - if(mTexture_IDMap.empty()) { - LogInfo("No textures for import."); - } -} - -void CGLView::BBox_GetForNode(const aiNode& pNode, const aiMatrix4x4& pParent_TransformationMatrix, SBBox& pNodeBBox, bool& pFirstAssign) -{ - aiMatrix4x4 mat_trans = pParent_TransformationMatrix * pNode.mTransformation; - - // Check if node has meshes - for(size_t idx_idx_mesh = 0; idx_idx_mesh < pNode.mNumMeshes; idx_idx_mesh++) - { - size_t idx_mesh; - SBBox bbox_local; - aiVector3D bbox_vertices[8]; - - idx_mesh = pNode.mMeshes[idx_idx_mesh]; - // Get vertices of mesh BBox - BBox_GetVertices(mHelper_Mesh[idx_mesh]->BBox, bbox_vertices); - // Transform vertices - for(size_t idx_vert = 0; idx_vert < 8; idx_vert++) bbox_vertices[idx_vert] *= mat_trans; - - // And create BBox for transformed mesh - BBox_GetFromVertices(bbox_vertices, 8, bbox_local); - - if(!pFirstAssign) - { - BBox_Extend(bbox_local, pNodeBBox); - } - else - { - pFirstAssign = false; - pNodeBBox = bbox_local; - } - }// for(size_t idx_idx_mesh = 0; idx_idx_mesh < pNode.mNumMeshes; idx_idx_mesh++) - - for(size_t idx_node = 0; idx_node < pNode.mNumChildren; idx_node++) - { - BBox_GetForNode(*pNode.mChildren[idx_node], mat_trans, pNodeBBox, pFirstAssign); - } -} - -void CGLView::BBox_Extend(const SBBox& pChild, SBBox& pParent) -{ - // search minimal... - AssignIfLesser(&pParent.Minimum.x, pChild.Minimum.x); - AssignIfLesser(&pParent.Minimum.y, pChild.Minimum.y); - AssignIfLesser(&pParent.Minimum.z, pChild.Minimum.z); - // and maximal values - AssignIfGreater(&pParent.Maximum.x, pChild.Maximum.x); - AssignIfGreater(&pParent.Maximum.y, pChild.Maximum.y); - AssignIfGreater(&pParent.Maximum.z, pChild.Maximum.z); -} - -void CGLView::BBox_GetVertices(const SBBox& pBBox, aiVector3D pVertex[8]) -{ - pVertex[0] = pBBox.Minimum; - pVertex[1].Set(pBBox.Minimum.x, pBBox.Minimum.y, pBBox.Maximum.z); - pVertex[2].Set(pBBox.Minimum.x, pBBox.Maximum.y, pBBox.Maximum.z); - pVertex[3].Set(pBBox.Minimum.x, pBBox.Maximum.y, pBBox.Minimum.z); - - pVertex[4].Set(pBBox.Maximum.x, pBBox.Minimum.y, pBBox.Minimum.z); - pVertex[5].Set(pBBox.Maximum.x, pBBox.Minimum.y, pBBox.Maximum.z); - pVertex[6] = pBBox.Maximum; - pVertex[7].Set(pBBox.Maximum.x, pBBox.Maximum.y, pBBox.Minimum.z); - -} - -void CGLView::BBox_GetFromVertices(const aiVector3D* pVertices, const size_t pVerticesQuantity, SBBox& pBBox) -{ - if(pVerticesQuantity == 0) - { - pBBox.Maximum.Set(0, 0, 0); - pBBox.Minimum.Set(0, 0, 0); - - return; - } - - // Assign first values. - pBBox.Minimum = pVertices[0]; - pBBox.Maximum = pVertices[0]; - - for(size_t idx_vert = 1; idx_vert < pVerticesQuantity; idx_vert++) - { - const ai_real x = pVertices[idx_vert].x; - const ai_real y = pVertices[idx_vert].y; - const ai_real z = pVertices[idx_vert].z; - - // search minimal... - AssignIfLesser(&pBBox.Minimum.x, x); - AssignIfLesser(&pBBox.Minimum.y, y); - AssignIfLesser(&pBBox.Minimum.z, z); - // and maximal values - AssignIfGreater(&pBBox.Maximum.x, x); - AssignIfGreater(&pBBox.Maximum.y, y); - AssignIfGreater(&pBBox.Maximum.z, z); - } -} - -void CGLView::LogInfo(const QString& pMessage) { - Assimp::DefaultLogger::get()->info(pMessage.toStdString()); -} - -void CGLView::LogError(const QString& pMessage) { - Assimp::DefaultLogger::get()->error(pMessage.toStdString()); -} - -void CGLView::Draw_Node(const aiNode* pNode) { - aiMatrix4x4 mat_node = pNode->mTransformation; - - // Apply node transformation matrix. - mat_node.Transpose(); - glPushMatrix(); -#ifdef ASSIMP_DOUBLE_PRECISION - glMultMatrixd((GLdouble*)mat_node[0]); -#else - glMultMatrixf((GLfloat*)&mat_node); -#endif // ASSIMP_DOUBLE_PRECISION - - // Draw all meshes assigned to this node - for(size_t idx_mesh_arr = 0; idx_mesh_arr < pNode->mNumMeshes; idx_mesh_arr++) Draw_Mesh(pNode->mMeshes[idx_mesh_arr]); - - // Draw all children nodes - for(size_t idx_node = 0; idx_node < pNode->mNumChildren; idx_node++) Draw_Node(pNode->mChildren[idx_node]); - - // Restore transformation matrix. - glPopMatrix(); -} - -void CGLView::Draw_Mesh(const size_t pMesh_Index) -{ - // Check argument - if(pMesh_Index >= mHelper_Mesh_Quantity) return; - - aiMesh& mesh_cur = *mScene->mMeshes[pMesh_Index]; - - if(!mesh_cur.HasPositions()) return;// Nothing to draw. - - // If mesh use material then apply it - if(mScene->HasMaterials()) Material_Apply(mScene->mMaterials[mesh_cur.mMaterialIndex]); - - // - // Vertices array - // - glEnableClientState(GL_VERTEX_ARRAY); -#if ASSIMP_DOUBLE_PRECISION - glVertexPointer(3, GL_DOUBLE, 0, mesh_cur.mVertices); -#else - glVertexPointer(3, GL_FLOAT, 0, mesh_cur.mVertices); -#endif // ASSIMP_DOUBLE_PRECISION - - if(mesh_cur.HasVertexColors(0)) - { - glEnable(GL_COLOR_MATERIAL);///TODO: cache - glEnableClientState(GL_COLOR_ARRAY); -#ifdef ASSIMP_DOUBLE_PRECISION - glColorPointer(4, GL_DOUBLE, 0, mesh_cur.mColors[0]); -#else - glColorPointer(4, GL_FLOAT, 0, mesh_cur.mColors[0]); -#endif // ASSIMP_DOUBLE_PRECISION - } - - // - // Texture coordinates array - // - if(mesh_cur.HasTextureCoords(0)) - { - glEnableClientState(GL_TEXTURE_COORD_ARRAY); -#ifdef ASSIMP_DOUBLE_PRECISION - glTexCoordPointer(2, GL_DOUBLE, sizeof(aiVector3D), mesh_cur.mTextureCoords[0]); -#else - glTexCoordPointer(2, GL_FLOAT, sizeof(aiVector3D), mesh_cur.mTextureCoords[0]); -#endif // ASSIMP_DOUBLE_PRECISION - } - - // - // Normals array - // - if(mesh_cur.HasNormals()) - { - glEnableClientState(GL_NORMAL_ARRAY); -#ifdef ASSIMP_DOUBLE_PRECISION - glNormalPointer(GL_DOUBLE, 0, mesh_cur.mNormals); -#else - glNormalPointer(GL_FLOAT, 0, mesh_cur.mNormals); -#endif // ASSIMP_DOUBLE_PRECISION - } - - // - // Draw arrays - // - SHelper_Mesh& helper_cur = *mHelper_Mesh[pMesh_Index]; - - if(helper_cur.Quantity_Triangle > 0) glDrawElements(GL_TRIANGLES, helper_cur.Quantity_Triangle * 3, GL_UNSIGNED_INT, helper_cur.Index_Triangle); - if(helper_cur.Quantity_Line > 0) glDrawElements(GL_LINES,helper_cur.Quantity_Line * 2, GL_UNSIGNED_INT, helper_cur.Index_Line); - if(helper_cur.Quantity_Point > 0) glDrawElements(GL_POINTS, helper_cur.Quantity_Point, GL_UNSIGNED_INT, helper_cur.Index_Point); - - // - // Clean up - // - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glDisableClientState(GL_NORMAL_ARRAY); -} - -void CGLView::Draw_BBox(const SBBox& pBBox) -{ - aiVector3D vertex[8]; - - BBox_GetVertices(pBBox, vertex); - // Draw - if(mLightingEnabled) glDisable(GL_LIGHTING);///TODO: display list - - glEnable(GL_COLOR_MATERIAL); - glBindTexture(GL_TEXTURE_1D, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glBindTexture(GL_TEXTURE_3D, 0); - const QColor c_w(Qt::white); - - glColor3f(c_w.redF(), c_w.greenF(), c_w.blueF()); - - glBegin(GL_LINE_STRIP); -# ifdef ASSIMP_DOUBLE_PRECISION - glVertex3dv(&vertex[0][0]), glVertex3dv(&vertex[1][0]), glVertex3dv(&vertex[2][0]), glVertex3dv(&vertex[3][0]), glVertex3dv(&vertex[0][0]);// "Minimum" side. - glVertex3dv(&vertex[4][0]), glVertex3dv(&vertex[5][0]), glVertex3dv(&vertex[6][0]), glVertex3dv(&vertex[7][0]), glVertex3dv(&vertex[4][0]);// Edge and "maximum" side. -# else - glVertex3fv(&vertex[0][0]), glVertex3fv(&vertex[1][0]), glVertex3fv(&vertex[2][0]), glVertex3fv(&vertex[3][0]), glVertex3fv(&vertex[0][0]);// "Minimum" side. - glVertex3fv(&vertex[4][0]), glVertex3fv(&vertex[5][0]), glVertex3fv(&vertex[6][0]), glVertex3fv(&vertex[7][0]), glVertex3fv(&vertex[4][0]);// Edge and "maximum" side. -# endif // ASSIMP_DOUBLE_PRECISION - glEnd(); - - glBegin(GL_LINES); -# ifdef ASSIMP_DOUBLE_PRECISION - glVertex3dv(&vertex[1][0]), glVertex3dv(&vertex[5][0]); - glVertex3dv(&vertex[2][0]), glVertex3dv(&vertex[6][0]); - glVertex3dv(&vertex[3][0]), glVertex3dv(&vertex[7][0]); -# else - glVertex3fv(&vertex[1][0]), glVertex3fv(&vertex[5][0]); - glVertex3fv(&vertex[2][0]), glVertex3fv(&vertex[6][0]); - glVertex3fv(&vertex[3][0]), glVertex3fv(&vertex[7][0]); -# endif // ASSIMP_DOUBLE_PRECISION - glEnd(); - glDisable(GL_COLOR_MATERIAL); - if(mLightingEnabled) glEnable(GL_LIGHTING); - -} - -void CGLView::Enable_Textures(const bool pEnable) { - if(pEnable) { - glEnable(GL_TEXTURE_1D); - glEnable(GL_TEXTURE_2D); - glEnable(GL_TEXTURE_3D); - } else { - glDisable(GL_TEXTURE_1D); - glDisable(GL_TEXTURE_2D); - glDisable(GL_TEXTURE_3D); - } -} - -void CGLView::initializeGL() { - mGLContext_Current = true; - initializeOpenGLFunctions(); - glClearColor(0.5f, 0.5f, 0.5f, 1.0f); - glShadeModel(GL_SMOOTH); - - glEnable(GL_DEPTH_TEST); - glEnable(GL_NORMALIZE); - glEnable(GL_TEXTURE_2D); - glEnable( GL_MULTISAMPLE ); - - glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT); - glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE); - glDisable(GL_COLOR_MATERIAL); - - glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); - - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - - glFrontFace(GL_CCW); -} - -void CGLView::resizeGL(int width, int height) { - mCamera_Viewport_AspectRatio = (GLdouble)width / height; - glViewport(0, 0, width, height); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - gluPerspective(mCamera_FOVY, mCamera_Viewport_AspectRatio, 1.0, 100000.0);///TODO: znear/zfar depend on scene size. -} - -void CGLView::drawCoordSystem() { - // Disable lighting. Colors must be bright and colorful) - if ( mLightingEnabled ) glDisable( GL_LIGHTING );///TODO: display list - - // For same reason - disable textures. - glBindTexture(GL_TEXTURE_1D, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glBindTexture(GL_TEXTURE_3D, 0); - glEnable(GL_COLOR_MATERIAL); - glBegin(GL_LINES); - - // X, -X - glColor3f(1.0f, 0.0f, 0.0f), glVertex3f(0.0, 0.0, 0.0), glVertex3f(100000.0, 0.0, 0.0); - glColor3f(0.5f, 0.5f, 1.0f), glVertex3f(0.0, 0.0, 0.0), glVertex3f(-100000.0, 0.0, 0.0); - // Y, -Y - glColor3f(0.0f, 1.0f, 0.0f), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, 100000.0, 0.0); - glColor3f(1.0f, 0.0f, 1.0f), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, -100000.0, 0.0); - // Z, -Z - glColor3f(0.0f, 0.0f, 1.0f), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, 0.0, 100000.0); - glColor3f(1.0f, 1.0f, 0.0f), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, 0.0, -100000.0); - glColor3f(1.0f, 1.0f, 1.0f); - - glEnd(); - // Restore previous state of lighting. - if (mLightingEnabled) { - glEnable( GL_LIGHTING ); - } -} - -void CGLView::paintGL() { - QTime time_paintbegin; - - time_paintbegin = QTime::currentTime(); - - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - // Apply current camera transformations. -#if ASSIMP_DOUBLE_PRECISION - glMultMatrixd((GLdouble*)&mHelper_Camera.Rotation_AroundCamera); - glTranslated(-mHelper_Camera.Translation_ToScene.x, -mHelper_Camera.Translation_ToScene.y, -mHelper_Camera.Translation_ToScene.z); - glMultMatrixd((GLdouble*)&mHelper_Camera.Rotation_Scene); -#else - glMultMatrixf((GLfloat*)&mHelper_Camera.Rotation_AroundCamera); - glTranslatef(-mHelper_Camera.Translation_ToScene.x, -mHelper_Camera.Translation_ToScene.y, -mHelper_Camera.Translation_ToScene.z); - glMultMatrixf((GLfloat*)&mHelper_Camera.Rotation_Scene); -#endif // ASSIMP_DOUBLE_PRECISION - - // Coordinate system - if ( mScene_AxesEnabled ) { - drawCoordSystem(); - } - - glDisable(GL_COLOR_MATERIAL); - - // Scene - if(mScene != nullptr) { - Draw_Node(mScene->mRootNode); - // Scene BBox - if (mScene_DrawBBox) { - Draw_BBox( mScene_BBox ); - } - } - - emit Paint_Finished((size_t) time_paintbegin.msecsTo(QTime::currentTime()), mHelper_Camera.Translation_ToScene.Length()); -} - - -CGLView::CGLView( QWidget *pParent ) -: QOpenGLWidget( pParent ) -, mGLContext_Current( false ) { - // set initial view - mHelper_CameraDefault.SetDefault(); - Camera_Set( 0 ); -} - -CGLView::~CGLView() { - FreeScene(); -} - -void CGLView::FreeScene() { - // Set scene to null and after that \ref paintGL will not try to render it. - mScene = nullptr; - // Clean helper objects. - if(mHelper_Mesh != nullptr) - { - for(size_t idx_mesh = 0; idx_mesh < mHelper_Mesh_Quantity; idx_mesh++) delete mHelper_Mesh[idx_mesh]; - - delete [] mHelper_Mesh; - mHelper_Mesh = nullptr; - } - - mHelper_Mesh_Quantity = 0; - // Delete textures - const int id_tex_size = mTexture_IDMap.size(); - - if(id_tex_size) - { - GLuint* id_tex = new GLuint[id_tex_size]; - QMap::iterator it = mTexture_IDMap.begin(); - - for(int idx = 0; idx < id_tex_size; idx++, it++) - { - id_tex[idx] = it.value(); - } - - glDeleteTextures(id_tex_size, id_tex); - mTexture_IDMap.clear(); - delete [] id_tex; - } -} - -void CGLView::SetScene(const aiScene *pScene, const QString& pScenePath) { - FreeScene();// Clear old data - // Why checking here, not at begin of function. Because old scene may not exist at know. So, need cleanup. - if (pScene == nullptr) { - return; - } - - mScene = pScene;// Copy pointer of new scene. - - // - // Meshes - // - // Create helper objects for meshes. This allow to render meshes as OpenGL arrays. - if(mScene->HasMeshes()) - { - // Create mesh helpers array. - mHelper_Mesh_Quantity = mScene->mNumMeshes; - mHelper_Mesh = new SHelper_Mesh*[mScene->mNumMeshes]; - - // Walk through the meshes and extract needed data and, also calculate BBox. - for(size_t idx_mesh = 0; idx_mesh < mScene->mNumMeshes; idx_mesh++) - { - aiMesh& mesh_cur = *mScene->mMeshes[idx_mesh]; - - // - // Calculate BBox - // - SBBox mesh_bbox; - - BBox_GetFromVertices(mesh_cur.mVertices, mesh_cur.mNumVertices, mesh_bbox); - // - // Create vertices indices arrays splitted by primitive type. - // - size_t indcnt_p = 0;// points quantity - size_t indcnt_l = 0;// lines quantity - size_t indcnt_t = 0;// triangles quantity - - if(mesh_cur.HasFaces()) - { - // Usual way: all faces are triangles - if(mesh_cur.mPrimitiveTypes == aiPrimitiveType_TRIANGLE) - { - indcnt_t = mesh_cur.mNumFaces; - } - else - { - // Calculate count of primitives by types. - for(size_t idx_face = 0; idx_face < mesh_cur.mNumFaces; idx_face++) - { - if(mesh_cur.mFaces[idx_face].mNumIndices == 3) - indcnt_t++; - else if(mesh_cur.mFaces[idx_face].mNumIndices == 2) - indcnt_l++; - else if(mesh_cur.mFaces[idx_face].mNumIndices == 1) - indcnt_p++; - } - }// if(mesh_cur.mPrimitiveTypes == aiPrimitiveType_TRIANGLE) else - - // Create helper - mHelper_Mesh[idx_mesh] = new SHelper_Mesh(indcnt_p, indcnt_l, indcnt_t, mesh_bbox); - // Fill indices arrays - indcnt_p = 0, indcnt_l = 0, indcnt_t = 0;// Reuse variables as indices - for(size_t idx_face = 0; idx_face < mesh_cur.mNumFaces; idx_face++) - { - if(mesh_cur.mFaces[idx_face].mNumIndices == 3) - { - mHelper_Mesh[idx_mesh]->Index_Triangle[indcnt_t++] = mesh_cur.mFaces[idx_face].mIndices[0]; - mHelper_Mesh[idx_mesh]->Index_Triangle[indcnt_t++] = mesh_cur.mFaces[idx_face].mIndices[1]; - mHelper_Mesh[idx_mesh]->Index_Triangle[indcnt_t++] = mesh_cur.mFaces[idx_face].mIndices[2]; - } - else if(mesh_cur.mFaces[idx_face].mNumIndices == 2) - { - mHelper_Mesh[idx_mesh]->Index_Line[indcnt_l++] = mesh_cur.mFaces[idx_face].mIndices[0]; - mHelper_Mesh[idx_mesh]->Index_Line[indcnt_l++] = mesh_cur.mFaces[idx_face].mIndices[1]; - } - else if(mesh_cur.mFaces[idx_face].mNumIndices == 1) - { - mHelper_Mesh[idx_mesh]->Index_Point[indcnt_p++] = mesh_cur.mFaces[idx_face].mIndices[0]; - } - }// for(size_t idx_face = 0; idx_face < mesh_cur.mNumFaces; idx_face++) - }// if(mesh_cur.HasFaces()) - else - { - // If mesh has no faces then vertices can be just points set. - indcnt_p = mesh_cur.mNumVertices; - // Create helper - mHelper_Mesh[idx_mesh] = new SHelper_Mesh(indcnt_p, 0, 0, mesh_bbox); - // Fill indices arrays - for(size_t idx = 0; idx < indcnt_p; idx++) mHelper_Mesh[idx_mesh]->Index_Point[idx] = idx; - - }// if(mesh_cur.HasFaces()) else - }// for(size_t idx_mesh = 0; idx_mesh < mScene->mNumMeshes; idx_mesh++) - }// if(mScene->HasMeshes()) - - // - // Scene BBox - // - // For calculating right BBox we must walk through all nodes and apply transformation to meshes BBoxes - if(mHelper_Mesh_Quantity > 0) - { - bool first_assign = true; - aiMatrix4x4 mat_root; - - BBox_GetForNode(*mScene->mRootNode, mat_root, mScene_BBox, first_assign); - mScene_Center = mScene_BBox.Maximum + mScene_BBox.Minimum; - mScene_Center /= 2; - } - else - { - mScene_BBox = {{0, 0, 0}, {0, 0, 0}}; - mScene_Center = {0, 0, 0}; - }// if(mHelper_Mesh_Count > 0) else - - // - // Textures - // - ImportTextures(pScenePath); - - // - // Light sources - // - Lighting_Enable(); - // If scene has no lights then enable default - if(!mScene->HasLights()) - { - const GLfloat col_amb[4] = { 0.2, 0.2, 0.2, 1.0 }; - SLightParameters lp; - - lp.Type = aiLightSource_POINT; - lp.Ambient.r = col_amb[0], lp.Ambient.g = col_amb[1], lp.Ambient.b = col_amb[2], lp.Ambient.a = col_amb[3]; - lp.Diffuse = { 1.0, 1.0, 1.0, 1.0 }; - lp.Specular = lp.Diffuse; - lp.For.Point.Position = mScene_Center; - lp.For.Point.Attenuation_Constant = 1; - lp.For.Point.Attenuation_Linear = 0; - lp.For.Point.Attenuation_Quadratic = 0; - glLightModelfv(GL_LIGHT_MODEL_AMBIENT, col_amb); - Lighting_EditSource(0, lp); - emit SceneObject_LightSource("_default");// Light source will be enabled in signal handler. - } - else - { - for(size_t idx_light = 0; idx_light < mScene->mNumLights; idx_light++) - { - SLightParameters lp; - QString name; - const aiLight& light_cur = *mScene->mLights[idx_light]; - - auto col3_to_col4 = [](const aiColor3D& pCol3) -> aiColor4D { return aiColor4D(pCol3.r, pCol3.g, pCol3.b, 1.0); }; - - ///TODO: find light source node and apply all transformations - // General properties - name = light_cur.mName.C_Str(); - lp.Ambient = col3_to_col4(light_cur.mColorAmbient); - lp.Diffuse = col3_to_col4(light_cur.mColorDiffuse); - lp.Specular = col3_to_col4(light_cur.mColorSpecular); - lp.Type = light_cur.mType; - // Depend on type properties - switch(light_cur.mType) - { - case aiLightSource_DIRECTIONAL: - lp.For.Directional.Direction = light_cur.mDirection; - break; - case aiLightSource_POINT: - lp.For.Point.Position = light_cur.mPosition; - lp.For.Point.Attenuation_Constant = light_cur.mAttenuationConstant; - lp.For.Point.Attenuation_Linear = light_cur.mAttenuationLinear; - lp.For.Point.Attenuation_Quadratic = light_cur.mAttenuationQuadratic; - break; - case aiLightSource_SPOT: - lp.For.Spot.Position = light_cur.mPosition; - lp.For.Spot.Direction = light_cur.mDirection; - lp.For.Spot.Attenuation_Constant = light_cur.mAttenuationConstant; - lp.For.Spot.Attenuation_Linear = light_cur.mAttenuationLinear; - lp.For.Spot.Attenuation_Quadratic = light_cur.mAttenuationQuadratic; - lp.For.Spot.CutOff = light_cur.mAngleOuterCone; - break; - case aiLightSource_AMBIENT: - lp.For.Point.Position = light_cur.mPosition, lp.For.Point.Attenuation_Constant = 1, lp.For.Point.Attenuation_Linear = 0, lp.For.Point.Attenuation_Quadratic = 0; - name.append("_unsup_ambient"); - break; - case aiLightSource_AREA: - lp.For.Point.Position = light_cur.mPosition, lp.For.Point.Attenuation_Constant = 1, lp.For.Point.Attenuation_Linear = 0, lp.For.Point.Attenuation_Quadratic = 0; - name.append("_unsup_area"); - break; - case aiLightSource_UNDEFINED: - lp.For.Point.Position = light_cur.mPosition, lp.For.Point.Attenuation_Constant = 1, lp.For.Point.Attenuation_Linear = 0, lp.For.Point.Attenuation_Quadratic = 0; - name.append("_unsup_undefined"); - break; - default: - lp.For.Point.Position = light_cur.mPosition, lp.For.Point.Attenuation_Constant = 1, lp.For.Point.Attenuation_Linear = 0, lp.For.Point.Attenuation_Quadratic = 0; - name.append("_unsupported_invalid"); - break; - }// switch(light_cur.mType) - - // Add light source - // Use index if name is empty. - if (name.isEmpty()) { - name += QString( "%1" ).arg( idx_light ); - } - - Lighting_EditSource(idx_light, lp); - emit SceneObject_LightSource(name);// Light source will be enabled in signal handler. - }// for(size_t idx_light = 0; idx_light < mScene->mNumLights; idx_light++) - }// if(!mScene->HasLights()) else - - // - // Cameras - // - if(!mScene->HasCameras()) - { - mCamera_DefaultAdded = true; - mHelper_CameraDefault.SetDefault(); - // Calculate distance from camera to scene. Distance must be enoguh for that viewport contain whole scene. - const GLfloat tg_angle = tan(mCamera_FOVY / 2); - - GLfloat val_x = ((mScene_BBox.Maximum.x - mScene_BBox.Minimum.x) / 2) / (mCamera_Viewport_AspectRatio * tg_angle); - GLfloat val_y = ((mScene_BBox.Maximum.y - mScene_BBox.Minimum.y) / 2) / tg_angle; - GLfloat val_step = val_x; - - AssignIfGreater(val_step, val_y); - mHelper_CameraDefault.Translation_ToScene.Set(mScene_Center.x, mScene_Center.y, val_step + mScene_BBox.Maximum.z); - emit SceneObject_Camera("_default"); - } - else - { - mCamera_DefaultAdded = false; - for(size_t idx_cam = 0; idx_cam < mScene->mNumCameras; idx_cam++) - { - emit SceneObject_Camera(mScene->mCameras[idx_cam]->mName.C_Str()); - } - }// if(!mScene->HasCameras()) else -} - -void CGLView::Lighting_Enable() { - mLightingEnabled = true; - glEnable(GL_LIGHTING); -} - -void CGLView::Lighting_Disable() { - glDisable( GL_LIGHTING ); - mLightingEnabled = false; -} - -void CGLView::Lighting_EditSource(const size_t pLightNumber, const SLightParameters& pLightParameters) -{ - const size_t light_num = GL_LIGHT0 + pLightNumber; - - GLfloat farr[4]; - - if(pLightNumber >= GL_MAX_LIGHTS) return;///TODO: return value; - - // Ambient color - farr[0] = pLightParameters.Ambient.r; - farr[1] = pLightParameters.Ambient.g; - farr[2] = pLightParameters.Ambient.b; - farr[3] = pLightParameters.Ambient.a; - glLightfv(light_num, GL_AMBIENT, farr); - - // Diffuse color - farr[0] = pLightParameters.Diffuse.r; - farr[1] = pLightParameters.Diffuse.g; - farr[2] = pLightParameters.Diffuse.b; - farr[3] = pLightParameters.Diffuse.a; - glLightfv(light_num, GL_DIFFUSE, farr); - - // Specular color - farr[0] = pLightParameters.Specular.r; - farr[1] = pLightParameters.Specular.g; - farr[2] = pLightParameters.Specular.b; - farr[3] = pLightParameters.Specular.a; - glLightfv(light_num, GL_SPECULAR, farr); - - // Other parameters - switch(pLightParameters.Type) - { - case aiLightSource_DIRECTIONAL: - // Direction - farr[0] = pLightParameters.For.Directional.Direction.x, farr[1] = pLightParameters.For.Directional.Direction.y; - farr[2] = pLightParameters.For.Directional.Direction.z; farr[3] = 0; - glLightfv(light_num, GL_POSITION, farr); - break; - case aiLightSource_POINT: - // Position - farr[0] = pLightParameters.For.Point.Position.x, farr[1] = pLightParameters.For.Point.Position.y; - farr[2] = pLightParameters.For.Point.Position.z; farr[3] = 1; - glLightfv(light_num, GL_POSITION, farr); - // Attenuation - glLightf(light_num, GL_CONSTANT_ATTENUATION, pLightParameters.For.Point.Attenuation_Constant); - glLightf(light_num, GL_LINEAR_ATTENUATION, pLightParameters.For.Point.Attenuation_Linear); - glLightf(light_num, GL_QUADRATIC_ATTENUATION, pLightParameters.For.Point.Attenuation_Quadratic); - glLightf(light_num, GL_SPOT_CUTOFF, 180.0); - break; - case aiLightSource_SPOT: - // Position - farr[0] = pLightParameters.For.Spot.Position.x, farr[1] = pLightParameters.For.Spot.Position.y, farr[2] = pLightParameters.For.Spot.Position.z; farr[3] = 1; - glLightfv(light_num, GL_POSITION, farr); - // Attenuation - glLightf(light_num, GL_CONSTANT_ATTENUATION, pLightParameters.For.Spot.Attenuation_Constant); - glLightf(light_num, GL_LINEAR_ATTENUATION, pLightParameters.For.Spot.Attenuation_Linear); - glLightf(light_num, GL_QUADRATIC_ATTENUATION, pLightParameters.For.Spot.Attenuation_Quadratic); - // Spot specific - farr[0] = pLightParameters.For.Spot.Direction.x, farr[1] = pLightParameters.For.Spot.Direction.y, farr[2] = pLightParameters.For.Spot.Direction.z; farr[3] = 0; - glLightfv(light_num, GL_SPOT_DIRECTION, farr); - glLightf(light_num, GL_SPOT_CUTOFF, pLightParameters.For.Spot.CutOff); - break; - default:// For unknown light source types use point source. - // Position - farr[0] = pLightParameters.For.Point.Position.x, farr[1] = pLightParameters.For.Point.Position.y; - farr[2] = pLightParameters.For.Point.Position.z; farr[3] = 1; - glLightfv(light_num, GL_POSITION, farr); - // Attenuation - glLightf(light_num, GL_CONSTANT_ATTENUATION, 1); - glLightf(light_num, GL_LINEAR_ATTENUATION, 0); - glLightf(light_num, GL_QUADRATIC_ATTENUATION, 0); - glLightf(light_num, GL_SPOT_CUTOFF, 180.0); - break; - }// switch(pLightParameters.Type) -} - -void CGLView::Lighting_EnableSource(const size_t pLightNumber) { - if(pLightNumber >= GL_MAX_LIGHTS) return;///TODO: return value; - - glEnable(GL_LIGHT0 + pLightNumber); -} - -void CGLView::Lighting_DisableSource(const size_t pLightNumber) -{ - if(pLightNumber >= GL_MAX_LIGHTS) return;///TODO: return value; - - glDisable(GL_LIGHT0 + pLightNumber); -} - -void CGLView::Camera_Set(const size_t pCameraNumber) -{ - SHelper_Camera& hcam = mHelper_Camera;// reference with short name for conveniance. - aiVector3D up; - - if(mCamera_DefaultAdded || (pCameraNumber >= mScene->mNumCameras))// If default camera used then 'pCameraNumber' doesn't matter. - { - // Transformation parameters - hcam = mHelper_CameraDefault; - up.Set(0, 1, 0); - } - else - { - const aiCamera& camera_cur = *mScene->mCameras[pCameraNumber]; - const aiNode* camera_node; - - aiMatrix4x4 camera_mat; - aiQuaternion camera_quat_rot; - aiVector3D camera_tr; - - up = camera_cur.mUp; - // - // Try to get real coordinates of the camera. - // - // Find node - camera_node = mScene->mRootNode->FindNode(camera_cur.mName); - if(camera_node != nullptr) Matrix_NodeToRoot(camera_node, camera_mat); - - hcam.Position = camera_cur.mLookAt; - hcam.Target = camera_cur.mPosition; - hcam.Rotation_AroundCamera = aiMatrix4x4(camera_quat_rot.GetMatrix()); - hcam.Rotation_AroundCamera.Transpose(); - // get components of transformation matrix. - camera_mat.DecomposeNoScaling(camera_quat_rot, camera_tr); - hcam.Rotation_Scene = aiMatrix4x4(); - hcam.Translation_ToScene = camera_tr; - } - - // Load identity matrix - travel to world begin. - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - // Set camera and update picture - gluLookAt(hcam.Position.x, hcam.Position.y, hcam.Position.z, hcam.Target.x, hcam.Target.y, hcam.Target.z, up.x, up.y, up.z); -} - -void CGLView::Camera_RotateScene(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z, const aiMatrix4x4* pMatrix_Rotation_Initial) { - auto deg2rad = [](const GLfloat pDegree) -> GLfloat { - return pDegree * AI_MATH_PI / 180.0f; - }; - - aiMatrix4x4 mat_rot; - - mat_rot.FromEulerAnglesXYZ(deg2rad(pAngle_X), deg2rad(pAngle_Y), deg2rad(pAngle_Z)); - if(pMatrix_Rotation_Initial != nullptr) - mHelper_Camera.Rotation_Scene = *pMatrix_Rotation_Initial * mat_rot; - else - mHelper_Camera.Rotation_Scene *= mat_rot; -} - -void CGLView::Camera_Rotate(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z, const aiMatrix4x4* pMatrix_Rotation_Initial) -{ - auto deg2rad = [](const GLfloat pDegree) -> GLfloat { return pDegree * AI_MATH_PI / 180.0; }; - - aiMatrix4x4 mat_rot; - - mat_rot.FromEulerAnglesXYZ(deg2rad(pAngle_X), deg2rad(pAngle_Y), deg2rad(pAngle_Z)); - if(pMatrix_Rotation_Initial != nullptr) - mHelper_Camera.Rotation_AroundCamera = *pMatrix_Rotation_Initial * mat_rot; - else - mHelper_Camera.Rotation_AroundCamera *= mat_rot; -} - -void CGLView::Camera_Translate(const GLfloat pTranslate_X, const GLfloat pTranslate_Y, const GLfloat pTranslate_Z) -{ - aiVector3D vect_tr(pTranslate_X, pTranslate_Y, pTranslate_Z); - - vect_tr *= mHelper_Camera.Rotation_AroundCamera; - mHelper_Camera.Translation_ToScene += vect_tr; -} - -void CGLView::Camera_Matrix(aiMatrix4x4& pRotation_Camera, aiMatrix4x4& pRotation_Scene, aiVector3D& pTranslation_Camera) -{ - pRotation_Camera = mHelper_Camera.Rotation_AroundCamera; - pRotation_Scene = mHelper_Camera.Rotation_Scene; - pTranslation_Camera = mHelper_Camera.Translation_ToScene; -} diff --git a/tools/assimp_qt_viewer/glview.hpp b/tools/assimp_qt_viewer/glview.hpp deleted file mode 100644 index 3cdb1fd11..000000000 --- a/tools/assimp_qt_viewer/glview.hpp +++ /dev/null @@ -1,456 +0,0 @@ -/* ---------------------------------------------------------------------------- -Open Asset Import Library (assimp) ---------------------------------------------------------------------------- - -Copyright (c) 2006-2018, assimp team - - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following -conditions are met: - -* Redistributions of source code must retain the above -copyright notice, this list of conditions and the -following disclaimer. - -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the -following disclaimer in the documentation and/or other -materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its -contributors may be used to endorse or promote products -derived from this software without specific prior -written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------- -*/ - -#pragma once - -// Header files, Qt. -#include -#if ASSIMP_QT4_VIEWER -# include -#else -# include -# include -#endif // ASSIMP_QT4_VIEWER - -// Header files Assimp -#include - -/// \class CGLView -/// Class which hold and render scene. -#if ASSIMP_QT4_VIEWER -class CGLView : public QGLWidget -#else -class CGLView : public QOpenGLWidget, protected QOpenGLFunctions -#endif // ASSIMP_QT4_VIEWER -{ - Q_OBJECT - - /**********************************/ - /************* Types **************/ - /**********************************/ - -private: - - /// \struct SBBox - /// Bounding box for object. - struct SBBox - { - aiVector3D Minimum;///< Minimum values of coordinates. - aiVector3D Maximum;///< Maximum values of coordinates. - }; - - /// \struct SHelper_Mesh - /// Helper object for fast rendering of mesh (\ref aiMesh). - struct SHelper_Mesh - { - const size_t Quantity_Point;///< Quantity of points. - const size_t Quantity_Line;///< Quantity of lines. - const size_t Quantity_Triangle;///< Quantity of triangles. - GLuint* Index_Point;///< Array of indices for drawing points. - GLuint* Index_Line;///< Array of indices for drawing lines. - GLuint* Index_Triangle;///< Array of indices for drawing triangles. - - const SBBox BBox;///< BBox of mesh. - - /// \fn explicit SHelper_Mesh(const size_t pQuantity_Point, const size_t pQuantity_Line, const size_t pQuantity_Triangle, const SBBox& pBBox = {{0, 0, 0}, {0, 0, 0}}) - /// Constructor. - /// \param [in] pQuantity_Point - quantity of points. - /// \param [in] pQuantity_Line - quantity of lines. - /// \param [in] pQuantity_Triangle - quantity of triangles. - /// \param [in] pBBox - BBox of mesh. - explicit SHelper_Mesh(const size_t pQuantity_Point, const size_t pQuantity_Line, const size_t pQuantity_Triangle, const SBBox& pBBox = {{0, 0, 0}, {0, 0, 0}}); - - /// \fn ~SHelper_Mesh() - /// Destructor. - ~SHelper_Mesh(); - }; - - /// \struct SHelper_Camera - /// Information about position of the camera in space. - struct SHelper_Camera - { - aiVector3D Position;///< Coordinates of the camera. - aiVector3D Target;///< Target point of the camera. - // Transformation path: - // set Camera -> Rotation_AroundCamera -> Translation_ToScene -> Rotation_Scene -> draw Scene - aiMatrix4x4 Rotation_AroundCamera;///< Rotation matrix which set rotation angles of the scene around camera. - aiMatrix4x4 Rotation_Scene;///< Rotation matrix which set rotation angles of the scene around own center. - aiVector3D Translation_ToScene;///< Translation vector from camera to the scene. - - /// \fn void SetDefault() - /// Set default parameters of camera. - void SetDefault(); - }; - -public: - - /// \enum ELightType - /// Type of light source. - enum class ELightType { Directional, Point, Spot }; - - /// \struct SLightParameters - /// Parameters of light source. - struct SLightParameters - { - aiLightSourceType Type;///< Type of light source. - - aiColor4D Ambient;///< Ambient RGBA intensity of the light. - aiColor4D Diffuse;///< Diffuse RGBA intensity of the light. - aiColor4D Specular;///< Specular RGBA intensity of the light. - - union UFor - { - /// \struct SDirectional - /// Parameters of directional light source. - struct SDirectional - { - aiVector3D Direction; - - SDirectional() {} - } Directional; - - /// \struct SPoint - /// Parameters of point light source. - struct SPoint - { - aiVector3D Position; - GLfloat Attenuation_Constant; - GLfloat Attenuation_Linear; - GLfloat Attenuation_Quadratic; - - SPoint() {} - } Point; - - /// \struct SSpot - /// Parameters of spot light source. - struct SSpot - { - aiVector3D Position; - GLfloat Attenuation_Constant; - GLfloat Attenuation_Linear; - GLfloat Attenuation_Quadratic; - aiVector3D Direction; - GLfloat CutOff; - - SSpot() {} - } Spot; - - UFor() {} - } For; - - SLightParameters() {} - }; - - /**********************************/ - /************ Variables ***********/ - /**********************************/ - -private: - -#if !ASSIMP_QT4_VIEWER - // Qt5 widget has another behavior, so you must to know that you already made context are current. Yes, its a dirty hack. Better decision are welcome. - bool mGLContext_Current;///< Widget's GL-context made current. -#endif // ASSIMP_QT4_VIEWER - // Scene - const aiScene* mScene = nullptr;///< Copy of pointer to scene (\ref aiScene). - SBBox mScene_BBox;///< Bounding box of scene. - aiVector3D mScene_Center;///< Coordinates of center of the scene. - bool mScene_DrawBBox = false;///< Flag which control drawing scene BBox. - bool mScene_AxesEnabled = true;///< Flag which control drawing axes of the coordinate system. - // Meshes - size_t mHelper_Mesh_Quantity = 0;///< Quantity of meshes in scene. - SHelper_Mesh** mHelper_Mesh = nullptr;///< Array of pointers to helper objects for drawing mesh. Sequence of meshes are equivalent to \ref aiScene::mMeshes. - // Cameras - SHelper_Camera mHelper_Camera;///< Information about current camera placing in space. - SHelper_Camera mHelper_CameraDefault;///< Information about default camera initial placing in space. - bool mCamera_DefaultAdded = true;///< If true then scene has no defined cameras and default was added, if false - scene has defined cameras. - GLdouble mCamera_FOVY = 45.0;///< Specifies the field of view angle, in degrees, in the y direction. - GLdouble mCamera_Viewport_AspectRatio;///< Specifies the aspect ratio that determines the field of view in the x direction. The aspect ratio is the ratio of x (width) to y (height). - // Lighting - bool mLightingEnabled = false;///< If true then OpenGL lighting is enabled (glEnable(GL_LIGHTING)), if false - disabled. - ///TODO: map is goooood, but not for case when one image can be used in different materials with difference in: texture transformation, targeting of the - /// texture (ambient or emission, or even height map), texture properties. - QMap mTexture_IDMap;///< Map image filenames to textures ID's. - - /**********************************/ - /************ Functions ***********/ - /**********************************/ - -private: - - // Why in some cases pointers are used? Because: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36566 - template void AssignIfLesser(TArg* pBaseValue, const TArg pTestValue) { if(pTestValue < *pBaseValue) *pBaseValue = pTestValue; } - template void AssignIfGreater(TArg* pBaseValue, const TArg pTestValue) { if(pTestValue > *pBaseValue) *pBaseValue = pTestValue; } - - template void AssignIfLesser(TArg& pBaseValue, const TArg pTestValue) { if(pTestValue < pBaseValue) pBaseValue = pTestValue; } - template void AssignIfGreater(TArg& pBaseValue, const TArg pTestValue) { if(pTestValue > pBaseValue) pBaseValue = pTestValue; } - - /// \fn void Material_Apply(const aiMaterial* pMaterial) - /// Enable pointed material. - /// \param [in] pMaterial - pointer to material which must be used. - void Material_Apply(const aiMaterial* pMaterial); - - /// \fn void Matrix_NodeToRoot(const aiNode* pNode, aiMatrix4x4& pOutMatrix) - /// Calculate matrix for transforming coordinates from pointed node to root node (read as "global coordinate system"). - /// \param [in] pNode - pointer initial node from which relative coordintaes will be taken, - /// \param [out] pOutMatrix - matrix for transform relative coordinates in \ref pNode to coordinates in root node (\ref aiScene::mRootNode). - void Matrix_NodeToRoot(const aiNode* pNode, aiMatrix4x4& pOutMatrix); - - /// \fn void ImportTextures() - /// Import textures. - /// \param [in] pScenePath - path to the file of the scene. - void ImportTextures(const QString& pScenePath); - - /// \fn void BBox_GetForNode(const aiNode& pNode, const aiMatrix4x4& pParentNode_TransformationMatrix, SBBox& pNodeBBox, bool& pFirstAssign) - /// Calculate BBox for pointed node. Function walk thru child nodes and apply all transformations. - /// \param [in] pNode - reference to node for which needed BBox. - /// \param [in] pParent_TransformationMatrix - reference to parent (parent for pNode) transformation matrix. - /// \param [in,out] pNodeBBox - reference to where pNode BBox will be placed. It will expanded by child nodes BBoxes. - /// \param [in] pFirstAssign - means that pNodeBBox not contain valid BBox at now and assign ('=') will used for setting new value, If - /// false then \ref BBox_Extend will be used for setting new BBox. - void BBox_GetForNode(const aiNode& pNode, const aiMatrix4x4& pParent_TransformationMatrix, SBBox& pNodeBBox, bool& pFirstAssign); - - /// \fn void BBox_Extend(const SBBox& pChild, SBBox& pParent) - /// Check and if need - extend current node BBox with BBox of child node. - /// \param [in] pChild - reference to BBox which used for extend parent BBox. - /// \param [in.out] pParent - BBox which will be extended using child BBox. - void BBox_Extend(const SBBox& pChild, SBBox& pParent); - - /// \fn void BBox_GetVertices(const SBBox& pBBox, aiVector3D pVertices[8]) - /// Get vertices of a parallelepiped which is described by BBox. - /// \param [in] pBBox - input BBox. - /// \param [out] pVertices - array of vertices. - void BBox_GetVertices(const SBBox& pBBox, aiVector3D pVertices[8]); - - /// \fn void BBox_GetFromVertices(const aiVector3D* pVertices, const size_t pVerticesQuantity, SBBox& pBBox) - /// Calculate BBox for vertices array. - /// \param [in] pVertices - vertices array. - /// \param [in] pVerticesQuantity - quantity of vertices in array. If 0 then pBBox will be assigned with {{0, 0, 0}, {0, 0, 0}}. - /// \param [out] pBBox - calculated BBox. - void BBox_GetFromVertices(const aiVector3D* pVertices, const size_t pVerticesQuantity, SBBox& pBBox); - - /********************************************************************/ - /************************ Logging functions *************************/ - /********************************************************************/ - - /// \fn void LogInfo(const QString& pMessage) - /// Add message with severity "Warning" to log. - void LogInfo(const QString& pMessage); - - /// \fn void LogError(const QString& pMessage) - /// Add message with severity "Error" to log. - void LogError(const QString& pMessage); - - /********************************************************************/ - /************************** Draw functions **************************/ - /********************************************************************/ - - /// \fn void Draw_Node(const aiNode* pNode) - /// Apply node transformation and draw meshes assigned to this node. - /// \param [in] pNode - pointer to node for drawing (\ref aiNode). - void Draw_Node(const aiNode* pNode); - - /// \fn void Draw_Mesh(const size_t pMesh_Index) - /// Draw mesh. - /// \param [in] pMesh_Index - index of mesh which must be drawn. Index point to mesh in \ref mHelper_Mesh. - void Draw_Mesh(const size_t pMesh_Index); - - /// \fn void Draw_BBox(const SBBox& pBBox) - /// Draw bounding box using lines. - /// \param [in] pBBox - bounding box for drawing. - void Draw_BBox(const SBBox& pBBox); - - /********************************************************************/ - /*********************** Override functions ************************/ - /********************************************************************/ - -protected: - - /// \fn void drawCoordSystem() - /// Draw axes of the coordinate system. - void drawCoordSystem(); - - /// \fn void initializeGL() override - /// Override function to initialise OpenGL. - void initializeGL() override; - - /// \fn void resizeGL(int pWidth, int pHeight) override - /// \param [in] pWidth - new width of viewport. - /// \param [in] pHeight - new height of viewport. - void resizeGL(int pWidth, int pHeight) override; - - /// \fn void paintGL() override - /// Override function for rendering. - void paintGL() override; - -public: - - /********************************************************************/ - /********************** Constructor/Destructor **********************/ - /********************************************************************/ - - /// \fn explicit CGLView(QWidget* pParent) - /// Constructor. - /// \param [in] pParent - parent widget. - explicit CGLView(QWidget* pParent); - - /// \fn virtual ~CGLView() - /// Destructor. - virtual ~CGLView(); - - /********************************************************************/ - /********************* Scene control functions **********************/ - /********************************************************************/ - - /// \fn void FreeScene() - /// Free all helper objects data. - void FreeScene(); - - /// \fn void SetScene(const aiScene* pScene) - /// Set scene for rendering. - /// \param [in] pScene - pointer to scene. - /// \param [in] pScenePath - path to the file of the scene. - void SetScene(const aiScene* pScene, const QString& pScenePath); - - /// \fn void Enable_SceneBBox(const bool pEnable) - /// Enable drawing scene bounding box. - /// \param [in] pEnable - if true then bbox will be drawing, if false - will not be drawing. - void Enable_SceneBBox(const bool pEnable) { mScene_DrawBBox = pEnable; } - - /// \fn void Enable_Textures(const bool pEnable) - /// Control textures drawing. - /// \param [in] pEnable - if true then enable textures, false - disable textures. - void Enable_Textures(const bool pEnable); - - /// \fn void Enable_Axes(const bool pEnable) - /// Control axes drawing. - /// \param [in] pEnable - if true then enable axes, false - disable axes. - void Enable_Axes(const bool pEnable) { this->mScene_AxesEnabled = pEnable; } - - /********************************************************************/ - /******************** Lighting control functions ********************/ - /********************************************************************/ - - /// \fn void Lighting_Enable() - /// Enable OpenGL lighting. - void Lighting_Enable(); - - /// \fn void Lighting_Disable() - /// Disable OpenGL lighting. - void Lighting_Disable(); - - /// \fn void Lighting_EditSource(const size_t pLightNumber, const SLightParameters& pLightParameters) - /// Edit light source properties. - /// \param [in] pLightNumber - light source number. \ref aiScene::mLights. - /// \param [in] pLightParameters - light source parameters. - void Lighting_EditSource(const size_t pLightNumber, const SLightParameters& pLightParameters);///TODO: function set - - /// \fn void Lighting_EnableSource(const size_t pLightNumber) - /// Enable light source. - /// \param [in] pLightNumber - light source number. \ref aiScene::mLights. - void Lighting_EnableSource(const size_t pLightNumber); - - ///void Lighting_DisableSource(const size_t pLightNumber) - /// Disable light source, - /// \param [in] pLightNumber - light source number. \ref aiScene::mLights. - void Lighting_DisableSource(const size_t pLightNumber); - - /********************************************************************/ - /******************** Cameras control functions *********************/ - /********************************************************************/ - - /// \fn void Camera_Set(const size_t pCameraNumber) - /// Set view from pointed camera. - /// \param [in] pCamera_Index - index of the camera (\ref aiScene::mCameras). - void Camera_Set(const size_t pCameraNumber); - - /// \fn void Camera_RotateScene(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z, const aiMatrix4x4* pMatrix_Rotation_Initial) - /// Rotate scene around axisees. - /// \param [in] pAngle_X - specifies the angle of rotation around axis oX, in degrees. - /// \param [in] pAngle_Y - specifies the angle of rotation around axis oY, in degrees. - /// \param [in] pAngle_Z - specifies the angle of rotation around axis oZ, in degrees. - /// \param [in] pMatrix_Rotation_Initial - matrix from which calculates new transformation matrix. If not set (equal to nullptr) then current transformation matrix - /// will be used. - void Camera_RotateScene(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z, const aiMatrix4x4* pMatrix_Rotation_Initial = nullptr); - - /// \fn void Camera_Rotate(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z, const aiMatrix4x4* pMatrix_Rotation_Initial = nullptr) - /// Rotate camera around axisees. - /// \param [in] pAngle_X - specifies the angle of rotation around axis oX, in degrees. - /// \param [in] pAngle_Y - specifies the angle of rotation around axis oY, in degrees. - /// \param [in] pAngle_Z - specifies the angle of rotation around axis oZ, in degrees. - /// \param [in] pMatrix_Rotation_Initial - matrix from which calculates new transformation matrix. If not set (equal to nullptr) then current transformation matrix - /// will be used. - void Camera_Rotate(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z, const aiMatrix4x4* pMatrix_Rotation_Initial = nullptr); - - /// \fn void Camera_Translate(const size_t pTranslate_X, const size_t pTranslate_Y, const size_t pTranslate_Z) - /// Translate camera along axises. In local coordinates. - /// \param [in] pTranslate_X - specifies the X coordinate of translation vector. - /// \param [in] pTranslate_Y - specifies the Y coordinate of translation vector. - /// \param [in] pTranslate_Z - specifies the Z coordinate of translation vector. - void Camera_Translate(const GLfloat pTranslate_X, const GLfloat pTranslate_Y, const GLfloat pTranslate_Z); - - /// \fn void Camera_Matrix(aiMatrix4x4& pRotation_Camera, aiMatrix4x4& pRotation_Scene, aiVector3D& pTranslation_Camera) - /// Return data about camera position in world. - /// \param [out] pRotation_Camera - rotation matrix which set rotation angles of the scene around camera. - /// \param [out] pRotation_Scene - rotation matrix which set rotation angles of the scene around own center. - /// \param [out] pTranslation_Camera - translation vector from camera to the scene. - void Camera_Matrix(aiMatrix4x4& pRotation_Camera, aiMatrix4x4& pRotation_Scene, aiVector3D& pTranslation_Camera); - -signals: - - /// \fn void Paint_Finished(const size_t pPaintTime, const GLfloat pDistance) - ///< Signal. Emits when execution of \ref paintGL is end. - /// \param [out] pPaintTime_ms - time spent for rendering, in milliseconds. - /// \param [out] pDistance - distance between current camera and center of the scene. \sa SHelper_Camera::Translation_ToScene. - void Paint_Finished(const size_t pPaintTime_ms, const GLfloat pDistance); - - /// \fn void SceneObject_Camera(const QString& pName) - /// Signal. Emit for every camera found in scene. Also for default camera. - /// \param [out] pName - name of the camera. - void SceneObject_Camera(const QString& pName); - - /// \fn void SceneObject_LightSource(const QString& pName) - /// Signal. Emit for every light source found in scene. Also for default light source. - /// \param [out] pName - name of the light source. - void SceneObject_LightSource(const QString& pName); -};// class CGLView diff --git a/tools/assimp_qt_viewer/loggerview.hpp b/tools/assimp_qt_viewer/loggerview.hpp deleted file mode 100644 index 9011f671f..000000000 --- a/tools/assimp_qt_viewer/loggerview.hpp +++ /dev/null @@ -1,67 +0,0 @@ -/* ---------------------------------------------------------------------------- -Open Asset Import Library (assimp) ---------------------------------------------------------------------------- - -Copyright (c) 2006-2018, assimp team - - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following -conditions are met: - -* Redistributions of source code must retain the above -copyright notice, this list of conditions and the -following disclaimer. - -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the -following disclaimer in the documentation and/or other -materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its -contributors may be used to endorse or promote products -derived from this software without specific prior -written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------- -*/ -#pragma once - -// Header files, Assimp. -#include - -class QTextBrowser; - -/// @class CLoggerView -/// @brief GUI-stream for Assimp logging sub-sytem. Get data for logging and write it to output widget. -class CLoggerView final : public ::Assimp::LogStream { -public: - /// @brief The class constructor. - /// @param [in] pOutputWidget - pointer to output widget. - explicit CLoggerView( QTextBrowser* pOutputWidget ); - - /// @brief The class destructor. - virtual ~CLoggerView(); - - /// Write message to output widget. Used by Assimp. - /// \param [in] pMessage - message for displaying. - virtual void write(const char *pMessage); - -private: - QTextBrowser * mOutputWidget; ///< Widget for displaying messages. -}; diff --git a/tools/assimp_qt_viewer/mainwindow.cpp b/tools/assimp_qt_viewer/mainwindow.cpp deleted file mode 100644 index 82c2fcbe3..000000000 --- a/tools/assimp_qt_viewer/mainwindow.cpp +++ /dev/null @@ -1,466 +0,0 @@ -/* ---------------------------------------------------------------------------- -Open Asset Import Library (assimp) ---------------------------------------------------------------------------- - -Copyright (c) 2006-2018, assimp team - - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following -conditions are met: - -* Redistributions of source code must retain the above -copyright notice, this list of conditions and the -following disclaimer. - -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the -following disclaimer in the documentation and/or other -materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its -contributors may be used to endorse or promote products -derived from this software without specific prior -written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------- -*/ - -#include "mainwindow.hpp" -#include "ui_mainwindow.h" - -// Header files, Assimp. -#include -#include - -#ifndef __unused - #define __unused __attribute__((unused)) -#endif // __unused - -using namespace Assimp; - - -void MainWindow::ImportFile(const QString &pFileName) { - QTime time_begin = QTime::currentTime(); - - if ( mScene != nullptr ) { - mImporter.FreeScene(); - mGLView->FreeScene(); - } - - // Try to import scene. - mScene = mImporter.ReadFile(pFileName.toStdString(), aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_ValidateDataStructure | \ - aiProcess_GenUVCoords | aiProcess_TransformUVCoords | aiProcess_FlipUVs); - if ( mScene != nullptr ) { - ui->lblLoadTime->setText(QString::number(time_begin.secsTo(QTime::currentTime()))); - LogInfo("Import done: " + pFileName); - // Prepare widgets for new scene. - ui->leFileName->setText(pFileName.right(pFileName.length() - pFileName.lastIndexOf('/') - 1)); - ui->lstLight->clear(); - ui->lstCamera->clear(); - ui->cbxLighting->setChecked(true); mGLView->Lighting_Enable(); - ui->cbxBBox->setChecked(false); mGLView->Enable_SceneBBox(false); - ui->cbxTextures->setChecked(true); mGLView->Enable_Textures(true); - - // - // Fill info labels - // - // Cameras - ui->lblCameraCount->setText(QString::number(mScene->mNumCameras)); - // Lights - ui->lblLightCount->setText(QString::number(mScene->mNumLights)); - // Meshes, faces, vertices. - size_t qty_face = 0; - size_t qty_vert = 0; - - for(size_t idx_mesh = 0; idx_mesh < mScene->mNumMeshes; idx_mesh++) { - qty_face += mScene->mMeshes[idx_mesh]->mNumFaces; - qty_vert += mScene->mMeshes[idx_mesh]->mNumVertices; - } - - ui->lblMeshCount->setText(QString::number(mScene->mNumMeshes)); - ui->lblFaceCount->setText(QString::number(qty_face)); - ui->lblVertexCount->setText(QString::number(qty_vert)); - // Animation - if(mScene->mNumAnimations) - ui->lblHasAnimation->setText("yes"); - else - ui->lblHasAnimation->setText("no"); - - // - // Set scene for GL viewer. - // - mGLView->SetScene(mScene, pFileName); - // Select first camera - ui->lstCamera->setCurrentRow(0); - mGLView->Camera_Set(0); - // Scene is loaded, do first rendering. - LogInfo("Scene is ready for rendering."); -#if ASSIMP_QT4_VIEWER - mGLView->updateGL(); -#else - mGLView->update(); -#endif // ASSIMP_QT4_VIEWER - } - else - { - ResetSceneInfos(); - - QString errorMessage = QString("Error parsing \'%1\' : \'%2\'").arg(pFileName).arg(mImporter.GetErrorString()); - QMessageBox::critical(this, "Import error", errorMessage); - LogError(errorMessage); - }// if(mScene != nullptr) -} - -void MainWindow::ResetSceneInfos() -{ - ui->lblLoadTime->clear(); - ui->leFileName->clear(); - ui->lblMeshCount->setText("0"); - ui->lblFaceCount->setText("0"); - ui->lblVertexCount->setText("0"); - ui->lblCameraCount->setText("0"); - ui->lblLightCount->setText("0"); - ui->lblHasAnimation->setText("no"); -} - -/********************************************************************/ -/************************ Logging functions *************************/ -/********************************************************************/ - -void MainWindow::LogInfo(const QString& pMessage) -{ - Assimp::DefaultLogger::get()->info(pMessage.toStdString()); -} - -void MainWindow::LogError(const QString& pMessage) -{ - Assimp::DefaultLogger::get()->error(pMessage.toStdString()); -} - -/********************************************************************/ -/*********************** Override functions ************************/ -/********************************************************************/ - -void MainWindow::mousePressEvent(QMouseEvent* pEvent) -{ - const QPoint ms_pt = pEvent->pos(); - aiVector3D temp_v3; - - // Check if GLView is pointed. - if(childAt(ms_pt) == mGLView) - { - if(!mMouse_Transformation.Position_Pressed_Valid) - { - mMouse_Transformation.Position_Pressed_Valid = true;// set flag - // Store current transformation matrices. - mGLView->Camera_Matrix(mMouse_Transformation.Rotation_AroundCamera, mMouse_Transformation.Rotation_Scene, temp_v3); - } - - if(pEvent->button() & Qt::LeftButton) - mMouse_Transformation.Position_Pressed_LMB = ms_pt; - else if(pEvent->button() & Qt::RightButton) - mMouse_Transformation.Position_Pressed_RMB = ms_pt; - } - else - { - mMouse_Transformation.Position_Pressed_Valid = false; - } -} - -void MainWindow::mouseReleaseEvent(QMouseEvent *pEvent) -{ - if(pEvent->buttons() == 0) mMouse_Transformation.Position_Pressed_Valid = false; - -} - -void MainWindow::mouseMoveEvent(QMouseEvent* pEvent) -{ - if(mMouse_Transformation.Position_Pressed_Valid) - { - if(pEvent->buttons() & Qt::LeftButton) - { - GLfloat dx = 180 * GLfloat(pEvent->x() - mMouse_Transformation.Position_Pressed_LMB.x()) / mGLView->width(); - GLfloat dy = 180 * GLfloat(pEvent->y() - mMouse_Transformation.Position_Pressed_LMB.y()) / mGLView->height(); - - if(pEvent->modifiers() & Qt::ShiftModifier) - mGLView->Camera_RotateScene(dy, 0, dx, &mMouse_Transformation.Rotation_Scene);// Rotate around oX and oZ axises. - else - mGLView->Camera_RotateScene(dy, dx, 0, &mMouse_Transformation.Rotation_Scene);// Rotate around oX and oY axises. - - #if ASSIMP_QT4_VIEWER - mGLView->updateGL(); - #else - mGLView->update(); - #endif // ASSIMP_QT4_VIEWER - } - - if(pEvent->buttons() & Qt::RightButton) - { - GLfloat dx = 180 * GLfloat(pEvent->x() - mMouse_Transformation.Position_Pressed_RMB.x()) / mGLView->width(); - GLfloat dy = 180 * GLfloat(pEvent->y() - mMouse_Transformation.Position_Pressed_RMB.y()) / mGLView->height(); - - if(pEvent->modifiers() & Qt::ShiftModifier) - mGLView->Camera_Rotate(dy, 0, dx, &mMouse_Transformation.Rotation_AroundCamera);// Rotate around oX and oZ axises. - else - mGLView->Camera_Rotate(dy, dx, 0, &mMouse_Transformation.Rotation_AroundCamera);// Rotate around oX and oY axises. - - #if ASSIMP_QT4_VIEWER - mGLView->updateGL(); - #else - mGLView->update(); - #endif // ASSIMP_QT4_VIEWER - } - } -} - -void MainWindow::keyPressEvent(QKeyEvent* pEvent) -{ -GLfloat step; - - if(pEvent->modifiers() & Qt::ControlModifier) - step = 10; - else if(pEvent->modifiers() & Qt::AltModifier) - step = 100; - else - step = 1; - - if(pEvent->key() == Qt::Key_A) - mGLView->Camera_Translate(-step, 0, 0); - else if(pEvent->key() == Qt::Key_D) - mGLView->Camera_Translate(step, 0, 0); - else if(pEvent->key() == Qt::Key_W) - mGLView->Camera_Translate(0, step, 0); - else if(pEvent->key() == Qt::Key_S) - mGLView->Camera_Translate(0, -step, 0); - else if(pEvent->key() == Qt::Key_Up) - mGLView->Camera_Translate(0, 0, -step); - else if(pEvent->key() == Qt::Key_Down) - mGLView->Camera_Translate(0, 0, step); - -#if ASSIMP_QT4_VIEWER - mGLView->updateGL(); -#else - mGLView->update(); -#endif // ASSIMP_QT4_VIEWER -} - -/********************************************************************/ -/********************** Constructor/Destructor **********************/ -/********************************************************************/ - -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent), ui(new Ui::MainWindow), - mScene(nullptr) -{ - - // other variables - mMouse_Transformation.Position_Pressed_Valid = false; - - ui->setupUi(this); - // Create OpenGL widget - mGLView = new CGLView(this); - mGLView->setMinimumSize(800, 600); - mGLView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); - mGLView->setFocusPolicy(Qt::StrongFocus); - // Connect to GLView signals. - connect(mGLView, SIGNAL(Paint_Finished(size_t, GLfloat)), SLOT(Paint_Finished(size_t, GLfloat))); - connect(mGLView, SIGNAL(SceneObject_Camera(QString)), SLOT(SceneObject_Camera(QString))); - connect(mGLView, SIGNAL(SceneObject_LightSource(QString)), SLOT(SceneObject_LightSource(QString))); - // and add it to layout - ui->hlMainView->insertWidget(0, mGLView, 4); - // Create logger - mLoggerView = new CLoggerView(ui->tbLog); - DefaultLogger::create("", Logger::VERBOSE); - DefaultLogger::get()->attachStream(mLoggerView, DefaultLogger::Debugging | DefaultLogger::Info | DefaultLogger::Err | DefaultLogger::Warn); - - ResetSceneInfos(); -} - -MainWindow::~MainWindow() -{ -using namespace Assimp; - - DefaultLogger::get()->detatchStream(mLoggerView, DefaultLogger::Debugging | DefaultLogger::Info | DefaultLogger::Err | DefaultLogger::Warn); - DefaultLogger::kill(); - - if(mScene != nullptr) mImporter.FreeScene(); - if(mLoggerView != nullptr) delete mLoggerView; - if(mGLView != nullptr) delete mGLView; - delete ui; -} - -/********************************************************************/ -/****************************** Slots *******************************/ -/********************************************************************/ - -void MainWindow::Paint_Finished(const size_t pPaintTime_ms, const GLfloat pDistance) -{ - ui->lblRenderTime->setText(QString::number(pPaintTime_ms)); - ui->lblDistance->setText(QString::number(pDistance)); -} - -void MainWindow::SceneObject_Camera(const QString& pName) -{ - ui->lstCamera->addItem(pName); -} - -void MainWindow::SceneObject_LightSource(const QString& pName) -{ - ui->lstLight->addItem(pName); - // After item added "currentRow" is still contain old value (even '-1' if first item added). Because "currentRow"/"currentItem" is changed by user interaction, - // not by "addItem". So, "currentRow" must be set manually. - ui->lstLight->setCurrentRow(ui->lstLight->count() - 1); - // And after "selectAll" handler of "signal itemSelectionChanged" will get right "currentItem" and "currentRow" values. - ui->lstLight->selectAll(); -} - -void MainWindow::on_butOpenFile_clicked() { - aiString filter_temp; - mImporter.GetExtensionList( filter_temp ); - - QString filename, filter; - filter = filter_temp.C_Str(); - filter.replace(';', ' '); - filter.append(" ;; All (*.*)"); - filename = QFileDialog::getOpenFileName(this, "Choose the file", "", filter); - - if (!filename.isEmpty()) { - ImportFile( filename ); - } -} - -void MainWindow::on_butExport_clicked() -{ - using namespace Assimp; - -#ifndef ASSIMP_BUILD_NO_EXPORT - QString filename, filter, format_id; - Exporter exporter; - QTime time_begin; - aiReturn rv; - QStringList exportersList; - QMap exportersMap; - - - if(mScene == nullptr) - { - QMessageBox::critical(this, "Export error", "Scene is empty"); - - return; - } - - for (size_t i = 0; i < exporter.GetExportFormatCount(); ++i) - { - const aiExportFormatDesc* desc = exporter.GetExportFormatDescription(i); - exportersList.push_back(desc->id + QString(": ") + desc->description); - exportersMap.insert(desc->id, desc); - } - - // get an exporter - bool dialogSelectExporterOk; - QString selectedExporter = QInputDialog::getItem(this, "Export format", "Select the exporter : ", exportersList, 0, false, &dialogSelectExporterOk); - if (!dialogSelectExporterOk) - return; - - // build the filter - QString selectedId = selectedExporter.left(selectedExporter.indexOf(':')); - filter = QString("*.") + exportersMap[selectedId]->fileExtension; - - // get file path - filename = QFileDialog::getSaveFileName(this, "Set file name", "", filter); - // if it's canceled - if (filename == "") - return; - - // begin export - time_begin = QTime::currentTime(); - rv = exporter.Export(mScene, selectedId.toLocal8Bit(), filename.toLocal8Bit(), aiProcess_FlipUVs); - ui->lblExportTime->setText(QString::number(time_begin.secsTo(QTime::currentTime()))); - if(rv == aiReturn_SUCCESS) - LogInfo("Export done: " + filename); - else - { - QString errorMessage = QString("Export failed: ") + filename; - LogError(errorMessage); - QMessageBox::critical(this, "Export error", errorMessage); - } -#endif -} - -void MainWindow::on_cbxLighting_clicked(bool pChecked) -{ - if(pChecked) - mGLView->Lighting_Enable(); - else - mGLView->Lighting_Disable(); - - mGLView->update(); -} - -void MainWindow::on_lstLight_itemSelectionChanged() -{ -bool selected = ui->lstLight->isItemSelected(ui->lstLight->currentItem()); - - if(selected) - mGLView->Lighting_EnableSource(ui->lstLight->currentRow()); - else - mGLView->Lighting_DisableSource(ui->lstLight->currentRow()); - -#if ASSIMP_QT4_VIEWER - mGLView->updateGL(); -#else - mGLView->update(); -#endif // ASSIMP_QT4_VIEWER -} - -void MainWindow::on_lstCamera_clicked( const QModelIndex &) -{ - mGLView->Camera_Set(ui->lstLight->currentRow()); -#if ASSIMP_QT4_VIEWER - mGLView->updateGL(); -#else - mGLView->update(); -#endif // ASSIMP_QT4_VIEWER -} - -void MainWindow::on_cbxBBox_clicked(bool checked) -{ - mGLView->Enable_SceneBBox(checked); -#if ASSIMP_QT4_VIEWER - mGLView->updateGL(); -#else - mGLView->update(); -#endif // ASSIMP_QT4_VIEWER -} - -void MainWindow::on_cbxDrawAxes_clicked(bool checked) -{ - mGLView->Enable_Axes(checked); -#if ASSIMP_QT4_VIEWER - mGLView->updateGL(); -#else - mGLView->update(); -#endif // ASSIMP_QT4_VIEWER -} - -void MainWindow::on_cbxTextures_clicked(bool checked) -{ - mGLView->Enable_Textures(checked); - mGLView->update(); -} diff --git a/tools/assimp_qt_viewer/mainwindow.hpp b/tools/assimp_qt_viewer/mainwindow.hpp deleted file mode 100644 index 1ebd19996..000000000 --- a/tools/assimp_qt_viewer/mainwindow.hpp +++ /dev/null @@ -1,148 +0,0 @@ -/* ---------------------------------------------------------------------------- -Open Asset Import Library (assimp) ---------------------------------------------------------------------------- - -Copyright (c) 2006-2018, assimp team - - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following -conditions are met: - -* Redistributions of source code must retain the above -copyright notice, this list of conditions and the -following disclaimer. - -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the -following disclaimer in the documentation and/or other -materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its -contributors may be used to endorse or promote products -derived from this software without specific prior -written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------- -*/ - -#pragma once - -// Header files, Qt. -#if defined ASSIMP_QT4_VIEWER -# include -#else -# include -#endif - -// Header files, project. -#include "glview.hpp" -#include "loggerview.hpp" - -// Header files, Assimp. -#include -#include - -namespace Ui { - class MainWindow; -} - -/// \class MainWindow -/// Main window and algorithms. -class MainWindow : public QMainWindow { - Q_OBJECT - - struct SMouse_Transformation; - -public: - /// @brief The class constructor. - /// \param [in] pParent - pointer to parent widget. - explicit MainWindow( QWidget* pParent = 0 ); - - /// @brief The class destructor. - ~MainWindow(); - - /// Import scene from file. - /// \param [in] pFileName - path and name of the file. - void ImportFile(const QString& pFileName); - - /// Reset informations about the scene - void ResetSceneInfos(); - - /// Add message with severity "Warning" to log. - void LogInfo(const QString& pMessage); - - /// Add message with severity "Error" to log. - void LogError(const QString& pMessage); - -protected: - /// Override function which handles mouse event "button pressed". - /// \param [in] pEvent - pointer to event data. - void mousePressEvent(QMouseEvent* pEvent) override; - - /// Override function which handles mouse event "button released". - /// \param [in] pEvent - pointer to event data. - void mouseReleaseEvent(QMouseEvent *pEvent) override; - - /// Override function which handles mouse event "move". - /// \param [in] pEvent - pointer to event data. - void mouseMoveEvent(QMouseEvent* pEvent) override; - - /// Override function which handles key event "key pressed". - /// \param [in] pEvent - pointer to event data. - void keyPressEvent(QKeyEvent* pEvent) override; - -private slots: - /// Show paint/render time and distance between camera and center of the scene. - /// \param [in] pPaintTime_ms - paint time in milliseconds. - void Paint_Finished(const size_t pPaintTime_ms, const GLfloat pDistance); - - /// Add camera name to list. - /// \param [in] pName - name of the camera. - void SceneObject_Camera(const QString& pName); - - /// Add lighting source name to list. - /// \param [in] pName - name of the light source, - void SceneObject_LightSource(const QString& pName); - - void on_butOpenFile_clicked(); - void on_butExport_clicked(); - void on_cbxLighting_clicked(bool pChecked); - void on_lstLight_itemSelectionChanged(); - void on_lstCamera_clicked(const QModelIndex &index); - void on_cbxBBox_clicked(bool checked); - void on_cbxTextures_clicked(bool checked); - void on_cbxDrawAxes_clicked(bool checked); - -private: - Ui::MainWindow *ui; - CGLView *mGLView;///< Pointer to OpenGL render. - CLoggerView *mLoggerView;///< Pointer to logging object. - Assimp::Importer mImporter;///< Assimp importer. - const aiScene* mScene;///< Pointer to loaded scene (\ref aiScene). - - /// \struct SMouse_Transformation - /// Holds data about transformation of the scene/camera when mouse us used. - struct SMouse_Transformation { - bool Position_Pressed_Valid;///< Mouse button pressed on GLView. - QPoint Position_Pressed_LMB;///< Position where was pressed left mouse button. - QPoint Position_Pressed_RMB;///< Position where was pressed right mouse button. - aiMatrix4x4 Rotation_AroundCamera;///< Rotation matrix which set rotation angles of the scene around camera. - aiMatrix4x4 Rotation_Scene;///< Rotation matrix which set rotation angles of the scene around own center. - } mMouse_Transformation; -}; diff --git a/tools/assimp_qt_viewer/mainwindow.ui b/tools/assimp_qt_viewer/mainwindow.ui deleted file mode 100644 index 04208f585..000000000 --- a/tools/assimp_qt_viewer/mainwindow.ui +++ /dev/null @@ -1,544 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 641 - 778 - - - - MainWindow - - - - - - - - - QLayout::SetDefaultConstraint - - - - - - - - 0 - 0 - - - - Qt::NoFocus - - - File - - - - - - Qt::NoFocus - - - Open file - - - - - - - Qt::NoFocus - - - File name - - - - - - - - 160 - 16777215 - - - - Qt::NoFocus - - - false - - - true - - - - - - - Qt::NoFocus - - - Load time, s - - - - - - - Qt::NoFocus - - - - - - Qt::AlignCenter - - - - - - - Qt::NoFocus - - - Qt::Horizontal - - - - - - - Qt::NoFocus - - - Export - - - - - - - Qt::NoFocus - - - Export time, s - - - - - - - Qt::NoFocus - - - - - - - - - - - - - - 0 - 0 - - - - Qt::NoFocus - - - Info - - - - - - Qt::NoFocus - - - Render time, ms - - - - - - - Qt::NoFocus - - - - - - Qt::AlignCenter - - - - - - - Qt::NoFocus - - - Meshes - - - - - - - Qt::NoFocus - - - Faces - - - - - - - Qt::NoFocus - - - - - - Qt::AlignCenter - - - - - - - Qt::NoFocus - - - - - - Qt::AlignCenter - - - - - - - Qt::NoFocus - - - Vertices - - - - - - - Qt::NoFocus - - - - - - Qt::AlignCenter - - - - - - - Qt::NoFocus - - - Lights - - - - - - - Qt::NoFocus - - - Cameras - - - - - - - Qt::NoFocus - - - Animation - - - - - - - false - - - Qt::NoFocus - - - Shaders - - - - - - - Qt::NoFocus - - - - - - Qt::AlignCenter - - - - - - - Qt::NoFocus - - - - - - Qt::AlignCenter - - - - - - - false - - - Qt::NoFocus - - - - - - Qt::AlignCenter - - - - - - - Qt::NoFocus - - - - - - Qt::AlignCenter - - - - - - - Distance - - - - - - - - - - Qt::AlignCenter - - - - - - - - - - false - - - Qt::NoFocus - - - Dynamics - - - - - - Qt::NoFocus - - - Animation start - - - - - - - Qt::NoFocus - - - Animation stop - - - - - - - - - - - - - - Qt::NoFocus - - - 2 - - - - Log - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - Qt::NoFocus - - - - - - - - Lights and cameras - - - - - - Qt::NoFocus - - - Light sources of the scene - - - QAbstractItemView::SelectedClicked - - - false - - - QAbstractItemView::MultiSelection - - - - - - - Qt::NoFocus - - - Cameras of the scene - - - QAbstractItemView::NoEditTriggers - - - false - - - - - - - - Control - - - - - - Enable/Disable OpenGL lighting - - - Lighting - - - true - - - - - - - Scene BBox - - - - - - - Textures - - - - - - - Show Axes - - - true - - - - - - - - - - - - - - - - - installEventFilter() - -