Jelajahi Sumber

Merge branch 'master' into pugi_xml

Kim Kulling 6 tahun lalu
induk
melakukan
d48b93cf34
100 mengubah file dengan 13680 tambahan dan 2022 penghapusan
  1. 127 0
      .clang-format
  2. 2 0
      .github/FUNDING.yml
  3. 5 0
      .gitignore
  4. 2 1
      .travis.sh
  5. 3 0
      .travis.yml
  6. 26 0
      BUILDBINARIES_EXAMPLE.bat
  7. 22 7
      Build.md
  8. 32 17
      CMakeLists.txt
  9. 9 3
      Readme.md
  10. 13 5
      appveyor.yml
  11. 10 1
      assimpTargets-debug.cmake.in
  12. 11 1
      assimpTargets-release.cmake.in
  13. 4 5
      assimpTargets.cmake.in
  14. 7 3
      cmake-modules/Findassimp.cmake
  15. 1 1
      code/3DS/3DSConverter.cpp
  16. 0 2
      code/3DS/3DSLoader.cpp
  17. 2 6
      code/3MF/D3MFImporter.cpp
  18. 5 346
      code/3MF/D3MFOpcPackage.cpp
  19. 3 4
      code/3MF/D3MFOpcPackage.h
  20. 1 1
      code/AMF/AMFImporter.cpp
  21. 13 13
      code/AMF/AMFImporter_Postprocess.cpp
  22. 5 5
      code/Assjson/cencode.c
  23. 0 9
      code/Assjson/json_exporter.cpp
  24. 94 93
      code/B3D/B3DImporter.cpp
  25. 35 4
      code/CMakeLists.txt
  26. 1 1
      code/CSM/CSMLoader.cpp
  27. 101 69
      code/Collada/ColladaExporter.cpp
  28. 107 0
      code/Collada/ColladaHelper.cpp
  29. 34 12
      code/Collada/ColladaHelper.h
  30. 256 208
      code/Collada/ColladaLoader.cpp
  31. 5 8
      code/Collada/ColladaLoader.h
  32. 411 275
      code/Collada/ColladaParser.cpp
  33. 12 5
      code/Collada/ColladaParser.h
  34. 39 3
      code/Common/BaseImporter.cpp
  35. 31 2
      code/Common/DefaultIOStream.cpp
  36. 60 101
      code/Common/DefaultIOSystem.cpp
  37. 2 2
      code/Common/DefaultLogger.cpp
  38. 73 91
      code/Common/Exporter.cpp
  39. 159 127
      code/Common/Importer.cpp
  40. 6 0
      code/Common/ImporterRegistry.cpp
  41. 7 0
      code/Common/PostStepRegistry.cpp
  42. 50 0
      code/Common/SceneCombiner.cpp
  43. 11 9
      code/Common/Version.cpp
  44. 2 2
      code/Common/VertexTriangleAdjacency.cpp
  45. 539 0
      code/Common/ZipArchiveIOSystem.cpp
  46. 9 9
      code/Common/scene.cpp
  47. 4 3
      code/FBX/FBXCommon.h
  48. 8 0
      code/FBX/FBXCompileConfig.h
  49. 291 185
      code/FBX/FBXConverter.cpp
  50. 57 46
      code/FBX/FBXConverter.h
  51. 0 8
      code/FBX/FBXDocument.cpp
  52. 37 2
      code/FBX/FBXDocument.h
  53. 7 7
      code/FBX/FBXExportNode.cpp
  54. 1 5
      code/FBX/FBXExportProperty.cpp
  55. 120 75
      code/FBX/FBXExporter.cpp
  56. 1 1
      code/FBX/FBXExporter.h
  57. 102 107
      code/FBX/FBXImporter.cpp
  58. 11 14
      code/FBX/FBXMeshGeometry.cpp
  59. 3 4
      code/Importer/IFC/IFCCurve.cpp
  60. 4 3
      code/Importer/IFC/IFCGeometry.cpp
  61. 3 3
      code/Importer/IFC/IFCOpenings.cpp
  62. 0 1
      code/Irr/IRRMeshLoader.cpp
  63. 0 2
      code/LWO/LWOLoader.cpp
  64. 437 0
      code/M3D/M3DExporter.cpp
  65. 94 0
      code/M3D/M3DExporter.h
  66. 770 0
      code/M3D/M3DImporter.cpp
  67. 103 0
      code/M3D/M3DImporter.h
  68. 106 0
      code/M3D/M3DMaterials.h
  69. 142 0
      code/M3D/M3DWrapper.cpp
  70. 101 0
      code/M3D/M3DWrapper.h
  71. 5612 0
      code/M3D/m3d.h
  72. 1 1
      code/MD2/MD2Loader.cpp
  73. 1 1
      code/MD3/MD3Loader.cpp
  74. 2 1
      code/MD5/MD5Loader.cpp
  75. 1 1
      code/MD5/MD5Parser.cpp
  76. 1 1
      code/MDC/MDCLoader.cpp
  77. 600 0
      code/MDL/HalfLife/HL1FileData.h
  78. 64 0
      code/MDL/HalfLife/HL1ImportDefinitions.h
  79. 85 0
      code/MDL/HalfLife/HL1ImportSettings.h
  80. 1338 0
      code/MDL/HalfLife/HL1MDLLoader.cpp
  81. 241 0
      code/MDL/HalfLife/HL1MDLLoader.h
  82. 127 0
      code/MDL/HalfLife/HL1MeshTrivert.h
  83. 25 7
      code/MDL/HalfLife/HalfLifeMDLBaseHeader.h
  84. 95 0
      code/MDL/HalfLife/LogFunctions.h
  85. 180 0
      code/MDL/HalfLife/UniqueNameGenerator.cpp
  86. 81 0
      code/MDL/HalfLife/UniqueNameGenerator.h
  87. 61 31
      code/MDL/MDLLoader.cpp
  88. 11 17
      code/MDL/MDLLoader.h
  89. 3 3
      code/MDL/MDLMaterialLoader.cpp
  90. 7 24
      code/Material/MaterialSystem.cpp
  91. 1 4
      code/Obj/ObjFileImporter.cpp
  92. 2 2
      code/Obj/ObjFileParser.cpp
  93. 3 2
      code/Ply/PlyExporter.cpp
  94. 0 1
      code/Ply/PlyLoader.cpp
  95. 268 0
      code/PostProcessing/ArmaturePopulate.cpp
  96. 112 0
      code/PostProcessing/ArmaturePopulate.h
  97. 3 3
      code/PostProcessing/CalcTangentsProcess.cpp
  98. 3 3
      code/PostProcessing/ComputeUVMappingProcess.cpp
  99. 3 2
      code/PostProcessing/ConvertToLHProcess.h
  100. 0 1
      code/PostProcessing/FindInvalidDataProcess.cpp

+ 127 - 0
.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
+...

+ 2 - 0
.github/FUNDING.yml

@@ -0,0 +1,2 @@
+patreon: assimp
+ko_fi: kimkulling

+ 5 - 0
.gitignore

@@ -2,6 +2,11 @@
 build
 build
 .project
 .project
 *.kdev4*
 *.kdev4*
+.DS_Store
+
+# build artefacts
+*.o
+*.a
 
 
 # Visual Studio
 # Visual Studio
 *.sln
 *.sln

+ 2 - 1
.travis.sh

@@ -7,7 +7,8 @@
 #
 #
 function generate() {
 function generate() {
     OPTIONS="-DASSIMP_WERROR=ON"
     OPTIONS="-DASSIMP_WERROR=ON"
-
+    OPTIONS="$OPTIONS -DASSIMP_NO_EXPORT=NO"
+    
     if [ "$DISABLE_EXPORTERS" = "YES" ] ; then
     if [ "$DISABLE_EXPORTERS" = "YES" ] ; then
         OPTIONS="$OPTIONS -DASSIMP_NO_EXPORT=YES"
         OPTIONS="$OPTIONS -DASSIMP_NO_EXPORT=YES"
     else
     else

+ 3 - 0
.travis.yml

@@ -30,6 +30,9 @@ env:
     - secure: "lZ7pHQvl5dpZWzBQAaIMf0wqrvtcZ4wiZKeIZjf83TEsflW8+z0uTpIuN30ZV6Glth/Sq1OhLnTP5+N57fZU/1ebA5twHdvP4bS5CIUUg71/CXQZNl36xeaqvxsG/xRrdpKOsPdjAOsQ9KPTQulsX43XDLS7CasMiLvYOpqKcPc="
     - secure: "lZ7pHQvl5dpZWzBQAaIMf0wqrvtcZ4wiZKeIZjf83TEsflW8+z0uTpIuN30ZV6Glth/Sq1OhLnTP5+N57fZU/1ebA5twHdvP4bS5CIUUg71/CXQZNl36xeaqvxsG/xRrdpKOsPdjAOsQ9KPTQulsX43XDLS7CasMiLvYOpqKcPc="
     - PV=r8e PLATF=linux-x86_64 NDK_HOME=${TRAVIS_BUILD_DIR}/android-ndk-${PV} PATH=${PATH}:${NDK_HOME}
     - PV=r8e PLATF=linux-x86_64 NDK_HOME=${TRAVIS_BUILD_DIR}/android-ndk-${PV} PATH=${PATH}:${NDK_HOME}
 
 
+git:
+  depth: 1
+
 matrix:
 matrix:
   include:
   include:
     - os: linux
     - os: linux

+ 26 - 0
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

+ 22 - 7
Build.md

@@ -1,17 +1,31 @@
 # Build Instructions
 # 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
 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
 ```bash
 sudo apt-get install cmake
 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:
 Make sure you have a working git-installation. Open a command prompt and clone the Asset-Importer-Lib via:
 ```bash
 ```bash
 git clone https://github.com/assimp/assimp.git
 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/
 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:
 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.
 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 <https://stackoverflow.com/questions/40803170/cmake-uwp-using-cmake-to-build-universal-windows-app>
 See <https://stackoverflow.com/questions/40803170/cmake-uwp-using-cmake-to-build-universal-windows-app>
 
 
-## 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:
 Open a terminal and got to your repository. You can generate the makefiles and build the library via:
 
 
 ```bash
 ```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. 
 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 
  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.
 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.
 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.
 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:
 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_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
 - **BUILD_FRAMEWORK ( default OFF, MacOnly)**: Build package as Mac OS X Framework bundle
@@ -63,6 +77,7 @@ The cmake-build-environment provides options to configure the build. The followi
 - **ASSIMP_BUILD_SAMPLES ( default OFF )**: If the official samples are built as well (needs Glut).
 - **ASSIMP_BUILD_SAMPLES ( default OFF )**: If the official samples are built as well (needs Glut).
 - **ASSIMP_BUILD_TESTS ( default ON )**: If the test suite for Assimp is built in addition to the library.
 - **ASSIMP_BUILD_TESTS ( default ON )**: If the test suite for Assimp is built in addition to the library.
 - **ASSIMP_COVERALLS ( default OFF )**: Enable this to measure test coverage.
 - **ASSIMP_COVERALLS ( default OFF )**: Enable this to measure test coverage.
+- **ASSIMP_ERROR_MAX( default OFF)**: Enable all warnings.
 - **ASSIMP_WERROR( default OFF )**: Treat warnings as errors.
 - **ASSIMP_WERROR( default OFF )**: Treat warnings as errors.
 - **ASSIMP_ASAN ( default OFF )**: Enable AddressSanitizer.
 - **ASSIMP_ASAN ( default OFF )**: Enable AddressSanitizer.
 - **ASSIMP_UBSAN ( default OFF )**: Enable Undefined Behavior sanitizer.
 - **ASSIMP_UBSAN ( default OFF )**: Enable Undefined Behavior sanitizer.

+ 32 - 17
CMakeLists.txt

@@ -100,6 +100,10 @@ OPTION ( ASSIMP_COVERALLS
   "Enable this to measure test coverage."
   "Enable this to measure test coverage."
   OFF
   OFF
 )
 )
+OPTION ( ASSIMP_ERROR_MAX
+  "Enable all warnings."
+  OFF
+)
 OPTION ( ASSIMP_WERROR
 OPTION ( ASSIMP_WERROR
   "Treat warnings as errors."
   "Treat warnings as errors."
   OFF
   OFF
@@ -173,7 +177,6 @@ SET (ASSIMP_VERSION ${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}.${ASSIMP_VER
 SET (ASSIMP_SOVERSION 5)
 SET (ASSIMP_SOVERSION 5)
 
 
 SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" )
 SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" )
-
 if(NOT HUNTER_ENABLED)
 if(NOT HUNTER_ENABLED)
   # Enable C++11 support globally
   # Enable C++11 support globally
   set_property( GLOBAL PROPERTY CXX_STANDARD 11 )
   set_property( GLOBAL PROPERTY CXX_STANDARD 11 )
@@ -255,6 +258,7 @@ ELSEIF(MSVC)
   IF(MSVC12)
   IF(MSVC12)
     ADD_COMPILE_OPTIONS(/wd4351)
     ADD_COMPILE_OPTIONS(/wd4351)
   ENDIF()
   ENDIF()
+  SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG /Zi /Od")
 ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" )
 ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" )
   IF(NOT HUNTER_ENABLED)
   IF(NOT HUNTER_ENABLED)
     SET(CMAKE_CXX_FLAGS "-fPIC -std=c++11 ${CMAKE_CXX_FLAGS}")
     SET(CMAKE_CXX_FLAGS "-fPIC -std=c++11 ${CMAKE_CXX_FLAGS}")
@@ -272,22 +276,20 @@ ELSEIF( CMAKE_COMPILER_IS_MINGW )
     SET(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
     SET(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
     SET(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}")
     SET(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}")
   ENDIF()
   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}")
   SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}")
   ADD_DEFINITIONS( -U__STRICT_ANSI__ )
   ADD_DEFINITIONS( -U__STRICT_ANSI__ )
 ENDIF()
 ENDIF()
 
 
 IF ( IOS AND NOT HUNTER_ENABLED)
 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)
 ENDIF( IOS AND NOT HUNTER_ENABLED)
 
 
 IF (ASSIMP_COVERALLS)
 IF (ASSIMP_COVERALLS)
@@ -297,6 +299,16 @@ IF (ASSIMP_COVERALLS)
   SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
   SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
 ENDIF()
 ENDIF()
 
 
+IF (ASSIMP_ERROR_MAX)
+  MESSAGE(STATUS "Turning on all warnings")
+  IF (MSVC)
+    ADD_COMPILE_OPTIONS(/W4) # NB: there is a /Wall option, pedantic mode
+  ELSE()
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
+  ENDIF()
+ENDIF()
+
 IF (ASSIMP_WERROR)
 IF (ASSIMP_WERROR)
   MESSAGE(STATUS "Treating warnings as errors")
   MESSAGE(STATUS "Treating warnings as errors")
   IF (MSVC)
   IF (MSVC)
@@ -342,7 +354,7 @@ SET( ASSIMP_BIN_INSTALL_DIR "bin" CACHE STRING
 
 
 get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
 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")
   SET(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "Debug Postfix for lib, samples and tools")
 ELSE()
 ELSE()
   SET(CMAKE_DEBUG_POSTFIX "" CACHE STRING "Debug Postfix for lib, samples and tools")
   SET(CMAKE_DEBUG_POSTFIX "" CACHE STRING "Debug Postfix for lib, samples and tools")
@@ -394,6 +406,11 @@ IF(HUNTER_ENABLED)
   )
   )
 ELSE(HUNTER_ENABLED)
 ELSE(HUNTER_ENABLED)
   # cmake configuration files
   # 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}/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)
   CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimpTargets.cmake.in"         "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets.cmake" @ONLY IMMEDIATE)
   IF (is_multi_config)
   IF (is_multi_config)
@@ -560,17 +577,15 @@ ENDIF(NOT HUNTER_ENABLED)
 
 
 ADD_SUBDIRECTORY( code/ )
 ADD_SUBDIRECTORY( code/ )
 IF ( ASSIMP_BUILD_ASSIMP_TOOLS )
 IF ( ASSIMP_BUILD_ASSIMP_TOOLS )
+  # The viewer for windows only
   IF ( WIN32 AND DirectX_D3DX9_LIBRARY )
   IF ( WIN32 AND DirectX_D3DX9_LIBRARY )
     OPTION ( ASSIMP_BUILD_ASSIMP_VIEW "If the Assimp view tool is built. (requires DirectX)" ${DirectX_FOUND} )
     OPTION ( ASSIMP_BUILD_ASSIMP_VIEW "If the Assimp view tool is built. (requires DirectX)" ${DirectX_FOUND} )
     IF ( ASSIMP_BUILD_ASSIMP_VIEW )
     IF ( ASSIMP_BUILD_ASSIMP_VIEW )
       ADD_SUBDIRECTORY( tools/assimp_view/ )
       ADD_SUBDIRECTORY( tools/assimp_view/ )
     ENDIF ( ASSIMP_BUILD_ASSIMP_VIEW )
     ENDIF ( ASSIMP_BUILD_ASSIMP_VIEW )
   ENDIF ( WIN32 AND DirectX_D3DX9_LIBRARY )
   ENDIF ( WIN32 AND DirectX_D3DX9_LIBRARY )
-
+  # Te command line tool
   ADD_SUBDIRECTORY( tools/assimp_cmd/ )
   ADD_SUBDIRECTORY( tools/assimp_cmd/ )
-IF (NOT IOS)
-  ADD_SUBDIRECTORY( tools/assimp_qt_viewer/ )
-ENDIF (NOT IOS)
 ENDIF ( ASSIMP_BUILD_ASSIMP_TOOLS )
 ENDIF ( ASSIMP_BUILD_ASSIMP_TOOLS )
 
 
 IF ( ASSIMP_BUILD_SAMPLES)
 IF ( ASSIMP_BUILD_SAMPLES)

+ 9 - 3
Readme.md

@@ -60,13 +60,19 @@ __Importers__:
 - ENFF
 - ENFF
 - [FBX](https://en.wikipedia.org/wiki/FBX)
 - [FBX](https://en.wikipedia.org/wiki/FBX)
 - [glTF 1.0](https://en.wikipedia.org/wiki/GlTF#glTF_1.0) + GLB
 - [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
 - HMB
 - IFC-STEP
 - IFC-STEP
 - IRR / IRRMESH
 - IRR / IRRMESH
 - [LWO](https://en.wikipedia.org/wiki/LightWave_3D)
 - [LWO](https://en.wikipedia.org/wiki/LightWave_3D)
 - LWS
 - LWS
 - LXO
 - LXO
+- [M3D](https://bztsrc.gitlab.io/model3d)
 - MD2
 - MD2
 - MD3
 - MD3
 - MD5
 - MD5
@@ -120,12 +126,12 @@ __Exporters__:
 - FBX ( experimental )
 - FBX ( experimental )
 
 
 ### Building ###
 ### 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 ###
 ### Ports ###
 * [Android](port/AndroidJNI/README.md)
 * [Android](port/AndroidJNI/README.md)
 * [Python](port/PyAssimp/README.md)
 * [Python](port/PyAssimp/README.md)
-* [.NET](https://github.com/kebby/assimp-net)
+* [.NET](https://github.com/assimp/assimp-net)
 * [Pascal](port/AssimpPascal/Readme.md)
 * [Pascal](port/AssimpPascal/Readme.md)
 * [Javascript (Alpha)](https://github.com/makc/assimp2json)
 * [Javascript (Alpha)](https://github.com/makc/assimp2json)
 * [Unity 3d Plugin](https://www.assetstore.unity3d.com/en/#!/content/91777)
 * [Unity 3d Plugin](https://www.assetstore.unity3d.com/en/#!/content/91777)

+ 13 - 5
appveyor.yml

@@ -4,6 +4,8 @@
 # clone directory
 # clone directory
 clone_folder: c:\projects\assimp
 clone_folder: c:\projects\assimp
 
 
+clone_depth: 1
+
 # branches to build
 # branches to build
 branches:
 branches:
   # whitelist
   # whitelist
@@ -17,6 +19,8 @@ image:
   - Visual Studio 2013
   - Visual Studio 2013
   - Visual Studio 2015
   - Visual Studio 2015
   - Visual Studio 2017
   - Visual Studio 2017
+  - Visual Studio 2019
+  - MinGW  
     
     
 platform:
 platform:
   - Win32
   - Win32
@@ -27,11 +31,15 @@ configuration: Release
 install:
 install:
   - set PATH=C:\Ruby24-x64\bin;%PATH%
   - set PATH=C:\Ruby24-x64\bin;%PATH%
   - set CMAKE_DEFINES -DASSIMP_WERROR=ON
   - 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 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 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 "%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"
   - 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/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
   - 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
@@ -46,11 +54,11 @@ cache:
   - bin\.mtime_cache
   - bin\.mtime_cache
   
   
 before_build:
 before_build:
+  - echo NUMBER_OF_PROCESSORS=%NUMBER_OF_PROCESSORS%
   - ruby scripts\AppVeyor\mtime_cache -g scripts\AppVeyor\cacheglobs.txt -c bin\.mtime_cache\cache.json
   - ruby scripts\AppVeyor\mtime_cache -g scripts\AppVeyor\cacheglobs.txt -c bin\.mtime_cache\cache.json
   
   
-build:
-  parallel: true
-  project: Assimp.sln
+build_script:
+  cmake --build . --config Release -- /maxcpucount:2
   
   
 after_build:
 after_build:
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" (
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" (

+ 10 - 1
assimpTargets-debug.cmake.in

@@ -35,6 +35,8 @@ if(MSVC)
   endif()
   endif()
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" )
   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)
   if(ASSIMP_BUILD_SHARED_LIBS)
     set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@")
     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@")
     set(importLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_IMPORT_LIBRARY_SUFFIX@")
@@ -63,7 +65,14 @@ if(MSVC)
 else()
 else()
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" )
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" )
   if(ASSIMP_BUILD_SHARED_LIBS)
   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
     set_target_properties(assimp::assimp PROPERTIES
       IMPORTED_SONAME_DEBUG "${sharedLibraryName}"
       IMPORTED_SONAME_DEBUG "${sharedLibraryName}"
       IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/${sharedLibraryName}"
       IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/${sharedLibraryName}"

+ 11 - 1
assimpTargets-release.cmake.in

@@ -34,6 +34,8 @@ if(MSVC)
     endif()
     endif()
   endif()
   endif()
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" )
   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)
   if(ASSIMP_BUILD_SHARED_LIBS)
     set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@")
     set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@")
@@ -63,9 +65,17 @@ if(MSVC)
 else()
 else()
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" )
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" )
   if(ASSIMP_BUILD_SHARED_LIBS)
   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
     set_target_properties(assimp::assimp PROPERTIES
       IMPORTED_SONAME_RELEASE "${sharedLibraryName}"
       IMPORTED_SONAME_RELEASE "${sharedLibraryName}"
+
       IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/${sharedLibraryName}"
       IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/${sharedLibraryName}"
     )
     )
     list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp )
     list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp )

+ 4 - 5
assimpTargets.cmake.in

@@ -5,6 +5,9 @@ if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.5)
 endif()
 endif()
 cmake_policy(PUSH)
 cmake_policy(PUSH)
 cmake_policy(VERSION 2.6)
 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.
 # Generated CMake target import file.
 #----------------------------------------------------------------
 #----------------------------------------------------------------
@@ -51,11 +54,7 @@ if(_IMPORT_PREFIX STREQUAL "/")
 endif()
 endif()
 
 
 # Create imported target assimp::assimp
 # 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
 set_target_properties(assimp::assimp PROPERTIES
   COMPATIBLE_INTERFACE_STRING "assimp_MAJOR_VERSION"
   COMPATIBLE_INTERFACE_STRING "assimp_MAJOR_VERSION"

+ 7 - 3
cmake-modules/Findassimp.cmake

@@ -54,14 +54,18 @@ else(WIN32)
 
 
 	find_path(
 	find_path(
 	  assimp_INCLUDE_DIRS
 	  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(
 	find_library(
 	  assimp_LIBRARIES
 	  assimp_LIBRARIES
 	  NAMES assimp
 	  NAMES assimp
 	  PATHS /usr/local/lib/
 	  PATHS /usr/local/lib/
+	  PATHS /usr/lib64/
+	  PATHS /usr/lib/
 	)
 	)
 
 
 	if (assimp_INCLUDE_DIRS AND assimp_LIBRARIES)
 	if (assimp_INCLUDE_DIRS AND assimp_LIBRARIES)
@@ -78,4 +82,4 @@ else(WIN32)
 	  endif (assimp_FIND_REQUIRED)
 	  endif (assimp_FIND_REQUIRED)
 	endif (assimp_FOUND)
 	endif (assimp_FOUND)
 	
 	
-endif(WIN32)
+endif(WIN32)

+ 1 - 1
code/3DS/3DSConverter.cpp

@@ -72,7 +72,7 @@ void Discreet3DSImporter::ReplaceDefaultMaterial()
     unsigned int idx( NotSet );
     unsigned int idx( NotSet );
     for (unsigned int i = 0; i < mScene->mMaterials.size();++i)
     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 ) {
         for ( std::string::iterator it = s.begin(); it != s.end(); ++it ) {
             *it = static_cast< char >( ::tolower( *it ) );
             *it = static_cast< char >( ::tolower( *it ) );
         }
         }

+ 0 - 2
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
 #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
 
 
-// internal headers
 #include "3DSLoader.h"
 #include "3DSLoader.h"
-#include <assimp/Macros.h>
 #include <assimp/IOSystem.hpp>
 #include <assimp/IOSystem.hpp>
 #include <assimp/scene.h>
 #include <assimp/scene.h>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/DefaultLogger.hpp>

+ 2 - 6
code/3MF/D3MFImporter.cpp

@@ -50,6 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/importerdesc.h>
 #include <assimp/importerdesc.h>
 #include <assimp/StringComparison.h>
 #include <assimp/StringComparison.h>
 #include <assimp/StringUtils.h>
 #include <assimp/StringUtils.h>
+#include <assimp/ZipArchiveIOSystem.h>
 
 
 #include <string>
 #include <string>
 #include <vector>
 #include <vector>
@@ -58,11 +59,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <memory>
 #include <memory>
 
 
 #include "D3MFOpcPackage.h"
 #include "D3MFOpcPackage.h"
-#ifdef ASSIMP_USE_HUNTER
-#  include <minizip/unzip.h>
-#else
-#  include <unzip.h>
-#endif
 #include <assimp/irrXMLWrapper.h>
 #include <assimp/irrXMLWrapper.h>
 #include "3MFXmlTags.h"
 #include "3MFXmlTags.h"
 #include <assimp/fast_atof.h>
 #include <assimp/fast_atof.h>
@@ -453,7 +449,7 @@ bool D3MFImporter::CanRead(const std::string &filename, IOSystem *pIOHandler, bo
         if ( nullptr == pIOHandler ) {
         if ( nullptr == pIOHandler ) {
             return false;
             return false;
         }
         }
-        if ( !D3MF::D3MFOpcPackage::isZipArchive( pIOHandler, filename ) ) {
+        if ( !ZipArchiveIOSystem::isZipArchive( pIOHandler, filename ) ) {
             return false;
             return false;
         }
         }
         D3MF::D3MFOpcPackage opcPackage( pIOHandler, filename );
         D3MF::D3MFOpcPackage opcPackage( pIOHandler, filename );

+ 5 - 346
code/3MF/D3MFOpcPackage.cpp

@@ -49,6 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/IOSystem.hpp>
 #include <assimp/IOSystem.hpp>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/ai_assert.h>
 #include <assimp/ai_assert.h>
+#include <assimp/ZipArchiveIOSystem.h>
 
 
 #include <cstdlib>
 #include <cstdlib>
 #include <memory>
 #include <memory>
@@ -56,344 +57,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <map>
 #include <map>
 #include <algorithm>
 #include <algorithm>
 #include <cassert>
 #include <cassert>
-#ifdef ASSIMP_USE_HUNTER
-#  include <minizip/unzip.h>
-#else
-#  include <unzip.h>
-#endif
 #include "3MFXmlTags.h"
 #include "3MFXmlTags.h"
 
 
 namespace Assimp {
 namespace Assimp {
 
 
 namespace D3MF {
 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<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<uLong>(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<uLong>(io_stream->Write(buf, 1, size));
-}
-
-long IOSystem2Unzip::tell(voidpf /*opaque*/, voidpf stream) {
-    IOStream* io_stream = (IOStream*) stream;
-
-    return static_cast<long>(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<voidpf>(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<std::string> &rFileList);
-
-private:
-    bool mapArchive();
-
-private:
-    unzFile m_ZipFileHandle;
-    std::map<std::string, ZipFile*> 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<std::string, ZipFile*>::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<std::string, ZipFile*>::iterator it = m_ArchiveMap.find(pFile);
-
-    if(it != m_ArchiveMap.end()) {
-        result = static_cast<IOStream*>(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<std::string> &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<std::map<std::string, ZipFile*>::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<OpcPackageRelationship> OpcPackageRelationshipPtr;
 typedef std::shared_ptr<OpcPackageRelationship> OpcPackageRelationshipPtr;
@@ -453,7 +121,7 @@ public:
 D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
 D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
 : mRootStream(nullptr)
 : mRootStream(nullptr)
 , mZipArchive() {    
 , mZipArchive() {    
-    mZipArchive.reset( new D3MF::D3MFZipArchive( pIOHandler, rFile ) );    
+    mZipArchive.reset( new ZipArchiveIOSystem( pIOHandler, rFile ) );
     if(!mZipArchive->isOpen()) {
     if(!mZipArchive->isOpen()) {
         throw DeadlyImportError("Failed to open file " + rFile+ ".");
         throw DeadlyImportError("Failed to open file " + rFile+ ".");
     }
     }
@@ -481,14 +149,14 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
 
 
             ASSIMP_LOG_DEBUG(rootFile);
             ASSIMP_LOG_DEBUG(rootFile);
 
 
+            mZipArchive->Close(fileStream);
+
             mRootStream = mZipArchive->Open(rootFile.c_str());
             mRootStream = mZipArchive->Open(rootFile.c_str());
             ai_assert( mRootStream != nullptr );
             ai_assert( mRootStream != nullptr );
             if ( nullptr == mRootStream ) {
             if ( nullptr == mRootStream ) {
                 throw DeadlyExportError( "Cannot open root-file in archive : " + rootFile );
                 throw DeadlyExportError( "Cannot open root-file in archive : " + rootFile );
             }
             }
 
 
-            mZipArchive->Close( fileStream );
-
         } else if( file == D3MF::XmlTag::CONTENT_TYPES_ARCHIVE) {
         } else if( file == D3MF::XmlTag::CONTENT_TYPES_ARCHIVE) {
             ASSIMP_LOG_WARN_F("Ignored file of unsupported type CONTENT_TYPES_ARCHIVES",file);
             ASSIMP_LOG_WARN_F("Ignored file of unsupported type CONTENT_TYPES_ARCHIVES",file);
         } else {
         } else {
@@ -499,7 +167,7 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
 }
 }
 
 
 D3MFOpcPackage::~D3MFOpcPackage() {
 D3MFOpcPackage::~D3MFOpcPackage() {
-    // empty
+    mZipArchive->Close(mRootStream);
 }
 }
 
 
 IOStream* D3MFOpcPackage::RootStream() const {
 IOStream* D3MFOpcPackage::RootStream() const {
@@ -516,15 +184,6 @@ bool D3MFOpcPackage::validate() {
     return mZipArchive->Exists( ModelRef.c_str() );
     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::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream* stream) {
     std::unique_ptr<CIrrXML_IOStreamReader> xmlStream(new CIrrXML_IOStreamReader(stream));
     std::unique_ptr<CIrrXML_IOStreamReader> xmlStream(new CIrrXML_IOStreamReader(stream));
     std::unique_ptr<XmlReader> xml(irr::io::createIrrXMLReader(xmlStream.get()));
     std::unique_ptr<XmlReader> xml(irr::io::createIrrXMLReader(xmlStream.get()));

+ 3 - 4
code/3MF/D3MFOpcPackage.h

@@ -49,6 +49,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/irrXMLWrapper.h>
 #include <assimp/irrXMLWrapper.h>
 
 
 namespace Assimp {
 namespace Assimp {
+    class ZipArchiveIOSystem;
+
 namespace D3MF {
 namespace D3MF {
 
 
 using XmlReader = irr::io::IrrXMLReader ;
 using XmlReader = irr::io::IrrXMLReader ;
@@ -60,22 +62,19 @@ struct OpcPackageRelationship {
     std::string target;
     std::string target;
 };
 };
 
 
-class D3MFZipArchive;
-
 class D3MFOpcPackage {
 class D3MFOpcPackage {
 public:
 public:
     D3MFOpcPackage( IOSystem* pIOHandler, const std::string& rFile );
     D3MFOpcPackage( IOSystem* pIOHandler, const std::string& rFile );
     ~D3MFOpcPackage();
     ~D3MFOpcPackage();
     IOStream* RootStream() const;
     IOStream* RootStream() const;
     bool validate();
     bool validate();
-    static bool isZipArchive( IOSystem* pIOHandler, const std::string& rFile );
 
 
 protected:
 protected:
     std::string ReadPackageRootRelationship(IOStream* stream);
     std::string ReadPackageRootRelationship(IOStream* stream);
 
 
 private:
 private:
     IOStream* mRootStream;
     IOStream* mRootStream;
-    std::unique_ptr<D3MFZipArchive> mZipArchive;
+    std::unique_ptr<ZipArchiveIOSystem> mZipArchive;
 };
 };
 
 
 } // Namespace D3MF
 } // Namespace D3MF

+ 1 - 1
code/AMF/AMFImporter.cpp

@@ -83,7 +83,7 @@ void AMFImporter::Clear()
 	mMaterial_Converted.clear();
 	mMaterial_Converted.clear();
 	mTexture_Converted.clear();
 	mTexture_Converted.clear();
 	// Delete all elements
 	// Delete all elements
-	if(mNodeElement_List.size())
+	if(!mNodeElement_List.empty())
 	{
 	{
 		for(CAMFImporter_NodeElement* ne: mNodeElement_List) { delete ne; }
 		for(CAMFImporter_NodeElement* ne: mNodeElement_List) { delete ne; }
 
 

+ 13 - 13
code/AMF/AMFImporter_Postprocess.cpp

@@ -66,7 +66,7 @@ aiColor4D AMFImporter::SPP_Material::GetColor(const float /*pX*/, const float /*
     aiColor4D tcol;
     aiColor4D tcol;
 
 
 	// Check if stored data are supported.
 	// Check if stored data are supported.
-	if(Composition.size() != 0)
+	if(!Composition.empty())
 	{
 	{
 		throw DeadlyImportError("IME. GetColor for composition");
 		throw DeadlyImportError("IME. GetColor for composition");
 	}
 	}
@@ -321,7 +321,7 @@ void AMFImporter::PostprocessHelper_SplitFacesByTextureID(std::list<SComplexFace
     };
     };
 
 
 	pOutputList_Separated.clear();
 	pOutputList_Separated.clear();
-	if(pInputList.size() == 0) return;
+	if(pInputList.empty()) return;
 
 
 	do
 	do
 	{
 	{
@@ -334,19 +334,19 @@ void AMFImporter::PostprocessHelper_SplitFacesByTextureID(std::list<SComplexFace
 			{
 			{
 				auto it_old = it;
 				auto it_old = it;
 
 
-				it++;
+				++it;
 				face_list_cur.push_back(*it_old);
 				face_list_cur.push_back(*it_old);
 				pInputList.erase(it_old);
 				pInputList.erase(it_old);
 			}
 			}
 			else
 			else
 			{
 			{
-				it++;
+				++it;
 			}
 			}
 		}
 		}
 
 
-		if(face_list_cur.size() > 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<CAMFImporter_NodeElement_Metadata*>& metadataList, aiNode& sceneNode) const
 void AMFImporter::Postprocess_AddMetadata(const std::list<CAMFImporter_NodeElement_Metadata*>& metadataList, aiNode& sceneNode) const
@@ -712,7 +712,7 @@ std::list<unsigned int> mesh_idx;
 	}// for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child)
 	}// for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child)
 
 
 	// if meshes was created then assign new indices with current aiNode
 	// if meshes was created then assign new indices with current aiNode
-	if(mesh_idx.size() > 0)
+	if(!mesh_idx.empty())
 	{
 	{
 		std::list<unsigned int>::const_iterator mit = mesh_idx.begin();
 		std::list<unsigned int>::const_iterator mit = mesh_idx.begin();
 
 
@@ -787,7 +787,7 @@ std::list<aiNode*> ch_node;
 	}// for(const CAMFImporter_NodeElement* ne: pConstellation.Child)
 	}// for(const CAMFImporter_NodeElement* ne: pConstellation.Child)
 
 
 	// copy found aiNode's as children
 	// copy found aiNode's as children
-	if(ch_node.size() == 0) throw DeadlyImportError("<constellation> must have at least one <instance>.");
+	if(ch_node.empty()) throw DeadlyImportError("<constellation> must have at least one <instance>.");
 
 
 	size_t ch_idx = 0;
 	size_t ch_idx = 0;
 
 
@@ -883,13 +883,13 @@ nl_clean_loop:
 	if(node_list.size() > 1)
 	if(node_list.size() > 1)
 	{
 	{
 		// walk through all nodes
 		// walk through all nodes
-		for(std::list<aiNode*>::iterator nl_it = node_list.begin(); nl_it != node_list.end(); nl_it++)
+		for(std::list<aiNode*>::iterator nl_it = node_list.begin(); nl_it != node_list.end(); ++nl_it)
 		{
 		{
 			// and try to find them in another top nodes.
 			// and try to find them in another top nodes.
 			std::list<aiNode*>::const_iterator next_it = nl_it;
 			std::list<aiNode*>::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)
 				if((*next_it)->FindNode((*nl_it)->mName) != nullptr)
 				{
 				{
@@ -907,7 +907,7 @@ nl_clean_loop:
 	//
 	//
 	//
 	//
 	// Nodes
 	// Nodes
-	if(node_list.size() > 0)
+	if(!node_list.empty())
 	{
 	{
 		std::list<aiNode*>::const_iterator nl_it = node_list.begin();
 		std::list<aiNode*>::const_iterator nl_it = node_list.begin();
 
 
@@ -924,7 +924,7 @@ nl_clean_loop:
 
 
 	//
 	//
 	// Meshes
 	// Meshes
-	if(mesh_list.size() > 0)
+	if(!mesh_list.empty())
 	{
 	{
 		std::list<aiMesh*>::const_iterator ml_it = mesh_list.begin();
 		std::list<aiMesh*>::const_iterator ml_it = mesh_list.begin();
 
 

+ 5 - 5
code/Assjson/cencode.c

@@ -42,7 +42,7 @@ int base64_encode_block(const char* plaintext_in, int length_in, char* code_out,
 			{
 			{
 				state_in->result = result;
 				state_in->result = result;
 				state_in->step = step_A;
 				state_in->step = step_A;
-				return codechar - code_out;
+				return (int)(codechar - code_out);
 			}
 			}
 			fragment = *plainchar++;
 			fragment = *plainchar++;
 			result = (fragment & 0x0fc) >> 2;
 			result = (fragment & 0x0fc) >> 2;
@@ -53,7 +53,7 @@ int base64_encode_block(const char* plaintext_in, int length_in, char* code_out,
 			{
 			{
 				state_in->result = result;
 				state_in->result = result;
 				state_in->step = step_B;
 				state_in->step = step_B;
-				return codechar - code_out;
+				return (int)(codechar - code_out);
 			}
 			}
 			fragment = *plainchar++;
 			fragment = *plainchar++;
 			result |= (fragment & 0x0f0) >> 4;
 			result |= (fragment & 0x0f0) >> 4;
@@ -64,7 +64,7 @@ int base64_encode_block(const char* plaintext_in, int length_in, char* code_out,
 			{
 			{
 				state_in->result = result;
 				state_in->result = result;
 				state_in->step = step_C;
 				state_in->step = step_C;
-				return codechar - code_out;
+				return (int)(codechar - code_out);
 			}
 			}
 			fragment = *plainchar++;
 			fragment = *plainchar++;
 			result |= (fragment & 0x0c0) >> 6;
 			result |= (fragment & 0x0c0) >> 6;
@@ -81,7 +81,7 @@ int base64_encode_block(const char* plaintext_in, int length_in, char* code_out,
 		}
 		}
 	}
 	}
 	/* control should not reach here */
 	/* control should not reach here */
-	return codechar - code_out;
+	return (int)(codechar - code_out);
 }
 }
 
 
 int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
 int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
@@ -104,6 +104,6 @@ int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
 	}
 	}
 	*codechar++ = '\n';
 	*codechar++ = '\n';
 	
 	
-	return codechar - code_out;
+	return (int)(codechar - code_out);
 }
 }
 
 

+ 0 - 9
code/Assjson/json_exporter.cpp

@@ -34,15 +34,6 @@ namespace Assimp {
 
 
 void ExportAssimp2Json(const char*, Assimp::IOSystem*, const aiScene*, const Assimp::ExportProperties*);
 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
 // small utility class to simplify serializing the aiScene to Json
 class JSONWriter {
 class JSONWriter {
 public:
 public:

+ 94 - 93
code/B3D/B3DImporter.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2019, assimp team
 Copyright (c) 2006-2019, assimp team
 
 
-
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -78,7 +76,6 @@ static const aiImporterDesc desc = {
     "b3d"
     "b3d"
 };
 };
 
 
-// (fixme, Aramis) quick workaround to get rid of all those signed to unsigned warnings
 #ifdef _MSC_VER
 #ifdef _MSC_VER
 #	pragma warning (disable: 4018)
 #	pragma warning (disable: 4018)
 #endif
 #endif
@@ -86,10 +83,8 @@ static const aiImporterDesc desc = {
 //#define DEBUG_B3D
 //#define DEBUG_B3D
 
 
 template<typename T>
 template<typename T>
-void DeleteAllBarePointers(std::vector<T>& x)
-{
-    for(auto p : x)
-    {
+void DeleteAllBarePointers(std::vector<T>& x) {
+    for(auto p : x) {
         delete p;
         delete p;
     }
     }
 }
 }
@@ -102,10 +97,14 @@ B3DImporter::~B3DImporter()
 bool B3DImporter::CanRead( const std::string& pFile, IOSystem* /*pIOHandler*/, bool /*checkSig*/) const{
 bool B3DImporter::CanRead( const std::string& pFile, IOSystem* /*pIOHandler*/, bool /*checkSig*/) const{
 
 
     size_t pos=pFile.find_last_of( '.' );
     size_t pos=pFile.find_last_of( '.' );
-    if( pos==string::npos ) return false;
+    if( pos==string::npos ) {
+        return false;
+    }
 
 
     string ext=pFile.substr( pos+1 );
     string ext=pFile.substr( pos+1 );
-    if( ext.size()!=3 ) return false;
+    if( ext.size()!=3 ) {
+        return false;
+    }
 
 
     return (ext[0]=='b' || ext[0]=='B') && (ext[1]=='3') && (ext[2]=='d' || ext[2]=='D');
     return (ext[0]=='b' || ext[0]=='B') && (ext[1]=='3') && (ext[2]=='d' || ext[2]=='D');
 }
 }
@@ -117,30 +116,21 @@ const aiImporterDesc* B3DImporter::GetInfo () const
     return &desc;
     return &desc;
 }
 }
 
 
-#ifdef DEBUG_B3D
-    extern "C"{ void _stdcall AllocConsole(); }
-#endif
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void B3DImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler){
 void B3DImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler){
-
-#ifdef DEBUG_B3D
-    AllocConsole();
-    freopen( "conin$","r",stdin );
-    freopen( "conout$","w",stdout );
-    freopen( "conout$","w",stderr );
-    cout<<"Hello world from the B3DImporter!"<<endl;
-#endif
-
     std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
     std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
 
 
     // Check whether we can read from the file
     // Check whether we can read from the file
-    if( file.get() == NULL)
+    if( file.get() == nullptr) {
         throw DeadlyImportError( "Failed to open B3D file " + pFile + ".");
         throw DeadlyImportError( "Failed to open B3D file " + pFile + ".");
+    }
 
 
     // check whether the .b3d file is large enough to contain
     // check whether the .b3d file is large enough to contain
     // at least one chunk.
     // at least one chunk.
     size_t fileSize = file->FileSize();
     size_t fileSize = file->FileSize();
-    if( fileSize<8 ) throw DeadlyImportError( "B3D File is too small.");
+    if( fileSize<8 ) {
+        throw DeadlyImportError( "B3D File is too small.");
+    }
 
 
     _pos=0;
     _pos=0;
     _buf.resize( fileSize );
     _buf.resize( fileSize );
@@ -158,14 +148,17 @@ AI_WONT_RETURN void B3DImporter::Oops(){
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 AI_WONT_RETURN void B3DImporter::Fail( string str ){
 AI_WONT_RETURN void B3DImporter::Fail( string str ){
 #ifdef DEBUG_B3D
 #ifdef DEBUG_B3D
-    cout<<"Error in B3D file data: "<<str<<endl;
+    ASSIMP_LOG_ERROR_F("Error in B3D file data: ", str);
 #endif
 #endif
     throw DeadlyImportError( "B3D Importer - error in B3D file data: "+str );
     throw DeadlyImportError( "B3D Importer - error in B3D file data: "+str );
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 int B3DImporter::ReadByte(){
 int B3DImporter::ReadByte(){
-    if( _pos<_buf.size() ) return _buf[_pos++];
+    if( _pos<_buf.size() ) {
+        return _buf[_pos++];
+    }
+    
     Fail( "EOF" );
     Fail( "EOF" );
     return 0;
     return 0;
 }
 }
@@ -224,7 +217,9 @@ string B3DImporter::ReadString(){
     string str;
     string str;
     while( _pos<_buf.size() ){
     while( _pos<_buf.size() ){
         char c=(char)ReadByte();
         char c=(char)ReadByte();
-        if( !c ) return str;
+        if( !c ) {
+            return str;
+        }
         str+=c;
         str+=c;
     }
     }
     Fail( "EOF" );
     Fail( "EOF" );
@@ -238,7 +233,7 @@ string B3DImporter::ReadChunk(){
         tag+=char( ReadByte() );
         tag+=char( ReadByte() );
     }
     }
 #ifdef DEBUG_B3D
 #ifdef DEBUG_B3D
-//	cout<<"ReadChunk:"<<tag<<endl;
+    ASSIMP_LOG_DEBUG_F("ReadChunk: ", tag);
 #endif
 #endif
     unsigned sz=(unsigned)ReadInt();
     unsigned sz=(unsigned)ReadInt();
     _stack.push_back( _pos+sz );
     _stack.push_back( _pos+sz );
@@ -269,7 +264,6 @@ T *B3DImporter::to_array( const vector<T> &v ){
     return p;
     return p;
 }
 }
 
 
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 template<class T>
 template<class T>
 T **unique_to_array( vector<std::unique_ptr<T> > &v ){
 T **unique_to_array( vector<std::unique_ptr<T> > &v ){
@@ -283,7 +277,6 @@ T **unique_to_array( vector<std::unique_ptr<T> > &v ){
     return p;
     return p;
 }
 }
 
 
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void B3DImporter::ReadTEXS(){
 void B3DImporter::ReadTEXS(){
     while( ChunkSize() ){
     while( ChunkSize() ){
@@ -376,9 +369,13 @@ void B3DImporter::ReadVRTS(){
 
 
         v.vertex=ReadVec3();
         v.vertex=ReadVec3();
 
 
-        if( _vflags & 1 ) v.normal=ReadVec3();
+        if( _vflags & 1 ) {
+            v.normal=ReadVec3();
+        }
 
 
-        if( _vflags & 2 ) ReadQuat();	//skip v 4bytes...
+        if( _vflags & 2 ) {
+            ReadQuat();	//skip v 4bytes...
+        }
 
 
         for( int i=0;i<_tcsets;++i ){
         for( int i=0;i<_tcsets;++i ){
             float t[4]={0,0,0,0};
             float t[4]={0,0,0,0};
@@ -386,53 +383,55 @@ void B3DImporter::ReadVRTS(){
                 t[j]=ReadFloat();
                 t[j]=ReadFloat();
             }
             }
             t[1]=1-t[1];
             t[1]=1-t[1];
-            if( !i ) v.texcoords=aiVector3D( t[0],t[1],t[2] );
+            if( !i ) {
+                v.texcoords=aiVector3D( t[0],t[1],t[2] );
+            }
         }
         }
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void B3DImporter::ReadTRIS( int v0 ){
-    int matid=ReadInt();
-    if( matid==-1 ){
-        matid=0;
-    }else if( matid<0 || matid>=(int)_materials.size() ){
+void B3DImporter::ReadTRIS(int v0) {
+	int matid = ReadInt();
+	if (matid == -1) {
+		matid = 0;
+	} else if (matid < 0 || matid >= (int)_materials.size()) {
 #ifdef DEBUG_B3D
 #ifdef DEBUG_B3D
-        cout<<"material id="<<matid<<endl;
+		ASSIMP_LOG_ERROR_F("material id=", matid);
 #endif
 #endif
-        Fail( "Bad material id" );
-    }
+		Fail("Bad material id");
+	}
 
 
-    std::unique_ptr<aiMesh> mesh(new aiMesh);
+	std::unique_ptr<aiMesh> mesh(new aiMesh);
 
 
-    mesh->mMaterialIndex=matid;
-    mesh->mNumFaces=0;
-    mesh->mPrimitiveTypes=aiPrimitiveType_TRIANGLE;
+	mesh->mMaterialIndex = matid;
+	mesh->mNumFaces = 0;
+	mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
 
 
-    int n_tris=ChunkSize()/12;
-    aiFace *face=mesh->mFaces=new aiFace[n_tris];
+	int n_tris = ChunkSize() / 12;
+	aiFace *face = mesh->mFaces = new aiFace[n_tris];
 
 
-    for( int i=0;i<n_tris;++i ){
-        int i0=ReadInt()+v0;
-        int i1=ReadInt()+v0;
-        int i2=ReadInt()+v0;
-        if( i0<0 || i0>=(int)_vertices.size() || i1<0 || i1>=(int)_vertices.size() || i2<0 || i2>=(int)_vertices.size() ){
+	for (int i = 0; i < n_tris; ++i) {
+		int i0 = ReadInt() + v0;
+		int i1 = ReadInt() + v0;
+		int i2 = ReadInt() + v0;
+		if (i0 < 0 || i0 >= (int)_vertices.size() || i1 < 0 || i1 >= (int)_vertices.size() || i2 < 0 || i2 >= (int)_vertices.size()) {
 #ifdef DEBUG_B3D
 #ifdef DEBUG_B3D
-            cout<<"Bad triangle index: i0="<<i0<<", i1="<<i1<<", i2="<<i2<<endl;
+			ASSIMP_LOG_ERROR_F("Bad triangle index: i0=", i0, ", i1=", i1, ", i2=", i2);
 #endif
 #endif
-            Fail( "Bad triangle index" );
-            continue;
-        }
-        face->mNumIndices=3;
-        face->mIndices=new unsigned[3];
-        face->mIndices[0]=i0;
-        face->mIndices[1]=i1;
-        face->mIndices[2]=i2;
-        ++mesh->mNumFaces;
-        ++face;
-    }
-
-    _meshes.emplace_back( std::move(mesh) );
+			Fail("Bad triangle index");
+			continue;
+		}
+		face->mNumIndices = 3;
+		face->mIndices = new unsigned[3];
+		face->mIndices[0] = i0;
+		face->mIndices[1] = i1;
+		face->mIndices[2] = i2;
+		++mesh->mNumFaces;
+		++face;
+	}
+
+	_meshes.emplace_back(std::move(mesh));
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -453,29 +452,23 @@ void B3DImporter::ReadMESH(){
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void B3DImporter::ReadBONE( int id ){
-    while( ChunkSize() ){
-        int vertex=ReadInt();
-        float weight=ReadFloat();
-        if( vertex<0 || vertex>=(int)_vertices.size() ){
-            Fail( "Bad vertex index" );
-        }
-
-        Vertex &v=_vertices[vertex];
-        int i;
-        for( i=0;i<4;++i ){
-            if( !v.weights[i] ){
-                v.bones[i]=id;
-                v.weights[i]=weight;
-                break;
-            }
-        }
-#ifdef DEBUG_B3D
-        if( i==4 ){
-            cout<<"Too many bone weights"<<endl;
-        }
-#endif
-    }
+void B3DImporter::ReadBONE(int id) {
+	while (ChunkSize()) {
+		int vertex = ReadInt();
+		float weight = ReadFloat();
+		if (vertex < 0 || vertex >= (int)_vertices.size()) {
+			Fail("Bad vertex index");
+		}
+
+		Vertex &v = _vertices[vertex];
+		for (int i = 0; i < 4; ++i) {
+			if (!v.weights[i]) {
+				v.bones[i] = id;
+				v.weights[i] = weight;
+				break;
+			}
+		}
+	}
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -633,11 +626,15 @@ void B3DImporter::ReadBB3D( aiScene *scene ){
     }
     }
     ExitChunk();
     ExitChunk();
 
 
-    if( !_nodes.size() ) Fail( "No nodes" );
+    if( !_nodes.size() ) {
+        Fail( "No nodes" );
+    }
 
 
-    if( !_meshes.size() ) Fail( "No meshes" );
+    if( !_meshes.size() ) {
+        Fail( "No meshes" );
+    }
 
 
-    //Fix nodes/meshes/bones
+    // Fix nodes/meshes/bones
     for(size_t i=0;i<_nodes.size();++i ){
     for(size_t i=0;i<_nodes.size();++i ){
         aiNode *node=_nodes[i];
         aiNode *node=_nodes[i];
 
 
@@ -648,8 +645,12 @@ void B3DImporter::ReadBB3D( aiScene *scene ){
             int n_verts=mesh->mNumVertices=n_tris * 3;
             int n_verts=mesh->mNumVertices=n_tris * 3;
 
 
             aiVector3D *mv=mesh->mVertices=new aiVector3D[ n_verts ],*mn=0,*mc=0;
             aiVector3D *mv=mesh->mVertices=new aiVector3D[ n_verts ],*mn=0,*mc=0;
-            if( _vflags & 1 ) mn=mesh->mNormals=new aiVector3D[ n_verts ];
-            if( _tcsets ) mc=mesh->mTextureCoords[0]=new aiVector3D[ n_verts ];
+            if( _vflags & 1 ) {
+                mn=mesh->mNormals=new aiVector3D[ n_verts ];
+            }
+            if( _tcsets ) {
+                mc=mesh->mTextureCoords[0]=new aiVector3D[ n_verts ];
+            }
 
 
             aiFace *face=mesh->mFaces;
             aiFace *face=mesh->mFaces;
 
 

+ 35 - 4
code/CMakeLists.txt

@@ -66,6 +66,7 @@ SET( PUBLIC_HEADERS
   ${HEADER_PATH}/color4.h
   ${HEADER_PATH}/color4.h
   ${HEADER_PATH}/color4.inl
   ${HEADER_PATH}/color4.inl
   ${CMAKE_CURRENT_BINARY_DIR}/../include/assimp/config.h
   ${CMAKE_CURRENT_BINARY_DIR}/../include/assimp/config.h
+  ${HEADER_PATH}/commonMetaData.h
   ${HEADER_PATH}/defs.h
   ${HEADER_PATH}/defs.h
   ${HEADER_PATH}/Defines.h
   ${HEADER_PATH}/Defines.h
   ${HEADER_PATH}/cfileio.h
   ${HEADER_PATH}/cfileio.h
@@ -104,6 +105,7 @@ SET( PUBLIC_HEADERS
   ${HEADER_PATH}/Exporter.hpp
   ${HEADER_PATH}/Exporter.hpp
   ${HEADER_PATH}/DefaultIOStream.h
   ${HEADER_PATH}/DefaultIOStream.h
   ${HEADER_PATH}/DefaultIOSystem.h
   ${HEADER_PATH}/DefaultIOSystem.h
+  ${HEADER_PATH}/ZipArchiveIOSystem.h
   ${HEADER_PATH}/SceneCombiner.h
   ${HEADER_PATH}/SceneCombiner.h
   ${HEADER_PATH}/fast_atof.h
   ${HEADER_PATH}/fast_atof.h
   ${HEADER_PATH}/qnan.h
   ${HEADER_PATH}/qnan.h
@@ -136,7 +138,6 @@ SET( PUBLIC_HEADERS
   ${HEADER_PATH}/irrXMLWrapper.h
   ${HEADER_PATH}/irrXMLWrapper.h
   ${HEADER_PATH}/BlobIOSystem.h
   ${HEADER_PATH}/BlobIOSystem.h
   ${HEADER_PATH}/MathFunctions.h
   ${HEADER_PATH}/MathFunctions.h
-  ${HEADER_PATH}/Macros.h
   ${HEADER_PATH}/Exceptional.h
   ${HEADER_PATH}/Exceptional.h
   ${HEADER_PATH}/ByteSwapper.h
   ${HEADER_PATH}/ByteSwapper.h
 )
 )
@@ -172,6 +173,7 @@ SET( Common_SRCS
   Common/DefaultProgressHandler.h
   Common/DefaultProgressHandler.h
   Common/DefaultIOStream.cpp
   Common/DefaultIOStream.cpp
   Common/DefaultIOSystem.cpp
   Common/DefaultIOSystem.cpp
+  Common/ZipArchiveIOSystem.cpp
   Common/PolyTools.h
   Common/PolyTools.h
   Common/Importer.cpp
   Common/Importer.cpp
   Common/IFF.h
   Common/IFF.h
@@ -347,6 +349,7 @@ ADD_ASSIMP_IMPORTER( BVH
 )
 )
 
 
 ADD_ASSIMP_IMPORTER( COLLADA
 ADD_ASSIMP_IMPORTER( COLLADA
+  Collada/ColladaHelper.cpp
   Collada/ColladaHelper.h
   Collada/ColladaHelper.h
   Collada/ColladaLoader.cpp
   Collada/ColladaLoader.cpp
   Collada/ColladaLoader.h
   Collada/ColladaLoader.h
@@ -406,6 +409,20 @@ ADD_ASSIMP_IMPORTER( LWS
   LWS/LWSLoader.h
   LWS/LWSLoader.h
 )
 )
 
 
+ADD_ASSIMP_IMPORTER( M3D
+  M3D/M3DMaterials.h
+  M3D/M3DImporter.h
+  M3D/M3DImporter.cpp
+  M3D/M3DWrapper.h
+  M3D/M3DWrapper.cpp
+  M3D/m3d.h
+)
+
+ADD_ASSIMP_EXPORTER( M3D
+  M3D/M3DExporter.h
+  M3D/M3DExporter.cpp
+)
+
 ADD_ASSIMP_IMPORTER( MD2
 ADD_ASSIMP_IMPORTER( MD2
   MD2/MD2FileData.h
   MD2/MD2FileData.h
   MD2/MD2Loader.cpp
   MD2/MD2Loader.cpp
@@ -439,6 +456,16 @@ ADD_ASSIMP_IMPORTER( MDL
   MDL/MDLLoader.cpp
   MDL/MDLLoader.cpp
   MDL/MDLLoader.h
   MDL/MDLLoader.h
   MDL/MDLMaterialLoader.cpp
   MDL/MDLMaterialLoader.cpp
+  MDL/HalfLife/HalfLifeMDLBaseHeader.h
+  MDL/HalfLife/HL1FileData.h
+  MDL/HalfLife/HL1MDLLoader.cpp
+  MDL/HalfLife/HL1MDLLoader.h
+  MDL/HalfLife/HL1ImportDefinitions.h
+  MDL/HalfLife/HL1ImportSettings.h
+  MDL/HalfLife/HL1MeshTrivert.h
+  MDL/HalfLife/LogFunctions.h
+  MDL/HalfLife/UniqueNameGenerator.cpp
+  MDL/HalfLife/UniqueNameGenerator.h
 )
 )
 
 
 SET( MaterialSystem_SRCS
 SET( MaterialSystem_SRCS
@@ -669,6 +696,8 @@ SET( PostProcessing_SRCS
   PostProcessing/MakeVerboseFormat.h
   PostProcessing/MakeVerboseFormat.h
   PostProcessing/ScaleProcess.cpp
   PostProcessing/ScaleProcess.cpp
   PostProcessing/ScaleProcess.h
   PostProcessing/ScaleProcess.h
+  PostProcessing/ArmaturePopulate.cpp
+  PostProcessing/ArmaturePopulate.h
   PostProcessing/GenBoundingBoxesProcess.cpp
   PostProcessing/GenBoundingBoxesProcess.cpp
   PostProcessing/GenBoundingBoxesProcess.h
   PostProcessing/GenBoundingBoxesProcess.h
 )
 )
@@ -688,8 +717,6 @@ ADD_ASSIMP_IMPORTER( Q3BSP
   Q3BSP/Q3BSPFileParser.cpp
   Q3BSP/Q3BSPFileParser.cpp
   Q3BSP/Q3BSPFileImporter.h
   Q3BSP/Q3BSPFileImporter.h
   Q3BSP/Q3BSPFileImporter.cpp
   Q3BSP/Q3BSPFileImporter.cpp
-  Q3BSP/Q3BSPZipArchive.h
-  Q3BSP/Q3BSPZipArchive.cpp
 )
 )
 
 
 ADD_ASSIMP_IMPORTER( RAW
 ADD_ASSIMP_IMPORTER( RAW
@@ -766,6 +793,8 @@ ADD_ASSIMP_EXPORTER( X3D
 )
 )
 
 
 ADD_ASSIMP_IMPORTER( GLTF
 ADD_ASSIMP_IMPORTER( GLTF
+  glTF/glTFCommon.h
+  glTF/glTFCommon.cpp
   glTF/glTFAsset.h
   glTF/glTFAsset.h
   glTF/glTFAsset.inl
   glTF/glTFAsset.inl
   glTF/glTFAssetWriter.h
   glTF/glTFAssetWriter.h
@@ -810,7 +839,7 @@ ADD_ASSIMP_IMPORTER( MMD
   MMD/MMDVmdParser.h
   MMD/MMDVmdParser.h
 )
 )
 
 
-ADD_ASSIMP_EXPORTER( Assjson
+ADD_ASSIMP_EXPORTER( ASSJSON
   Assjson/cencode.c
   Assjson/cencode.c
   Assjson/cencode.h
   Assjson/cencode.h
   Assjson/json_exporter.cpp
   Assjson/json_exporter.cpp
@@ -1057,6 +1086,8 @@ MESSAGE(STATUS "Disabled importer formats:${ASSIMP_IMPORTERS_DISABLED}")
 MESSAGE(STATUS "Enabled exporter formats:${ASSIMP_EXPORTERS_ENABLED}")
 MESSAGE(STATUS "Enabled exporter formats:${ASSIMP_EXPORTERS_ENABLED}")
 MESSAGE(STATUS "Disabled exporter formats:${ASSIMP_EXPORTERS_DISABLED}")
 MESSAGE(STATUS "Disabled exporter formats:${ASSIMP_EXPORTERS_DISABLED}")
 
 
+SOURCE_GROUP( include\\assimp    FILES ${PUBLIC_HEADERS} )
+
 SET( assimp_src
 SET( assimp_src
   # Assimp Files
   # Assimp Files
   ${Core_SRCS}
   ${Core_SRCS}

+ 1 - 1
code/CSM/CSMLoader.cpp

@@ -178,7 +178,7 @@ void CSMImporter::InternReadFile( const std::string& pFile,
                         *ot++ = *buffer++;
                         *ot++ = *buffer++;
 
 
                     *ot = '\0';
                     *ot = '\0';
-                    nda->mNodeName.length = (size_t)(ot-nda->mNodeName.data);
+                    nda->mNodeName.length = (ai_uint32)(ot-nda->mNodeName.data);
                 }
                 }
 
 
                 anim->mNumChannels = static_cast<unsigned int>(anims_temp.size());
                 anim->mNumChannels = static_cast<unsigned int>(anims_temp.size());

+ 101 - 69
code/Collada/ColladaExporter.cpp

@@ -45,6 +45,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 #include "ColladaExporter.h"
 #include "ColladaExporter.h"
 #include <assimp/Bitmap.h>
 #include <assimp/Bitmap.h>
+#include <assimp/commonMetaData.h>
+#include <assimp/MathFunctions.h>
 #include <assimp/fast_atof.h>
 #include <assimp/fast_atof.h>
 #include <assimp/SceneCombiner.h>
 #include <assimp/SceneCombiner.h>
 #include <assimp/StringUtils.h>
 #include <assimp/StringUtils.h>
@@ -91,6 +93,36 @@ void ExportSceneCollada(const char* pFile, IOSystem* pIOSystem, const aiScene* p
 
 
 } // end of namespace Assimp
 } // 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
 // Constructor for a specific scene to export
 ColladaExporter::ColladaExporter( const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file) 
 ColladaExporter::ColladaExporter( const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file) 
@@ -145,7 +177,7 @@ void ColladaExporter::WriteFile() {
     // useless Collada fu at the end, just in case we haven't had enough indirections, yet.
     // useless Collada fu at the end, just in case we haven't had enough indirections, yet.
     mOutput << startstr << "<scene>" << endstr;
     mOutput << startstr << "<scene>" << endstr;
     PushTag();
     PushTag();
-    mOutput << startstr << "<instance_visual_scene url=\"#" + XMLEscape(mScene->mRootNode->mName.C_Str()) + "\" />" << endstr;
+    mOutput << startstr << "<instance_visual_scene url=\"#" + XMLIDEncode(mScene->mRootNode->mName.C_Str()) + "\" />" << endstr;
     PopTag();
     PopTag();
     mOutput << startstr << "</scene>" << endstr;
     mOutput << startstr << "</scene>" << endstr;
     PopTag();
     PopTag();
@@ -155,7 +187,7 @@ void ColladaExporter::WriteFile() {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Writes the asset header
 // Writes the asset header
 void ColladaExporter::WriteHeader() {
 void ColladaExporter::WriteHeader() {
-    static const ai_real epsilon = ai_real( 0.00001 );
+    static const ai_real epsilon = Math::getEpsilon<ai_real>();
     static const aiQuaternion x_rot(aiMatrix3x3(
     static const aiQuaternion x_rot(aiMatrix3x3(
         0, -1,  0,
         0, -1,  0,
         1,  0,  0,
         1,  0,  0,
@@ -246,7 +278,7 @@ void ColladaExporter::WriteHeader() {
         mOutput << startstr << "<author>" << XMLEscape(value.C_Str()) << "</author>" << endstr;
         mOutput << startstr << "<author>" << XMLEscape(value.C_Str()) << "</author>" << endstr;
     }
     }
 
 
-    if (nullptr == meta || !meta->Get("AuthoringTool", value)) {
+    if (nullptr == meta || !meta->Get(AI_METADATA_SOURCE_GENERATOR, value)) {
         mOutput << startstr << "<authoring_tool>" << "Assimp Exporter" << "</authoring_tool>" << endstr;
         mOutput << startstr << "<authoring_tool>" << "Assimp Exporter" << "</authoring_tool>" << endstr;
     } else {
     } else {
         mOutput << startstr << "<authoring_tool>" << XMLEscape(value.C_Str()) << "</authoring_tool>" << endstr;
         mOutput << startstr << "<authoring_tool>" << XMLEscape(value.C_Str()) << "</authoring_tool>" << endstr;
@@ -256,7 +288,7 @@ void ColladaExporter::WriteHeader() {
         if (meta->Get("Comments", value)) {
         if (meta->Get("Comments", value)) {
             mOutput << startstr << "<comments>" << XMLEscape(value.C_Str()) << "</comments>" << endstr;
             mOutput << startstr << "<comments>" << XMLEscape(value.C_Str()) << "</comments>" << endstr;
         }
         }
-        if (meta->Get("Copyright", value)) {
+        if (meta->Get(AI_METADATA_SOURCE_COPYRIGHT, value)) {
             mOutput << startstr << "<copyright>" << XMLEscape(value.C_Str()) << "</copyright>" << endstr;
             mOutput << startstr << "<copyright>" << XMLEscape(value.C_Str()) << "</copyright>" << endstr;
         }
         }
         if (meta->Get("SourceData", value)) {
         if (meta->Get("SourceData", value)) {
@@ -317,7 +349,7 @@ void ColladaExporter::WriteTextures() {
 
 
             std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char*) texture->achFormatHint);
             std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char*) texture->achFormatHint);
 
 
-            std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + name, "wb"));
+            std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + mIOSystem->getOsSeparator() + name, "wb"));
             if(outfile == NULL) {
             if(outfile == NULL) {
                 throw DeadlyExportError("could not open output texture file: " + mPath + name);
                 throw DeadlyExportError("could not open output texture file: " + mPath + name);
             }
             }
@@ -355,9 +387,10 @@ void ColladaExporter::WriteCamerasLibrary() {
 void ColladaExporter::WriteCamera(size_t pIndex){
 void ColladaExporter::WriteCamera(size_t pIndex){
 
 
     const aiCamera *cam = mScene->mCameras[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 << "<camera id=\"" << idstrEscaped << "-camera\" name=\"" << idstrEscaped << "_name\" >" << endstr;
+    mOutput << startstr << "<camera id=\"" << cameraId << "-camera\" name=\"" << cameraName << "\" >" << endstr;
     PushTag();
     PushTag();
     mOutput << startstr << "<optics>" << endstr;
     mOutput << startstr << "<optics>" << endstr;
     PushTag();
     PushTag();
@@ -411,10 +444,11 @@ void ColladaExporter::WriteLightsLibrary() {
 void ColladaExporter::WriteLight(size_t pIndex){
 void ColladaExporter::WriteLight(size_t pIndex){
 
 
     const aiLight *light = mScene->mLights[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 << "<light id=\"" << idstrEscaped << "-light\" name=\""
-            << idstrEscaped << "_name\" >" << endstr;
+    mOutput << startstr << "<light id=\"" << lightId << "-light\" name=\""
+            << lightName << "\" >" << endstr;
     PushTag();
     PushTag();
     mOutput << startstr << "<technique_common>" << endstr;
     mOutput << startstr << "<technique_common>" << endstr;
     PushTag();
     PushTag();
@@ -585,7 +619,7 @@ static bool isalnum_C(char c) {
 void ColladaExporter::WriteImageEntry( const Surface& pSurface, const std::string& pNameAdd) {
 void ColladaExporter::WriteImageEntry( const Surface& pSurface, const std::string& pNameAdd) {
   if( !pSurface.texture.empty() )
   if( !pSurface.texture.empty() )
   {
   {
-    mOutput << startstr << "<image id=\"" << XMLEscape(pNameAdd) << "\">" << endstr;
+    mOutput << startstr << "<image id=\"" << XMLIDEncode(pNameAdd) << "\">" << endstr;
     PushTag();
     PushTag();
     mOutput << startstr << "<init_from>";
     mOutput << startstr << "<init_from>";
 
 
@@ -618,7 +652,7 @@ void ColladaExporter::WriteTextureColorEntry( const Surface& pSurface, const std
     }
     }
     else
     else
     {
     {
-      mOutput << startstr << "<texture texture=\"" << XMLEscape(pImageName) << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
+      mOutput << startstr << "<texture texture=\"" << XMLIDEncode(pImageName) << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
     }
     }
     PopTag();
     PopTag();
     mOutput << startstr << "</" << pTypeName << ">" << endstr;
     mOutput << startstr << "</" << pTypeName << ">" << endstr;
@@ -632,21 +666,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 surface is a texture, write out the sampler and the surface parameters necessary to reference the texture
   if( !pSurface.texture.empty() )
   if( !pSurface.texture.empty() )
   {
   {
-    mOutput << startstr << "<newparam sid=\"" << XMLEscape(pMatName) << "-" << pTypeName << "-surface\">" << endstr;
+    mOutput << startstr << "<newparam sid=\"" << XMLIDEncode(pMatName) << "-" << pTypeName << "-surface\">" << endstr;
     PushTag();
     PushTag();
     mOutput << startstr << "<surface type=\"2D\">" << endstr;
     mOutput << startstr << "<surface type=\"2D\">" << endstr;
     PushTag();
     PushTag();
-    mOutput << startstr << "<init_from>" << XMLEscape(pMatName) << "-" << pTypeName << "-image</init_from>" << endstr;
+    mOutput << startstr << "<init_from>" << XMLIDEncode(pMatName) << "-" << pTypeName << "-image</init_from>" << endstr;
     PopTag();
     PopTag();
     mOutput << startstr << "</surface>" << endstr;
     mOutput << startstr << "</surface>" << endstr;
     PopTag();
     PopTag();
     mOutput << startstr << "</newparam>" << endstr;
     mOutput << startstr << "</newparam>" << endstr;
 
 
-    mOutput << startstr << "<newparam sid=\"" << XMLEscape(pMatName) << "-" << pTypeName << "-sampler\">" << endstr;
+    mOutput << startstr << "<newparam sid=\"" << XMLIDEncode(pMatName) << "-" << pTypeName << "-sampler\">" << endstr;
     PushTag();
     PushTag();
     mOutput << startstr << "<sampler2D>" << endstr;
     mOutput << startstr << "<sampler2D>" << endstr;
     PushTag();
     PushTag();
-    mOutput << startstr << "<source>" << XMLEscape(pMatName) << "-" << pTypeName << "-surface</source>" << endstr;
+    mOutput << startstr << "<source>" << XMLIDEncode(pMatName) << "-" << pTypeName << "-surface</source>" << endstr;
     PopTag();
     PopTag();
     mOutput << startstr << "</sampler2D>" << endstr;
     mOutput << startstr << "</sampler2D>" << endstr;
     PopTag();
     PopTag();
@@ -698,11 +732,6 @@ void ColladaExporter::WriteMaterials()
         materials[a].name = std::string(name.C_Str()) + to_string(materialCountWithThisName);
         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;
     aiShadingMode shading = aiShadingMode_Flat;
     materials[a].shading_model = "phong";
     materials[a].shading_model = "phong";
@@ -767,7 +796,7 @@ void ColladaExporter::WriteMaterials()
     {
     {
       const Material& mat = *it;
       const Material& mat = *it;
       // this is so ridiculous it must be right
       // this is so ridiculous it must be right
-      mOutput << startstr << "<effect id=\"" << XMLEscape(mat.name) << "-fx\" name=\"" << XMLEscape(mat.name) << "\">" << endstr;
+      mOutput << startstr << "<effect id=\"" << XMLIDEncode(mat.name) << "-fx\" name=\"" << XMLEscape(mat.name) << "\">" << endstr;
       PushTag();
       PushTag();
       mOutput << startstr << "<profile_COMMON>" << endstr;
       mOutput << startstr << "<profile_COMMON>" << endstr;
       PushTag();
       PushTag();
@@ -818,9 +847,9 @@ void ColladaExporter::WriteMaterials()
     for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it )
     for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it )
     {
     {
       const Material& mat = *it;
       const Material& mat = *it;
-      mOutput << startstr << "<material id=\"" << XMLEscape(mat.name) << "\" name=\"" << mat.name << "\">" << endstr;
+      mOutput << startstr << "<material id=\"" << XMLIDEncode(mat.name) << "\" name=\"" << XMLEscape(mat.name) << "\">" << endstr;
       PushTag();
       PushTag();
-      mOutput << startstr << "<instance_effect url=\"#" << XMLEscape(mat.name) << "-fx\"/>" << endstr;
+      mOutput << startstr << "<instance_effect url=\"#" << XMLIDEncode(mat.name) << "-fx\"/>" << endstr;
       PopTag();
       PopTag();
       mOutput << startstr << "</material>" << endstr;
       mOutput << startstr << "</material>" << endstr;
     }
     }
@@ -849,8 +878,8 @@ void ColladaExporter::WriteControllerLibrary()
 void ColladaExporter::WriteController( size_t pIndex)
 void ColladaExporter::WriteController( size_t pIndex)
 {
 {
     const aiMesh* mesh = mScene->mMeshes[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 )
     if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
         return;
         return;
@@ -885,7 +914,7 @@ void ColladaExporter::WriteController( size_t pIndex)
     mOutput << startstr << "<Name_array id=\"" << idstrEscaped << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">";
     mOutput << startstr << "<Name_array id=\"" << idstrEscaped << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">";
 
 
     for( size_t i = 0; i < mesh->mNumBones; ++i )
     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 << "</Name_array>" << endstr;
     mOutput << "</Name_array>" << endstr;
 
 
@@ -1020,14 +1049,15 @@ void ColladaExporter::WriteGeometryLibrary()
 void ColladaExporter::WriteGeometry( size_t pIndex)
 void ColladaExporter::WriteGeometry( size_t pIndex)
 {
 {
     const aiMesh* mesh = mScene->mMeshes[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 )
     if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
         return;
         return;
 
 
     // opening tag
     // opening tag
-    mOutput << startstr << "<geometry id=\"" << idstrEscaped << "\" name=\"" << idstrEscaped << "_name\" >" << endstr;
+    mOutput << startstr << "<geometry id=\"" << geometryId << "\" name=\"" << geometryName << "\" >" << endstr;
     PushTag();
     PushTag();
 
 
     mOutput << startstr << "<mesh>" << endstr;
     mOutput << startstr << "<mesh>" << endstr;
@@ -1058,9 +1088,9 @@ void ColladaExporter::WriteGeometry( size_t pIndex)
 
 
     // assemble vertex structure
     // assemble vertex structure
     // Only write input for POSITION since we will write other as shared inputs in polygon definition
     // Only write input for POSITION since we will write other as shared inputs in polygon definition
-    mOutput << startstr << "<vertices id=\"" << idstrEscaped << "-vertices" << "\">" << endstr;
+    mOutput << startstr << "<vertices id=\"" << geometryId << "-vertices" << "\">" << endstr;
     PushTag();
     PushTag();
-    mOutput << startstr << "<input semantic=\"POSITION\" source=\"#" << idstrEscaped << "-positions\" />" << endstr;
+    mOutput << startstr << "<input semantic=\"POSITION\" source=\"#" << geometryId << "-positions\" />" << endstr;
     PopTag();
     PopTag();
     mOutput << startstr << "</vertices>" << endstr;
     mOutput << startstr << "</vertices>" << endstr;
 
 
@@ -1078,18 +1108,18 @@ void ColladaExporter::WriteGeometry( size_t pIndex)
     {
     {
         mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr;
         mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr;
         PushTag();
         PushTag();
-        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << idstrEscaped << "-vertices\" />" << endstr;
+        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
         if( mesh->HasNormals() )
         if( mesh->HasNormals() )
-            mOutput << startstr << "<input semantic=\"NORMAL\" source=\"#" << idstrEscaped << "-normals\" />" << endstr;
+            mOutput << startstr << "<input semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
         {
         {
             if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
             if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
-                mOutput << startstr << "<input semantic=\"TEXCOORD\" source=\"#" << idstrEscaped << "-tex" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+                mOutput << startstr << "<input semantic=\"TEXCOORD\" source=\"#" << geometryId << "-tex" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
         }
         }
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a )
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a )
         {
         {
             if( mesh->HasVertexColors(static_cast<unsigned int>(a) ) )
             if( mesh->HasVertexColors(static_cast<unsigned int>(a) ) )
-                mOutput << startstr << "<input semantic=\"COLOR\" source=\"#" << idstrEscaped << "-color" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+                mOutput << startstr << "<input semantic=\"COLOR\" source=\"#" << geometryId << "-color" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
         }
         }
 
 
         mOutput << startstr << "<p>";
         mOutput << startstr << "<p>";
@@ -1112,18 +1142,18 @@ void ColladaExporter::WriteGeometry( size_t pIndex)
     {
     {
         mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr;
         mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr;
         PushTag();
         PushTag();
-        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << idstrEscaped << "-vertices\" />" << endstr;
+        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
         if( mesh->HasNormals() )
         if( mesh->HasNormals() )
-            mOutput << startstr << "<input offset=\"0\" semantic=\"NORMAL\" source=\"#" << idstrEscaped << "-normals\" />" << endstr;
+            mOutput << startstr << "<input offset=\"0\" semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
         {
         {
             if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
             if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
-                mOutput << startstr << "<input offset=\"0\" semantic=\"TEXCOORD\" source=\"#" << idstrEscaped << "-tex" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+                mOutput << startstr << "<input offset=\"0\" semantic=\"TEXCOORD\" source=\"#" << geometryId << "-tex" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
         }
         }
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a )
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a )
         {
         {
             if( mesh->HasVertexColors(static_cast<unsigned int>(a) ) )
             if( mesh->HasVertexColors(static_cast<unsigned int>(a) ) )
-                mOutput << startstr << "<input offset=\"0\" semantic=\"COLOR\" source=\"#" << idstrEscaped << "-color" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+                mOutput << startstr << "<input offset=\"0\" semantic=\"COLOR\" source=\"#" << geometryId << "-color" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
         }
         }
 
 
         mOutput << startstr << "<vcount>";
         mOutput << startstr << "<vcount>";
@@ -1172,13 +1202,13 @@ void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataTy
             return;
             return;
     }
     }
 
 
-    std::string arrayId = pIdString + "-array";
+    std::string arrayId = XMLIDEncode(pIdString) + "-array";
 
 
-    mOutput << startstr << "<source id=\"" << XMLEscape(pIdString) << "\" name=\"" << XMLEscape(pIdString) << "\">" << endstr;
+    mOutput << startstr << "<source id=\"" << XMLIDEncode(pIdString) << "\" name=\"" << XMLEscape(pIdString) << "\">" << endstr;
     PushTag();
     PushTag();
 
 
     // source array
     // source array
-    mOutput << startstr << "<float_array id=\"" << XMLEscape(arrayId) << "\" count=\"" << pElementCount * floatsPerElement << "\"> ";
+    mOutput << startstr << "<float_array id=\"" << arrayId << "\" count=\"" << pElementCount * floatsPerElement << "\"> ";
     PushTag();
     PushTag();
 
 
     if( pType == FloatType_TexCoord2 )
     if( pType == FloatType_TexCoord2 )
@@ -1264,11 +1294,12 @@ void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataTy
 // Writes the scene library
 // Writes the scene library
 void ColladaExporter::WriteSceneLibrary()
 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 << "<library_visual_scenes>" << endstr;
     mOutput << startstr << "<library_visual_scenes>" << endstr;
     PushTag();
     PushTag();
-    mOutput << startstr << "<visual_scene id=\"" + scene_name_escaped + "\" name=\"" + scene_name_escaped + "\">" << endstr;
+    mOutput << startstr << "<visual_scene id=\"" + sceneId + "\" name=\"" + sceneName + "\">" << endstr;
     PushTag();
     PushTag();
 
 
     // start recursive write at the root node
     // start recursive write at the root node
@@ -1299,7 +1330,7 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 		idstr = idstr + ending;
 		idstr = idstr + ending;
 	}
 	}
 
 
-	const std::string idstrEscaped = XMLEscape(idstr);
+	const std::string idstrEscaped = XMLIDEncode(idstr);
 	
 	
 	mOutput << startstr << "<animation id=\"" + idstrEscaped + "\" name=\"" + animation_name_escaped + "\">" << endstr;
 	mOutput << startstr << "<animation id=\"" + idstrEscaped + "\" name=\"" + animation_name_escaped + "\">" << endstr;
 	PushTag();
 	PushTag();
@@ -1371,13 +1402,13 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 			}
 			}
 			
 			
 			const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-interpolation");
 			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 << "<source id=\"" << XMLEscape(node_idstr) << "\">" << endstr;
+			mOutput << startstr << "<source id=\"" << XMLIDEncode(node_idstr) << "\">" << endstr;
 			PushTag();
 			PushTag();
 			
 			
 			// source array
 			// source array
-			mOutput << startstr << "<Name_array id=\"" << XMLEscape(arrayId) << "\" count=\"" << names.size() << "\"> ";
+			mOutput << startstr << "<Name_array id=\"" << arrayId << "\" count=\"" << names.size() << "\"> ";
 			for( size_t a = 0; a < names.size(); ++a ) {
 			for( size_t a = 0; a < names.size(); ++a ) {
 				mOutput << names[a] << " ";
 				mOutput << names[a] << " ";
             }
             }
@@ -1386,7 +1417,7 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 			mOutput << startstr << "<technique_common>" << endstr;
 			mOutput << startstr << "<technique_common>" << endstr;
 			PushTag();
 			PushTag();
 
 
-			mOutput << startstr << "<accessor source=\"#" << XMLEscape(arrayId) << "\" count=\"" << names.size() << "\" stride=\"" << 1 << "\">" << endstr;
+			mOutput << startstr << "<accessor source=\"#" << arrayId << "\" count=\"" << names.size() << "\" stride=\"" << 1 << "\">" << endstr;
 			PushTag();
 			PushTag();
 			
 			
 			mOutput << startstr << "<param name=\"INTERPOLATION\" type=\"name\"></param>" << endstr;
 			mOutput << startstr << "<param name=\"INTERPOLATION\" type=\"name\"></param>" << endstr;
@@ -1408,12 +1439,12 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 		{
 		{
 		// samplers
 		// samplers
 			const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-sampler");
 			const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-sampler");
-			mOutput << startstr << "<sampler id=\"" << XMLEscape(node_idstr) << "\">" << endstr;
+			mOutput << startstr << "<sampler id=\"" << XMLIDEncode(node_idstr) << "\">" << endstr;
 			PushTag();
 			PushTag();
 			
 			
-			mOutput << startstr << "<input semantic=\"INPUT\" source=\"#" << XMLEscape( nodeAnim->mNodeName.data + std::string("_matrix-input") ) << "\"/>" << endstr;
-			mOutput << startstr << "<input semantic=\"OUTPUT\" source=\"#" << XMLEscape( nodeAnim->mNodeName.data + std::string("_matrix-output") ) << "\"/>" << endstr;
-			mOutput << startstr << "<input semantic=\"INTERPOLATION\" source=\"#" << XMLEscape( nodeAnim->mNodeName.data + std::string("_matrix-interpolation") ) << "\"/>" << endstr;
+			mOutput << startstr << "<input semantic=\"INPUT\" source=\"#" << XMLIDEncode( nodeAnim->mNodeName.data + std::string("_matrix-input") ) << "\"/>" << endstr;
+			mOutput << startstr << "<input semantic=\"OUTPUT\" source=\"#" << XMLIDEncode( nodeAnim->mNodeName.data + std::string("_matrix-output") ) << "\"/>" << endstr;
+			mOutput << startstr << "<input semantic=\"INTERPOLATION\" source=\"#" << XMLIDEncode( nodeAnim->mNodeName.data + std::string("_matrix-interpolation") ) << "\"/>" << endstr;
 			
 			
 			PopTag();
 			PopTag();
 			mOutput << startstr << "</sampler>" << endstr;
 			mOutput << startstr << "</sampler>" << endstr;
@@ -1425,7 +1456,7 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 		
 		
 		{
 		{
 		// channels
 		// channels
-			mOutput << startstr << "<channel source=\"#" << XMLEscape( nodeAnim->mNodeName.data + std::string("_matrix-sampler") ) << "\" target=\"" << XMLEscape(nodeAnim->mNodeName.data) << "/matrix\"/>" << endstr;
+			mOutput << startstr << "<channel source=\"#" << XMLIDEncode( nodeAnim->mNodeName.data + std::string("_matrix-sampler") ) << "\" target=\"" << XMLIDEncode(nodeAnim->mNodeName.data) << "/matrix\"/>" << endstr;
 		}
 		}
 	}
 	}
 	
 	
@@ -1436,8 +1467,6 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void ColladaExporter::WriteAnimationsLibrary()
 void ColladaExporter::WriteAnimationsLibrary()
 {
 {
-	const std::string scene_name_escaped = XMLEscape(mScene->mRootNode->mName.C_Str());
-	
 	if ( mScene->mNumAnimations > 0 ) {
 	if ( mScene->mNumAnimations > 0 ) {
 		mOutput << startstr << "<library_animations>" << endstr;
 		mOutput << startstr << "<library_animations>" << endstr;
 		PushTag();
 		PushTag();
@@ -1545,16 +1574,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 << "<node ";
 	mOutput << startstr << "<node ";
 	if(is_skeleton_root) {
 	if(is_skeleton_root) {
-		mOutput << "id=\"" << node_name_escaped << "\" " << (is_joint ? "sid=\"" + node_name_escaped +"\"" : "") ; // For now, only support one skeleton in a scene.
-		mFoundSkeletonRootNodeID = node_name_escaped;
+		mOutput << "id=\"" << node_id << "\" " << (is_joint ? "sid=\"" + node_id +"\"" : "") ; // For now, only support one skeleton in a scene.
+		mFoundSkeletonRootNodeID = node_id;
 	} else {
 	} else {
-		mOutput << "id=\"" << node_name_escaped << "\" " << (is_joint ? "sid=\"" + node_name_escaped +"\"": "") ;
+		mOutput << "id=\"" << node_id << "\" " << (is_joint ? "sid=\"" + node_id +"\"": "") ;
 	}
 	}
 	
 	
-    mOutput << " name=\"" << node_name_escaped
+    mOutput << " name=\"" << node_name
             << "\" type=\"" << node_type
             << "\" type=\"" << node_type
             << "\">" << endstr;
             << "\">" << endstr;
     PushTag();
     PushTag();
@@ -1593,14 +1623,14 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
         //check if it is a camera node
         //check if it is a camera node
         for(size_t i=0; i<mScene->mNumCameras; i++){
         for(size_t i=0; i<mScene->mNumCameras; i++){
             if(mScene->mCameras[i]->mName == pNode->mName){
             if(mScene->mCameras[i]->mName == pNode->mName){
-                mOutput << startstr <<"<instance_camera url=\"#" << node_name_escaped << "-camera\"/>" << endstr;
+                mOutput << startstr <<"<instance_camera url=\"#" << node_id << "-camera\"/>" << endstr;
                 break;
                 break;
             }
             }
         }
         }
         //check if it is a light node
         //check if it is a light node
         for(size_t i=0; i<mScene->mNumLights; i++){
         for(size_t i=0; i<mScene->mNumLights; i++){
             if(mScene->mLights[i]->mName == pNode->mName){
             if(mScene->mLights[i]->mName == pNode->mName){
-                mOutput << startstr <<"<instance_light url=\"#" << node_name_escaped << "-light\"/>" << endstr;
+                mOutput << startstr <<"<instance_light url=\"#" << node_id << "-light\"/>" << endstr;
                 break;
                 break;
             }
             }
         }
         }
@@ -1614,15 +1644,17 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
         if( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
         if( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
             continue;
             continue;
 
 
+        const std::string meshName = mesh->mName.length == 0 ? GetMeshId(pNode->mMeshes[a]) : mesh->mName.C_Str();
+
         if( mesh->mNumBones == 0 )
         if( mesh->mNumBones == 0 )
         {
         {
-            mOutput << startstr << "<instance_geometry url=\"#" << XMLEscape(GetMeshId( pNode->mMeshes[a])) << "\">" << endstr;
+            mOutput << startstr << "<instance_geometry url=\"#" << XMLIDEncode(meshName) << "\">" << endstr;
             PushTag();
             PushTag();
         }
         }
         else
         else
         {
         {
             mOutput << startstr
             mOutput << startstr
-                    << "<instance_controller url=\"#" << XMLEscape(GetMeshId( pNode->mMeshes[a])) << "-skin\">"
+                    << "<instance_controller url=\"#" << XMLIDEncode(meshName) << "-skin\">"
                     << endstr;
                     << endstr;
             PushTag();
             PushTag();
 
 
@@ -1630,7 +1662,7 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
 			// use the first bone to find skeleton root
 			// use the first bone to find skeleton root
 			const aiNode * skeletonRootBoneNode = findSkeletonRootNode( pScene, mesh );
 			const aiNode * skeletonRootBoneNode = findSkeletonRootNode( pScene, mesh );
 			if ( skeletonRootBoneNode ) {
 			if ( skeletonRootBoneNode ) {
-				mFoundSkeletonRootNodeID = XMLEscape( skeletonRootBoneNode->mName.C_Str() );
+				mFoundSkeletonRootNodeID = XMLIDEncode( skeletonRootBoneNode->mName.C_Str() );
 			}
 			}
             mOutput << startstr << "<skeleton>#" << mFoundSkeletonRootNodeID << "</skeleton>" << endstr;
             mOutput << startstr << "<skeleton>#" << mFoundSkeletonRootNodeID << "</skeleton>" << endstr;
         }
         }
@@ -1638,7 +1670,7 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
         PushTag();
         PushTag();
         mOutput << startstr << "<technique_common>" << endstr;
         mOutput << startstr << "<technique_common>" << endstr;
         PushTag();
         PushTag();
-        mOutput << startstr << "<instance_material symbol=\"defaultMaterial\" target=\"#" << XMLEscape(materials[mesh->mMaterialIndex].name) << "\">" << endstr;
+        mOutput << startstr << "<instance_material symbol=\"defaultMaterial\" target=\"#" << XMLIDEncode(materials[mesh->mMaterialIndex].name) << "\">" << endstr;
         PushTag();
         PushTag();
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
         {
         {
@@ -1671,4 +1703,4 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
 }
 }
 
 
 #endif
 #endif
-#endif
+#endif

+ 107 - 0
code/Collada/ColladaHelper.cpp

@@ -0,0 +1,107 @@
+/** Helper structures for the Collada loader */
+
+/*
+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 "ColladaHelper.h"
+
+#include <assimp/commonMetaData.h>
+#include <assimp/ParsingUtils.h>
+
+namespace Assimp {
+namespace Collada {
+
+const MetaKeyPairVector MakeColladaAssimpMetaKeys() {
+    MetaKeyPairVector result;
+    result.emplace_back("authoring_tool", AI_METADATA_SOURCE_GENERATOR);
+    result.emplace_back("copyright", AI_METADATA_SOURCE_COPYRIGHT);
+    return result;
+};
+
+const MetaKeyPairVector &GetColladaAssimpMetaKeys() {
+    static const MetaKeyPairVector result = MakeColladaAssimpMetaKeys();
+    return result;
+}
+
+const MetaKeyPairVector MakeColladaAssimpMetaKeysCamelCase() {
+    MetaKeyPairVector result = MakeColladaAssimpMetaKeys();
+    for (auto &val : result)
+    {
+        ToCamelCase(val.first);
+    }
+    return result;
+};
+
+const MetaKeyPairVector &GetColladaAssimpMetaKeysCamelCase()
+{
+    static const MetaKeyPairVector result = MakeColladaAssimpMetaKeysCamelCase();
+    return result;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert underscore_separated to CamelCase: "authoring_tool" becomes "AuthoringTool"
+void ToCamelCase(std::string &text)
+{
+    if (text.empty())
+        return;
+    // Capitalise first character
+    auto it = text.begin();
+    (*it) = ToUpper(*it);
+    ++it;
+    for (/*started above*/ ; it != text.end(); /*iterated below*/)
+    {
+        if ((*it) == '_')
+        {
+            it = text.erase(it);
+            if (it != text.end())
+                (*it) = ToUpper(*it);
+        }
+        else
+        {
+            // Make lower case
+            (*it) = ToLower(*it);
+            ++it;               
+        }
+    }
+}
+
+} // namespace Collada
+} // namespace Assimp

+ 34 - 12
code/Collada/ColladaHelper.h

@@ -47,6 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 #include <map>
 #include <map>
 #include <vector>
 #include <vector>
+#include <set>
 #include <stdint.h>
 #include <stdint.h>
 #include <assimp/light.h>
 #include <assimp/light.h>
 #include <assimp/mesh.h>
 #include <assimp/mesh.h>
@@ -104,6 +105,17 @@ enum MorphMethod
     Relative
     Relative
 };
 };
 
 
+/** Common metadata keys as <Collada, Assimp> */
+typedef std::pair<std::string, std::string> MetaKeyPair;
+typedef std::vector<MetaKeyPair> MetaKeyPairVector;
+
+// Collada as lower_case (native)
+const MetaKeyPairVector &GetColladaAssimpMetaKeys();
+// Collada as CamelCase (used by Assimp for consistency)
+const MetaKeyPairVector &GetColladaAssimpMetaKeysCamelCase();
+
+/** Convert underscore_separated to CamelCase "authoring_tool" becomes "AuthoringTool" */
+void ToCamelCase(std::string &text);
 
 
 /** Contains all data for one of the different transformation types */
 /** Contains all data for one of the different transformation types */
 struct Transform
 struct Transform
@@ -580,15 +592,11 @@ struct Image
 {
 {
     std::string mFileName;
     std::string mFileName;
 
 
-    /** If image file name is zero, embedded image data
-     */
+    /** Embedded image data */
     std::vector<uint8_t> mImageData;
     std::vector<uint8_t> mImageData;
 
 
-    /** If image file name is zero, file format of
-     *  embedded image data.
-     */
+    /** File format hint of embedded image data */
     std::string mEmbeddedFormat;
     std::string mEmbeddedFormat;
-
 };
 };
 
 
 /** An animation channel. */
 /** An animation channel. */
@@ -651,23 +659,37 @@ struct Animation
 
 
 	void CombineSingleChannelAnimationsRecursively(Animation *pParent)
 	void CombineSingleChannelAnimationsRecursively(Animation *pParent)
 	{
 	{
+		std::set<std::string> childrenTargets;
+		bool childrenAnimationsHaveDifferentChannels = true;
+
 		for (std::vector<Animation*>::iterator it = pParent->mSubAnims.begin(); it != pParent->mSubAnims.end();)
 		for (std::vector<Animation*>::iterator it = pParent->mSubAnims.begin(); it != pParent->mSubAnims.end();)
 		{
 		{
 			Animation *anim = *it;
 			Animation *anim = *it;
-
 			CombineSingleChannelAnimationsRecursively(anim);
 			CombineSingleChannelAnimationsRecursively(anim);
 
 
-			if (anim->mChannels.size() == 1)
+			if (childrenAnimationsHaveDifferentChannels && anim->mChannels.size() == 1 &&
+				childrenTargets.find(anim->mChannels[0].mTarget) == childrenTargets.end()) {
+				childrenTargets.insert(anim->mChannels[0].mTarget);
+			} else {
+				childrenAnimationsHaveDifferentChannels = false;
+			}
+
+			++it;
+		}
+
+		// We only want to combine animations if they have different channels
+		if (childrenAnimationsHaveDifferentChannels)
+		{
+			for (std::vector<Animation*>::iterator it = pParent->mSubAnims.begin(); it != pParent->mSubAnims.end();)
 			{
 			{
+				Animation *anim = *it;
+
 				pParent->mChannels.push_back(anim->mChannels[0]);
 				pParent->mChannels.push_back(anim->mChannels[0]);
 
 
 				it = pParent->mSubAnims.erase(it);
 				it = pParent->mSubAnims.erase(it);
 
 
 				delete anim;
 				delete anim;
-			}
-			else
-			{
-				++it;
+				continue;
 			}
 			}
 		}
 		}
 	}
 	}

File diff ditekan karena terlalu besar
+ 256 - 208
code/Collada/ColladaLoader.cpp


+ 5 - 8
code/Collada/ColladaLoader.h

@@ -94,20 +94,20 @@ public:
 public:
 public:
     /** Returns whether the class can handle the format of the given file.
     /** Returns whether the class can handle the format of the given file.
      * See BaseImporter::CanRead() for details. */
      * 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:
 protected:
     /** Return importer meta information.
     /** Return importer meta information.
      * See #BaseImporter::GetInfo for the details
      * 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.
     /** Imports the given file into the given scene structure.
      * See BaseImporter::InternReadFile() for details
      * 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. */
     /** Recursively constructs a scene node for the given parser node and returns it. */
     aiNode* BuildHierarchy( const ColladaParser& pParser, const Collada::Node* pNode);
     aiNode* BuildHierarchy( const ColladaParser& pParser, const Collada::Node* pNode);
@@ -120,7 +120,7 @@ protected:
     void BuildMeshesForNode( const ColladaParser& pParser, const Collada::Node* pNode,
     void BuildMeshesForNode( const ColladaParser& pParser, const Collada::Node* pNode,
         aiNode* pTarget);
         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 */
     /** 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,
     aiMesh* CreateMesh( const ColladaParser& pParser, const Collada::Mesh* pSrcMesh, const Collada::SubMesh& pSubMesh,
@@ -184,9 +184,6 @@ protected:
     aiString FindFilenameForEffectTexture( const ColladaParser& pParser,
     aiString FindFilenameForEffectTexture( const ColladaParser& pParser,
         const Collada::Effect& pEffect, const std::string& pName);
         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.
     /** Reads a float value from an accessor and its data array.
      * @param pAccessor The accessor to use for reading
      * @param pAccessor The accessor to use for reading
      * @param pData The data array to read from
      * @param pData The data array to read from

File diff ditekan karena terlalu besar
+ 411 - 275
code/Collada/ColladaParser.cpp


+ 12 - 5
code/Collada/ColladaParser.h

@@ -54,6 +54,7 @@
 
 
 namespace Assimp
 namespace Assimp
 {
 {
+    class ZipArchiveIOSystem;
 
 
     // ------------------------------------------------------------------------------------------
     // ------------------------------------------------------------------------------------------
     /** Parser helper class for the Collada loader.
     /** Parser helper class for the Collada loader.
@@ -65,16 +66,22 @@ namespace Assimp
     {
     {
         friend class ColladaLoader;
         friend class ColladaLoader;
 
 
+        /** Converts a path read from a collada file to the usual representation */
+        static void UriDecodePath(aiString& ss);
+
     protected:
     protected:
         /** Map for generic metadata as aiString */
         /** Map for generic metadata as aiString */
         typedef std::map<std::string, aiString> StringMetaData;
         typedef std::map<std::string, aiString> StringMetaData;
 
 
         /** Constructor from XML file */
         /** Constructor from XML file */
-        ColladaParser( IOSystem* pIOHandler, const std::string& pFile);
+        ColladaParser(IOSystem* pIOHandler, const std::string& pFile);
 
 
         /** Destructor */
         /** Destructor */
         ~ColladaParser();
         ~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 */
         /** Reads the contents of the file */
         void ReadContents();
         void ReadContents();
 
 
@@ -87,12 +94,9 @@ namespace Assimp
         /** Reads contributor information such as author and legal blah */
         /** Reads contributor information such as author and legal blah */
         void ReadContributorInfo();
         void ReadContributorInfo();
 
 
-        /** Reads generic metadata into provided map */
+        /** Reads generic metadata into provided map and renames keys for Assimp */
         void ReadMetaDataItem(StringMetaData &metadata);
         void ReadMetaDataItem(StringMetaData &metadata);
 
 
-        /** Convert underscore_seperated to CamelCase "authoring_tool" becomes "AuthoringTool" */
-        static void ToCamelCase(std::string &text);
-
         /** Reads the animation library */
         /** Reads the animation library */
         void ReadAnimationLibrary();
         void ReadAnimationLibrary();
 
 
@@ -235,6 +239,9 @@ namespace Assimp
         // Processes bind_vertex_input and bind elements
         // Processes bind_vertex_input and bind elements
         void ReadMaterialVertexInputBinding( Collada::SemanticMappingTable& tbl);
         void ReadMaterialVertexInputBinding( Collada::SemanticMappingTable& tbl);
 
 
+        /** Reads embedded textures from a ZAE archive*/
+        void ReadEmbeddedTextures(ZipArchiveIOSystem &zip_archive);
+
     protected:
     protected:
         /** Aborts the file reading with an exception */
         /** Aborts the file reading with an exception */
         AI_WONT_RETURN void ThrowException( const std::string& pError) const AI_WONT_RETURN_SUFFIX;
         AI_WONT_RETURN void ThrowException( const std::string& pError) const AI_WONT_RETURN_SUFFIX;

+ 39 - 3
code/Common/BaseImporter.cpp

@@ -67,7 +67,20 @@ using namespace Assimp;
 // Constructor to be privately used by Importer
 // Constructor to be privately used by Importer
 BaseImporter::BaseImporter() AI_NO_EXCEPT
 BaseImporter::BaseImporter() AI_NO_EXCEPT
 : m_progress() {
 : 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
     // 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<float>( activeScale) );
+
+    ASSIMP_LOG_DEBUG_F("UpdateImporterScale scale set: %f", activeScale );
+}
+
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Imports the given file and returns the imported data.
 // 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();
     m_progress = pImp->GetProgressHandler();
     if (nullptr == m_progress) {
     if (nullptr == m_progress) {
         return nullptr;
         return nullptr;
@@ -100,6 +129,11 @@ aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile,
     {
     {
         InternReadFile( pFile, sc.get(), &filter);
         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 )    {
     } catch( const std::exception& err )    {
         // extract error description
         // extract error description
         m_ErrorText = err.what();
         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
     // the default implementation does nothing
 }
 }
@@ -588,6 +622,8 @@ aiScene* BatchLoader::GetImport( unsigned int which )
     return nullptr;
     return nullptr;
 }
 }
 
 
+
+
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void BatchLoader::LoadAll()
 void BatchLoader::LoadAll()
 {
 {

+ 31 - 2
code/Common/DefaultIOStream.cpp

@@ -52,6 +52,35 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 using namespace Assimp;
 using namespace Assimp;
 
 
+namespace
+{
+    template<size_t sizeOfPointer>
+    size_t select_ftell(FILE* file)
+    {
+        return ::ftell(file);
+    }
+
+    template<size_t sizeOfPointer>
+    int select_fseek(FILE* file, int64_t offset, int origin)
+    {
+        return ::fseek(file, static_cast<long>(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()
 DefaultIOStream::~DefaultIOStream()
 {
 {
@@ -93,7 +122,7 @@ aiReturn DefaultIOStream::Seek(size_t pOffset,
         aiOrigin_END == SEEK_END && aiOrigin_SET == SEEK_SET");
         aiOrigin_END == SEEK_END && aiOrigin_SET == SEEK_SET");
 
 
     // do the seek
     // do the seek
-    return (0 == ::fseek(mFile, (long)pOffset,(int)pOrigin) ? AI_SUCCESS : AI_FAILURE);
+    return (0 == select_fseek<sizeof(void*)>(mFile, (int64_t)pOffset,(int)pOrigin) ? AI_SUCCESS : AI_FAILURE);
 }
 }
 
 
 // ----------------------------------------------------------------------------------
 // ----------------------------------------------------------------------------------
@@ -102,7 +131,7 @@ size_t DefaultIOStream::Tell() const
     if (!mFile) {
     if (!mFile) {
         return 0;
         return 0;
     }
     }
-    return ::ftell(mFile);
+    return select_ftell<sizeof(void*)>(mFile);
 }
 }
 
 
 // ----------------------------------------------------------------------------------
 // ----------------------------------------------------------------------------------

+ 60 - 101
code/Common/DefaultIOSystem.cpp

@@ -61,83 +61,66 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 using namespace Assimp;
 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_t>(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_t>(size) - 1, '\0');
+    WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], size, nullptr, nullptr);
+    return out;
+}
 #endif
 #endif
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Tests for the existence of a file at the given path.
 // 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
 #ifdef _WIN32
-    wchar_t fileName16[PATHLIMIT];
-
-#ifndef WindowsStore
-    bool isUnicode = IsTextUnicode(pFile, static_cast<int>(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
 #else
-    FILE* file = ::fopen( pFile, "rb");
-    if( !file)
+    FILE* file = ::fopen(pFile, "rb");
+    if (!file)
         return false;
         return false;
 
 
-    ::fclose( file);
+    ::fclose(file);
 #endif
 #endif
     return true;
     return true;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Open a new file with a given path.
 // 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;
     FILE* file;
 #ifdef _WIN32
 #ifdef _WIN32
-    wchar_t fileName16[PATHLIMIT];
-#ifndef WindowsStore
-    bool isUnicode = IsTextUnicode(strFile, static_cast<int>(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
 #else
     file = ::fopen(strFile, strMode);
     file = ::fopen(strFile, strMode);
 #endif
 #endif
-    if (nullptr == file)
+    if (!file)
         return nullptr;
         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.
 // Closes the given file and releases all resources associated with it.
-void DefaultIOSystem::Close( IOStream* pFile)
+void DefaultIOSystem::Close(IOStream* pFile)
 {
 {
     delete pFile;
     delete pFile;
 }
 }
@@ -155,78 +138,56 @@ char DefaultIOSystem::getOsSeparator() const
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // IOSystem default implementation (ComparePaths isn't a pure virtual function)
 // 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
 // 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<int>(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);
     }
     }
-#endif
 #else
 #else
-    // use realpath
-    char* ret = realpath(in, _out);
-    if(!ret) {
+    char* ret = realpath(in, nullptr);
+    if (ret) {
+        out = ret;
+        free(ret);
+    }
+#endif
+    if (!ret) {
         // preserve the input path, maybe someone else is able to fix
         // preserve the input path, maybe someone else is able to fix
         // the path before it is accessed (e.g. our file system filter)
         // the path before it is accessed (e.g. our file system filter)
         ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
         ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
-        strcpy(_out,in);
+        out = in;
     }
     }
-#endif
+    return out;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // DefaultIOSystem's more specialized implementation
 // 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,
     // chances are quite good both paths are formatted identically,
     // so we can hopefully return here already
     // so we can hopefully return here already
-    if( !ASSIMP_stricmp(one,second) )
+    if (!ASSIMP_stricmp(one, second))
         return true;
         return true;
 
 
-    char temp1[PATHLIMIT];
-    char temp2[PATHLIMIT];
-
-    MakeAbsolutePath (one, temp1);
-    MakeAbsolutePath (second, temp2);
+    std::string temp1 = MakeAbsolutePath(one);
+    std::string temp2 = MakeAbsolutePath(second);
 
 
-    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::string ret = path;
     std::size_t last = ret.find_last_of("\\/");
     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::string ret = fileName(path);
     std::size_t pos = ret.find_last_of('.');
     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;
     return ret;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-std::string DefaultIOSystem::absolutePath( const std::string &path )
+std::string DefaultIOSystem::absolutePath(const std::string& path)
 {
 {
     std::string ret = path;
     std::string ret = path;
     std::size_t last = ret.find_last_of("\\/");
     std::size_t last = ret.find_last_of("\\/");
@@ -253,5 +214,3 @@ std::string DefaultIOSystem::absolutePath( const std::string &path )
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-
-#undef PATHLIMIT

+ 2 - 2
code/Common/DefaultLogger.cpp

@@ -107,7 +107,7 @@ LogStream* LogStream::createDefaultStream(aiDefaultLogStream    streams,
         return nullptr;
         return nullptr;
 #endif
 #endif
 
 
-        // Platform-independent default streams
+    // Platform-independent default streams
     case aiDefaultLogStream_STDERR:
     case aiDefaultLogStream_STDERR:
         return new StdOStreamLogStream(std::cerr);
         return new StdOStreamLogStream(std::cerr);
     case aiDefaultLogStream_STDOUT:
     case aiDefaultLogStream_STDOUT:
@@ -121,7 +121,7 @@ LogStream* LogStream::createDefaultStream(aiDefaultLogStream    streams,
     };
     };
 
 
     // For compilers without dead code path detection
     // For compilers without dead code path detection
-    return NULL;
+    return nullptr;
 }
 }
 
 
 // ----------------------------------------------------------------------------------
 // ----------------------------------------------------------------------------------

+ 73 - 91
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 ExportSceneFBX(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneFBXA(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 ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* );
+void ExportSceneM3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneM3DA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportAssimp2Json(const char* , IOSystem*, const aiScene* , const Assimp::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<Exporter::ExportFormatEntry> &exporters) {
 #ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER
 #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
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_X_EXPORTER
 #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
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_STEP_EXPORTER
 #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
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER
 #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
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_STL_EXPORTER
 #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
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_PLY_EXPORTER
 #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
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_3DS_EXPORTER
 #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
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER
 #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
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER
 #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
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER
 #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
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_X3D_EXPORTER
 #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
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
 #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("m3da", "Model 3D (ascii)", "a3d", &ExportSceneM3DA, 0));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_3MF_EXPORTER
 #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
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER
 #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
 #endif
-};
-
-#define ASSIMP_NUM_EXPORTERS (sizeof(gExporters)/sizeof(gExporters[0]))
-
+}
 
 
 class ExporterPimpl {
 class ExporterPimpl {
 public:
 public:
@@ -205,10 +203,7 @@ public:
         GetPostProcessingStepInstanceList(mPostProcessingSteps);
         GetPostProcessingStepInstanceList(mPostProcessingSteps);
 
 
         // grab all built-in exporters
         // 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() {
     ~ExporterPimpl() {
@@ -252,24 +247,28 @@ Exporter :: Exporter()
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 Exporter::~Exporter() {
 Exporter::~Exporter() {
-    FreeBlob();
+	ai_assert(nullptr != pimpl);
+	FreeBlob();
     delete pimpl;
     delete pimpl;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Exporter::SetIOHandler( IOSystem* pIOHandler) {
 void Exporter::SetIOHandler( IOSystem* pIOHandler) {
-    pimpl->mIsDefaultIOHandler = !pIOHandler;
+	ai_assert(nullptr != pimpl);
+	pimpl->mIsDefaultIOHandler = !pIOHandler;
     pimpl->mIOSystem.reset(pIOHandler);
     pimpl->mIOSystem.reset(pIOHandler);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 IOSystem* Exporter::GetIOHandler() const {
 IOSystem* Exporter::GetIOHandler() const {
-    return pimpl->mIOSystem.get();
+	ai_assert(nullptr != pimpl);
+	return pimpl->mIOSystem.get();
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 bool Exporter::IsDefaultIOHandler() const {
 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,
 const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const char* pFormatId,
                                                 unsigned int pPreprocessing, const ExportProperties* pProperties) {
                                                 unsigned int pPreprocessing, const ExportProperties* pProperties) {
+	ai_assert(nullptr != pimpl);
     if (pimpl->blob) {
     if (pimpl->blob) {
         delete pimpl->blob;
         delete pimpl->blob;
         pimpl->blob = nullptr;
         pimpl->blob = nullptr;
@@ -315,44 +315,16 @@ const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const cha
     return pimpl->blob;
     return pimpl->blob;
 }
 }
 
 
-// ------------------------------------------------------------------------------------------------
-bool IsVerboseFormat(const aiMesh* mesh) {
-    // avoid slow vector<bool> specialization
-    std::vector<unsigned int> 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,
 aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const char* pPath,
         unsigned int pPreprocessing, const ExportProperties* pProperties) {
         unsigned int pPreprocessing, const ExportProperties* pProperties) {
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
-
+	ai_assert(nullptr != pimpl);
     // when they create scenes from scratch, users will likely create them not in verbose
     // 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
     // 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
     // this, however. To avoid surprises and bug reports, we check for duplicates in
     // meshes upfront.
     // 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);
     pimpl->mProgressHandler->UpdateFileWrite(0, 4);
 
 
@@ -472,7 +444,9 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
                 }
                 }
 
 
                 ExportProperties emptyProperties;  // Never pass NULL ExportProperties so Exporters don't have to worry.
                 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", pp & aiProcess_JoinIdenticalVertices);
+                exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProp);
 
 
                 pimpl->mProgressHandler->UpdateFileWrite(4, 4);
                 pimpl->mProgressHandler->UpdateFileWrite(4, 4);
             } catch (DeadlyExportError& err) {
             } catch (DeadlyExportError& err) {
@@ -491,11 +465,13 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 const char* Exporter::GetErrorString() const {
 const char* Exporter::GetErrorString() const {
+	ai_assert(nullptr != pimpl);
     return pimpl->mError.c_str();
     return pimpl->mError.c_str();
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Exporter::FreeBlob() {
 void Exporter::FreeBlob() {
+	ai_assert(nullptr != pimpl);
     delete pimpl->blob;
     delete pimpl->blob;
     pimpl->blob = nullptr;
     pimpl->blob = nullptr;
 
 
@@ -504,30 +480,34 @@ void Exporter::FreeBlob() {
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 const aiExportDataBlob* Exporter::GetBlob() const {
 const aiExportDataBlob* Exporter::GetBlob() const {
-    return pimpl->blob;
+	ai_assert(nullptr != pimpl);
+	return pimpl->blob;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 const aiExportDataBlob* Exporter::GetOrphanedBlob() const {
 const aiExportDataBlob* Exporter::GetOrphanedBlob() const {
-    const aiExportDataBlob* tmp = pimpl->blob;
+	ai_assert(nullptr != pimpl);
+	const aiExportDataBlob *tmp = pimpl->blob;
     pimpl->blob = nullptr;
     pimpl->blob = nullptr;
     return tmp;
     return tmp;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 size_t Exporter::GetExportFormatCount() const {
 size_t Exporter::GetExportFormatCount() const {
+	ai_assert(nullptr != pimpl);
     return pimpl->mExporters.size();
     return pimpl->mExporters.size();
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) const {
 const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) const {
-    if (index >= GetExportFormatCount()) {
+	ai_assert(nullptr != pimpl);
+	if (index >= GetExportFormatCount()) {
         return nullptr;
         return nullptr;
     }
     }
 
 
     // Return from static storage if the requested index is built-in.
     // 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;
     return &pimpl->mExporters[index].mDescription;
@@ -535,7 +515,8 @@ const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) c
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) {
 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)) {
         if (!strcmp(e.mDescription.id,desc.mDescription.id)) {
             return aiReturn_FAILURE;
             return aiReturn_FAILURE;
         }
         }
@@ -547,7 +528,8 @@ aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) {
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Exporter::UnregisterExporter(const char* id) {
 void Exporter::UnregisterExporter(const char* id) {
-    for(std::vector<ExportFormatEntry>::iterator it = pimpl->mExporters.begin();
+	ai_assert(nullptr != pimpl);
+	for (std::vector<ExportFormatEntry>::iterator it = pimpl->mExporters.begin();
             it != pimpl->mExporters.end(); ++it) {
             it != pimpl->mExporters.end(); ++it) {
         if (!strcmp((*it).mDescription.id,id)) {
         if (!strcmp((*it).mDescription.id,id)) {
             pimpl->mExporters.erase(it);
             pimpl->mExporters.erase(it);

+ 159 - 127
code/Common/Importer.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2019, assimp team
 Copyright (c) 2006-2019, assimp team
 
 
-
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -78,6 +76,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/TinyFormatter.h>
 #include <assimp/TinyFormatter.h>
 #include <assimp/Exceptional.h>
 #include <assimp/Exceptional.h>
 #include <assimp/Profiler.h>
 #include <assimp/Profiler.h>
+#include <assimp/commonMetaData.h>
+
 #include <set>
 #include <set>
 #include <memory>
 #include <memory>
 #include <cctype>
 #include <cctype>
@@ -119,7 +119,7 @@ void* AllocateFromAssimpHeap::operator new ( size_t num_bytes, const std::nothro
         return AllocateFromAssimpHeap::operator new( num_bytes );
         return AllocateFromAssimpHeap::operator new( num_bytes );
     }
     }
     catch( ... )    {
     catch( ... )    {
-        return NULL;
+        return nullptr;
     }
     }
 }
 }
 
 
@@ -134,9 +134,8 @@ void* AllocateFromAssimpHeap::operator new[] ( size_t num_bytes)    {
 void* AllocateFromAssimpHeap::operator new[] ( size_t num_bytes, const std::nothrow_t& ) throw() {
 void* AllocateFromAssimpHeap::operator new[] ( size_t num_bytes, const std::nothrow_t& ) throw() {
     try {
     try {
         return AllocateFromAssimpHeap::operator new[]( num_bytes );
         return AllocateFromAssimpHeap::operator new[]( num_bytes );
-    }
-    catch( ... )    {
-        return NULL;
+    } catch( ... )    {
+        return nullptr;
     }
     }
 }
 }
 
 
@@ -148,7 +147,7 @@ void AllocateFromAssimpHeap::operator delete[] ( void* data)    {
 // Importer constructor.
 // Importer constructor.
 Importer::Importer()
 Importer::Importer()
  : pimpl( new ImporterPimpl ) {
  : pimpl( new ImporterPimpl ) {
-    pimpl->mScene = NULL;
+    pimpl->mScene = nullptr;
     pimpl->mErrorString = "";
     pimpl->mErrorString = "";
 
 
     // Allocate a default IO handler
     // Allocate a default IO handler
@@ -174,14 +173,14 @@ Importer::Importer()
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Destructor of Importer
 // Destructor of Importer
-Importer::~Importer()
-{
+Importer::~Importer() {
     // Delete all import plugins
     // Delete all import plugins
 	DeleteImporterInstanceList(pimpl->mImporter);
 	DeleteImporterInstanceList(pimpl->mImporter);
 
 
     // Delete all post-processing plug-ins
     // Delete all post-processing plug-ins
-    for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++)
+    for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); ++a ) {
         delete pimpl->mPostProcessingSteps[a];
         delete pimpl->mPostProcessingSteps[a];
+    }
 
 
     // Delete the assigned IO and progress handler
     // Delete the assigned IO and progress handler
     delete pimpl->mIOHandler;
     delete pimpl->mIOHandler;
@@ -199,9 +198,9 @@ Importer::~Importer()
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Register a custom post-processing step
 // Register a custom post-processing step
-aiReturn Importer::RegisterPPStep(BaseProcess* pImp)
-{
-    ai_assert(NULL != pImp);
+aiReturn Importer::RegisterPPStep(BaseProcess* pImp) {
+    ai_assert( nullptr != pImp );
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
 
 
         pimpl->mPostProcessingSteps.push_back(pImp);
         pimpl->mPostProcessingSteps.push_back(pImp);
@@ -213,9 +212,9 @@ aiReturn Importer::RegisterPPStep(BaseProcess* pImp)
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Register a custom loader plugin
 // Register a custom loader plugin
-aiReturn Importer::RegisterLoader(BaseImporter* pImp)
-{
-    ai_assert(NULL != pImp);
+aiReturn Importer::RegisterLoader(BaseImporter* pImp) {
+    ai_assert(nullptr != pImp);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
 
 
     // --------------------------------------------------------------------
     // --------------------------------------------------------------------
@@ -242,13 +241,13 @@ aiReturn Importer::RegisterLoader(BaseImporter* pImp)
     pimpl->mImporter.push_back(pImp);
     pimpl->mImporter.push_back(pImp);
     ASSIMP_LOG_INFO_F("Registering custom importer for these file extensions: ", baked);
     ASSIMP_LOG_INFO_F("Registering custom importer for these file extensions: ", baked);
     ASSIMP_END_EXCEPTION_REGION(aiReturn);
     ASSIMP_END_EXCEPTION_REGION(aiReturn);
+    
     return AI_SUCCESS;
     return AI_SUCCESS;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Unregister a custom loader plugin
 // Unregister a custom loader plugin
-aiReturn Importer::UnregisterLoader(BaseImporter* pImp)
-{
+aiReturn Importer::UnregisterLoader(BaseImporter* pImp) {
     if(!pImp) {
     if(!pImp) {
         // unregistering a NULL importer is no problem for us ... really!
         // unregistering a NULL importer is no problem for us ... really!
         return AI_SUCCESS;
         return AI_SUCCESS;
@@ -265,13 +264,13 @@ aiReturn Importer::UnregisterLoader(BaseImporter* pImp)
     }
     }
     ASSIMP_LOG_WARN("Unable to remove custom importer: I can't find you ...");
     ASSIMP_LOG_WARN("Unable to remove custom importer: I can't find you ...");
     ASSIMP_END_EXCEPTION_REGION(aiReturn);
     ASSIMP_END_EXCEPTION_REGION(aiReturn);
+
     return AI_FAILURE;
     return AI_FAILURE;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Unregister a custom loader plugin
 // Unregister a custom loader plugin
-aiReturn Importer::UnregisterPPStep(BaseProcess* pImp)
-{
+aiReturn Importer::UnregisterPPStep(BaseProcess* pImp) {
     if(!pImp) {
     if(!pImp) {
         // unregistering a NULL ppstep is no problem for us ... really!
         // unregistering a NULL ppstep is no problem for us ... really!
         return AI_SUCCESS;
         return AI_SUCCESS;
@@ -288,24 +287,22 @@ aiReturn Importer::UnregisterPPStep(BaseProcess* pImp)
     }
     }
     ASSIMP_LOG_WARN("Unable to remove custom post-processing step: I can't find you ..");
     ASSIMP_LOG_WARN("Unable to remove custom post-processing step: I can't find you ..");
     ASSIMP_END_EXCEPTION_REGION(aiReturn);
     ASSIMP_END_EXCEPTION_REGION(aiReturn);
+
     return AI_FAILURE;
     return AI_FAILURE;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Supplies a custom IO handler to the importer to open and access files.
 // Supplies a custom IO handler to the importer to open and access files.
-void Importer::SetIOHandler( IOSystem* pIOHandler)
-{
+void Importer::SetIOHandler( IOSystem* pIOHandler) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
     // If the new handler is zero, allocate a default IO implementation.
     // If the new handler is zero, allocate a default IO implementation.
-    if (!pIOHandler)
-    {
+    if (!pIOHandler) {
         // Release pointer in the possession of the caller
         // Release pointer in the possession of the caller
         pimpl->mIOHandler = new DefaultIOSystem();
         pimpl->mIOHandler = new DefaultIOSystem();
         pimpl->mIsDefaultHandler = true;
         pimpl->mIsDefaultHandler = true;
-    }
-    // Otherwise register the custom handler
-    else if (pimpl->mIOHandler != pIOHandler)
-    {
+    } else if (pimpl->mIOHandler != pIOHandler) { // Otherwise register the custom handler
         delete pimpl->mIOHandler;
         delete pimpl->mIOHandler;
         pimpl->mIOHandler = pIOHandler;
         pimpl->mIOHandler = pIOHandler;
         pimpl->mIsDefaultHandler = false;
         pimpl->mIsDefaultHandler = false;
@@ -316,29 +313,32 @@ void Importer::SetIOHandler( IOSystem* pIOHandler)
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get the currently set IO handler
 // Get the currently set IO handler
 IOSystem* Importer::GetIOHandler() const {
 IOSystem* Importer::GetIOHandler() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mIOHandler;
     return pimpl->mIOHandler;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Check whether a custom IO handler is currently set
 // Check whether a custom IO handler is currently set
 bool Importer::IsDefaultIOHandler() const {
 bool Importer::IsDefaultIOHandler() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mIsDefaultHandler;
     return pimpl->mIsDefaultHandler;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Supplies a custom progress handler to get regular callbacks during importing
 // Supplies a custom progress handler to get regular callbacks during importing
 void Importer::SetProgressHandler ( ProgressHandler* pHandler ) {
 void Importer::SetProgressHandler ( ProgressHandler* pHandler ) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
+    
     // If the new handler is zero, allocate a default implementation.
     // If the new handler is zero, allocate a default implementation.
-    if (!pHandler)
-    {
+    if (!pHandler) {
         // Release pointer in the possession of the caller
         // Release pointer in the possession of the caller
         pimpl->mProgressHandler = new DefaultProgressHandler();
         pimpl->mProgressHandler = new DefaultProgressHandler();
         pimpl->mIsDefaultProgressHandler = true;
         pimpl->mIsDefaultProgressHandler = true;
-    }
-    // Otherwise register the custom handler
-    else if (pimpl->mProgressHandler != pHandler)
-    {
+    } else if (pimpl->mProgressHandler != pHandler) { // Otherwise register the custom handler
         delete pimpl->mProgressHandler;
         delete pimpl->mProgressHandler;
         pimpl->mProgressHandler = pHandler;
         pimpl->mProgressHandler = pHandler;
         pimpl->mIsDefaultProgressHandler = false;
         pimpl->mIsDefaultProgressHandler = false;
@@ -349,19 +349,22 @@ void Importer::SetProgressHandler ( ProgressHandler* pHandler ) {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get the currently set progress handler
 // Get the currently set progress handler
 ProgressHandler* Importer::GetProgressHandler() const {
 ProgressHandler* Importer::GetProgressHandler() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mProgressHandler;
     return pimpl->mProgressHandler;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Check whether a custom progress handler is currently set
 // Check whether a custom progress handler is currently set
 bool Importer::IsDefaultProgressHandler() const {
 bool Importer::IsDefaultProgressHandler() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mIsDefaultProgressHandler;
     return pimpl->mIsDefaultProgressHandler;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Validate post process step flags
 // Validate post process step flags
-bool _ValidateFlags(unsigned int pFlags)
-{
+bool _ValidateFlags(unsigned int pFlags) {
     if (pFlags & aiProcess_GenSmoothNormals && pFlags & aiProcess_GenNormals)   {
     if (pFlags & aiProcess_GenSmoothNormals && pFlags & aiProcess_GenNormals)   {
         ASSIMP_LOG_ERROR("#aiProcess_GenSmoothNormals and #aiProcess_GenNormals are incompatible");
         ASSIMP_LOG_ERROR("#aiProcess_GenSmoothNormals and #aiProcess_GenNormals are incompatible");
         return false;
         return false;
@@ -375,12 +378,13 @@ bool _ValidateFlags(unsigned int pFlags)
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Free the current scene
 // Free the current scene
-void Importer::FreeScene( )
-{
+void Importer::FreeScene( ) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
 
 
     delete pimpl->mScene;
     delete pimpl->mScene;
-    pimpl->mScene = NULL;
+    pimpl->mScene = nullptr;
 
 
     pimpl->mErrorString = "";
     pimpl->mErrorString = "";
     ASSIMP_END_EXCEPTION_REGION(void);
     ASSIMP_END_EXCEPTION_REGION(void);
@@ -388,44 +392,48 @@ void Importer::FreeScene( )
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get the current error string, if any
 // Get the current error string, if any
-const char* Importer::GetErrorString() const
-{
-     /* Must remain valid as long as ReadFile() or FreeFile() are not called */
+const char* Importer::GetErrorString() const {
+    ai_assert(nullptr != pimpl);
+    
+    // Must remain valid as long as ReadFile() or FreeFile() are not called
     return pimpl->mErrorString.c_str();
     return pimpl->mErrorString.c_str();
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Enable extra-verbose mode
 // Enable extra-verbose mode
-void Importer::SetExtraVerbose(bool bDo)
-{
+void Importer::SetExtraVerbose(bool bDo) {
+    ai_assert(nullptr != pimpl);
+    
     pimpl->bExtraVerbose = bDo;
     pimpl->bExtraVerbose = bDo;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get the current scene
 // Get the current scene
-const aiScene* Importer::GetScene() const
-{
+const aiScene* Importer::GetScene() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mScene;
     return pimpl->mScene;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Orphan the current scene and return it.
 // Orphan the current scene and return it.
-aiScene* Importer::GetOrphanedScene()
-{
+aiScene* Importer::GetOrphanedScene() {
+    ai_assert(nullptr != pimpl);
+    
     aiScene* s = pimpl->mScene;
     aiScene* s = pimpl->mScene;
 
 
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
-    pimpl->mScene = NULL;
+    pimpl->mScene = nullptr;
 
 
-    pimpl->mErrorString = ""; /* reset error string */
+    pimpl->mErrorString = ""; // reset error string
     ASSIMP_END_EXCEPTION_REGION(aiScene*);
     ASSIMP_END_EXCEPTION_REGION(aiScene*);
+    
     return s;
     return s;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Validate post-processing flags
 // Validate post-processing flags
-bool Importer::ValidateFlags(unsigned int pFlags) const
-{
+bool Importer::ValidateFlags(unsigned int pFlags) const {
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
     // run basic checks for mutually exclusive flags
     // run basic checks for mutually exclusive flags
     if(!_ValidateFlags(pFlags)) {
     if(!_ValidateFlags(pFlags)) {
@@ -467,8 +475,9 @@ bool Importer::ValidateFlags(unsigned int pFlags) const
 const aiScene* Importer::ReadFileFromMemory( const void* pBuffer,
 const aiScene* Importer::ReadFileFromMemory( const void* pBuffer,
     size_t pLength,
     size_t pLength,
     unsigned int pFlags,
     unsigned int pFlags,
-    const char* pHint /*= ""*/)
-{
+    const char* pHint /*= ""*/) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
     if (!pHint) {
     if (!pHint) {
         pHint = "";
         pHint = "";
@@ -476,12 +485,12 @@ const aiScene* Importer::ReadFileFromMemory( const void* pBuffer,
 
 
     if (!pBuffer || !pLength || strlen(pHint) > MaxLenHint ) {
     if (!pBuffer || !pLength || strlen(pHint) > MaxLenHint ) {
         pimpl->mErrorString = "Invalid parameters passed to ReadFileFromMemory()";
         pimpl->mErrorString = "Invalid parameters passed to ReadFileFromMemory()";
-        return NULL;
+        return nullptr;
     }
     }
 
 
     // prevent deletion of the previous IOHandler
     // prevent deletion of the previous IOHandler
     IOSystem* io = pimpl->mIOHandler;
     IOSystem* io = pimpl->mIOHandler;
-    pimpl->mIOHandler = NULL;
+    pimpl->mIOHandler = nullptr;
 
 
     SetIOHandler(new MemoryIOSystem((const uint8_t*)pBuffer,pLength,io));
     SetIOHandler(new MemoryIOSystem((const uint8_t*)pBuffer,pLength,io));
 
 
@@ -493,13 +502,13 @@ const aiScene* Importer::ReadFileFromMemory( const void* pBuffer,
     ReadFile(fbuff,pFlags);
     ReadFile(fbuff,pFlags);
     SetIOHandler(io);
     SetIOHandler(io);
 
 
-    ASSIMP_END_EXCEPTION_REGION(const aiScene*);
+    ASSIMP_END_EXCEPTION_REGION_WITH_ERROR_STRING(const aiScene*, pimpl->mErrorString);
     return pimpl->mScene;
     return pimpl->mScene;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void WriteLogOpening(const std::string& file)
-{
+void WriteLogOpening(const std::string& file) {
+    
     ASSIMP_LOG_INFO_F("Load ", file);
     ASSIMP_LOG_INFO_F("Load ", file);
 
 
     // print a full version dump. This is nice because we don't
     // print a full version dump. This is nice because we don't
@@ -550,8 +559,9 @@ void WriteLogOpening(const std::string& file)
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Reads the given file and returns its contents if successful.
 // Reads the given file and returns its contents if successful.
-const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
-{
+const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
     const std::string pFile(_pFile);
     const std::string pFile(_pFile);
 
 
@@ -580,7 +590,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 
 
             pimpl->mErrorString = "Unable to open file \"" + pFile + "\".";
             pimpl->mErrorString = "Unable to open file \"" + pFile + "\".";
             ASSIMP_LOG_ERROR(pimpl->mErrorString);
             ASSIMP_LOG_ERROR(pimpl->mErrorString);
-            return NULL;
+            return nullptr;
         }
         }
 
 
         std::unique_ptr<Profiler> profiler(GetPropertyInteger(AI_CONFIG_GLOB_MEASURE_TIME,0)?new Profiler():NULL);
         std::unique_ptr<Profiler> profiler(GetPropertyInteger(AI_CONFIG_GLOB_MEASURE_TIME,0)?new Profiler():NULL);
@@ -589,7 +599,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
         }
         }
 
 
         // Find an worker class which can handle the file
         // Find an worker class which can handle the file
-        BaseImporter* imp = NULL;
+        BaseImporter* imp = nullptr;
         SetPropertyInteger("importerIndex", -1);
         SetPropertyInteger("importerIndex", -1);
         for( unsigned int a = 0; a < pimpl->mImporter.size(); a++)  {
         for( unsigned int a = 0; a < pimpl->mImporter.size(); a++)  {
 
 
@@ -617,7 +627,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
             if( !imp)   {
             if( !imp)   {
                 pimpl->mErrorString = "No suitable reader found for the file format of file \"" + pFile + "\".";
                 pimpl->mErrorString = "No suitable reader found for the file format of file \"" + pFile + "\".";
                 ASSIMP_LOG_ERROR(pimpl->mErrorString);
                 ASSIMP_LOG_ERROR(pimpl->mErrorString);
-                return NULL;
+                return nullptr;
             }
             }
         }
         }
 
 
@@ -633,7 +643,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
         // Dispatch the reading to the worker class for this format
         // Dispatch the reading to the worker class for this format
         const aiImporterDesc *desc( imp->GetInfo() );
         const aiImporterDesc *desc( imp->GetInfo() );
         std::string ext( "unknown" );
         std::string ext( "unknown" );
-        if ( NULL != desc ) {
+        if ( nullptr != desc ) {
             ext = desc->mName;
             ext = desc->mName;
         }
         }
         ASSIMP_LOG_INFO("Found a matching importer for this file format: " + ext + "." );
         ASSIMP_LOG_INFO("Found a matching importer for this file format: " + ext + "." );
@@ -654,15 +664,20 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 
 
         // If successful, apply all active post processing steps to the imported data
         // If successful, apply all active post processing steps to the imported data
         if( pimpl->mScene)  {
         if( pimpl->mScene)  {
+            if (!pimpl->mScene->mMetaData || !pimpl->mScene->mMetaData->HasKey(AI_METADATA_SOURCE_FORMAT)) {
+                if (!pimpl->mScene->mMetaData) {
+                    pimpl->mScene->mMetaData = new aiMetadata;
+                }
+                pimpl->mScene->mMetaData->Add(AI_METADATA_SOURCE_FORMAT, aiString(ext));
+            }
 
 
 #ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
 #ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
             // The ValidateDS process is an exception. It is executed first, even before ScenePreprocessor is called.
             // The ValidateDS process is an exception. It is executed first, even before ScenePreprocessor is called.
-            if (pFlags & aiProcess_ValidateDataStructure)
-            {
+            if (pFlags & aiProcess_ValidateDataStructure) {
                 ValidateDSProcess ds;
                 ValidateDSProcess ds;
                 ds.ExecuteOnScene (this);
                 ds.ExecuteOnScene (this);
                 if (!pimpl->mScene) {
                 if (!pimpl->mScene) {
-                    return NULL;
+                    return nullptr;
                 }
                 }
             }
             }
 #endif // no validation
 #endif // no validation
@@ -695,8 +710,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
         }
         }
     }
     }
 #ifdef ASSIMP_CATCH_GLOBAL_EXCEPTIONS
 #ifdef ASSIMP_CATCH_GLOBAL_EXCEPTIONS
-    catch (std::exception &e)
-    {
+    catch (std::exception &e) {
 #if (defined _MSC_VER) &&   (defined _CPPRTTI)
 #if (defined _MSC_VER) &&   (defined _CPPRTTI)
         // if we have RTTI get the full name of the exception that occurred
         // if we have RTTI get the full name of the exception that occurred
         pimpl->mErrorString = std::string(typeid( e ).name()) + ": " + e.what();
         pimpl->mErrorString = std::string(typeid( e ).name()) + ": " + e.what();
@@ -705,24 +719,26 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 #endif
 #endif
 
 
         ASSIMP_LOG_ERROR(pimpl->mErrorString);
         ASSIMP_LOG_ERROR(pimpl->mErrorString);
-        delete pimpl->mScene; pimpl->mScene = NULL;
+        delete pimpl->mScene; pimpl->mScene = nullptr;
     }
     }
 #endif // ! ASSIMP_CATCH_GLOBAL_EXCEPTIONS
 #endif // ! ASSIMP_CATCH_GLOBAL_EXCEPTIONS
 
 
     // either successful or failure - the pointer expresses it anyways
     // either successful or failure - the pointer expresses it anyways
-    ASSIMP_END_EXCEPTION_REGION(const aiScene*);
+    ASSIMP_END_EXCEPTION_REGION_WITH_ERROR_STRING(const aiScene*, pimpl->mErrorString);
+    
     return pimpl->mScene;
     return pimpl->mScene;
 }
 }
 
 
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Apply post-processing to the currently bound scene
 // Apply post-processing to the currently bound scene
-const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags)
-{
+const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
     // Return immediately if no scene is active
     // Return immediately if no scene is active
     if (!pimpl->mScene) {
     if (!pimpl->mScene) {
-        return NULL;
+        return nullptr;
     }
     }
 
 
     // If no flags are given, return the current scene with no further action
     // If no flags are given, return the current scene with no further action
@@ -737,12 +753,11 @@ const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags)
 #ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
 #ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
     // The ValidateDS process plays an exceptional role. It isn't contained in the global
     // The ValidateDS process plays an exceptional role. It isn't contained in the global
     // list of post-processing steps, so we need to call it manually.
     // list of post-processing steps, so we need to call it manually.
-    if (pFlags & aiProcess_ValidateDataStructure)
-    {
+    if (pFlags & aiProcess_ValidateDataStructure) {
         ValidateDSProcess ds;
         ValidateDSProcess ds;
         ds.ExecuteOnScene (this);
         ds.ExecuteOnScene (this);
         if (!pimpl->mScene) {
         if (!pimpl->mScene) {
-            return NULL;
+            return nullptr;
         }
         }
     }
     }
 #endif // no validation
 #endif // no validation
@@ -762,11 +777,9 @@ const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags)
 
 
     std::unique_ptr<Profiler> profiler(GetPropertyInteger(AI_CONFIG_GLOB_MEASURE_TIME,0)?new Profiler():NULL);
     std::unique_ptr<Profiler> profiler(GetPropertyInteger(AI_CONFIG_GLOB_MEASURE_TIME,0)?new Profiler():NULL);
     for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++)   {
     for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++)   {
-
         BaseProcess* process = pimpl->mPostProcessingSteps[a];
         BaseProcess* process = pimpl->mPostProcessingSteps[a];
         pimpl->mProgressHandler->UpdatePostProcess(static_cast<int>(a), static_cast<int>(pimpl->mPostProcessingSteps.size()) );
         pimpl->mProgressHandler->UpdatePostProcess(static_cast<int>(a), static_cast<int>(pimpl->mPostProcessingSteps.size()) );
         if( process->IsActive( pFlags)) {
         if( process->IsActive( pFlags)) {
-
             if (profiler) {
             if (profiler) {
                 profiler->BeginRegion("postprocess");
                 profiler->BeginRegion("postprocess");
             }
             }
@@ -803,24 +816,28 @@ const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags)
         static_cast<int>(pimpl->mPostProcessingSteps.size()) );
         static_cast<int>(pimpl->mPostProcessingSteps.size()) );
 
 
     // update private scene flags
     // update private scene flags
-    if( pimpl->mScene )
+    if( pimpl->mScene ) {
       ScenePriv(pimpl->mScene)->mPPStepsApplied |= pFlags;
       ScenePriv(pimpl->mScene)->mPPStepsApplied |= pFlags;
+    }
 
 
     // clear any data allocated by post-process steps
     // clear any data allocated by post-process steps
     pimpl->mPPShared->Clean();
     pimpl->mPPShared->Clean();
     ASSIMP_LOG_INFO("Leaving post processing pipeline");
     ASSIMP_LOG_INFO("Leaving post processing pipeline");
 
 
     ASSIMP_END_EXCEPTION_REGION(const aiScene*);
     ASSIMP_END_EXCEPTION_REGION(const aiScene*);
+    
     return pimpl->mScene;
     return pimpl->mScene;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess, bool requestValidation ) {
 const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess, bool requestValidation ) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
 
 
     // Return immediately if no scene is active
     // Return immediately if no scene is active
-    if ( NULL == pimpl->mScene ) {
-        return NULL;
+    if ( nullptr == pimpl->mScene ) {
+        return nullptr;
     }
     }
 
 
     // If no flags are given, return the current scene with no further action
     // If no flags are given, return the current scene with no further action
@@ -839,7 +856,7 @@ const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess
         ValidateDSProcess ds;
         ValidateDSProcess ds;
         ds.ExecuteOnScene( this );
         ds.ExecuteOnScene( this );
         if ( !pimpl->mScene ) {
         if ( !pimpl->mScene ) {
-            return NULL;
+            return nullptr;
         }
         }
     }
     }
 #endif // no validation
 #endif // no validation
@@ -890,46 +907,50 @@ const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Helper function to check whether an extension is supported by ASSIMP
 // Helper function to check whether an extension is supported by ASSIMP
-bool Importer::IsExtensionSupported(const char* szExtension) const
-{
+bool Importer::IsExtensionSupported(const char* szExtension) const {
     return nullptr != GetImporter(szExtension);
     return nullptr != GetImporter(szExtension);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-size_t Importer::GetImporterCount() const
-{
+size_t Importer::GetImporterCount() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mImporter.size();
     return pimpl->mImporter.size();
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-const aiImporterDesc* Importer::GetImporterInfo(size_t index) const
-{
+const aiImporterDesc* Importer::GetImporterInfo(size_t index) const {
+    ai_assert(nullptr != pimpl);
+    
     if (index >= pimpl->mImporter.size()) {
     if (index >= pimpl->mImporter.size()) {
-        return NULL;
+        return nullptr;
     }
     }
     return pimpl->mImporter[index]->GetInfo();
     return pimpl->mImporter[index]->GetInfo();
 }
 }
 
 
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-BaseImporter* Importer::GetImporter (size_t index) const
-{
+BaseImporter* Importer::GetImporter (size_t index) const {
+    ai_assert(nullptr != pimpl);
+    
     if (index >= pimpl->mImporter.size()) {
     if (index >= pimpl->mImporter.size()) {
-        return NULL;
+        return nullptr;
     }
     }
     return pimpl->mImporter[index];
     return pimpl->mImporter[index];
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Find a loader plugin for a given file extension
 // Find a loader plugin for a given file extension
-BaseImporter* Importer::GetImporter (const char* szExtension) const
-{
+BaseImporter* Importer::GetImporter (const char* szExtension) const {
+    ai_assert(nullptr != pimpl);
+    
     return GetImporter(GetImporterIndex(szExtension));
     return GetImporter(GetImporterIndex(szExtension));
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Find a loader plugin for a given file extension
 // Find a loader plugin for a given file extension
 size_t Importer::GetImporterIndex (const char* szExtension) const {
 size_t Importer::GetImporterIndex (const char* szExtension) const {
+    ai_assert(nullptr != pimpl);
     ai_assert(nullptr != szExtension);
     ai_assert(nullptr != szExtension);
 
 
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
@@ -960,8 +981,9 @@ size_t Importer::GetImporterIndex (const char* szExtension) const {
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Helper function to build a list of all file extensions supported by ASSIMP
 // Helper function to build a list of all file extensions supported by ASSIMP
-void Importer::GetExtensionList(aiString& szOut) const
-{
+void Importer::GetExtensionList(aiString& szOut) const {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
     std::set<std::string> str;
     std::set<std::string> str;
     for (std::vector<BaseImporter*>::const_iterator i =  pimpl->mImporter.begin();i != pimpl->mImporter.end();++i)  {
     for (std::vector<BaseImporter*>::const_iterator i =  pimpl->mImporter.begin();i != pimpl->mImporter.end();++i)  {
@@ -985,8 +1007,9 @@ void Importer::GetExtensionList(aiString& szOut) const
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Set a configuration property
 // Set a configuration property
-bool Importer::SetPropertyInteger(const char* szName, int iValue)
-{
+bool Importer::SetPropertyInteger(const char* szName, int iValue) {
+    ai_assert(nullptr != pimpl);
+    
     bool existing;
     bool existing;
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
         existing = SetGenericProperty<int>(pimpl->mIntProperties, szName,iValue);
         existing = SetGenericProperty<int>(pimpl->mIntProperties, szName,iValue);
@@ -996,8 +1019,9 @@ bool Importer::SetPropertyInteger(const char* szName, int iValue)
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Set a configuration property
 // Set a configuration property
-bool Importer::SetPropertyFloat(const char* szName, ai_real iValue)
-{
+bool Importer::SetPropertyFloat(const char* szName, ai_real iValue) {
+    ai_assert(nullptr != pimpl);
+    
     bool existing;
     bool existing;
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
         existing = SetGenericProperty<ai_real>(pimpl->mFloatProperties, szName,iValue);
         existing = SetGenericProperty<ai_real>(pimpl->mFloatProperties, szName,iValue);
@@ -1007,8 +1031,9 @@ bool Importer::SetPropertyFloat(const char* szName, ai_real iValue)
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Set a configuration property
 // Set a configuration property
-bool Importer::SetPropertyString(const char* szName, const std::string& value)
-{
+bool Importer::SetPropertyString(const char* szName, const std::string& value) {
+    ai_assert(nullptr != pimpl);
+    
     bool existing;
     bool existing;
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
         existing = SetGenericProperty<std::string>(pimpl->mStringProperties, szName,value);
         existing = SetGenericProperty<std::string>(pimpl->mStringProperties, szName,value);
@@ -1018,8 +1043,9 @@ bool Importer::SetPropertyString(const char* szName, const std::string& value)
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Set a configuration property
 // Set a configuration property
-bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value)
-{
+bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value) {
+    ai_assert(nullptr != pimpl);
+    
     bool existing;
     bool existing;
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
         existing = SetGenericProperty<aiMatrix4x4>(pimpl->mMatrixProperties, szName,value);
         existing = SetGenericProperty<aiMatrix4x4>(pimpl->mMatrixProperties, szName,value);
@@ -1029,40 +1055,43 @@ bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value)
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get a configuration property
 // Get a configuration property
-int Importer::GetPropertyInteger(const char* szName,
-    int iErrorReturn /*= 0xffffffff*/) const
-{
+int Importer::GetPropertyInteger(const char* szName, int iErrorReturn /*= 0xffffffff*/) const {
+    ai_assert(nullptr != pimpl);
+    
     return GetGenericProperty<int>(pimpl->mIntProperties,szName,iErrorReturn);
     return GetGenericProperty<int>(pimpl->mIntProperties,szName,iErrorReturn);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get a configuration property
 // Get a configuration property
-ai_real Importer::GetPropertyFloat(const char* szName,
-    ai_real iErrorReturn /*= 10e10*/) const
-{
+ai_real Importer::GetPropertyFloat(const char* szName, ai_real iErrorReturn /*= 10e10*/) const {
+    ai_assert(nullptr != pimpl);
+    
     return GetGenericProperty<ai_real>(pimpl->mFloatProperties,szName,iErrorReturn);
     return GetGenericProperty<ai_real>(pimpl->mFloatProperties,szName,iErrorReturn);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get a configuration property
 // Get a configuration property
-const std::string Importer::GetPropertyString(const char* szName,
-    const std::string& iErrorReturn /*= ""*/) const
-{
+const std::string Importer::GetPropertyString(const char* szName, const std::string& iErrorReturn /*= ""*/) const {
+    ai_assert(nullptr != pimpl);
+    
     return GetGenericProperty<std::string>(pimpl->mStringProperties,szName,iErrorReturn);
     return GetGenericProperty<std::string>(pimpl->mStringProperties,szName,iErrorReturn);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get a configuration property
 // Get a configuration property
-const aiMatrix4x4 Importer::GetPropertyMatrix(const char* szName,
-    const aiMatrix4x4& iErrorReturn /*= aiMatrix4x4()*/) const
-{
+const aiMatrix4x4 Importer::GetPropertyMatrix(const char* szName, const aiMatrix4x4& iErrorReturn /*= aiMatrix4x4()*/) const {
+    ai_assert(nullptr != pimpl);
+    
     return GetGenericProperty<aiMatrix4x4>(pimpl->mMatrixProperties,szName,iErrorReturn);
     return GetGenericProperty<aiMatrix4x4>(pimpl->mMatrixProperties,szName,iErrorReturn);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get the memory requirements of a single node
 // Get the memory requirements of a single node
-inline void AddNodeWeight(unsigned int& iScene,const aiNode* pcNode)
-{
+inline 
+void AddNodeWeight(unsigned int& iScene,const aiNode* pcNode) {
+    if ( nullptr == pcNode ) {
+        return;
+    }
     iScene += sizeof(aiNode);
     iScene += sizeof(aiNode);
     iScene += sizeof(unsigned int) * pcNode->mNumMeshes;
     iScene += sizeof(unsigned int) * pcNode->mNumMeshes;
     iScene += sizeof(void*) * pcNode->mNumChildren;
     iScene += sizeof(void*) * pcNode->mNumChildren;
@@ -1074,8 +1103,9 @@ inline void AddNodeWeight(unsigned int& iScene,const aiNode* pcNode)
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get the memory requirements of the scene
 // Get the memory requirements of the scene
-void Importer::GetMemoryRequirements(aiMemoryInfo& in) const
-{
+void Importer::GetMemoryRequirements(aiMemoryInfo& in) const {
+    ai_assert(nullptr != pimpl);
+    
     in = aiMemoryInfo();
     in = aiMemoryInfo();
     aiScene* mScene = pimpl->mScene;
     aiScene* mScene = pimpl->mScene;
 
 
@@ -1087,8 +1117,7 @@ void Importer::GetMemoryRequirements(aiMemoryInfo& in) const
     in.total = sizeof(aiScene);
     in.total = sizeof(aiScene);
 
 
     // add all meshes
     // add all meshes
-    for (unsigned int i = 0; i < mScene->mNumMeshes;++i)
-    {
+    for (unsigned int i = 0; i < mScene->mNumMeshes;++i) {
         in.meshes += sizeof(aiMesh);
         in.meshes += sizeof(aiMesh);
         if (mScene->mMeshes[i]->HasPositions()) {
         if (mScene->mMeshes[i]->HasPositions()) {
             in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices;
             in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices;
@@ -1105,14 +1134,16 @@ void Importer::GetMemoryRequirements(aiMemoryInfo& in) const
         for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS;++a) {
         for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS;++a) {
             if (mScene->mMeshes[i]->HasVertexColors(a)) {
             if (mScene->mMeshes[i]->HasVertexColors(a)) {
                 in.meshes += sizeof(aiColor4D) * mScene->mMeshes[i]->mNumVertices;
                 in.meshes += sizeof(aiColor4D) * mScene->mMeshes[i]->mNumVertices;
+            } else {
+                break;
             }
             }
-            else break;
         }
         }
         for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS;++a) {
         for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS;++a) {
             if (mScene->mMeshes[i]->HasTextureCoords(a)) {
             if (mScene->mMeshes[i]->HasTextureCoords(a)) {
                 in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices;
                 in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices;
+            } else {
+                break;
             }
             }
-            else break;
         }
         }
         if (mScene->mMeshes[i]->HasBones()) {
         if (mScene->mMeshes[i]->HasBones()) {
             in.meshes += sizeof(void*) * mScene->mMeshes[i]->mNumBones;
             in.meshes += sizeof(void*) * mScene->mMeshes[i]->mNumBones;
@@ -1131,8 +1162,9 @@ void Importer::GetMemoryRequirements(aiMemoryInfo& in) const
         in.textures += sizeof(aiTexture);
         in.textures += sizeof(aiTexture);
         if (pc->mHeight) {
         if (pc->mHeight) {
             in.textures += 4 * pc->mHeight * pc->mWidth;
             in.textures += 4 * pc->mHeight * pc->mWidth;
+        } else {
+            in.textures += pc->mWidth;
         }
         }
-        else in.textures += pc->mWidth;
     }
     }
     in.total += in.textures;
     in.total += in.textures;
 
 

+ 6 - 0
code/Common/ImporterRegistry.cpp

@@ -197,6 +197,9 @@ corresponding preprocessor flag to selectively disable formats.
 #ifndef ASSIMP_BUILD_NO_MMD_IMPORTER
 #ifndef ASSIMP_BUILD_NO_MMD_IMPORTER
 #   include "MMD/MMDImporter.h"
 #   include "MMD/MMDImporter.h"
 #endif
 #endif
+#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
+#   include "M3D/M3DImporter.h"
+#endif
 #ifndef ASSIMP_BUILD_NO_STEP_IMPORTER
 #ifndef ASSIMP_BUILD_NO_STEP_IMPORTER
 #   include "Importer/StepFile/StepFileImporter.h"
 #   include "Importer/StepFile/StepFileImporter.h"
 #endif
 #endif
@@ -223,6 +226,9 @@ void GetImporterInstanceList(std::vector< BaseImporter* >& out)
 #if (!defined ASSIMP_BUILD_NO_3DS_IMPORTER)
 #if (!defined ASSIMP_BUILD_NO_3DS_IMPORTER)
     out.push_back( new Discreet3DSImporter());
     out.push_back( new Discreet3DSImporter());
 #endif
 #endif
+#if (!defined ASSIMP_BUILD_NO_M3D_IMPORTER)
+    out.push_back( new M3DImporter());
+#endif
 #if (!defined ASSIMP_BUILD_NO_MD3_IMPORTER)
 #if (!defined ASSIMP_BUILD_NO_MD3_IMPORTER)
     out.push_back( new MD3Importer());
     out.push_back( new MD3Importer());
 #endif
 #endif

+ 7 - 0
code/Common/PostStepRegistry.cpp

@@ -131,11 +131,15 @@ corresponding preprocessor flag to selectively disable steps.
 #if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS)
 #if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS)
 #   include "PostProcessing/ScaleProcess.h"
 #   include "PostProcessing/ScaleProcess.h"
 #endif
 #endif
+#if (!defined ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS)
+#   include "PostProcessing/ArmaturePopulate.h"
+#endif
 #if (!defined ASSIMP_BUILD_NO_GENBOUNDINGBOXES_PROCESS)
 #if (!defined ASSIMP_BUILD_NO_GENBOUNDINGBOXES_PROCESS)
 #   include "PostProcessing/GenBoundingBoxesProcess.h"
 #   include "PostProcessing/GenBoundingBoxesProcess.h"
 #endif
 #endif
 
 
 
 
+
 namespace Assimp {
 namespace Assimp {
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -180,6 +184,9 @@ void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out)
 #if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS)
 #if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS)
     out.push_back( new ScaleProcess());
     out.push_back( new ScaleProcess());
 #endif
 #endif
+#if (!defined ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS)
+    out.push_back( new ArmaturePopulate());
+#endif
 #if (!defined ASSIMP_BUILD_NO_PRETRANSFORMVERTICES_PROCESS)
 #if (!defined ASSIMP_BUILD_NO_PRETRANSFORMVERTICES_PROCESS)
     out.push_back( new PretransformVertices());
     out.push_back( new PretransformVertices());
 #endif
 #endif

+ 50 - 0
code/Common/SceneCombiner.cpp

@@ -1091,6 +1091,35 @@ void SceneCombiner::Copy( aiMesh** _dest, const aiMesh* src ) {
         aiFace& f = dest->mFaces[i];
         aiFace& f = dest->mFaces[i];
         GetArrayCopy(f.mIndices,f.mNumIndices);
         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
     // and reallocate all arrays
     CopyPtrArray( dest->mChannels, src->mChannels, dest->mNumChannels );
     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 );
     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) {
 void SceneCombiner::Copy( aiCamera** _dest,const  aiCamera* src) {
     if ( nullptr == _dest || nullptr == src ) {
     if ( nullptr == _dest || nullptr == src ) {

+ 11 - 9
code/Common/Version.cpp

@@ -46,8 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/scene.h>
 #include <assimp/scene.h>
 #include "ScenePrivate.h"
 #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.
 // Legal information string - don't remove this.
@@ -56,9 +55,9 @@ static const char* LEGAL_INFORMATION =
 "Open Asset Import Library (Assimp).\n"
 "Open Asset Import Library (Assimp).\n"
 "A free C/C++ library to import various 3D file formats into applications\n\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"
 "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;
     return LEGAL_INFORMATION;
 }
 }
 
 
+// ------------------------------------------------------------------------------------------------
+// Get Assimp patch version
+ASSIMP_API unsigned int aiGetVersionPatch() {
+	return VER_PATCH;
+}
+
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get Assimp minor version
 // Get Assimp minor version
 ASSIMP_API unsigned int aiGetVersionMinor ()    {
 ASSIMP_API unsigned int aiGetVersionMinor ()    {
-    return MinorVersion;
+    return VER_MINOR;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get Assimp major version
 // Get Assimp major version
 ASSIMP_API unsigned int aiGetVersionMajor ()    {
 ASSIMP_API unsigned int aiGetVersionMajor ()    {
-    return MajorVersion;
+    return VER_MAJOR;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -104,9 +109,6 @@ ASSIMP_API unsigned int aiGetCompileFlags ()    {
     return flags;
     return flags;
 }
 }
 
 
-// include current build revision, which is even updated from time to time -- :-)
-#include "revision.h"
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 ASSIMP_API unsigned int aiGetVersionRevision() {
 ASSIMP_API unsigned int aiGetVersionRevision() {
     return GitVersion;
     return GitVersion;

+ 2 - 2
code/Common/VertexTriangleAdjacency.cpp

@@ -58,7 +58,7 @@ VertexTriangleAdjacency::VertexTriangleAdjacency(aiFace *pcFaces,
 {
 {
     // compute the number of referenced vertices if it wasn't specified by the caller
     // compute the number of referenced vertices if it wasn't specified by the caller
     const aiFace* const pcFaceEnd = pcFaces + iNumFaces;
     const aiFace* const pcFaceEnd = pcFaces + iNumFaces;
-    if (!iNumVertices)  {
+    if (0 == iNumVertices)  {
         for (aiFace* pcFace = pcFaces; pcFace != pcFaceEnd; ++pcFace)   {
         for (aiFace* pcFace = pcFaces; pcFace != pcFaceEnd; ++pcFace)   {
             ai_assert( nullptr != pcFace );
             ai_assert( nullptr != pcFace );
             ai_assert(3 == pcFace->mNumIndices);
             ai_assert(3 == pcFace->mNumIndices);
@@ -68,7 +68,7 @@ VertexTriangleAdjacency::VertexTriangleAdjacency(aiFace *pcFaces,
         }
         }
     }
     }
 
 
-    mNumVertices = iNumVertices;
+    mNumVertices = iNumVertices + 1;
 
 
     unsigned int* pi;
     unsigned int* pi;
 
 

+ 539 - 0
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 <assimp/ZipArchiveIOSystem.h>
+#include <assimp/BaseImporter.h>
+
+#include <assimp/ai_assert.h>
+
+#include <map>
+#include <memory>
+
+#ifdef ASSIMP_USE_HUNTER
+#  include <minizip/unzip.h>
+#else
+#  include <unzip.h>
+#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<IOSystem*>(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<uLong>(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<uLong>(io_stream->Write(buf, 1, size));
+    }
+
+    long IOSystem2Unzip::tell(voidpf /*opaque*/, voidpf stream) {
+        IOStream* io_stream = (IOStream*)stream;
+
+        return static_cast<long>(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<voidpf>(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<uint8_t[]> 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<unz_file_pos_s*>(&(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<unsigned int>(m_Size)) != static_cast<int>(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<uint8_t[]>(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<std::string>& rFileList);
+        void getFileListExtension(std::vector<std::string>& 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<std::string, ZipFileInfo> 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<std::string>& rFileList) {
+        MapArchive();
+        rFileList.clear();
+
+        for (const auto &file : m_ArchiveMap) {
+            rFileList.push_back(file.first);
+        }
+    }
+
+    void ZipArchiveIOSystem::Implement::getFileListExtension(std::vector<std::string>& 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<std::string>& rFileList) const {
+        return pImpl->getFileList(rFileList);
+    }
+
+    void ZipArchiveIOSystem::getFileListExtension(std::vector<std::string>& 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());
+    }
+
+}

+ 9 - 9
code/Common/scene.cpp

@@ -44,23 +44,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 aiNode::aiNode()
 aiNode::aiNode()
 : mName("")
 : mName("")
-, mParent(NULL)
+, mParent(nullptr)
 , mNumChildren(0)
 , mNumChildren(0)
-, mChildren(NULL)
+, mChildren(nullptr)
 , mNumMeshes(0)
 , mNumMeshes(0)
-, mMeshes(NULL)
-, mMetaData(NULL) {
+, mMeshes(nullptr)
+, mMetaData(nullptr) {
     // empty
     // empty
 }
 }
 
 
 aiNode::aiNode(const std::string& name)
 aiNode::aiNode(const std::string& name)
 : mName(name)
 : mName(name)
-, mParent(NULL)
+, mParent(nullptr)
 , mNumChildren(0)
 , mNumChildren(0)
-, mChildren(NULL)
+, mChildren(nullptr)
 , mNumMeshes(0)
 , mNumMeshes(0)
-, mMeshes(NULL)
-, mMetaData(NULL) {
+, mMeshes(nullptr)
+, mMetaData(nullptr) {
     // empty
     // empty
 }
 }
 
 
@@ -68,7 +68,7 @@ aiNode::aiNode(const std::string& name)
 aiNode::~aiNode() {
 aiNode::~aiNode() {
     // delete all children recursively
     // delete all children recursively
     // to make sure we won't crash if the data is invalid ...
     // 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++)
         for (unsigned int a = 0; a < mNumChildren; a++)
             delete mChildren[a];
             delete mChildren[a];

+ 4 - 3
code/FBX/FBXCommon.h

@@ -50,9 +50,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp {
 namespace Assimp {
 namespace FBX
 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 SEPARATOR = {'\x00', '\x01'}; // for use inside strings
     const std::string MAGIC_NODE_TAG = "_$AssimpFbx$"; // from import
     const std::string MAGIC_NODE_TAG = "_$AssimpFbx$"; // from import
     const int64_t SECOND = 46186158000; // FBX's kTime unit
     const int64_t SECOND = 46186158000; // FBX's kTime unit

+ 8 - 0
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
 #define INCLUDED_AI_FBX_COMPILECONFIG_H
 
 
 #include <map>
 #include <map>
+#include <set>
 
 
 //
 //
 #if _MSC_VER > 1500 || (defined __GNUC___)
 #if _MSC_VER > 1500 || (defined __GNUC___)
@@ -54,16 +55,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #   else
 #   else
 #   define fbx_unordered_map map
 #   define fbx_unordered_map map
 #   define fbx_unordered_multimap multimap
 #   define fbx_unordered_multimap multimap
+#   define fbx_unordered_set set
+#   define fbx_unordered_multiset multiset
 #endif
 #endif
 
 
 #ifdef ASSIMP_FBX_USE_UNORDERED_MULTIMAP
 #ifdef ASSIMP_FBX_USE_UNORDERED_MULTIMAP
 #   include <unordered_map>
 #   include <unordered_map>
+#   include <unordered_set>
 #   if _MSC_VER > 1600
 #   if _MSC_VER > 1600
 #       define fbx_unordered_map unordered_map
 #       define fbx_unordered_map unordered_map
 #       define fbx_unordered_multimap unordered_multimap
 #       define fbx_unordered_multimap unordered_multimap
+#       define fbx_unordered_set unordered_set
+#       define fbx_unordered_multiset unordered_multiset
 #   else
 #   else
 #       define fbx_unordered_map tr1::unordered_map
 #       define fbx_unordered_map tr1::unordered_map
 #       define fbx_unordered_multimap tr1::unordered_multimap
 #       define fbx_unordered_multimap tr1::unordered_multimap
+#       define fbx_unordered_set tr1::unordered_set
+#       define fbx_unordered_multiset tr1::unordered_multiset
 #   endif
 #   endif
 #endif
 #endif
 
 

+ 291 - 185
code/FBX/FBXConverter.cpp

@@ -55,10 +55,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "FBXImporter.h"
 #include "FBXImporter.h"
 
 
 #include <assimp/StringComparison.h>
 #include <assimp/StringComparison.h>
+#include <assimp/MathFunctions.h>
 
 
 #include <assimp/scene.h>
 #include <assimp/scene.h>
 
 
 #include <assimp/CreateAnimMesh.h>
 #include <assimp/CreateAnimMesh.h>
+#include <assimp/commonMetaData.h>
+#include <assimp/StringUtils.h>
 
 
 #include <tuple>
 #include <tuple>
 #include <memory>
 #include <memory>
@@ -66,7 +69,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <vector>
 #include <vector>
 #include <sstream>
 #include <sstream>
 #include <iomanip>
 #include <iomanip>
-
+#include <cstdint>
+#include <iostream>
+#include <stdlib.h>
 
 
 namespace Assimp {
 namespace Assimp {
     namespace FBX {
     namespace FBX {
@@ -75,9 +80,9 @@ namespace Assimp {
 
 
 #define MAGIC_NODE_TAG "_$AssimpFbx$"
 #define MAGIC_NODE_TAG "_$AssimpFbx$"
 
 
-#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000L
+#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000LL
 
 
-        FBXConverter::FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones, FbxUnit unit )
+        FBXConverter::FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones )
         : defaultMaterialIndex()
         : defaultMaterialIndex()
         , lights()
         , lights()
         , cameras()
         , cameras()
@@ -89,13 +94,19 @@ namespace Assimp {
         , mNodeNames()
         , mNodeNames()
         , anim_fps()
         , anim_fps()
         , out(out)
         , out(out)
-        , doc(doc)
-        , mRemoveEmptyBones( removeEmptyBones )
-        , mCurrentUnit(FbxUnit::cm) {
+        , doc(doc) {
             // animations need to be converted first since this will
             // animations need to be converted first since this will
             // populate the node_anim_chain_bits map, which is needed
             // populate the node_anim_chain_bits map, which is needed
             // to determine which nodes need to be generated.
             // to determine which nodes need to be generated.
             ConvertAnimations();
             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();
             ConvertRootNode();
 
 
             if (doc.Settings().readAllMaterials) {
             if (doc.Settings().readAllMaterials) {
@@ -119,7 +130,6 @@ namespace Assimp {
 
 
             ConvertGlobalSettings();
             ConvertGlobalSettings();
             TransferDataToScene();
             TransferDataToScene();
-            ConvertToUnitScale(unit);
 
 
             // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE
             // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE
             // to make sure the scene passes assimp's validation. FBX files
             // to make sure the scene passes assimp's validation. FBX files
@@ -146,7 +156,7 @@ namespace Assimp {
             out->mRootNode->mName.Set(unique_name);
             out->mRootNode->mName.Set(unique_name);
 
 
             // root has ID 0
             // root has ID 0
-            ConvertNodes(0L, *out->mRootNode);
+            ConvertNodes(0L, out->mRootNode, out->mRootNode);
         }
         }
 
 
         static std::string getAncestorBaseName(const aiNode* node)
         static std::string getAncestorBaseName(const aiNode* node)
@@ -180,8 +190,11 @@ namespace Assimp {
             GetUniqueName(original_name, unique_name);
             GetUniqueName(original_name, unique_name);
             return 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<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(id, "Model");
             const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(id, "Model");
 
 
             std::vector<aiNode*> nodes;
             std::vector<aiNode*> nodes;
@@ -192,62 +205,69 @@ namespace Assimp {
 
 
             try {
             try {
                 for (const Connection* con : conns) {
                 for (const Connection* con : conns) {
-
                     // ignore object-property links
                     // ignore object-property links
                     if (con->PropertyName().length()) {
                     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();
                     const Object* const object = con->SourceObject();
                     if (nullptr == object) {
                     if (nullptr == object) {
-                        FBXImporter::LogWarn("failed to convert source object for Model link");
+                        FBXImporter::LogError("failed to convert source object for Model link");
                         continue;
                         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<const Model*>(object);
                     const Model* const model = dynamic_cast<const Model*>(object);
 
 
                     if (nullptr != model) {
                     if (nullptr != model) {
                         nodes_chain.clear();
                         nodes_chain.clear();
                         post_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
                         // even though there is only a single input node, the design of
                         // assimp (or rather: the complicated transformation chain that
                         // assimp (or rather: the complicated transformation chain that
                         // is employed by fbx) means that we may need multiple aiNode's
                         // is employed by fbx) means that we may need multiple aiNode's
                         // to represent a fbx node's transformation.
                         // 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());
                         ai_assert(nodes_chain.size());
 
 
                         if (need_additional_node) {
                         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
                         //setup metadata on newest node
                         SetupNodeMetadata(*model, *nodes_chain.back());
                         SetupNodeMetadata(*model, *nodes_chain.back());
 
 
                         // link all nodes in a row
                         // 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->mNumChildren = 1;
                                 last_parent->mChildren = new aiNode*[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
                         // 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
                         // check if there will be any child nodes
                         const std::vector<const Connection*>& child_conns
                         const std::vector<const Connection*>& child_conns
@@ -259,7 +279,7 @@ namespace Assimp {
                             for (aiNode* postnode : post_nodes_chain) {
                             for (aiNode* postnode : post_nodes_chain) {
                                 ai_assert(postnode);
                                 ai_assert(postnode);
 
 
-                                if (last_parent != &parent) {
+                                if (last_parent != parent) {
                                     last_parent->mNumChildren = 1;
                                     last_parent->mNumChildren = 1;
                                     last_parent->mChildren = new aiNode*[1];
                                     last_parent->mChildren = new aiNode*[1];
                                     last_parent->mChildren[0] = postnode;
                                     last_parent->mChildren[0] = postnode;
@@ -281,15 +301,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) {
                         if (doc.Settings().readLights) {
-                            ConvertLights(*model, unique_name);
+                            ConvertLights(*model, node_name);
                         }
                         }
 
 
                         if (doc.Settings().readCameras) {
                         if (doc.Settings().readCameras) {
-                            ConvertCameras(*model, unique_name);
+                            ConvertCameras(*model, node_name);
                         }
                         }
 
 
                         nodes.push_back(nodes_chain.front());
                         nodes.push_back(nodes_chain.front());
@@ -298,11 +318,17 @@ namespace Assimp {
                 }
                 }
 
 
                 if (nodes.size()) {
                 if (nodes.size()) {
-                    parent.mChildren = new aiNode*[nodes.size()]();
-                    parent.mNumChildren = static_cast<unsigned int>(nodes.size());
+                    parent->mChildren = new aiNode*[nodes.size()]();
+                    parent->mNumChildren = static_cast<unsigned int>(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&) {
             catch (std::exception&) {
                 Util::delete_fun<aiNode> deleter;
                 Util::delete_fun<aiNode> deleter;
@@ -555,7 +581,7 @@ namespace Assimp {
                 return;
                 return;
             }
             }
 
 
-            const float angle_epsilon = 1e-6f;
+            const float angle_epsilon = Math::getEpsilon<float>();
 
 
             out = aiMatrix4x4();
             out = aiMatrix4x4();
 
 
@@ -685,30 +711,37 @@ namespace Assimp {
             bool ok;
             bool ok;
 
 
             aiMatrix4x4 chain[TransformationComp_MAXIMUM];
             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<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4());
             std::fill_n(chain, static_cast<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4());
 
 
             // generate transformation matrices for all the different transformation components
             // generate transformation matrices for all the different transformation components
-            const float zero_epsilon = 1e-6f;
+            const float zero_epsilon = Math::getEpsilon<float>();
             const aiVector3D all_ones(1.0f, 1.0f, 1.0f);
             const aiVector3D all_ones(1.0f, 1.0f, 1.0f);
-            bool is_complex = false;
 
 
             const aiVector3D& PreRotation = PropertyGet<aiVector3D>(props, "PreRotation", ok);
             const aiVector3D& PreRotation = PropertyGet<aiVector3D>(props, "PreRotation", ok);
             if (ok && PreRotation.SquareLength() > zero_epsilon) {
             if (ok && PreRotation.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_PreRotation);
 
 
                 GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PreRotation, chain[TransformationComp_PreRotation]);
                 GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PreRotation, chain[TransformationComp_PreRotation]);
             }
             }
 
 
             const aiVector3D& PostRotation = PropertyGet<aiVector3D>(props, "PostRotation", ok);
             const aiVector3D& PostRotation = PropertyGet<aiVector3D>(props, "PostRotation", ok);
             if (ok && PostRotation.SquareLength() > zero_epsilon) {
             if (ok && PostRotation.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_PostRotation);
 
 
                 GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PostRotation, chain[TransformationComp_PostRotation]);
                 GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PostRotation, chain[TransformationComp_PostRotation]);
             }
             }
 
 
             const aiVector3D& RotationPivot = PropertyGet<aiVector3D>(props, "RotationPivot", ok);
             const aiVector3D& RotationPivot = PropertyGet<aiVector3D>(props, "RotationPivot", ok);
             if (ok && RotationPivot.SquareLength() > zero_epsilon) {
             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_RotationPivot]);
                 aiMatrix4x4::Translation(-RotationPivot, chain[TransformationComp_RotationPivotInverse]);
                 aiMatrix4x4::Translation(-RotationPivot, chain[TransformationComp_RotationPivotInverse]);
@@ -716,21 +749,21 @@ namespace Assimp {
 
 
             const aiVector3D& RotationOffset = PropertyGet<aiVector3D>(props, "RotationOffset", ok);
             const aiVector3D& RotationOffset = PropertyGet<aiVector3D>(props, "RotationOffset", ok);
             if (ok && RotationOffset.SquareLength() > zero_epsilon) {
             if (ok && RotationOffset.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_RotationOffset);
 
 
                 aiMatrix4x4::Translation(RotationOffset, chain[TransformationComp_RotationOffset]);
                 aiMatrix4x4::Translation(RotationOffset, chain[TransformationComp_RotationOffset]);
             }
             }
 
 
             const aiVector3D& ScalingOffset = PropertyGet<aiVector3D>(props, "ScalingOffset", ok);
             const aiVector3D& ScalingOffset = PropertyGet<aiVector3D>(props, "ScalingOffset", ok);
             if (ok && ScalingOffset.SquareLength() > zero_epsilon) {
             if (ok && ScalingOffset.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_ScalingOffset);
 
 
                 aiMatrix4x4::Translation(ScalingOffset, chain[TransformationComp_ScalingOffset]);
                 aiMatrix4x4::Translation(ScalingOffset, chain[TransformationComp_ScalingOffset]);
             }
             }
 
 
             const aiVector3D& ScalingPivot = PropertyGet<aiVector3D>(props, "ScalingPivot", ok);
             const aiVector3D& ScalingPivot = PropertyGet<aiVector3D>(props, "ScalingPivot", ok);
             if (ok && ScalingPivot.SquareLength() > zero_epsilon) {
             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_ScalingPivot]);
                 aiMatrix4x4::Translation(-ScalingPivot, chain[TransformationComp_ScalingPivotInverse]);
                 aiMatrix4x4::Translation(-ScalingPivot, chain[TransformationComp_ScalingPivotInverse]);
@@ -738,22 +771,28 @@ namespace Assimp {
 
 
             const aiVector3D& Translation = PropertyGet<aiVector3D>(props, "Lcl Translation", ok);
             const aiVector3D& Translation = PropertyGet<aiVector3D>(props, "Lcl Translation", ok);
             if (ok && Translation.SquareLength() > zero_epsilon) {
             if (ok && Translation.SquareLength() > zero_epsilon) {
+                chainBits = chainBits | (1 << TransformationComp_Translation);
+
                 aiMatrix4x4::Translation(Translation, chain[TransformationComp_Translation]);
                 aiMatrix4x4::Translation(Translation, chain[TransformationComp_Translation]);
             }
             }
 
 
             const aiVector3D& Scaling = PropertyGet<aiVector3D>(props, "Lcl Scaling", ok);
             const aiVector3D& Scaling = PropertyGet<aiVector3D>(props, "Lcl Scaling", ok);
             if (ok && (Scaling - all_ones).SquareLength() > zero_epsilon) {
             if (ok && (Scaling - all_ones).SquareLength() > zero_epsilon) {
+                chainBits = chainBits | (1 << TransformationComp_Scaling);
+
                 aiMatrix4x4::Scaling(Scaling, chain[TransformationComp_Scaling]);
                 aiMatrix4x4::Scaling(Scaling, chain[TransformationComp_Scaling]);
             }
             }
 
 
             const aiVector3D& Rotation = PropertyGet<aiVector3D>(props, "Lcl Rotation", ok);
             const aiVector3D& Rotation = PropertyGet<aiVector3D>(props, "Lcl Rotation", ok);
             if (ok && Rotation.SquareLength() > zero_epsilon) {
             if (ok && Rotation.SquareLength() > zero_epsilon) {
+                chainBits = chainBits | (1 << TransformationComp_Rotation);
+
                 GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]);
                 GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]);
             }
             }
 
 
             const aiVector3D& GeometricScaling = PropertyGet<aiVector3D>(props, "GeometricScaling", ok);
             const aiVector3D& GeometricScaling = PropertyGet<aiVector3D>(props, "GeometricScaling", ok);
             if (ok && (GeometricScaling - all_ones).SquareLength() > zero_epsilon) {
             if (ok && (GeometricScaling - all_ones).SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_GeometricScaling);
                 aiMatrix4x4::Scaling(GeometricScaling, chain[TransformationComp_GeometricScaling]);
                 aiMatrix4x4::Scaling(GeometricScaling, chain[TransformationComp_GeometricScaling]);
                 aiVector3D GeometricScalingInverse = GeometricScaling;
                 aiVector3D GeometricScalingInverse = GeometricScaling;
                 bool canscale = true;
                 bool canscale = true;
@@ -768,13 +807,14 @@ namespace Assimp {
                     }
                     }
                 }
                 }
                 if (canscale) {
                 if (canscale) {
+                    chainBits = chainBits | (1 << TransformationComp_GeometricScalingInverse);
                     aiMatrix4x4::Scaling(GeometricScalingInverse, chain[TransformationComp_GeometricScalingInverse]);
                     aiMatrix4x4::Scaling(GeometricScalingInverse, chain[TransformationComp_GeometricScalingInverse]);
                 }
                 }
             }
             }
 
 
             const aiVector3D& GeometricRotation = PropertyGet<aiVector3D>(props, "GeometricRotation", ok);
             const aiVector3D& GeometricRotation = PropertyGet<aiVector3D>(props, "GeometricRotation", ok);
             if (ok && GeometricRotation.SquareLength() > zero_epsilon) {
             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_GeometricRotation]);
                 GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotationInverse]);
                 GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotationInverse]);
                 chain[TransformationComp_GeometricRotationInverse].Inverse();
                 chain[TransformationComp_GeometricRotationInverse].Inverse();
@@ -782,7 +822,7 @@ namespace Assimp {
 
 
             const aiVector3D& GeometricTranslation = PropertyGet<aiVector3D>(props, "GeometricTranslation", ok);
             const aiVector3D& GeometricTranslation = PropertyGet<aiVector3D>(props, "GeometricTranslation", ok);
             if (ok && GeometricTranslation.SquareLength() > zero_epsilon) {
             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_GeometricTranslation]);
                 aiMatrix4x4::Translation(-GeometricTranslation, chain[TransformationComp_GeometricTranslationInverse]);
                 aiMatrix4x4::Translation(-GeometricTranslation, chain[TransformationComp_GeometricTranslationInverse]);
             }
             }
@@ -790,12 +830,12 @@ namespace Assimp {
             // is_complex needs to be consistent with NeedsComplexTransformationChain()
             // is_complex needs to be consistent with NeedsComplexTransformationChain()
             // or the interplay between this code and the animation converter would
             // or the interplay between this code and the animation converter would
             // not be guaranteed.
             // 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,
             // now, if we have more than just Translation, Scaling and Rotation,
             // we need to generate a full node chain to accommodate for assimp's
             // we need to generate a full node chain to accommodate for assimp's
             // lack to express pivots and offsets.
             // 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);
                 FBXImporter::LogInfo("generating full transformation chain for node: " + name);
 
 
                 // query the anim_chain_bits dictionary to find out which chain elements
                 // query the anim_chain_bits dictionary to find out which chain elements
@@ -808,7 +848,7 @@ namespace Assimp {
                 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) {
                 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) {
                     const TransformationComp comp = static_cast<TransformationComp>(i);
                     const TransformationComp comp = static_cast<TransformationComp>(i);
 
 
-                    if (chain[i].IsIdentity() && (anim_chain_bitmask & bit) == 0) {
+                    if ((chainBits & bit) == 0 && (anim_chain_bitmask & bit) == 0) {
                         continue;
                         continue;
                     }
                     }
 
 
@@ -892,7 +932,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<const Geometry*>& geos = model.GetGeometry();
             const std::vector<const Geometry*>& geos = model.GetGeometry();
 
 
@@ -904,11 +945,12 @@ namespace Assimp {
                 const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*>(geo);
                 const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*>(geo);
                 const LineGeometry* const line = dynamic_cast<const LineGeometry*>(geo);
                 const LineGeometry* const line = dynamic_cast<const LineGeometry*>(geo);
                 if (mesh) {
                 if (mesh) {
-                    const std::vector<unsigned int>& indices = ConvertMesh(*mesh, model, node_global_transform, nd);
+                    const std::vector<unsigned int>& indices = ConvertMesh(*mesh, model, parent, root_node,
+                                                                           absolute_transform);
                     std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
                     std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
                 }
                 }
                 else if (line) {
                 else if (line) {
-                    const std::vector<unsigned int>& indices = ConvertLine(*line, model, node_global_transform, nd);
+                    const std::vector<unsigned int>& indices = ConvertLine(*line, model, parent, root_node);
                     std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
                     std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
                 }
                 }
                 else {
                 else {
@@ -917,15 +959,16 @@ namespace Assimp {
             }
             }
 
 
             if (meshes.size()) {
             if (meshes.size()) {
-                nd.mMeshes = new unsigned int[meshes.size()]();
-                nd.mNumMeshes = static_cast<unsigned int>(meshes.size());
+                parent->mMeshes = new unsigned int[meshes.size()]();
+                parent->mNumMeshes = static_cast<unsigned int>(meshes.size());
 
 
-                std::swap_ranges(meshes.begin(), meshes.end(), nd.mMeshes);
+                std::swap_ranges(meshes.begin(), meshes.end(), parent->mMeshes);
             }
             }
         }
         }
 
 
-        std::vector<unsigned int> FBXConverter::ConvertMesh(const MeshGeometry& mesh, const Model& model,
-            const aiMatrix4x4& node_global_transform, aiNode& nd)
+        std::vector<unsigned int>
+        FBXConverter::ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node,
+                                  const aiMatrix4x4 &absolute_transform)
         {
         {
             std::vector<unsigned int> temp;
             std::vector<unsigned int> temp;
 
 
@@ -949,18 +992,18 @@ namespace Assimp {
                 const MatIndexArray::value_type base = mindices[0];
                 const MatIndexArray::value_type base = mindices[0];
                 for (MatIndexArray::value_type index : mindices) {
                 for (MatIndexArray::value_type index : mindices) {
                     if (index != base) {
                     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
             // 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;
             return temp;
         }
         }
 
 
         std::vector<unsigned int> FBXConverter::ConvertLine(const LineGeometry& line, const Model& model,
         std::vector<unsigned int> FBXConverter::ConvertLine(const LineGeometry& line, const Model& model,
-            const aiMatrix4x4& node_global_transform, aiNode& nd)
+                                                            aiNode *parent, aiNode *root_node)
         {
         {
             std::vector<unsigned int> temp;
             std::vector<unsigned int> temp;
 
 
@@ -971,7 +1014,7 @@ namespace Assimp {
                 return temp;
                 return temp;
             }
             }
 
 
-            aiMesh* const out_mesh = SetupEmptyMesh(line, nd);
+            aiMesh* const out_mesh = SetupEmptyMesh(line, root_node);
             out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
             out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
 
 
             // copy vertices
             // copy vertices
@@ -1006,7 +1049,7 @@ namespace Assimp {
             return temp;
             return temp;
         }
         }
 
 
-        aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode& nd)
+        aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode *parent)
         {
         {
             aiMesh* const out_mesh = new aiMesh();
             aiMesh* const out_mesh = new aiMesh();
             meshes.push_back(out_mesh);
             meshes.push_back(out_mesh);
@@ -1023,17 +1066,18 @@ namespace Assimp {
             }
             }
             else
             else
             {
             {
-                out_mesh->mName = nd.mName;
+                out_mesh->mName = parent->mName;
             }
             }
 
 
             return out_mesh;
             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();
             const MatIndexArray& mindices = mesh.GetMaterialIndices();
-            aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd);
+            aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent);
 
 
             const std::vector<aiVector3D>& vertices = mesh.GetVertices();
             const std::vector<aiVector3D>& vertices = mesh.GetVertices();
             const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
             const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
@@ -1100,7 +1144,7 @@ namespace Assimp {
                         binormals = &tempBinormals;
                         binormals = &tempBinormals;
                     }
                     }
                     else {
                     else {
-                        binormals = NULL;
+                        binormals = nullptr;
                     }
                     }
                 }
                 }
 
 
@@ -1150,8 +1194,9 @@ namespace Assimp {
                 ConvertMaterialForMesh(out_mesh, model, mesh, mindices[0]);
                 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<aiAnimMesh*> animMeshes;
             std::vector<aiAnimMesh*> animMeshes;
@@ -1196,8 +1241,10 @@ namespace Assimp {
             return static_cast<unsigned int>(meshes.size() - 1);
             return static_cast<unsigned int>(meshes.size() - 1);
         }
         }
 
 
-        std::vector<unsigned int> FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
-            const aiMatrix4x4& node_global_transform, aiNode& nd)
+        std::vector<unsigned int>
+        FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent,
+                                               aiNode *root_node,
+                                               const aiMatrix4x4 &absolute_transform)
         {
         {
             const MatIndexArray& mindices = mesh.GetMaterialIndices();
             const MatIndexArray& mindices = mesh.GetMaterialIndices();
             ai_assert(mindices.size());
             ai_assert(mindices.size());
@@ -1208,7 +1255,7 @@ namespace Assimp {
             for (MatIndexArray::value_type index : mindices) {
             for (MatIndexArray::value_type index : mindices) {
                 if (had.find(index) == had.end()) {
                 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);
                     had.insert(index);
                 }
                 }
             }
             }
@@ -1216,18 +1263,18 @@ namespace Assimp {
             return indices;
             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 MatIndexArray& mindices = mesh.GetMaterialIndices();
             const std::vector<aiVector3D>& vertices = mesh.GetVertices();
             const std::vector<aiVector3D>& vertices = mesh.GetVertices();
             const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
             const std::vector<unsigned int>& 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_faces = 0;
             unsigned int count_vertices = 0;
             unsigned int count_vertices = 0;
@@ -1287,7 +1334,7 @@ namespace Assimp {
                         binormals = &tempBinormals;
                         binormals = &tempBinormals;
                     }
                     }
                     else {
                     else {
-                        binormals = NULL;
+                        binormals = nullptr;
                     }
                     }
                 }
                 }
 
 
@@ -1386,7 +1433,7 @@ namespace Assimp {
             ConvertMaterialForMesh(out_mesh, model, mesh, index);
             ConvertMaterialForMesh(out_mesh, model, mesh, index);
 
 
             if (process_weights) {
             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<aiAnimMesh*> animMeshes;
             std::vector<aiAnimMesh*> animMeshes;
@@ -1436,10 +1483,10 @@ namespace Assimp {
             return static_cast<unsigned int>(meshes.size() - 1);
             return static_cast<unsigned int>(meshes.size() - 1);
         }
         }
 
 
-        void FBXConverter::ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo,
-            const aiMatrix4x4& node_global_transform,
-            unsigned int materialIndex,
-            std::vector<unsigned int>* 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<unsigned int> *outputVertStartIndices)
         {
         {
             ai_assert(geo.DeformerSkin());
             ai_assert(geo.DeformerSkin());
 
 
@@ -1450,41 +1497,35 @@ namespace Assimp {
             const Skin& sk = *geo.DeformerSkin();
             const Skin& sk = *geo.DeformerSkin();
 
 
             std::vector<aiBone*> bones;
             std::vector<aiBone*> bones;
-            bones.reserve(sk.Clusters().size());
 
 
             const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION;
             const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION;
             ai_assert(no_mat_check || outputVertStartIndices);
             ai_assert(no_mat_check || outputVertStartIndices);
 
 
             try {
             try {
-
+                // iterate over the sub deformers
                 for (const Cluster* cluster : sk.Clusters()) {
                 for (const Cluster* cluster : sk.Clusters()) {
                     ai_assert(cluster);
                     ai_assert(cluster);
 
 
                     const WeightIndexArray& indices = cluster->GetIndices();
                     const WeightIndexArray& indices = cluster->GetIndices();
 
 
-                    if (indices.empty() && mRemoveEmptyBones ) {
-                        continue;
-                    }
-
                     const MatIndexArray& mats = geo.GetMaterialIndices();
                     const MatIndexArray& mats = geo.GetMaterialIndices();
 
 
-                    bool ok = false;
-
                     const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
                     const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
 
 
                     count_out_indices.clear();
                     count_out_indices.clear();
                     index_out_indices.clear();
                     index_out_indices.clear();
                     out_indices.clear();
                     out_indices.clear();
 
 
+
                     // now check if *any* of these weights is contained in the output mesh,
                     // now check if *any* of these weights is contained in the output mesh,
                     // taking notes so we don't need to do it twice.
                     // taking notes so we don't need to do it twice.
                     for (WeightIndexArray::value_type index : indices) {
                     for (WeightIndexArray::value_type index : indices) {
 
 
                         unsigned int count = 0;
                         unsigned int count = 0;
                         const unsigned int* const out_idx = geo.ToOutputVertexIndex(index, count);
                         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
                         // which should never happen
-                        ai_assert(out_idx != NULL);
+                        ai_assert(out_idx != nullptr);
 
 
                         index_out_indices.push_back(no_index_sentinel);
                         index_out_indices.push_back(no_index_sentinel);
                         count_out_indices.push_back(0);
                         count_out_indices.push_back(0);
@@ -1509,75 +1550,110 @@ namespace Assimp {
                                     out_indices.push_back(std::distance(outputVertStartIndices->begin(), it));
                                     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
                     // if we found at least one, generate the output bones
                     // XXX this could be heavily simplified by collecting the bone
                     // XXX this could be heavily simplified by collecting the bone
                     // data in a single step.
                     // 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<aiBone>());
                 std::for_each(bones.begin(), bones.end(), Util::delete_fun<aiBone>());
                 throw;
                 throw;
             }
             }
 
 
             if (bones.empty()) {
             if (bones.empty()) {
+                out->mBones = nullptr;
+                out->mNumBones = 0;
                 return;
                 return;
-            }
-
-            out->mBones = new aiBone*[bones.size()]();
-            out->mNumBones = static_cast<unsigned int>(bones.size());
+            } else {
+                out->mBones = new aiBone *[bones.size()]();
+                out->mNumBones = static_cast<unsigned int>(bones.size());
 
 
-            std::swap_ranges(bones.begin(), bones.end(), out->mBones);
+                std::swap_ranges(bones.begin(), bones.end(), out->mBones);
+            }
         }
         }
 
 
-        void FBXConverter::ConvertCluster(std::vector<aiBone*>& bones, const Model& /*model*/, const Cluster& cl,
-            std::vector<size_t>& out_indices,
-            std::vector<size_t>& index_out_indices,
-            std::vector<size_t>& 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<aiBone *> &local_mesh_bones, const Cluster *cl,
+                                          std::vector<size_t> &out_indices, std::vector<size_t> &index_out_indices,
+                                          std::vector<size_t> &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)) {
+				ASSIMP_LOG_DEBUG_F("retrieved bone from lookup ", bone_name.C_Str(), ". Deformer:", deformer_name);
+				bone = bone_map[deformer_name];
+			} else {
+				ASSIMP_LOG_DEBUG_F("created new bone ", bone_name.C_Str(), ". Deformer: ", deformer_name);
+				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<unsigned int>(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<size_t>::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<unsigned int>(out_indices.size());
+                cursor = bone->mWeights = new aiVertexWeight[out_indices.size()];
+
+                const size_t no_index_sentinel = std::numeric_limits<size_t>::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) {
-                    aiVertexWeight& out_weight = *cursor++;
+                    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<unsigned int>(out_indices[index_index + j]);
-                    out_weight.mWeight = weights[i];
+                        out_weight.mVertexId = static_cast<unsigned int>(out_indices[index_index + j]);
+                        out_weight.mWeight = weights[i];
+                    }
                 }
                 }
+
+                bone_map.insert(std::pair<const std::string, aiBone *>(deformer_name, bone));
             }
             }
+
+            ASSIMP_LOG_DEBUG_F("bone research: Indicies size: ", out_indices.size());
+
+            // 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,
         void FBXConverter::ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
@@ -1707,7 +1783,7 @@ namespace Assimp {
                 bool textureReady = false; //tells if our texture is ready (if it was loaded or if it was found)
                 bool textureReady = false; //tells if our texture is ready (if it was loaded or if it was found)
                 unsigned int index;
                 unsigned int index;
 
 
-                VideoMap::const_iterator it = textures_converted.find(media);
+                VideoMap::const_iterator it = textures_converted.find(*media);
                 if (it != textures_converted.end()) {
                 if (it != textures_converted.end()) {
                     index = (*it).second;
                     index = (*it).second;
                     textureReady = true;
                     textureReady = true;
@@ -1715,7 +1791,7 @@ namespace Assimp {
                 else {
                 else {
                     if (media->ContentLength() > 0) {
                     if (media->ContentLength() > 0) {
                         index = ConvertVideo(*media);
                         index = ConvertVideo(*media);
-                        textures_converted[media] = index;
+                        textures_converted[*media] = index;
                         textureReady = true;
                         textureReady = true;
                     }
                     }
                 }
                 }
@@ -1998,6 +2074,28 @@ namespace Assimp {
             TrySetTextureProperties(out_mat, textures, "Maya|SpecularTexture", aiTextureType_SPECULAR, mesh);
             TrySetTextureProperties(out_mat, textures, "Maya|SpecularTexture", aiTextureType_SPECULAR, mesh);
             TrySetTextureProperties(out_mat, textures, "Maya|FalloffTexture", aiTextureType_OPACITY, mesh);
             TrySetTextureProperties(out_mat, textures, "Maya|FalloffTexture", aiTextureType_OPACITY, mesh);
             TrySetTextureProperties(out_mat, textures, "Maya|ReflectionMapTexture", aiTextureType_REFLECTION, 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);
+
+            // 3DSMax PBR
+            TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|base_color_map", aiTextureType_BASE_COLOR, mesh);
+            TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|bump_map", aiTextureType_NORMAL_CAMERA, mesh);
+            TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|emission_map", aiTextureType_EMISSION_COLOR, mesh);
+            TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|metalness_map", aiTextureType_METALNESS, mesh);
+            TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|roughness_map", aiTextureType_DIFFUSE_ROUGHNESS, mesh);
         }
         }
 
 
         void FBXConverter::SetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh)
         void FBXConverter::SetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh)
@@ -2224,13 +2322,13 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
             if (media != nullptr && media->ContentLength() > 0) {
             if (media != nullptr && media->ContentLength() > 0) {
                 unsigned int index;
                 unsigned int index;
 
 
-                VideoMap::const_iterator it = textures_converted.find(media);
+                VideoMap::const_iterator it = textures_converted.find(*media);
                 if (it != textures_converted.end()) {
                 if (it != textures_converted.end()) {
                     index = (*it).second;
                     index = (*it).second;
                 }
                 }
                 else {
                 else {
                     index = ConvertVideo(*media);
                     index = ConvertVideo(*media);
-                    textures_converted[media] = index;
+                    textures_converted[*media] = index;
                 }
                 }
 
 
                 // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture)
                 // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture)
@@ -2658,7 +2756,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
         // sanity check whether the input is ok
         // sanity check whether the input is ok
         static void validateAnimCurveNodes(const std::vector<const AnimationCurveNode*>& curves,
         static void validateAnimCurveNodes(const std::vector<const AnimationCurveNode*>& curves,
             bool strictMode) {
             bool strictMode) {
-            const Object* target(NULL);
+            const Object* target(nullptr);
             for (const AnimationCurveNode* node : curves) {
             for (const AnimationCurveNode* node : curves) {
                 if (!target) {
                 if (!target) {
                     target = node->Target();
                     target = node->Target();
@@ -2689,7 +2787,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
 #ifdef ASSIMP_BUILD_DEBUG
 #ifdef ASSIMP_BUILD_DEBUG
             validateAnimCurveNodes(curves, doc.Settings().strictMode);
             validateAnimCurveNodes(curves, doc.Settings().strictMode);
 #endif
 #endif
-            const AnimationCurveNode* curve_node = NULL;
+            const AnimationCurveNode* curve_node = nullptr;
             for (const AnimationCurveNode* node : curves) {
             for (const AnimationCurveNode* node : curves) {
                 ai_assert(node);
                 ai_assert(node);
 
 
@@ -2949,7 +3047,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
                 TransformationCompDefaultValue(comp)
                 TransformationCompDefaultValue(comp)
                 );
                 );
 
 
-            const float epsilon = 1e-6f;
+            const float epsilon = Math::getEpsilon<float>();
             return (dyn_val - static_val).SquareLength() < epsilon;
             return (dyn_val - static_val).SquareLength() < epsilon;
         }
         }
 
 
@@ -3514,7 +3612,9 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
                 return;
                 return;
             }
             }
 
 
-            out->mMetaData = aiMetadata::Alloc(15);
+            const bool hasGenerator = !doc.Creator().empty();
+
+            out->mMetaData = aiMetadata::Alloc(16 + (hasGenerator ? 1 : 0));
             out->mMetaData->Set(0, "UpAxis", doc.GlobalSettings().UpAxis());
             out->mMetaData->Set(0, "UpAxis", doc.GlobalSettings().UpAxis());
             out->mMetaData->Set(1, "UpAxisSign", doc.GlobalSettings().UpAxisSign());
             out->mMetaData->Set(1, "UpAxisSign", doc.GlobalSettings().UpAxisSign());
             out->mMetaData->Set(2, "FrontAxis", doc.GlobalSettings().FrontAxis());
             out->mMetaData->Set(2, "FrontAxis", doc.GlobalSettings().FrontAxis());
@@ -3530,45 +3630,10 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
             out->mMetaData->Set(12, "TimeSpanStart", doc.GlobalSettings().TimeSpanStart());
             out->mMetaData->Set(12, "TimeSpanStart", doc.GlobalSettings().TimeSpanStart());
             out->mMetaData->Set(13, "TimeSpanStop", doc.GlobalSettings().TimeSpanStop());
             out->mMetaData->Set(13, "TimeSpanStop", doc.GlobalSettings().TimeSpanStop());
             out->mMetaData->Set(14, "CustomFrameRate", doc.GlobalSettings().CustomFrameRate());
             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;
-                    }
-                }
+            out->mMetaData->Set(15, AI_METADATA_SOURCE_FORMAT_VERSION, aiString(to_string(doc.FBXVersion())));
+            if (hasGenerator)
+            {
+                out->mMetaData->Set(16, AI_METADATA_SOURCE_GENERATOR, aiString(doc.Creator()));
             }
             }
         }
         }
 
 
@@ -3577,7 +3642,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
             ai_assert(!out->mMeshes);
             ai_assert(!out->mMeshes);
             ai_assert(!out->mNumMeshes);
             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
             // many C++ users seem to know this, so pointing it out to avoid
             // confusion why this code works.
             // confusion why this code works.
 
 
@@ -3624,10 +3689,51 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
             }
             }
         }
         }
 
 
+        void FBXConverter::ConvertOrphantEmbeddedTextures()
+        {
+            // 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<size_t>(key.end() - key.begin());
+                        if (strncmp(obtype, "Texture", length) == 0)
+                        {
+                            const Texture* texture = static_cast<const Texture*>(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, FbxUnit unit)
+        void ConvertToAssimpScene(aiScene* out, const Document& doc, bool removeEmptyBones)
         {
         {
-            FBXConverter converter(out, doc, removeEmptyBones, unit);
+            FBXConverter converter(out, doc, removeEmptyBones);
         }
         }
 
 
     } // !FBX
     } // !FBX

+ 57 - 46
code/FBX/FBXConverter.h

@@ -76,23 +76,13 @@ namespace Assimp {
 namespace FBX {
 namespace FBX {
 
 
 class Document;
 class Document;
-
-enum class FbxUnit {
-    cm = 0,
-    m,
-    km,
-    NumUnits,
-
-    Undefined
-};
-
 /** 
 /** 
  *  Convert a FBX #Document to #aiScene
  *  Convert a FBX #Document to #aiScene
  *  @param out Empty scene to be populated
  *  @param out Empty scene to be populated
  *  @param doc Parsed FBX document
  *  @param doc Parsed FBX document
  *  @param removeEmptyBones Will remove bones, which do not have any references to vertices.
  *  @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 */
 /** Dummy class to encapsulate the conversion process */
 class FBXConverter {
 class FBXConverter {
@@ -123,7 +113,7 @@ public:
     };
     };
 
 
 public:
 public:
-    FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones, FbxUnit unit);
+    FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones);
     ~FBXConverter();
     ~FBXConverter();
 
 
 private:
 private:
@@ -133,7 +123,7 @@ private:
 
 
     // ------------------------------------------------------------------------------------------------
     // ------------------------------------------------------------------------------------------------
     // collect and assign child nodes
     // 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 );
     void ConvertLights(const Model& model, const std::string &orig_name );
@@ -189,32 +179,35 @@ private:
     void SetupNodeMetadata(const Model& model, aiNode& nd);
     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
     // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed
-    std::vector<unsigned int> ConvertMesh(const MeshGeometry& mesh, const Model& model,
-        const aiMatrix4x4& node_global_transform, aiNode& nd);
+    std::vector<unsigned int>
+    ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node,
+                const aiMatrix4x4 &absolute_transform);
 
 
     // ------------------------------------------------------------------------------------------------
     // ------------------------------------------------------------------------------------------------
     std::vector<unsigned int> ConvertLine(const LineGeometry& line, const Model& model,
     std::vector<unsigned int> 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<unsigned int> ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
-        const aiMatrix4x4& node_global_transform, aiNode& nd);
+    std::vector<unsigned int>
+    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<unsigned int>::max() */
     static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits<unsigned int>::max() */
@@ -227,17 +220,17 @@ private:
     *  - outputVertStartIndices is only used when a material index is specified, it gives for
     *  - outputVertStartIndices is only used when a material index is specified, it gives for
     *    each output vertex the DOM index it maps to.
     *    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<unsigned int>* 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<unsigned int> *outputVertStartIndices = NULL);
+    // lookup
+    static const aiNode* GetNodeByName( const aiString& name, aiNode *current_node );
     // ------------------------------------------------------------------------------------------------
     // ------------------------------------------------------------------------------------------------
-    void ConvertCluster(std::vector<aiBone*>& bones, const Model& /*model*/, const Cluster& cl,
-        std::vector<size_t>& out_indices,
-        std::vector<size_t>& index_out_indices,
-        std::vector<size_t>& count_out_indices,
-        const aiMatrix4x4& node_global_transform);
+    void ConvertCluster(std::vector<aiBone *> &local_mesh_bones, const Cluster *cl,
+                        std::vector<size_t> &out_indices, std::vector<size_t> &index_out_indices,
+                        std::vector<size_t> &count_out_indices, const aiMatrix4x4 &absolute_transform,
+                        aiNode *parent, aiNode *root_node);
 
 
     // ------------------------------------------------------------------------------------------------
     // ------------------------------------------------------------------------------------------------
     void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
     void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
@@ -428,16 +421,18 @@ private:
         double& minTime,
         double& minTime,
         Model::RotOrder order);
         Model::RotOrder order);
 
 
-    void ConvertGlobalSettings();
-
     // ------------------------------------------------------------------------------------------------
     // ------------------------------------------------------------------------------------------------
-    //  Will perform the conversion from a given unit to the requested unit.
-    void ConvertToUnitScale(FbxUnit unit);
+    // Copy global geometric data and some information about the source asset into scene metadata.
+    void ConvertGlobalSettings();
 
 
     // ------------------------------------------------------------------------------------------------
     // ------------------------------------------------------------------------------------------------
     // copy generated meshes, animations, lights, cameras and textures to the output scene
     // copy generated meshes, animations, lights, cameras and textures to the output scene
     void TransferDataToScene();
     void TransferDataToScene();
 
 
+    // ------------------------------------------------------------------------------------------------
+    // FBX file could have embedded textures not connected to anything
+    void ConvertOrphantEmbeddedTextures();
+
 private:
 private:
     // 0: not assigned yet, others: index is value - 1
     // 0: not assigned yet, others: index is value - 1
     unsigned int defaultMaterialIndex;
     unsigned int defaultMaterialIndex;
@@ -449,31 +444,47 @@ private:
     std::vector<aiCamera*> cameras;
     std::vector<aiCamera*> cameras;
     std::vector<aiTexture*> textures;
     std::vector<aiTexture*> textures;
 
 
-    using MaterialMap = std::map<const Material*, unsigned int>;
+    using MaterialMap = std::fbx_unordered_map<const Material*, unsigned int>;
     MaterialMap materials_converted;
     MaterialMap materials_converted;
 
 
-    using VideoMap = std::map<const Video*, unsigned int>;
+    using VideoMap = std::fbx_unordered_map<const Video, unsigned int>;
     VideoMap textures_converted;
     VideoMap textures_converted;
 
 
-    using MeshMap = std::map<const Geometry*, std::vector<unsigned int> >;
+    using MeshMap = std::fbx_unordered_map<const Geometry*, std::vector<unsigned int> >;
     MeshMap meshes_converted;
     MeshMap meshes_converted;
 
 
     // fixed node name -> which trafo chain components have animations?
     // fixed node name -> which trafo chain components have animations?
-    using NodeAnimBitMap = std::map<std::string, unsigned int> ;
+    using NodeAnimBitMap = std::fbx_unordered_map<std::string, unsigned int> ;
     NodeAnimBitMap node_anim_chain_bits;
     NodeAnimBitMap node_anim_chain_bits;
 
 
     // number of nodes with the same name
     // number of nodes with the same name
-    using NodeNameCache = std::unordered_map<std::string, unsigned int>;
+    using NodeNameCache = std::fbx_unordered_map<std::string, unsigned int>;
     NodeNameCache mNodeNames;
     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<const std::string, aiBone *> bone_map;
+
     double anim_fps;
     double anim_fps;
 
 
     aiScene* const out;
     aiScene* const out;
     const FBX::Document& doc;
     const FBX::Document& doc;
 
 
-    bool mRemoveEmptyBones;
+    static void BuildBoneList(aiNode *current_node, const aiNode *root_node, const aiScene *scene,
+                             std::vector<aiBone*>& bones);
+
+    void BuildBoneStack(aiNode *current_node, const aiNode *root_node, const aiScene *scene,
+                   const std::vector<aiBone *> &bones,
+                   std::map<aiBone *, aiNode *> &bone_stack,
+                   std::vector<aiNode*> &node_stack );
+
+    static void BuildNodeList(aiNode *current_node, std::vector<aiNode *> &nodes);
+
+    static aiNode *GetNodeFromStack(const aiString &node_name, std::vector<aiNode *> &nodes);
+
+    static aiNode *GetArmatureRoot(aiNode *bone_node, std::vector<aiBone*> &bone_list);
 
 
-    FbxUnit mCurrentUnit;
+    static bool IsBoneNode(const aiString &bone_name, std::vector<aiBone *> &bones);
 };
 };
 
 
 }
 }

+ 0 - 8
code/FBX/FBXDocument.cpp

@@ -90,14 +90,6 @@ const Object* LazyObject::Get(bool dieOnError)
         return object.get();
         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 Token& key = element.KeyToken();
     const TokenList& tokens = element.Tokens();
     const TokenList& tokens = element.Tokens();
 
 

+ 37 - 2
code/FBX/FBXDocument.h

@@ -637,6 +637,20 @@ public:
         return ptr;
         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:
 private:
     std::string type;
     std::string type;
     std::string relativeFileName;
     std::string relativeFileName;
@@ -1005,10 +1019,10 @@ public:
 // during their entire lifetime (Document). FBX files have
 // during their entire lifetime (Document). FBX files have
 // up to many thousands of objects (most of which we never use),
 // up to many thousands of objects (most of which we never use),
 // so the memory overhead for them should be kept at a minimum.
 // so the memory overhead for them should be kept at a minimum.
-typedef std::map<uint64_t, LazyObject*> ObjectMap;
+typedef std::fbx_unordered_map<uint64_t, LazyObject*> ObjectMap;
 typedef std::fbx_unordered_map<std::string, std::shared_ptr<const PropertyTable> > PropertyTemplateMap;
 typedef std::fbx_unordered_map<std::string, std::shared_ptr<const PropertyTable> > PropertyTemplateMap;
 
 
-typedef std::multimap<uint64_t, const Connection*> ConnectionMap;
+typedef std::fbx_unordered_multimap<uint64_t, const Connection*> ConnectionMap;
 
 
 /** DOM class for global document settings, a single instance per document can
 /** DOM class for global document settings, a single instance per document can
  *  be accessed via Document.Globals(). */
  *  be accessed via Document.Globals(). */
@@ -1177,4 +1191,25 @@ private:
 } // Namespace FBX
 } // Namespace FBX
 } // Namespace Assimp
 } // Namespace Assimp
 
 
+namespace std
+{
+    template <>
+    struct hash<const Assimp::FBX::Video>
+    {
+        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<string>()(video.Name());
+            res = res * 31 + hash<string>()(video.RelativeFilename());
+            res = res * 31 + hash<string>()(video.Type());
+
+            return res;
+        }
+    };
+}
+
 #endif // INCLUDED_AI_FBX_DOCUMENT_H
 #endif // INCLUDED_AI_FBX_DOCUMENT_H

+ 7 - 7
code/FBX/FBXExportNode.cpp

@@ -325,9 +325,9 @@ void FBX::Node::BeginBinary(Assimp::StreamWriterLE &s)
     this->start_pos = s.Tell();
     this->start_pos = s.Tell();
 
 
     // placeholders for end pos and property section info
     // 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
     // node name
     s.PutU1(uint8_t(name.size())); // length of 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();
     size_t pos = s.Tell();
     ai_assert(pos > property_start);
     ai_assert(pos > property_start);
     size_t property_section_size = 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);
     s.Seek(pos);
 }
 }
 
 
@@ -375,7 +375,7 @@ void FBX::Node::EndBinary(
     // now go back and write initial pos
     // now go back and write initial pos
     this->end_pos = s.Tell();
     this->end_pos = s.Tell();
     s.Seek(start_pos);
     s.Seek(start_pos);
-    s.PutU4(uint32_t(end_pos));
+    s.PutU8(end_pos);
     s.Seek(end_pos);
     s.Seek(end_pos);
 }
 }
 
 

+ 1 - 5
code/FBX/FBXExportProperty.cpp

@@ -59,11 +59,7 @@ namespace FBX {
 
 
 FBXExportProperty::FBXExportProperty(bool v)
 FBXExportProperty::FBXExportProperty(bool v)
 : type('C')
 : type('C')
-, data(1) {
-    data = {
-        uint8_t(v)
-    };
-}
+, data(1, uint8_t(v)) {}
 
 
 FBXExportProperty::FBXExportProperty(int16_t v)
 FBXExportProperty::FBXExportProperty(int16_t v)
 : type('Y')
 : type('Y')

+ 120 - 75
code/FBX/FBXExporter.cpp

@@ -67,6 +67,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <vector>
 #include <vector>
 #include <array>
 #include <array>
 #include <unordered_set>
 #include <unordered_set>
+#include <numeric>
 
 
 // RESOURCES:
 // RESOURCES:
 // https://code.blender.org/2013/08/fbx-binary-file-format-specification/
 // 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
 // some constants that we'll use for writing metadata
 namespace Assimp {
 namespace Assimp {
 namespace FBX {
 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,
     // 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.
     // but for now we don't actually know how to generate these.
     // what we can do is set them to a known-working version.
     // 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.EndProperties(outstream, binary, indent);
     object_node.BeginChildren(outstream, binary, indent);
     object_node.BeginChildren(outstream, binary, indent);
 
 
+    bool bJoinIdenticalVertices = mProperties->GetPropertyBool("bJoinIdenticalVertices", true);
+    std::vector<std::vector<int32_t>> vVertexIndice;//save vertex_indices as it is needed later
+
     // geometry (aiMesh)
     // geometry (aiMesh)
     mesh_uids.clear();
     mesh_uids.clear();
     indent = 1;
     indent = 1;
@@ -1031,21 +1035,35 @@ void FBXExporter::WriteObjects ()
         std::vector<int32_t> vertex_indices;
         std::vector<int32_t> vertex_indices;
         // map of vertex value to its index in the data vector
         // map of vertex value to its index in the data vector
         std::map<aiVector3D,size_t> index_by_vertex_value;
         std::map<aiVector3D,size_t> 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(
         FBX::Node::WritePropertyNode(
             "Vertices", flattened_vertices, outstream, binary, indent
             "Vertices", flattened_vertices, outstream, binary, indent
         );
         );
@@ -1116,6 +1134,51 @@ void FBXExporter::WriteObjects ()
             normals.End(outstream, binary, indent, true);
             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<double> 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
         // uvs, if any
         for (size_t uvi = 0; uvi < m->GetNumUVChannels(); ++uvi) {
         for (size_t uvi = 0; uvi < m->GetNumUVChannels(); ++uvi) {
             if (m->mNumUVComponents[uvi] > 2) {
             if (m->mNumUVComponents[uvi] > 2) {
@@ -1209,6 +1272,11 @@ void FBXExporter::WriteObjects ()
         le.AddChild("Type", "LayerElementNormal");
         le.AddChild("Type", "LayerElementNormal");
         le.AddChild("TypedIndex", int32_t(0));
         le.AddChild("TypedIndex", int32_t(0));
         layer.AddChild(le);
         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 = FBX::Node("LayerElement");
         le.AddChild("Type", "LayerElementMaterial");
         le.AddChild("Type", "LayerElementMaterial");
         le.AddChild("TypedIndex", int32_t(0));
         le.AddChild("TypedIndex", int32_t(0));
@@ -1219,6 +1287,16 @@ void FBXExporter::WriteObjects ()
         layer.AddChild(le);
         layer.AddChild(le);
         layer.Dump(outstream, binary, indent);
         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
         // finish the node record
         indent = 1;
         indent = 1;
         n.End(outstream, binary, indent, true);
         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,
     // 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".
     // which will be used later to mark them as type "limbNode".
     std::unordered_set<const aiNode*> limbnodes;
     std::unordered_set<const aiNode*> limbnodes;
+    
+    //actual bone nodes in fbx, without parenting-up
+    std::unordered_set<std::string> 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.
     // and a map of nodes by bone name, as finding them is annoying.
     std::map<std::string,aiNode*> node_by_bone;
     std::map<std::string,aiNode*> node_by_bone;
     for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
     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) {
                 if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) {
                     continue;
                     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
                 // otherwise check if this is the root of the skeleton
                 bool end = false;
                 bool end = false;
                 // is the mesh part of this node?
                 // is the mesh part of this node?
@@ -1680,8 +1774,7 @@ void FBXExporter::WriteObjects ()
                     }
                     }
                     if (end) { break; }
                     if (end) { break; }
                 }
                 }
-                limbnodes.insert(parent);
-                skeleton.insert(parent);
+                
                 // if it was the skeleton root we can finish here
                 // if it was the skeleton root we can finish here
                 if (end) { break; }
                 if (end) { break; }
             }
             }
@@ -1723,28 +1816,8 @@ void FBXExporter::WriteObjects ()
         // connect it
         // connect it
         connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]);
         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<int32_t> vertex_indices;
-        // map of vertex value to its index in the data vector
-        std::map<aiVector3D,size_t> 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<int32_t>& vertex_indices = vVertexIndice[mi];
 
 
         // TODO, FIXME: this won't work if anything is not in the bind pose.
         // 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.
         // for now if such a situation is detected, we throw an exception.
@@ -1787,6 +1860,7 @@ void FBXExporter::WriteObjects ()
             sdnode.AddChild("Version", int32_t(100));
             sdnode.AddChild("Version", int32_t(100));
             sdnode.AddChild("UserData", "", "");
             sdnode.AddChild("UserData", "", "");
 
 
+            std::set<int32_t> setWeightedVertex;
             // add indices and weights, if any
             // add indices and weights, if any
             if (b) {
             if (b) {
                 std::vector<int32_t> subdef_indices;
                 std::vector<int32_t> subdef_indices;
@@ -1794,7 +1868,8 @@ void FBXExporter::WriteObjects ()
                 int32_t last_index = -1;
                 int32_t last_index = -1;
                 for (size_t wi = 0; wi < b->mNumWeights; ++wi) {
                 for (size_t wi = 0; wi < b->mNumWeights; ++wi) {
                     int32_t vi = vertex_indices[b->mWeights[wi].mVertexId];
                     int32_t vi = vertex_indices[b->mWeights[wi].mVertexId];
-                    if (vi == last_index) {
+                    bool bIsWeightedAlready = (setWeightedVertex.find(vi) != setWeightedVertex.end());
+                    if (vi == last_index || bIsWeightedAlready) {
                         // only for vertices we exported to fbx
                         // only for vertices we exported to fbx
                         // TODO, FIXME: this assumes identically-located vertices
                         // TODO, FIXME: this assumes identically-located vertices
                         // will always deform in the same way.
                         // will always deform in the same way.
@@ -1804,6 +1879,7 @@ void FBXExporter::WriteObjects ()
                         // identical vertex.
                         // identical vertex.
                         continue;
                         continue;
                     }
                     }
+                    setWeightedVertex.insert(vi);
                     subdef_indices.push_back(vi);
                     subdef_indices.push_back(vi);
                     subdef_weights.push_back(b->mWeights[wi].mWeight);
                     subdef_weights.push_back(b->mWeights[wi].mWeight);
                     last_index = vi;
                     last_index = vi;
@@ -1822,41 +1898,10 @@ void FBXExporter::WriteObjects ()
             inverse_bone_xform.Inverse();
             inverse_bone_xform.Inverse();
             aiMatrix4x4 tr = inverse_bone_xform * mesh_xform;
             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
             // note: this means we ALWAYS rely on the mesh node transform
             // being unchanged from the time the skeleton was bound.
             // being unchanged from the time the skeleton was bound.
             // there's not really any way around this at the moment.
             // there's not really any way around this at the moment.
@@ -2441,7 +2486,7 @@ void FBXExporter::WriteModelNodes(
 void FBXExporter::WriteAnimationCurveNode(
 void FBXExporter::WriteAnimationCurveNode(
     StreamWriterLE& outstream,
     StreamWriterLE& outstream,
     int64_t uid,
     int64_t uid,
-    std::string name, // "T", "R", or "S"
+    const std::string& name, // "T", "R", or "S"
     aiVector3D default_value,
     aiVector3D default_value,
     std::string property_name, // "Lcl Translation" etc
     std::string property_name, // "Lcl Translation" etc
     int64_t layer_uid,
     int64_t layer_uid,

+ 1 - 1
code/FBX/FBXExporter.h

@@ -156,7 +156,7 @@ namespace Assimp
         void WriteAnimationCurveNode(
         void WriteAnimationCurveNode(
             StreamWriterLE& outstream,
             StreamWriterLE& outstream,
             int64_t uid,
             int64_t uid,
-            std::string name, // "T", "R", or "S"
+            const std::string& name, // "T", "R", or "S"
             aiVector3D default_value,
             aiVector3D default_value,
             std::string property_name, // "Lcl Translation" etc
             std::string property_name, // "Lcl Translation" etc
             int64_t animation_layer_uid,
             int64_t animation_layer_uid,

+ 102 - 107
code/FBX/FBXImporter.cpp

@@ -48,26 +48,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 #include "FBXImporter.h"
 #include "FBXImporter.h"
 
 
-#include "FBXTokenizer.h"
+#include "FBXConverter.h"
+#include "FBXDocument.h"
 #include "FBXParser.h"
 #include "FBXParser.h"
+#include "FBXTokenizer.h"
 #include "FBXUtil.h"
 #include "FBXUtil.h"
-#include "FBXDocument.h"
-#include "FBXConverter.h"
 
 
-#include <assimp/StreamReader.h>
 #include <assimp/MemoryIOWrapper.h>
 #include <assimp/MemoryIOWrapper.h>
-#include <assimp/Importer.hpp>
+#include <assimp/StreamReader.h>
 #include <assimp/importerdesc.h>
 #include <assimp/importerdesc.h>
+#include <assimp/Importer.hpp>
 
 
 namespace Assimp {
 namespace Assimp {
 
 
-template<>
-const char* LogFunctions<FBXImporter>::Prefix() {
-    static auto prefix = "FBX: ";
-    return prefix;
+template <>
+const char *LogFunctions<FBXImporter>::Prefix() {
+	static auto prefix = "FBX: ";
+	return prefix;
 }
 }
 
 
-}
+} // namespace Assimp
 
 
 using namespace Assimp;
 using namespace Assimp;
 using namespace Assimp::Formatter;
 using namespace Assimp::Formatter;
@@ -76,128 +76,123 @@ using namespace Assimp::FBX;
 namespace {
 namespace {
 
 
 static const aiImporterDesc desc = {
 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
 // Constructor to be privately used by #Importer
-FBXImporter::FBXImporter()
-{
+FBXImporter::FBXImporter() {
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
 // Destructor, private as well
-FBXImporter::~FBXImporter()
-{
+FBXImporter::~FBXImporter() {
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 // 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;
-    }
-
-    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;
+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;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // List all extensions handled by this loader
 // 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
 // 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.
 // Imports the given file into the given scene structure.
-void FBXImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
-{
-    std::unique_ptr<IOStream> 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<char> 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 {
-
-        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);
-
-        // 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);
-
-        std::for_each(tokens.begin(),tokens.end(),Util::delete_fun<Token>());
-    }
-    catch(std::exception&) {
-        std::for_each(tokens.begin(),tokens.end(),Util::delete_fun<Token>());
-        throw;
-    }
+void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
+	std::unique_ptr<IOStream> 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<char> 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 {
+
+		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);
+
+		// take the raw parse-tree and convert it to a FBX DOM
+		Document doc(parser, settings);
+
+		// convert the FBX DOM to aiScene
+		ConvertToAssimpScene(pScene, doc, settings.removeEmptyBones);
+
+		// 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<Token>());
+	} catch (std::exception &) {
+		std::for_each(tokens.begin(), tokens.end(), Util::delete_fun<Token>());
+		throw;
+	}
 }
 }
 
 
 #endif // !ASSIMP_BUILD_NO_FBX_IMPORTER
 #endif // !ASSIMP_BUILD_NO_FBX_IMPORTER

+ 11 - 14
code/FBX/FBXMeshGeometry.cpp

@@ -115,7 +115,6 @@ MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::strin
 
 
     if(tempVerts.empty()) {
     if(tempVerts.empty()) {
         FBXImporter::LogWarn("encountered mesh with no vertices");
         FBXImporter::LogWarn("encountered mesh with no vertices");
-        return;
     }
     }
 
 
     std::vector<int> tempFaces;
     std::vector<int> tempFaces;
@@ -123,7 +122,6 @@ MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::strin
 
 
     if(tempFaces.empty()) {
     if(tempFaces.empty()) {
         FBXImporter::LogWarn("encountered mesh with no faces");
         FBXImporter::LogWarn("encountered mesh with no faces");
-        return;
     }
     }
 
 
     m_vertices.reserve(tempFaces.size());
     m_vertices.reserve(tempFaces.size());
@@ -612,8 +610,11 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector<int>& materials_out, cons
     const std::string& ReferenceInformationType)
     const std::string& ReferenceInformationType)
 {
 {
     const size_t face_count = m_faces.size();
     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
     // materials are handled separately. First of all, they are assigned per-face
     // and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect
     // and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect
     // has a slightly different meaning for materials.
     // has a slightly different meaning for materials.
@@ -624,16 +625,14 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector<int>& materials_out, cons
         if (materials_out.empty()) {
         if (materials_out.empty()) {
             FBXImporter::LogError(Formatter::format("expected material index, ignoring"));
             FBXImporter::LogError(Formatter::format("expected material index, ignoring"));
             return;
             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"));
             FBXImporter::LogWarn(Formatter::format("expected only a single material index, ignoring all except the first one"));
             materials_out.clear();
             materials_out.clear();
         }
         }
 
 
         materials_out.resize(m_vertices.size());
         materials_out.resize(m_vertices.size());
         std::fill(materials_out.begin(), materials_out.end(), materials_out.at(0));
         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);
         materials_out.resize(face_count);
 
 
         if(materials_out.size() != face_count) {
         if(materials_out.size() != face_count) {
@@ -642,18 +641,16 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector<int>& materials_out, cons
             );
             );
             return;
             return;
         }
         }
-    }
-    else {
+    } else {
         FBXImporter::LogError(Formatter::format("ignoring material assignments, access type not implemented: ")
         FBXImporter::LogError(Formatter::format("ignoring material assignments, access type not implemented: ")
             << MappingInformationType << "," << ReferenceInformationType);
             << MappingInformationType << "," << ReferenceInformationType);
     }
     }
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 ShapeGeometry::ShapeGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc)
 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");
         DOMError("failed to read Geometry object (class: Shape), no data scope found");
     }
     }
     const Element& Indexes = GetRequiredElement(*sc, "Indexes", &element);
     const Element& Indexes = GetRequiredElement(*sc, "Indexes", &element);

+ 3 - 4
code/Importer/IFC/IFCCurve.cpp

@@ -311,10 +311,9 @@ class TrimmedCurve : public BoundedCurve {
 public:
 public:
     // --------------------------------------------------
     // --------------------------------------------------
     TrimmedCurve(const Schema_2x3::IfcTrimmedCurve& entity, ConversionData& conv)
     TrimmedCurve(const Schema_2x3::IfcTrimmedCurve& entity, ConversionData& conv)
-        : BoundedCurve(entity,conv)
+        : BoundedCurve(entity,conv),
+          base(std::shared_ptr<const Curve>(Curve::Convert(entity.BasisCurve,conv)))
     {
     {
-        base = std::shared_ptr<const Curve>(Curve::Convert(entity.BasisCurve,conv));
-
         typedef std::shared_ptr<const STEP::EXPRESS::DataType> Entry;
         typedef std::shared_ptr<const STEP::EXPRESS::DataType> Entry;
 
 
         // for some reason, trimmed curves can either specify a parametric value
         // for some reason, trimmed curves can either specify a parametric value
@@ -500,7 +499,7 @@ bool Curve::InRange(IfcFloat u) const {
     if (IsClosed()) {
     if (IsClosed()) {
         return true;
         return true;
     }
     }
-    const IfcFloat epsilon = 1e-5;
+    const IfcFloat epsilon = Math::getEpsilon<float>();
     return u - range.first > -epsilon && range.second - u > -epsilon;
     return u - range.first > -epsilon && range.second - u > -epsilon;
 }
 }
 #endif
 #endif

+ 4 - 3
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;
         outer_polygon_it = begin + master_bounds;
     }
     }
     else {
     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.
             // find the polygon with the largest area and take it as the outer bound.
             IfcVector3& n = normals[std::distance(begin,iit)];
             IfcVector3& n = normals[std::distance(begin,iit)];
             const IfcFloat area = n.SquareLength();
             const IfcFloat area = n.SquareLength();
@@ -138,8 +138,9 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m
             }
             }
         }
         }
     }
     }
-
-    ai_assert(outer_polygon_it != end);
+	if (outer_polygon_it == end) {
+		return;
+	}
 
 
     const size_t outer_polygon_size = *outer_polygon_it;
     const size_t outer_polygon_size = *outer_polygon_it;
     const IfcVector3& master_normal = normals[std::distance(begin, outer_polygon_it)];
     const IfcVector3& master_normal = normals[std::distance(begin, outer_polygon_it)];

+ 3 - 3
code/Importer/IFC/IFCOpenings.cpp

@@ -593,7 +593,7 @@ typedef std::vector<std::pair<
 bool BoundingBoxesAdjacent(const BoundingBox& bb, const BoundingBox& ibb)
 bool BoundingBoxesAdjacent(const BoundingBox& bb, const BoundingBox& ibb)
 {
 {
     // TODO: I'm pretty sure there is a much more compact way to check this
     // TODO: I'm pretty sure there is a much more compact way to check this
-    const IfcFloat epsilon = 1e-5f;
+    const IfcFloat epsilon = Math::getEpsilon<float>();
     return  (std::fabs(bb.second.x - ibb.first.x) < epsilon && bb.first.y <= ibb.second.y && bb.second.y >= ibb.first.y) ||
     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.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) ||
         (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)
 void FindAdjacentContours(ContourVector::iterator current, const ContourVector& contours)
 {
 {
-    const IfcFloat sqlen_epsilon = static_cast<IfcFloat>(1e-8);
+    const IfcFloat sqlen_epsilon = static_cast<IfcFloat>(Math::getEpsilon<float>());
     const BoundingBox& bb = (*current).bb;
     const BoundingBox& bb = (*current).bb;
 
 
     // What is to be done here is to populate the skip lists for the contour
     // 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)
 AI_FORCE_INLINE bool LikelyBorder(const IfcVector2& vdelta)
 {
 {
-    const IfcFloat dot_point_epsilon = static_cast<IfcFloat>(1e-5);
+    const IfcFloat dot_point_epsilon = static_cast<IfcFloat>(Math::getEpsilon<float>());
     return std::fabs(vdelta.x * vdelta.y) < dot_point_epsilon;
     return std::fabs(vdelta.x * vdelta.y) < dot_point_epsilon;
 }
 }
 
 

+ 0 - 1
code/Irr/IRRMeshLoader.cpp

@@ -57,7 +57,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/material.h>
 #include <assimp/material.h>
 #include <assimp/scene.h>
 #include <assimp/scene.h>
 #include <assimp/importerdesc.h>
 #include <assimp/importerdesc.h>
-#include <assimp/Macros.h>
 
 
 using namespace Assimp;
 using namespace Assimp;
 using namespace irr;
 using namespace irr;

+ 0 - 2
code/LWO/LWOLoader.cpp

@@ -586,7 +586,6 @@ void LWOImporter::GenerateNodeGraph(std::map<uint16_t,aiNode*>& apcNodes)
     root->mName.Set("<LWORoot>");
     root->mName.Set("<LWORoot>");
 
 
     //Set parent of all children, inserting pivots
     //Set parent of all children, inserting pivots
-    //std::cout << "Set parent of all children" << std::endl;
     std::map<uint16_t, aiNode*> mapPivot;
     std::map<uint16_t, aiNode*> mapPivot;
     for (auto itapcNodes = apcNodes.begin(); itapcNodes != apcNodes.end(); ++itapcNodes) {
     for (auto itapcNodes = apcNodes.begin(); itapcNodes != apcNodes.end(); ++itapcNodes) {
 
 
@@ -618,7 +617,6 @@ void LWOImporter::GenerateNodeGraph(std::map<uint16_t,aiNode*>& apcNodes)
     }
     }
 
 
     //Merge pivot map into node map
     //Merge pivot map into node map
-    //std::cout << "Merge pivot map into node map" << std::endl;
     for (auto itMapPivot = mapPivot.begin(); itMapPivot != mapPivot.end(); ++itMapPivot) {
     for (auto itMapPivot = mapPivot.begin(); itMapPivot != mapPivot.end(); ++itMapPivot) {
         apcNodes[itMapPivot->first] = itMapPivot->second;
         apcNodes[itMapPivot->first] = itMapPivot->second;
     }
     }

+ 437 - 0
code/M3D/M3DExporter.cpp

@@ -0,0 +1,437 @@
+/*
+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
+#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
+#define M3D_NODUP
+#endif
+
+// Header files, standard library.
+#include <memory> // shared_ptr
+#include <string>
+#include <vector>
+
+#include <assimp/Exceptional.h> // DeadlyExportError
+#include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/material.h> // aiTextureType
+#include <assimp/mesh.h>
+#include <assimp/scene.h>
+#include <assimp/version.h> // aiGetVersion
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Exporter.hpp>
+#include <assimp/IOSystem.hpp>
+
+#include "M3DWrapper.h"
+#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)
+ */
+
+// ------------------------------------------------------------------------------------------------
+// Conversion functions
+// ------------------------------------------------------------------------------------------------
+// helper to add a vertex (private to NodeWalk)
+m3dv_t *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 *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;
+}
+
+// ------------------------------------------------------------------------------------------------
+// convert aiColor4D into uint32_t
+uint32_t 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 property to the output
+void 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;
+}
+
+// ------------------------------------------------------------------------------------------------
+// convert aiString to identifier safe C string. This is a duplication of _m3d_safestr
+char *SafeStr(aiString str, bool isStrict)
+{
+	char *s = (char *)&str.data;
+	char *d, *ret;
+	int i, len;
+
+	for(len = str.length + 1; *s && (*s == ' ' || *s == '\t'); s++, len--);
+	if(len > 255) len = 255;
+	ret = (char *)M3D_MALLOC(len + 1);
+	if (!ret) {
+		throw DeadlyExportError("memory allocation error");
+	}
+	for(i = 0, d = ret; i < len && *s && *s != '\r' && *s != '\n'; s++, d++, i++) {
+		*d = isStrict && (*s == ' ' || *s == '\t' || *s == '/' || *s == '\\') ? '_' : (*s == '\t' ? ' ' : *s);
+	}
+	for(; d > ret && (*(d-1) == ' ' || *(d-1) == '\t'); d--);
+	*d = 0;
+	return ret;
+}
+
+// ------------------------------------------------------------------------------------------------
+// add a material to the output
+M3D_INDEX addMaterial(const Assimp::M3DWrapper &m3d, const aiMaterial *mat) {
+	unsigned int mi = M3D_NOTDEFINED;
+	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 == M3D_NOTDEFINED) {
+			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 = SafeStr(name, true);
+			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 = SafeStr(name, true);
+					for (j = 0, i = M3D_NOTDEFINED; j < m3d->numtexture; j++)
+						if (!strcmp(fn, m3d->texture[j].name)) {
+							i = j;
+							free(fn);
+							break;
+						}
+					if (i == M3D_NOTDEFINED) {
+						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;
+}
+
+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 ExportSceneM3DA(
+		const char *pFile,
+		IOSystem *pIOSystem,
+		const aiScene *pScene,
+		const ExportProperties *pProperties
+
+) {
+#ifdef M3D_ASCII
+	// initialize the exporter
+	M3DExporter exporter(pScene, pProperties);
+
+	// perform ascii export
+	exporter.doExport(pFile, pIOSystem, true);
+#else
+	throw DeadlyExportError("Assimp configured without M3D_ASCII support");
+#endif
+}
+
+// ------------------------------------------------------------------------------------------------
+M3DExporter::M3DExporter(const aiScene *pScene, const ExportProperties *pProperties) :
+		mScene(pScene),
+		mProperties(pProperties),
+		outfile() {}
+
+// ------------------------------------------------------------------------------------------------
+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));
+	}
+
+	M3DWrapper m3d;
+	if (!m3d) {
+		throw DeadlyExportError("memory allocation error");
+	}
+	m3d->name = SafeStr(mScene->mRootNode->mName, false);
+
+	// Create a model from assimp structures
+	aiMatrix4x4 m;
+	NodeWalk(m3d, mScene->mRootNode, m);
+
+	// serialize the structures
+	unsigned int size;
+	unsigned char *output = m3d.Save(M3D_EXP_FLOAT, M3D_EXP_EXTRA | (toAscii ? M3D_EXP_ASCII : 0), size);
+
+	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();
+}
+
+// ------------------------------------------------------------------------------------------------
+// recursive node walker
+void M3DExporter::NodeWalk(const M3DWrapper &m3d, 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_NOTDEFINED;
+		if (mScene->mMaterials) {
+			// get the material for this mesh
+			mi = addMaterial(m3d, 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] = M3D_UNDEF;
+			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 = M3D_UNDEF;
+				// 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(m3d, pNode->mChildren[i], nm);
+	}
+}
+} // namespace Assimp
+
+#endif // ASSIMP_BUILD_NO_M3D_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 94 - 0
code/M3D/M3DExporter.h

@@ -0,0 +1,94 @@
+/*
+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 <assimp/types.h>
+//#include <assimp/material.h>
+#include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/Exceptional.h> // DeadlyExportError
+
+#include <memory> // shared_ptr
+
+struct aiScene;
+struct aiNode;
+struct aiMaterial;
+struct aiFace;
+
+namespace Assimp
+{
+    class IOSystem;
+    class IOStream;
+    class ExportProperties;
+
+    class M3DWrapper;
+
+    // ---------------------------------------------------------------------
+    /** 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<IOStream> outfile; // file to write to
+
+        // helper to do the recursive walking
+        void NodeWalk(const M3DWrapper &m3d, const aiNode* pNode, aiMatrix4x4 m);
+    };
+}
+
+#endif // ASSIMP_BUILD_NO_M3D_EXPORTER
+
+#endif // AI_M3DEXPORTER_H_INC

+ 770 - 0
code/M3D/M3DImporter.cpp

@@ -0,0 +1,770 @@
+/*
+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_NONORMALS /* leave the post-processing to Assimp */
+#define M3D_NOWEIGHTS
+#define M3D_NOANIMATION
+
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/IOStreamBuffer.h>
+#include <assimp/ai_assert.h>
+#include <assimp/importerdesc.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+#include <memory>
+
+#include "M3DWrapper.h"
+#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_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour,
+	0,
+	0,
+	0,
+	0,
+#ifdef M3D_ASCII
+	"m3d a3d"
+#else
+	"m3d"
+#endif
+};
+
+namespace Assimp {
+
+using namespace std;
+
+// ------------------------------------------------------------------------------------------------
+//  Default constructor
+M3DImporter::M3DImporter() :
+		mScene(nullptr) {}
+
+// ------------------------------------------------------------------------------------------------
+//  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"
+#ifdef M3D_ASCII
+		|| extension == "a3d"
+#endif
+		)
+		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<IOStream> pStream(pIOHandler->Open(pFile, "rb"));
+		unsigned char data[4];
+		if (4 != pStream->Read(data, 1, 4)) {
+			return false;
+		}
+		return !memcmp(data, "3DMO", 4) /* bin */
+#ifdef M3D_ASCII
+			|| !memcmp(data, "3dmo", 4) /* ASCII */
+#endif
+		;
+	}
+	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<IOStream> 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::vector<unsigned char> buffer(fileSize);
+	if (fileSize != pStream->Read(buffer.data(), 1, fileSize)) {
+		throw DeadlyImportError("Failed to read the file " + file + ".");
+	}
+	// extra check for binary format's first 8 bytes. Not done for the ASCII variant
+	if(!memcmp(buffer.data(), "3DMO", 4) && memcmp(buffer.data() + 4, &fileSize, 4)) {
+		throw DeadlyImportError("Bad binary header in file " + file + ".");
+	}
+#ifdef M3D_ASCII
+	// make sure there's a terminator zero character, as input must be ASCIIZ
+	if(!memcmp(buffer.data(), "3dmo", 4)) {
+		buffer.push_back(0);
+	}
+#endif
+
+	// 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);
+		}
+	}
+
+    //DefaultLogger::create("/dev/stderr", Logger::VERBOSE);
+	ASSIMP_LOG_DEBUG_F("M3D: loading ", file);
+
+	// let the C SDK do the hard work for us
+	M3DWrapper m3d(pIOHandler, buffer);
+
+	if (!m3d) {
+		throw DeadlyImportError("Unable to parse " + file + " as M3D.");
+	}
+
+	// create the root node
+	pScene->mRootNode = new aiNode;
+	pScene->mRootNode->mName = aiString(m3d.Name());
+	pScene->mRootNode->mTransformation = aiMatrix4x4();
+	pScene->mRootNode->mNumChildren = 0;
+	mScene = pScene;
+
+	ASSIMP_LOG_DEBUG("M3D: root node " + m3d.Name());
+
+	// now we just have to fill up the Assimp structures in pScene
+	importMaterials(m3d);
+    importTextures(m3d);
+	importBones(m3d, M3D_NOTDEFINED, pScene->mRootNode);
+	importMeshes(m3d);
+	importAnimations(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(const M3DWrapper &m3d_wrap) {
+	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_wrap);
+
+	mScene->mNumMaterials = m3d_wrap->nummaterial + 1;
+	mScene->mMaterials = new aiMaterial *[mScene->mNumMaterials];
+
+	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.0f;
+	c.b = c.g = c.r = 0.6f;
+	mat->AddProperty(&c, 1, AI_MATKEY_COLOR_DIFFUSE);
+	mScene->mMaterials[0] = mat;
+
+	for (i = 0; i < m3d_wrap->nummaterial; i++) {
+		m = &m3d_wrap->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_wrap->numtexture &&
+					m3d_wrap->texture[m->prop[j].value.textureid].name) {
+				name.Set(std::string(std::string(m3d_wrap->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(const M3DWrapper &m3d) {
+	unsigned int i;
+	const char *formatHint[] = { "rgba0800", "rgba0808", "rgba8880", "rgba8888" };
+	m3dtx_t *t;
+
+	ai_assert(mScene != nullptr);
+	ai_assert(m3d);
+
+	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];
+		aiTexture *tx = new aiTexture;
+		tx->mFilename = aiString(std::string(t->name) + ".png");
+		if (!t->w || !t->h || !t->f || !t->d) {
+			/* without ASSIMP_USE_M3D_READFILECB, we only have the filename, but no texture data ever */
+			tx->mWidth = 0;
+			tx->mHeight = 0;
+			memcpy(tx->achFormatHint, "png\000", 4);
+			tx->pcData = nullptr;
+		} else {
+			/* if we have the texture loaded, set format hint and pcData too */
+			tx->mWidth = t->w;
+			tx->mHeight = t->h;
+			strcpy(tx->achFormatHint, formatHint[t->f - 1]);
+			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(const M3DWrapper &m3d) {
+	unsigned int i, j, k, l, numpoly = 3, lastMat = M3D_INDEXMAX;
+	std::vector<aiMesh *> *meshes = new std::vector<aiMesh *>();
+	std::vector<aiFace> *faces = nullptr;
+	std::vector<aiVector3D> *vertices = nullptr;
+	std::vector<aiVector3D> *normals = nullptr;
+	std::vector<aiVector3D> *texcoords = nullptr;
+	std::vector<aiColor4D> *colors = nullptr;
+	std::vector<unsigned int> *vertexids = nullptr;
+	aiMesh *pMesh = nullptr;
+
+	ai_assert(mScene != nullptr);
+	ai_assert(m3d);
+	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(m3d, 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<aiFace>();
+			vertices = new std::vector<aiVector3D>();
+			normals = new std::vector<aiVector3D>();
+			texcoords = new std::vector<aiVector3D>();
+			colors = new std::vector<aiColor4D>();
+			vertexids = new std::vector<unsigned int>();
+		}
+		// 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 = static_cast<unsigned int>(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 != M3D_UNDEF && m3d->vertex[l].skinid != M3D_INDEXMAX && 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 != M3D_UNDEF) {
+				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 != M3D_UNDEF) {
+				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(m3d, pMesh, faces, vertices, normals, texcoords, colors, vertexids);
+		meshes->push_back(pMesh);
+	}
+
+	// create global mesh list in scene
+	mScene->mNumMeshes = static_cast<unsigned int>(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 = static_cast<unsigned int>(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(const M3DWrapper &m3d, unsigned int parentid, aiNode *pParent) {
+	unsigned int i, n;
+
+	ai_assert(pParent != nullptr);
+	ai_assert(mScene != nullptr);
+	ai_assert(m3d);
+
+	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(m3d, &pChild->mTransformation, m3d->bone[i].pos, m3d->bone[i].ori);
+			pChild->mNumChildren = 0;
+			pParent->mChildren[pParent->mNumChildren] = pChild;
+			pParent->mNumChildren++;
+			importBones(m3d, 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(const M3DWrapper &m3d) {
+	unsigned int i, j, k, l, pos, ori;
+	double t;
+	m3da_t *a;
+
+	ai_assert(mScene != nullptr);
+	ai_assert(m3d);
+
+	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(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned int posid, unsigned int orientid) {
+	ai_assert(m != nullptr);
+	ai_assert(m3d);
+	ai_assert(posid != M3D_UNDEF && posid < m3d->numvertex);
+	ai_assert(orientid != M3D_UNDEF && 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(const M3DWrapper &m3d, aiMesh *pMesh, std::vector<aiFace> *faces, std::vector<aiVector3D> *vertices,
+		std::vector<aiVector3D> *normals, std::vector<aiVector3D> *texcoords, std::vector<aiColor4D> *colors,
+		std::vector<unsigned int> *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);
+
+	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 = static_cast<unsigned int>(faces->size());
+		pMesh->mFaces = new aiFace[pMesh->mNumFaces];
+		std::copy(faces->begin(), faces->end(), pMesh->mFaces);
+		pMesh->mNumVertices = static_cast<unsigned int>(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 != M3D_UNDEF && s != M3D_INDEXMAX) {
+						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 != M3D_UNDEF && s != M3D_INDEXMAX) {
+						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

+ 103 - 0
code/M3D/M3DImporter.h

@@ -0,0 +1,103 @@
+/*
+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 <assimp/BaseImporter.h>
+#include <assimp/material.h>
+#include <vector>
+
+struct aiMesh;
+struct aiNode;
+struct aiMaterial;
+struct aiFace;
+
+namespace Assimp {
+
+class M3DWrapper;
+
+class M3DImporter : public BaseImporter {
+public:
+	/// \brief  Default constructor
+	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 = nullptr; // the scene to import to
+
+	//! \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(const M3DWrapper &m3d);
+	void importTextures(const M3DWrapper &m3d);
+	void importMeshes(const M3DWrapper &m3d);
+	void importBones(const M3DWrapper &m3d, unsigned int parentid, aiNode *pParent);
+	void importAnimations(const M3DWrapper &m3d);
+
+	// helper functions
+	aiColor4D mkColor(uint32_t c);
+	void convertPose(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned int posid, unsigned int orientid);
+	aiNode *findNode(aiNode *pNode, aiString name);
+	void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m);
+	void populateMesh(const M3DWrapper &m3d, aiMesh *pMesh, std::vector<aiFace> *faces, std::vector<aiVector3D> *verteces,
+			std::vector<aiVector3D> *normals, std::vector<aiVector3D> *texcoords, std::vector<aiColor4D> *colors,
+			std::vector<unsigned int> *vertexids);
+};
+
+} // Namespace Assimp
+
+#endif // ASSIMP_BUILD_NO_M3D_IMPORTER
+
+#endif // AI_M3DIMPORTER_H_INC

+ 106 - 0
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(aiTextureType_AMBIENT_OCCLUSION,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_NORMALS(0) },                        /* m3dp_map_N */
+
+    { 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_REFLECTION,0) },       /* m3dp_map_Ni */
+    { NULL, 0, 0 },                                          /* m3dp_map_Nt */
+    { NULL, 0, 0 },
+    { NULL, 0, 0 },
+    { NULL, 0, 0 }
+};
+
+#endif // AI_M3DMATERIALS_H_INC

+ 142 - 0
code/M3D/M3DWrapper.cpp

@@ -0,0 +1,142 @@
+/*
+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.
+
+----------------------------------------------------------------------
+*/
+
+#if !(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER) || !ASSIMP_BUILD_NO_M3D_IMPORTER
+
+#include "M3DWrapper.h"
+
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/IOStreamBuffer.h>
+#include <assimp/ai_assert.h>
+
+#ifdef ASSIMP_USE_M3D_READFILECB
+
+# if (__cplusplus >= 201103L) || !defined(_MSC_VER) || (_MSC_VER >= 1900) // C++11 and MSVC 2015 onwards
+#  define threadlocal thread_local
+# else
+#  if defined(_MSC_VER) && (_MSC_VER >= 1800) // there's an alternative for MSVC 2013
+#   define threadlocal __declspec(thread)
+#  else
+#   define threadlocal
+#  endif
+# endif
+
+extern "C" {
+
+// workaround: the M3D SDK expects a C callback, but we want to use Assimp::IOSystem to implement that
+threadlocal 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<Assimp::IOStream> pStream(
+			(reinterpret_cast<Assimp::IOSystem *>(m3dimporter_pIOHandler))->Open(file, "rb"));
+	size_t fileSize = 0;
+	unsigned char *data = NULL;
+	// sometimes pStream is nullptr in a single-threaded scenario too 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;
+}
+}
+#endif
+
+namespace Assimp {
+M3DWrapper::M3DWrapper() {
+	// use malloc() here because m3d_free() will call free()
+	m3d_ = (m3d_t *)calloc(1, sizeof(m3d_t));
+}
+
+M3DWrapper::M3DWrapper(IOSystem *pIOHandler, const std::vector<unsigned char> &buffer) {
+#ifdef ASSIMP_USE_M3D_READFILECB
+	// pass this IOHandler to the C callback in a thread-local pointer
+	m3dimporter_pIOHandler = pIOHandler;
+	m3d_ = m3d_load(const_cast<unsigned char *>(buffer.data()), m3dimporter_readfile, free, nullptr);
+	// Clear the C callback
+	m3dimporter_pIOHandler = nullptr;
+#else
+	m3d_ = m3d_load(const_cast<unsigned char *>(buffer.data()), nullptr, nullptr, nullptr);
+#endif
+}
+
+M3DWrapper::~M3DWrapper() {
+	reset();
+}
+
+void M3DWrapper::reset() {
+	ClearSave();
+	if (m3d_)
+		m3d_free(m3d_);
+	m3d_ = nullptr;
+}
+
+unsigned char *M3DWrapper::Save(int quality, int flags, unsigned int &size) {
+#if (!(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER))
+	ClearSave();
+	saved_output_ = m3d_save(m3d_, quality, flags, &size);
+	return saved_output_;
+#else
+	return nullptr;
+#endif
+}
+
+void M3DWrapper::ClearSave() {
+	if (saved_output_)
+		M3D_FREE(saved_output_);
+	saved_output_ = nullptr;
+}
+} // namespace Assimp
+
+#endif

+ 101 - 0
code/M3D/M3DWrapper.h

@@ -0,0 +1,101 @@
+#pragma once
+/*
+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 M3DWrapper.h
+*   @brief Declares a class to wrap the C m3d SDK
+*/
+#ifndef AI_M3DWRAPPER_H_INC
+#define AI_M3DWRAPPER_H_INC
+#if !(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER) || !ASSIMP_BUILD_NO_M3D_IMPORTER
+
+#include <memory>
+#include <vector>
+#include <string>
+
+// Assimp specific M3D configuration. Comment out these defines to remove functionality
+//#define ASSIMP_USE_M3D_READFILECB
+//#define M3D_ASCII
+
+#include "m3d.h"
+
+namespace Assimp {
+class IOSystem;
+
+class M3DWrapper {
+	m3d_t *m3d_ = nullptr;
+	unsigned char *saved_output_ = nullptr;
+
+public:
+	// Construct an empty M3D model
+	explicit M3DWrapper();
+
+	// Construct an M3D model from provided buffer
+	// NOTE: The m3d.h SDK function does not mark the data as const. Have assumed it does not write.
+	// BUG: SECURITY: The m3d.h SDK cannot be informed of the buffer size. BUFFER OVERFLOW IS CERTAIN
+	explicit M3DWrapper(IOSystem *pIOHandler, const std::vector<unsigned char> &buffer);
+
+	~M3DWrapper();
+
+	void reset();
+
+	// Name
+	inline std::string Name() const {
+		if (m3d_) return std::string(m3d_->name);
+		return std::string();
+	}
+
+	// Execute a save
+	unsigned char *Save(int quality, int flags, unsigned int &size);
+	void ClearSave();
+
+	inline explicit operator bool() const { return m3d_ != nullptr; }
+
+	// Allow direct access to M3D API
+	inline m3d_t *operator->() const { return m3d_; }
+	inline m3d_t *M3D() const { return m3d_; }
+};
+} // namespace Assimp
+
+#endif
+
+#endif // AI_M3DWRAPPER_H_INC

+ 5612 - 0
code/M3D/m3d.h

@@ -0,0 +1,5612 @@
+/*
+ * 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 <stdint.h>
+
+/*** 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_UNDEF 0xffffffff
+#define M3D_INDEXMAX 0xfffffffe
+#else
+typedef uint16_t M3D_INDEX;
+#define M3D_UNDEF 0xffff
+#define M3D_INDEXMAX 0xfffe
+#endif
+#define M3D_NOTDEFINED 0xffffffff
+#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 __pragma(warning(suppress:4100))
+#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_N,  /* normal 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_map_il = m3dp_map_N,
+    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 layer 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 */
+    signed 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_N, "map_N"),/* as normal map has no scalar version, it's counterpart is 'il' */
+    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 <stdlib.h>
+#include <string.h>
+
+#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) {
+            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;
+   _m3dstbi__init_zdefaults();
+   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 <stdio.h>          /* get sprintf */
+#include <locale.h>         /* sprintf and strtod cares about number locale */
+#endif
+#ifdef M3D_PROFILING
+#include <sys/time.h>
+#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_NOIMPORTER) || 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, (uintptr_t)o - (uintptr_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 = (unsigned int)strlen(fn);
+        if(i < 5 || fn[i - 4] != '.') {
+            fn2 = (char*)M3D_MALLOC(i + 5);
+            if(!fn2) { model->errcode = M3D_ERR_ALLOC; return M3D_UNDEF; }
+            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_UNDEF;
+        }
+    }
+    /* add to textures array */
+    i = model->numtexture++;
+    model->texture = (m3dtx_t*)M3D_REALLOC(model->texture, model->numtexture * sizeof(m3dtx_t));
+    if(!model->texture) {
+        if(buff && freecb) (*freecb)(buff);
+        model->errcode = M3D_ERR_ALLOC;
+        return M3D_UNDEF;
+    }
+    model->texture[i].name = fn;
+    model->texture[i].w = model->texture[i].h = 0; model->texture[i].d = NULL;
+    if(buff) {
+        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
+    (void)readfilecb;
+    (void)freecb;
+    (void)fn;
+    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]<M3D_EPSILON) r[ 0]=(M3D_FLOAT)0.0;
+        r[ 1] = 2 * (q->x * q->y - q->z * q->w);     if(r[ 1]>-M3D_EPSILON && r[ 1]<M3D_EPSILON) r[ 1]=(M3D_FLOAT)0.0;
+        r[ 2] = 2 * (q->x * q->z + q->y * q->w);     if(r[ 2]>-M3D_EPSILON && r[ 2]<M3D_EPSILON) r[ 2]=(M3D_FLOAT)0.0;
+        r[ 4] = 2 * (q->x * q->y + q->z * q->w);     if(r[ 4]>-M3D_EPSILON && r[ 4]<M3D_EPSILON) r[ 4]=(M3D_FLOAT)0.0;
+        r[ 5] = 1 - 2 * (q->x * q->x + q->z * q->z); if(r[ 5]>-M3D_EPSILON && r[ 5]<M3D_EPSILON) r[ 5]=(M3D_FLOAT)0.0;
+        r[ 6] = 2 * (q->y * q->z - q->x * q->w);     if(r[ 6]>-M3D_EPSILON && r[ 6]<M3D_EPSILON) r[ 6]=(M3D_FLOAT)0.0;
+        r[ 8] = 2 * (q->x * q->z - q->y * q->w);     if(r[ 8]>-M3D_EPSILON && r[ 8]<M3D_EPSILON) r[ 8]=(M3D_FLOAT)0.0;
+        r[ 9] = 2 * (q->y * q->z + q->x * q->w);     if(r[ 9]>-M3D_EPSILON && r[ 9]<M3D_EPSILON) r[ 9]=(M3D_FLOAT)0.0;
+        r[10] = 1 - 2 * (q->x * q->x + q->y * q->y); if(r[10]>-M3D_EPSILON && r[10]<M3D_EPSILON) r[10]=(M3D_FLOAT)0.0;
+    }
+    r[ 3] = p->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)
+/* portable 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
+    char neednorm = 0;
+    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
+#ifdef M3D_PROFILING
+    struct timeval tv0, tv1, tvd;
+    gettimeofday(&tv0, NULL);
+#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_UNDEF;
+                    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_UNDEF && 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 = M3D_NOTDEFINED;
+                        if(model->skin) {
+                            for(j = 0; j < model->numskin; j++)
+                                if(!memcmp(&model->skin[j], &s, sizeof(m3ds_t))) { k = j; break; }
+                        }
+                        if(k == M3D_NOTDEFINED) {
+                            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_UNDEF;
+                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_INDEXMAX;
+                    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; }
+                                /* this error code only returned if readfilecb was specified */
+                                if(m->prop[j].value.textureid == M3D_UNDEF) {
+                                    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_UNDEF;
+                while(*ptr && *ptr != '\r' && *ptr != '\n') {
+                    if(*ptr == 'u') {
+                        ptr = _m3d_findarg(ptr);
+                        if(!*ptr) goto asciiend;
+                        mi = M3D_UNDEF;
+                        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_UNDEF && !(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;
+                                }
+                            }
+#ifndef M3D_NONORMALS
+                            if(model->face[i].normal[j] == M3D_UNDEF) neednorm = 1;
+#endif
+                            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_UNDEF;
+                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_UNDEF && h->group >= model->numbone) {
+                            M3D_LOG("Unknown bone id as shape group in shape");
+                            M3D_LOG(pe);
+                            h->group = M3D_UNDEF;
+                            model->errcode = M3D_ERR_SHPE;
+                        }
+                        continue;
+                    }
+                    for(cd = NULL, k = 0; k < (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])); k++) {
+                        j = (unsigned int)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_UNDEF;
+                                    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_UNDEF && !(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_INDEXMAX;
+                                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_INDEXMAX;
+                    }
+                    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)((uintptr_t)buff - (uintptr_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;
+    }
+#endif
+    /* Binary variant */
+    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;
+#ifdef M3D_PROFILING
+        gettimeofday(&tv1, NULL);
+        tvd.tv_sec = tv1.tv_sec - tv0.tv_sec;
+        tvd.tv_usec = tv1.tv_usec - tv0.tv_usec;
+        if(tvd.tv_usec < 0) { tvd.tv_sec--; tvd.tv_usec += 1000000L; }
+        printf("  Deflate model   %ld.%06ld sec\n", tvd.tv_sec, tvd.tv_usec);
+        memcpy(&tv0, &tv1, sizeof(struct timeval));
+#endif
+    } 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 was 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;
+        buff += len;
+        if(len < sizeof(m3dchunk_t) || buff >= end) {
+            M3D_LOG("Invalid chunk size");
+            break;
+        }
+        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;
+        chunk += len;
+        if(len < sizeof(m3dchunk_t) || chunk >= end) {
+            M3D_LOG("Invalid chunk size");
+            break;
+        }
+        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]) / (M3D_FLOAT)255.0;
+                        model->tmap[i].v = (M3D_FLOAT)(data[1]) / (M3D_FLOAT)255.0;
+                    break;
+                    case 2:
+                        model->tmap[i].u = (M3D_FLOAT)(*((int16_t*)(data+0))) / (M3D_FLOAT)65535.0;
+                        model->tmap[i].v = (M3D_FLOAT)(*((int16_t*)(data+2))) / (M3D_FLOAT)65535.0;
+                    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]) / (M3D_FLOAT)127.0;
+                        model->vertex[i].y = (M3D_FLOAT)((int8_t)data[1]) / (M3D_FLOAT)127.0;
+                        model->vertex[i].z = (M3D_FLOAT)((int8_t)data[2]) / (M3D_FLOAT)127.0;
+                        model->vertex[i].w = (M3D_FLOAT)((int8_t)data[3]) / (M3D_FLOAT)127.0;
+                        data += 4;
+                    break;
+                    case 2:
+                        model->vertex[i].x = (M3D_FLOAT)(*((int16_t*)(data+0))) / (M3D_FLOAT)32767.0;
+                        model->vertex[i].y = (M3D_FLOAT)(*((int16_t*)(data+2))) / (M3D_FLOAT)32767.0;
+                        model->vertex[i].z = (M3D_FLOAT)(*((int16_t*)(data+4))) / (M3D_FLOAT)32767.0;
+                        model->vertex[i].w = (M3D_FLOAT)(*((int16_t*)(data+6))) / (M3D_FLOAT)32767.0;
+                        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_UNDEF;
+                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_UNDEF;
+                        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]) / (M3D_FLOAT)255.0;
+                                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;
+                            /* this error code only returned if readfilecb was specified */
+                            if(m->prop[i].value.textureid == M3D_UNDEF) {
+                                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_UNDEF;
+            am = model->numface;
+            while(data < chunk) {
+                k = *data++;
+                n = k >> 4;
+                k &= 15;
+                if(!n) {
+                    /* use material */
+                    mi = M3D_UNDEF;
+                    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_UNDEF) 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]);
+#ifndef M3D_NONORMALS
+                    if(model->face[i].normal[j] == M3D_UNDEF) neednorm = 1;
+#endif
+                }
+            }
+            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_UNDEF;
+            data = _m3d_getidx(data, model->bi_s, &h->group);
+            if(h->group != M3D_UNDEF && h->group >= model->numbone) {
+                M3D_LOG("Unknown bone id as shape group in shape");
+                M3D_LOG(name);
+                h->group = M3D_UNDEF;
+                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] = M3D_NOTDEFINED;
+                            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] == M3D_NOTDEFINED) 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");
+#ifdef M3D_PROFILING
+        gettimeofday(&tv1, NULL);
+        tvd.tv_sec = tv1.tv_sec - tv0.tv_sec;
+        tvd.tv_usec = tv1.tv_usec - tv0.tv_usec;
+        if(tvd.tv_usec < 0) { tvd.tv_sec--; tvd.tv_usec += 1000000L; }
+        printf("  Parsing chunks  %ld.%06ld sec\n", tvd.tv_sec, tvd.tv_usec);
+#endif
+#ifndef M3D_NONORMALS
+        if(model->numface && model->face && neednorm) {
+            /* if they are missing, calculate triangle normals into a temporary buffer */
+            norm = (m3dv_t*)M3D_MALLOC(model->numface * sizeof(m3dv_t));
+            if(!norm) goto memerr;
+            for(i = 0, n = model->numvertex; i < model->numface; i++)
+                if(model->face[i].normal[0] == M3D_UNDEF) {
+                    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;
+                    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 */
+            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 = M3D_UNDEF;
+            }
+            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_UNDEF && sk->weight[j] > (M3D_FLOAT)0.0; j++)
+                        w += sk->weight[j];
+                    for(j = 0; j < M3D_NUMBONE && sk->boneid[j] != M3D_UNDEF && 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_UNDEF) {
+                    _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
+        }
+#ifdef M3D_PROFILING
+        gettimeofday(&tv0, NULL);
+        tvd.tv_sec = tv0.tv_sec - tv1.tv_sec;
+        tvd.tv_usec = tv0.tv_usec - tv1.tv_usec;
+        if(tvd.tv_usec < 0) { tvd.tv_sec--; tvd.tv_usec += 1000000L; }
+        printf("  Post-process    %ld.%06ld sec\n", tvd.tv_sec, tvd.tv_usec);
+#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_UNDEF && (!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_UNDEF || !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_UNDEF) {
+            _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 = (int)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 = (int)(src->x * 127 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->x = (M3D_FLOAT)t / (M3D_FLOAT)127.0;
+            t = (int)(src->y * 127 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->y = (M3D_FLOAT)t / (M3D_FLOAT)127.0;
+            t = (int)(src->z * 127 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->z = (M3D_FLOAT)t / (M3D_FLOAT)127.0;
+            t = (int)(src->w * 127 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->w = (M3D_FLOAT)t / (M3D_FLOAT)127.0;
+        break;
+        case M3D_EXP_INT16:
+            t = (int)(src->x * 32767 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->x = (M3D_FLOAT)t / (M3D_FLOAT)32767.0;
+            t = (int)(src->y * 32767 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->y = (M3D_FLOAT)t / (M3D_FLOAT)32767.0;
+            t = (int)(src->z * 32767 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->z = (M3D_FLOAT)t / (M3D_FLOAT)32767.0;
+            t = (int)(src->w * 32767 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->w = (M3D_FLOAT)t / (M3D_FLOAT)32767.0;
+        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_UNDEF;
+                }
+                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_UNDEF || !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_UNDEF) continue;
+            switch(quality) {
+                case M3D_EXP_INT8:
+                    l = (unsigned int)(model->tmap[i].u * 255); tcoord.data.u = (M3D_FLOAT)l / (M3D_FLOAT)255.0;
+                    l = (unsigned int)(model->tmap[i].v * 255); tcoord.data.v = (M3D_FLOAT)l / (M3D_FLOAT)255.0;
+                break;
+                case M3D_EXP_INT16:
+                    l = (unsigned int)(model->tmap[i].u * 65535); tcoord.data.u = (M3D_FLOAT)l / (M3D_FLOAT)65535.0;
+                    l = (unsigned int)(model->tmap[i].v * 65535); tcoord.data.v = (M3D_FLOAT)l / (M3D_FLOAT)65535.0;
+                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_UNDEF && model->vertex[i].skinid < model->numskin)
+                skinidx[model->vertex[i].skinid] = 0;
+        }
+        for(i = 0; i < model->numskin; i++) {
+            if(skinidx[i] == M3D_UNDEF) 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_UNDEF &&
+                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_UNDEF) continue;
+            _m3d_round(quality, &model->vertex[i], &vertex.data);
+            vertex.norm = norm ? norm[i] : 0;
+            if(vertex.data.skinid != M3D_INDEXMAX && !vertex.norm) {
+                vertex.data.skinid = vertex.data.skinid != M3D_UNDEF && skinidx ? skinidx[vertex.data.skinid] : M3D_UNDEF;
+                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_INDEXMAX) 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 + (unsigned int)(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 -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)20);
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_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 -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(maxtmap * 32) + (uintptr_t)12);
+            out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out;
+            if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+            ptr += sprintf(ptr, "Textmap\r\n");
+            last = M3D_UNDEF;
+            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 -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(maxvrtx * 128) + (uintptr_t)10);
+            out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out;
+            if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+            ptr += sprintf(ptr, "Vertex\r\n");
+            last = M3D_UNDEF;
+            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_UNDEF &&
+                            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 -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)9);
+            for(i = 0; i < model->numbone; i++) {
+                len += (unsigned int)strlen(model->bone[i].name) + 128;
+            }
+            out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_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_UNDEF, 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_UNDEF || !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 -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)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 += (unsigned int)strlen(model->texture[m->prop[i].value.textureid].name) + 16;
+                }
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_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 -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)18);
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_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 -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(model->numface * 128) + (uintptr_t)6);
+            last = M3D_UNDEF;
+            if(!(flags & M3D_EXP_NOMATERIAL))
+                for(i = 0; i < model->numface; i++) {
+                    j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : M3D_UNDEF;
+                    if(j != last) {
+                        last = j;
+                        if(last < model->nummaterial)
+                            len += (unsigned int)strlen(model->material[last].name);
+                        len += 6;
+                    }
+                }
+            out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out;
+            if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+            ptr += sprintf(ptr, "Mesh\r\n");
+            last = M3D_UNDEF;
+            for(i = 0; i < model->numface; i++) {
+                j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : M3D_UNDEF;
+                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 = M3D_NOTDEFINED;
+                    if(!(flags & M3D_EXP_NOTXTCRD) && (face[i].data.texcoord[j] != M3D_UNDEF) &&
+                        (tmapidx[face[i].data.texcoord[j]] != M3D_UNDEF)) {
+                            k = tmapidx[face[i].data.texcoord[j]];
+                            ptr += sprintf(ptr, "/%d", k);
+                    }
+                    if(!(flags & M3D_EXP_NONORMAL) && (face[i].data.normal[j] != M3D_UNDEF))
+                        ptr += sprintf(ptr, "%s/%d", k == M3D_NOTDEFINED? "/" : "", 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 -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)33);
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_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_UNDEF && !(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 -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(cd->key) + (uintptr_t)3);
+                    for(k = 0; k < cd->p; k++)
+                        switch(cd->a[k]) {
+                            case m3dcp_mi_t: if(cmd->arg[k] != M3D_NOTDEFINED) { len += (unsigned int)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 += (uintptr_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] != M3D_NOTDEFINED) {
+                                    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 += (unsigned int)strlen(model->label[i].name);
+                if(model->label[i].lang) j += (unsigned int)strlen(model->label[i].lang);
+                if(model->label[i].text) j += (unsigned int)strlen(model->label[i].text);
+                j += 40;
+            }
+            ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)j);
+            out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_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 -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)48);
+                for(i = 0; i < a->numframe; i++)
+                    len += a->frame[i].numtransform * 128 + 8;
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_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 += (unsigned int)strlen(model->inlined[i].name) + 6;
+            if(j > 0) {
+                ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)j + (uintptr_t)16);
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_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 -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)17 + (uintptr_t)(model->extra[i]->length * 3));
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_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 = (unsigned int)((uintptr_t)ptr - (uintptr_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 = (unsigned int)strlen(sn); memcpy((uint8_t*)h + h->length, sn, i+1); h->length += i+1; M3D_FREE(sn);
+        i = (unsigned int)strlen(sl); memcpy((uint8_t*)h + h->length, sl, i+1); h->length += i+1; M3D_FREE(sl);
+        i = (unsigned int)strlen(sa); memcpy((uint8_t*)h + h->length, sa, i+1); h->length += i+1; M3D_FREE(sa);
+        i = (unsigned int)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_UNDEF;
+            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 = (uint32_t)((uintptr_t)out - (uintptr_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_UNDEF;
+            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 = (uint32_t)((uintptr_t)out - (uintptr_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_UNDEF;
+                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_UNDEF &&
+                        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_UNDEF && weights[j]; j++) {
+                        out = _m3d_addidx(out, bi_s, skin[i].data.boneid[j]);
+                        *length += bi_s;
+                    }
+                }
+            }
+            *length = (uint32_t)((uintptr_t)out - (uintptr_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_UNDEF || !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 = (uint32_t)((uintptr_t)out - (uintptr_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_UNDEF;
+            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_UNDEF ||
+                    face[i].data.texcoord[1] == M3D_UNDEF || face[i].data.texcoord[2] == M3D_UNDEF) ? 0 : 1) |
+                    (((flags & M3D_EXP_NONORMAL) || face[i].data.normal[0] == M3D_UNDEF ||
+                    face[i].data.normal[1] == M3D_UNDEF || face[i].data.normal[2] == M3D_UNDEF) ? 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 = (uint32_t)((uintptr_t)out - (uintptr_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 = (uint32_t)((uintptr_t)out - (uintptr_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 = (uint32_t)((uintptr_t)out - (uintptr_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 = (uint32_t)((uintptr_t)out - (uintptr_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 = (uint32_t)((uintptr_t)out - (uintptr_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 <vector>
+#include <string>
+#include <memory>
+
+/*** 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<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[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<unsigned char> getPreview() { return this->model->preview.data ?
+                std::vector<unsigned char>(this->model->preview.data, this->model->preview.data + this->model->preview.length) :
+                std::vector<unsigned char>(); }
+            std::vector<uint32_t> getColorMap() { return this->model->cmap ? std::vector<uint32_t>(this->model->cmap,
+                this->model->cmap + this->model->numcmap) : std::vector<uint32_t>(); }
+            std::vector<m3dti_t> getTextureMap() { return this->model->tmap ? std::vector<m3dti_t>(this->model->tmap,
+                this->model->tmap + this->model->numtmap) : std::vector<m3dti_t>(); }
+            std::vector<m3dtx_t> getTextures() { return this->model->texture ? std::vector<m3dtx_t>(this->model->texture,
+                this->model->texture + this->model->numtexture) : std::vector<m3dtx_t>(); }
+            std::string getTextureName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numtexture ?
+                std::string(this->model->texture[idx].name) : nullptr; }
+            std::vector<m3db_t> getBones() { return this->model->bone ? std::vector<m3db_t>(this->model->bone, this->model->bone +
+                this->model->numbone) : std::vector<m3db_t>(); }
+            std::string getBoneName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numbone ?
+                std::string(this->model->bone[idx].name) : nullptr; }
+            std::vector<m3dm_t> getMaterials() { return this->model->material ? std::vector<m3dm_t>(this->model->material,
+                this->model->material + this->model->nummaterial) : std::vector<m3dm_t>(); }
+            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<m3dv_t> getVertices() { return this->model->vertex ? std::vector<m3dv_t>(this->model->vertex,
+                this->model->vertex + this->model->numvertex) : std::vector<m3dv_t>(); }
+            std::vector<m3df_t> getFace() { return this->model->face ? std::vector<m3df_t>(this->model->face, this->model->face +
+                this->model->numface) : std::vector<m3df_t>(); }
+            std::vector<m3dh_t> getShape() { return this->model->shape ? std::vector<m3dh_t>(this->model->shape,
+                this->model->shape + this->model->numshape) : std::vector<m3dh_t>(); }
+            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<m3dc_t> getShapeCommands(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape &&
+                this->model->shape[idx].cmd ? std::vector<m3dc_t>(this->model->shape[idx].cmd, this->model->shape[idx].cmd +
+                this->model->shape[idx].numcmd) : std::vector<m3dc_t>(); }
+            std::vector<m3dl_t> getAnnotationLabels() { return this->model->label ? std::vector<m3dl_t>(this->model->label,
+                this->model->label + this->model->numlabel) : std::vector<m3dl_t>(); }
+            std::vector<m3ds_t> getSkin() { return this->model->skin ? std::vector<m3ds_t>(this->model->skin, this->model->skin +
+                this->model->numskin) : std::vector<m3ds_t>(); }
+            std::vector<m3da_t> getActions() { return this->model->action ? std::vector<m3da_t>(this->model->action,
+                this->model->action + this->model->numaction) : std::vector<m3da_t>(); }
+            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<m3dfr_t> getActionFrames(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ?
+                std::vector<m3dfr_t>(this->model->action[aidx].frame, this->model->action[aidx].frame +
+                this->model->action[aidx].numframe) : std::vector<m3dfr_t>(); }
+            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<m3dtr_t> 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<m3dtr_t>(this->model->action[aidx].frame[fidx].transform,
+                    this->model->action[aidx].frame[fidx].transform + this->model->action[aidx].frame[fidx].numtransform) :
+                    std::vector<m3dtr_t>()) : std::vector<m3dtr_t>(); }
+            std::vector<m3dtr_t> getActionFrame(int aidx, int fidx, std::vector<m3dtr_t> skeleton) {
+                m3dtr_t *pose = m3d_frame(this->model, (unsigned int)aidx, (unsigned int)fidx,
+                    skeleton.size() ? &skeleton[0] : nullptr);
+                return std::vector<m3dtr_t>(pose, pose + this->model->numbone); }
+            std::vector<m3db_t> getActionPose(int aidx, unsigned int msec) {
+                m3db_t *pose = m3d_pose(this->model, (unsigned int)aidx, (unsigned int)msec);
+                return std::vector<m3db_t>(pose, pose + this->model->numbone); }
+            std::vector<m3di_t> getInlinedAssets() { return this->model->inlined ? std::vector<m3di_t>(this->model->inlined,
+                this->model->inlined + this->model->numinlined) : std::vector<m3di_t>(); }
+            std::vector<std::unique_ptr<m3dchunk_t>> getExtras() { return this->model->extra ?
+                std::vector<std::unique_ptr<m3dchunk_t>>(this->model->extra,
+                this->model->extra + this->model->numextra) : std::vector<std::unique_ptr<m3dchunk_t>>(); }
+            std::vector<unsigned char> 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<unsigned char>(ptr, ptr + size) : std::vector<unsigned char>();
+#else
+                return std::vector<unsigned char>();
+#endif
+            }
+    };
+
+#else
+    class Model {
+        public:
+            m3d_t *model;
+
+        public:
+            Model(const std::string &data, m3dread_t ReadFileCB, m3dfree_t FreeCB);
+            Model(const std::vector<unsigned char> 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<unsigned char> getPreview();
+            std::vector<uint32_t> getColorMap();
+            std::vector<m3dti_t> getTextureMap();
+            std::vector<m3dtx_t> getTextures();
+            std::string getTextureName(int idx);
+            std::vector<m3db_t> getBones();
+            std::string getBoneName(int idx);
+            std::vector<m3dm_t> 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<m3dv_t> getVertices();
+            std::vector<m3df_t> getFace();
+            std::vector<m3dh_t> getShape();
+            std::string getShapeName(int idx);
+            unsigned int getShapeGroup(int idx);
+            std::vector<m3dc_t> getShapeCommands(int idx);
+            std::vector<m3dl_t> getAnnotationLabels();
+            std::vector<m3ds_t> getSkin();
+            std::vector<m3da_t> getActions();
+            std::string getActionName(int aidx);
+            unsigned int getActionDuration(int aidx);
+            std::vector<m3dfr_t> getActionFrames(int aidx);
+            unsigned int getActionFrameTimestamp(int aidx, int fidx);
+            std::vector<m3dtr_t> getActionFrameTransforms(int aidx, int fidx);
+            std::vector<m3dtr_t> getActionFrame(int aidx, int fidx, std::vector<m3dtr_t> skeleton);
+            std::vector<m3db_t> getActionPose(int aidx, unsigned int msec);
+            std::vector<m3di_t> getInlinedAssets();
+            std::vector<std::unique_ptr<m3dchunk_t>> getExtras();
+            std::vector<unsigned char> Save(int quality, int flags);
+    };
+
+#endif /* impl */
+}
+#endif
+
+#endif /* __cplusplus */
+
+#endif

+ 1 - 1
code/MD2/MD2Loader.cpp

@@ -344,7 +344,7 @@ void MD2Importer::InternReadFile( const std::string& pFile,
         if (pcSkins->name[0])
         if (pcSkins->name[0])
         {
         {
             aiString szString;
             aiString szString;
-            const size_t iLen = ::strlen(pcSkins->name);
+            const ai_uint32 iLen = (ai_uint32) ::strlen(pcSkins->name);
             ::memcpy(szString.data,pcSkins->name,iLen);
             ::memcpy(szString.data,pcSkins->name,iLen);
             szString.data[iLen] = '\0';
             szString.data[iLen] = '\0';
             szString.length = iLen;
             szString.length = iLen;

+ 1 - 1
code/MD3/MD3Loader.cpp

@@ -973,7 +973,7 @@ void MD3Importer::InternReadFile( const std::string& pFile, aiScene* pScene, IOS
             AI_SWAP2( pcVertices[i].Z );
             AI_SWAP2( pcVertices[i].Z );
 
 
             AI_SWAP4( pcUVs[i].U );
             AI_SWAP4( pcUVs[i].U );
-            AI_SWAP4( pcUVs[i].U );
+            AI_SWAP4( pcUVs[i].V );
         }
         }
         for (uint32_t i = 0; i < pcSurfaces->NUM_TRIANGLES;++i) {
         for (uint32_t i = 0; i < pcSurfaces->NUM_TRIANGLES;++i) {
             AI_SWAP4(pcTriangles[i].INDEXES[0]);
             AI_SWAP4(pcTriangles[i].INDEXES[0]);

+ 2 - 1
code/MD5/MD5Loader.cpp

@@ -53,6 +53,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "MD5Loader.h"
 #include "MD5Loader.h"
 #include <assimp/StringComparison.h>
 #include <assimp/StringComparison.h>
 #include <assimp/fast_atof.h>
 #include <assimp/fast_atof.h>
+#include <assimp/MathFunctions.h>
 #include <assimp/SkeletonMeshBuilder.h>
 #include <assimp/SkeletonMeshBuilder.h>
 #include <assimp/Importer.hpp>
 #include <assimp/Importer.hpp>
 #include <assimp/scene.h>
 #include <assimp/scene.h>
@@ -64,7 +65,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 using namespace Assimp;
 using namespace Assimp;
 
 
 // Minimum weight value. Weights inside [-n ... n] are ignored
 // Minimum weight value. Weights inside [-n ... n] are ignored
-#define AI_MD5_WEIGHT_EPSILON 1e-5f
+#define AI_MD5_WEIGHT_EPSILON Math::getEpsilon<float>()
 
 
 
 
 static const aiImporterDesc desc = {
 static const aiImporterDesc desc = {

+ 1 - 1
code/MD5/MD5Parser.cpp

@@ -235,7 +235,7 @@ bool MD5Parser::ParseSection(Section& out)
     const char* szStart = ++sz; \
     const char* szStart = ++sz; \
 	while('\"'!=*sz)++sz; \
 	while('\"'!=*sz)++sz; \
     const char* szEnd = (sz++); \
     const char* szEnd = (sz++); \
-    out.length = (size_t)(szEnd - szStart); \
+    out.length = (ai_uint32) (szEnd - szStart); \
     ::memcpy(out.data,szStart,out.length); \
     ::memcpy(out.data,szStart,out.length); \
     out.data[out.length] = '\0';
     out.data[out.length] = '\0';
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------

+ 1 - 1
code/MDC/MDCLoader.cpp

@@ -355,7 +355,7 @@ void MDCImporter::InternReadFile(
         // swap all texture coordinates
         // swap all texture coordinates
         for (unsigned int i = 0; i < pcSurface->ulNumVertices;++i)
         for (unsigned int i = 0; i < pcSurface->ulNumVertices;++i)
         {
         {
-            AI_SWAP4( pcUVs->v );
+            AI_SWAP4( pcUVs->u );
             AI_SWAP4( pcUVs->v );
             AI_SWAP4( pcUVs->v );
         }
         }
 
 

+ 600 - 0
code/MDL/HalfLife/HL1FileData.h

@@ -0,0 +1,600 @@
+/*
+---------------------------------------------------------------------------
+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  HL1FileData.h
+ *  @brief Definition of in-memory structures for the
+ *         Half-Life 1 MDL file format.
+ */
+
+#ifndef AI_HL1FILEDATA_INCLUDED
+#define AI_HL1FILEDATA_INCLUDED
+
+#include "HalfLifeMDLBaseHeader.h"
+
+#include <assimp/Compiler/pushpack1.h>
+#include <assimp/types.h>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+using vec3_t = float[3];
+
+/** \struct Header_HL1
+ *  \brief Data structure for the HL1 MDL file header.
+ */
+struct Header_HL1 : HalfLifeMDLBaseHeader {
+    //! The model name.
+    char name[64];
+
+    //! The total file size in bytes.
+    int32_t length;
+
+    //! Ideal eye position.
+    vec3_t eyeposition;
+
+    //! Ideal movement hull size.
+    vec3_t min;
+    vec3_t max;
+
+    //! Clipping bounding box.
+    vec3_t bbmin;
+    vec3_t bbmax;
+
+    //! Was "flags".
+    int32_t unused;
+
+    //! The number of bones.
+    int32_t numbones;
+
+    //! Offset to the first bone chunk.
+    int32_t boneindex;
+
+    //! The number of bone controllers.
+    int32_t numbonecontrollers;
+
+    //! Offset to the first bone controller chunk.
+    int32_t bonecontrollerindex;
+
+    //! The number of hitboxes.
+    int32_t numhitboxes;
+
+    //! Offset to the first hitbox chunk.
+    int32_t hitboxindex;
+
+    //! The number of sequences.
+    int32_t numseq;
+
+    //! Offset to the first sequence description chunk.
+    int32_t seqindex;
+
+    //! The number of sequence groups.
+    int32_t numseqgroups;
+
+    //! Offset to the first sequence group chunk.
+    int32_t seqgroupindex;
+
+    //! The number of textures.
+    int32_t numtextures;
+
+    //! Offset to the first texture chunk.
+    int32_t textureindex;
+
+    //! Offset to the first texture's image data.
+    int32_t texturedataindex;
+
+    //! The number of replaceable textures.
+    int32_t numskinref;
+
+    //! The number of skin families.
+    int32_t numskinfamilies;
+
+    //! Offset to the first replaceable texture.
+    int32_t skinindex;
+
+    //! The number of bodyparts.
+    int32_t numbodyparts;
+
+    //! Offset the the first bodypart.
+    int32_t bodypartindex;
+
+    //! The number of attachments.
+    int32_t numattachments;
+
+    //! Offset the the first attachment chunk.
+    int32_t attachmentindex;
+
+    //! Was "soundtable".
+    int32_t unused2;
+
+    //! Was "soundindex".
+    int32_t unused3;
+
+    //! Was "soundgroups".
+    int32_t unused4;
+
+    //! Was "soundgroupindex".
+    int32_t unused5;
+
+    //! The number of nodes in the sequence transition graph.
+    int32_t numtransitions;
+
+    //! Offset the the first sequence transition.
+    int32_t transitionindex;
+} PACK_STRUCT;
+
+/** \struct SequenceHeader_HL1
+ *  \brief Data structure for the file header of a demand loaded
+ *         HL1 MDL sequence group file.
+ */
+struct SequenceHeader_HL1 : HalfLifeMDLBaseHeader {
+    //! The sequence group file name.
+    char name[64];
+
+    //! The total file size in bytes.
+    int32_t length;
+} PACK_STRUCT;
+
+/** \struct Bone_HL1
+ *  \brief Data structure for a bone in HL1 MDL files.
+ */
+struct Bone_HL1 {
+    //! The bone name.
+    char name[32];
+
+    //! The parent bone index. (-1) If it has no parent.
+    int32_t parent;
+
+    //! Was "flags".
+    int32_t unused;
+
+    //! Available bone controller per motion type.
+    //! (-1) if no controller is available.
+    int32_t bonecontroller[6];
+
+    /*! Default position and rotation values where
+     * scale[0] = position.X
+     * scale[1] = position.Y
+     * scale[2] = position.Z
+     * scale[3] = rotation.X
+     * scale[4] = rotation.Y
+     * scale[5] = rotation.Z
+     */
+    float value[6];
+
+    /*! Compressed scale values where
+     * scale[0] = position.X scale
+     * scale[1] = position.Y scale
+     * scale[2] = position.Z scale
+     * scale[3] = rotation.X scale
+     * scale[4] = rotation.Y scale
+     * scale[5] = rotation.Z scale
+     */
+    float scale[6];
+} PACK_STRUCT;
+
+/** \struct BoneController_HL1
+ *  \brief Data structure for a bone controller in HL1 MDL files.
+ */
+struct BoneController_HL1 {
+    //! Bone affected by this controller.
+    int32_t bone;
+
+    //! The motion type.
+    int32_t type;
+
+    //! The minimum and maximum values.
+    float start;
+    float end;
+
+    // Was "rest".
+    int32_t unused;
+
+    // The bone controller channel.
+    int32_t index;
+} PACK_STRUCT;
+
+/** \struct Hitbox_HL1
+ *  \brief Data structure for a hitbox in HL1 MDL files.
+ */
+struct Hitbox_HL1 {
+    //! The bone this hitbox follows.
+    int32_t bone;
+
+    //! The hit group.
+    int32_t group;
+
+    //! The hitbox minimum and maximum extents.
+    vec3_t bbmin;
+    vec3_t bbmax;
+} PACK_STRUCT;
+
+/** \struct SequenceGroup_HL1
+ *  \brief Data structure for a sequence group in HL1 MDL files.
+ */
+struct SequenceGroup_HL1 {
+    //! A textual name for this sequence group.
+    char label[32];
+
+    //! The file name.
+    char name[64];
+
+    //! Was "cache".
+    int32_t unused;
+
+    //! Was "data".
+    int32_t unused2;
+} PACK_STRUCT;
+
+//! The type of blending for a sequence.
+enum SequenceBlendMode_HL1 {
+    NoBlend = 1,
+    TwoWayBlending = 2,
+    FourWayBlending = 4,
+};
+
+/** \struct SequenceDesc_HL1
+ *  \brief Data structure for a sequence description in HL1 MDL files.
+ */
+struct SequenceDesc_HL1 {
+    //! The sequence name.
+    char label[32];
+
+    //! Frames per second.
+    float fps;
+
+    //! looping/non-looping flags.
+    int32_t flags;
+
+    //! The sequence activity.
+    int32_t activity;
+
+    //! The sequence activity weight.
+    int32_t actweight;
+
+    //! The number of animation events.
+    int32_t numevents;
+
+    //! Offset the the first animation event chunk.
+    int32_t eventindex;
+
+    //! The number of frames in the sequence.
+    int32_t numframes;
+
+    //! Was "numpivots".
+    int32_t unused;
+
+    //! Was "pivotindex".
+    int32_t unused2;
+
+    //! Linear motion type.
+    int32_t motiontype;
+
+    //! Linear motion bone.
+    int32_t motionbone;
+
+    //! Linear motion.
+    vec3_t linearmovement;
+
+    //! Was "automoveposindex".
+    int32_t unused3;
+
+    //! Was "automoveangleindex".
+    int32_t unused4;
+
+    //! The sequence minimum and maximum extents.
+    vec3_t bbmin;
+    vec3_t bbmax;
+
+    //! The number of blend animations.
+    int32_t numblends;
+
+    //! Offset to first the AnimValueOffset_HL1 chunk.
+    //! This offset is relative to the SequenceHeader_HL1 of the file
+    //! that contains the animation data.
+    int32_t animindex;
+
+    //! The motion type of each blend controller.
+    int32_t blendtype[2];
+
+    //! The starting value of each blend controller.
+    float blendstart[2];
+
+    //! The ending value of each blend controller.
+    float blendend[2];
+
+    //! Was "blendparent".
+    int32_t unused5;
+
+    //! The sequence group.
+    int32_t seqgroup;
+
+    //! The node at entry in the sequence transition graph.
+    int32_t entrynode;
+
+    //! The node at exit in the sequence transition graph.
+    int32_t exitnode;
+
+    //! Transition rules.
+    int32_t nodeflags;
+
+    //! Was "nextseq"
+    int32_t unused6;
+} PACK_STRUCT;
+
+/** \struct AnimEvent_HL1
+ *  \brief Data structure for an animation event in HL1 MDL files.
+ */
+struct AnimEvent_HL1 {
+    //! The frame at which this animation event occurs.
+    int32_t frame;
+
+    //! The script event type.
+    int32_t event;
+
+    //! was "type"
+    int32_t unused;
+
+    //! Options. Could be path to sound WAVE files.
+    char options[64];
+} PACK_STRUCT;
+
+/** \struct Attachment_HL1
+ *  \brief Data structure for an attachment in HL1 MDL files.
+ */
+struct Attachment_HL1 {
+    //! Was "name".
+    char unused[32];
+
+    //! Was "type".
+    int32_t unused2;
+
+    //! The bone this attachment follows.
+    int32_t bone;
+
+    //! The attachment origin.
+    vec3_t org;
+
+    //! Was "vectors"
+    vec3_t unused3[3];
+} PACK_STRUCT;
+
+/** \struct AnimValueOffset_HL1
+ *  \brief Data structure to hold offsets (one per motion type)
+ *         to the first animation frame value for a single bone
+ *         in HL1 MDL files.
+ */
+struct AnimValueOffset_HL1 {
+    unsigned short offset[6];
+} PACK_STRUCT;
+
+/** \struct AnimValue_HL1
+ *  \brief Data structure for an animation frame in HL1 MDL files.
+ */
+union AnimValue_HL1 {
+    struct {
+        uint8_t valid;
+        uint8_t total;
+    } num;
+    short value;
+} PACK_STRUCT;
+
+/** \struct Bodypart_HL1
+ *  \brief Data structure for a bodypart in HL1 MDL files.
+ */
+struct Bodypart_HL1 {
+    //! The bodypart name.
+    char name[64];
+
+    //! The number of available models for this bodypart.
+    int32_t nummodels;
+
+    //! Used to convert from a global model index
+    //! to a local bodypart model index.
+    int32_t base;
+
+    //! The offset to the first model chunk.
+    int32_t modelindex;
+} PACK_STRUCT;
+
+/** \struct Texture_HL1
+ *  \brief Data structure for a texture in HL1 MDL files.
+ */
+struct Texture_HL1 {
+    //! Texture file name.
+    char name[64];
+
+    //! Texture flags.
+    int32_t flags;
+
+    //! Texture width in pixels.
+    int32_t width;
+
+    //! Texture height in pixels.
+    int32_t height;
+
+    //! Offset to the image data.
+    //! This offset is relative to the texture file header.
+    int32_t index;
+} PACK_STRUCT;
+
+/** \struct Model_HL1
+ *  \brief Data structure for a model in HL1 MDL files.
+ */
+struct Model_HL1 {
+    //! Model name.
+    char name[64];
+
+    //! Was "type".
+    int32_t unused;
+
+    //! Was "boundingradius".
+    float unused2;
+
+    //! The number of meshes in the model.
+    int32_t nummesh;
+
+    //! Offset to the first mesh chunk.
+    int32_t meshindex;
+
+    //! The number of unique vertices.
+    int32_t numverts;
+
+    //! Offset to the vertex bone array.
+    int32_t vertinfoindex;
+
+    //! Offset to the vertex array.
+    int32_t vertindex;
+
+    //! The number of unique normals.
+    int32_t numnorms;
+
+    //! Offset to the normal bone array.
+    int32_t norminfoindex;
+
+    //! Offset to the normal array.
+    int32_t normindex;
+
+    //! Was "numgroups".
+    int32_t unused3;
+
+    //! Was "groupindex".
+    int32_t unused4;
+} PACK_STRUCT;
+
+/** \struct Mesh_HL1
+ *  \brief Data structure for a mesh in HL1 MDL files.
+ */
+struct Mesh_HL1 {
+    //! Can be interpreted as the number of triangles in the mesh.
+    int32_t numtris;
+
+    //! Offset to the start of the tris sequence.
+    int32_t triindex;
+
+    //! The skin index.
+    int32_t skinref;
+
+    //! The number of normals in the mesh.
+    int32_t numnorms;
+
+    //! Was "normindex".
+    int32_t unused;
+} PACK_STRUCT;
+
+/** \struct Trivert
+ *  \brief Data structure for a trivert in HL1 MDL files.
+ */
+struct Trivert {
+    //! Index into Model_HL1 vertex array.
+    short vertindex;
+
+    //! Index into Model_HL1 normal array.
+    short normindex;
+
+    //! Texture coordinates in absolute space (unnormalized).
+    short s, t;
+} PACK_STRUCT;
+
+#include <assimp/Compiler/poppack1.h>
+
+#if (!defined AI_MDL_HL1_VERSION)
+#define AI_MDL_HL1_VERSION 10
+#endif
+#if (!defined AI_MDL_HL1_MAX_TRIANGLES)
+#define AI_MDL_HL1_MAX_TRIANGLES 20000
+#endif
+#if (!defined AI_MDL_HL1_MAX_VERTICES)
+#define AI_MDL_HL1_MAX_VERTICES 2048
+#endif
+#if (!defined AI_MDL_HL1_MAX_SEQUENCES)
+#define AI_MDL_HL1_MAX_SEQUENCES 2048
+#endif
+#if (!defined AI_MDL_HL1_MAX_SEQUENCE_GROUPS)
+#define AI_MDL_HL1_MAX_SEQUENCE_GROUPS 32
+#endif
+#if (!defined AI_MDL_HL1_MAX_TEXTURES)
+#define AI_MDL_HL1_MAX_TEXTURES 100
+#endif
+#if (!defined AI_MDL_HL1_MAX_SKIN_FAMILIES)
+#define AI_MDL_HL1_MAX_SKIN_FAMILIES 100
+#endif
+#if (!defined AI_MDL_HL1_MAX_BONES)
+#define AI_MDL_HL1_MAX_BONES 128
+#endif
+#if (!defined AI_MDL_HL1_MAX_BODYPARTS)
+#define AI_MDL_HL1_MAX_BODYPARTS 32
+#endif
+#if (!defined AI_MDL_HL1_MAX_MODELS)
+#define AI_MDL_HL1_MAX_MODELS 32
+#endif
+#if (!defined AI_MDL_HL1_MAX_MESHES)
+#define AI_MDL_HL1_MAX_MESHES 256
+#endif
+#if (!defined AI_MDL_HL1_MAX_EVENTS)
+#define AI_MDL_HL1_MAX_EVENTS 1024
+#endif
+#if (!defined AI_MDL_HL1_MAX_BONE_CONTROLLERS)
+#define AI_MDL_HL1_MAX_BONE_CONTROLLERS 8
+#endif
+#if (!defined AI_MDL_HL1_MAX_ATTACHMENTS)
+#define AI_MDL_HL1_MAX_ATTACHMENTS 512
+#endif
+
+// lighting options
+#if (!defined AI_MDL_HL1_STUDIO_NF_FLATSHADE)
+#define AI_MDL_HL1_STUDIO_NF_FLATSHADE 0x0001
+#endif
+#if (!defined AI_MDL_HL1_STUDIO_NF_CHROME)
+#define AI_MDL_HL1_STUDIO_NF_CHROME 0x0002
+#endif
+#if (!defined AI_MDL_HL1_STUDIO_NF_ADDITIVE)
+#define AI_MDL_HL1_STUDIO_NF_ADDITIVE 0x0020
+#endif
+#if (!defined AI_MDL_HL1_STUDIO_NF_MASKED)
+#define AI_MDL_HL1_STUDIO_NF_MASKED 0x0040
+#endif
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
+
+#endif // AI_HL1FILEDATA_INCLUDED

+ 64 - 0
code/MDL/HalfLife/HL1ImportDefinitions.h

@@ -0,0 +1,64 @@
+/*
+---------------------------------------------------------------------------
+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 HL1ImportDefinitions.h
+ *  @brief HL1 MDL loader specific definitions.
+ */
+
+#ifndef AI_MDL_HL1_IMPORT_DEFINITIONS_INCLUDED
+#define AI_MDL_HL1_IMPORT_DEFINITIONS_INCLUDED
+
+#define AI_MDL_HL1_NODE_ROOT "<MDL_root>"
+#define AI_MDL_HL1_NODE_BODYPARTS "<MDL_bodyparts>"
+#define AI_MDL_HL1_NODE_BONES "<MDL_bones>"
+#define AI_MDL_HL1_NODE_BONE_CONTROLLERS "<MDL_bone_controllers>"
+#define AI_MDL_HL1_NODE_SEQUENCE_INFOS "<MDL_sequence_infos>"
+#define AI_MDL_HL1_NODE_SEQUENCE_GROUPS "<MDL_sequence_groups>"
+#define AI_MDL_HL1_NODE_SEQUENCE_TRANSITION_GRAPH "<MDL_sequence_transition_graph>"
+#define AI_MDL_HL1_NODE_ATTACHMENTS "<MDL_attachments>"
+#define AI_MDL_HL1_NODE_HITBOXES "<MDL_hitboxes>"
+#define AI_MDL_HL1_NODE_GLOBAL_INFO "<MDL_global_info>"
+#define AI_MDL_HL1_NODE_ANIMATION_EVENTS "AnimationEvents"
+#define AI_MDL_HL1_NODE_BLEND_CONTROLLERS "BlendControllers"
+
+#define AI_MDL_HL1_MATKEY_CHROME(type, N) "$mat.HL1.chrome", type, N
+
+#endif // AI_MDL_HL1_IMPORT_DEFINITIONS_INCLUDED

+ 85 - 0
code/MDL/HalfLife/HL1ImportSettings.h

@@ -0,0 +1,85 @@
+/*
+---------------------------------------------------------------------------
+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 HL1ImportSettings.h
+ *  @brief Half-Life 1 MDL loader configuration settings.
+ */
+
+#ifndef AI_HL1IMPORTSETTINGS_INCLUDED
+#define AI_HL1IMPORTSETTINGS_INCLUDED
+
+#include <string>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+struct HL1ImportSettings {
+    HL1ImportSettings() :
+        read_animations(false),
+        read_animation_events(false),
+        read_blend_controllers(false),
+        read_sequence_groups_info(false),
+        read_sequence_transitions(false),
+        read_attachments(false),
+        read_bone_controllers(false),
+        read_hitboxes(false),
+        read_textures(false),
+        read_misc_global_info(false) {
+    }
+
+    bool read_animations;
+    bool read_animation_events;
+    bool read_blend_controllers;
+    bool read_sequence_groups_info;
+    bool read_sequence_transitions;
+    bool read_attachments;
+    bool read_bone_controllers;
+    bool read_hitboxes;
+    bool read_textures;
+    bool read_misc_global_info;
+};
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
+
+#endif // AI_HL1IMPORTSETTINGS_INCLUDED

+ 1338 - 0
code/MDL/HalfLife/HL1MDLLoader.cpp

@@ -0,0 +1,1338 @@
+/*
+---------------------------------------------------------------------------
+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 HL1MDLLoader.cpp
+ *  @brief Implementation for the Half-Life 1 MDL loader.
+ */
+
+#include "HL1MDLLoader.h"
+#include "HL1ImportDefinitions.h"
+#include "HL1MeshTrivert.h"
+#include "UniqueNameGenerator.h"
+
+#include <assimp/BaseImporter.h>
+#include <assimp/StringUtils.h>
+#include <assimp/ai_assert.h>
+#include <assimp/qnan.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+
+#include <iomanip>
+#include <sstream>
+
+#ifdef MDL_HALFLIFE_LOG_WARN_HEADER
+#undef MDL_HALFLIFE_LOG_WARN_HEADER
+#endif
+#define MDL_HALFLIFE_LOG_HEADER "[Half-Life 1 MDL] "
+#include "LogFunctions.h"
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+// ------------------------------------------------------------------------------------------------
+HL1MDLLoader::HL1MDLLoader(
+    aiScene *scene,
+    IOSystem *io,
+    const unsigned char *buffer,
+    const std::string &file_path,
+    const HL1ImportSettings &import_settings) :
+    scene_(scene),
+    io_(io),
+    buffer_(buffer),
+    file_path_(file_path),
+    import_settings_(import_settings),
+    header_(nullptr),
+    texture_header_(nullptr),
+    anim_headers_(nullptr),
+    texture_buffer_(nullptr),
+    anim_buffers_(nullptr),
+    num_sequence_groups_(0),
+    rootnode_children_(),
+    unique_name_generator_(),
+    unique_sequence_names_(),
+    unique_sequence_groups_names_(),
+    temp_bones_(),
+    num_blend_controllers_(0),
+    total_models_(0) {
+    load_file();
+}
+
+// ------------------------------------------------------------------------------------------------
+HL1MDLLoader::~HL1MDLLoader() {
+    release_resources();
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::release_resources() {
+    if (buffer_ != texture_buffer_) {
+        delete[] texture_buffer_;
+        texture_buffer_ = nullptr;
+    }
+
+    if (num_sequence_groups_ && anim_buffers_) {
+        for (int i = 1; i < num_sequence_groups_; ++i) {
+            if (anim_buffers_[i]) {
+                delete[] anim_buffers_[i];
+                anim_buffers_[i] = nullptr;
+            }
+        }
+
+        delete[] anim_buffers_;
+        anim_buffers_ = nullptr;
+    }
+
+    if (anim_headers_) {
+        delete[] anim_headers_;
+        anim_headers_ = nullptr;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::load_file() {
+
+    try {
+        header_ = (const Header_HL1 *)buffer_;
+        validate_header(header_, false);
+
+        // Create the root scene node.
+        scene_->mRootNode = new aiNode(AI_MDL_HL1_NODE_ROOT);
+
+        load_texture_file();
+
+        if (import_settings_.read_animations)
+            load_sequence_groups_files();
+
+        read_textures();
+        read_skins();
+
+        read_bones();
+        read_meshes();
+
+        if (import_settings_.read_animations) {
+            read_sequence_groups_info();
+            read_animations();
+            read_sequence_infos();
+            if (import_settings_.read_sequence_transitions)
+                read_sequence_transitions();
+        }
+
+        if (import_settings_.read_attachments)
+            read_attachments();
+
+        if (import_settings_.read_hitboxes)
+            read_hitboxes();
+
+        if (import_settings_.read_bone_controllers)
+            read_bone_controllers();
+
+        read_global_info();
+
+        // Append children to root node.
+        if (rootnode_children_.size()) {
+            scene_->mRootNode->addChildren(
+                    static_cast<unsigned int>(rootnode_children_.size()),
+                    rootnode_children_.data());
+        }
+
+        release_resources();
+
+    } catch (...) {
+        release_resources();
+        throw;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::validate_header(const Header_HL1 *header, bool is_texture_header) {
+    if (is_texture_header) {
+        // Every single Half-Life model is assumed to have at least one texture.
+        if (!header->numtextures)
+            throw DeadlyImportError(MDL_HALFLIFE_LOG_HEADER "There are no textures in the file");
+
+        if (header->numtextures > AI_MDL_HL1_MAX_TEXTURES)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_TEXTURES>(header->numtextures, "textures");
+
+        if (header->numskinfamilies > AI_MDL_HL1_MAX_SKIN_FAMILIES)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_SKIN_FAMILIES>(header->numskinfamilies, "skin families");
+
+    } else {
+        // Every single Half-Life model is assumed to have at least one bodypart.
+        if (!header->numbodyparts)
+            throw DeadlyImportError(MDL_HALFLIFE_LOG_HEADER "Model has no bodyparts");
+
+        // Every single Half-Life model is assumed to have at least one bone.
+        if (!header->numbones)
+            throw DeadlyImportError(MDL_HALFLIFE_LOG_HEADER "Model has no bones");
+
+        // Every single Half-Life model is assumed to have at least one sequence group,
+        // which is the "default" sequence group.
+        if (!header->numseqgroups)
+            throw DeadlyImportError(MDL_HALFLIFE_LOG_HEADER "Model has no sequence groups");
+
+        if (header->numbodyparts > AI_MDL_HL1_MAX_BODYPARTS)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_BODYPARTS>(header->numbodyparts, "bodyparts");
+
+        if (header->numbones > AI_MDL_HL1_MAX_BONES)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_BONES>(header->numbones, "bones");
+
+        if (header->numbonecontrollers > AI_MDL_HL1_MAX_BONE_CONTROLLERS)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_BONE_CONTROLLERS>(header->numbonecontrollers, "bone controllers");
+
+        if (header->numseq > AI_MDL_HL1_MAX_SEQUENCES)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_SEQUENCES>(header->numseq, "sequences");
+
+        if (header->numseqgroups > AI_MDL_HL1_MAX_SEQUENCE_GROUPS)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_SEQUENCE_GROUPS>(header->numseqgroups, "sequence groups");
+
+        if (header->numattachments > AI_MDL_HL1_MAX_ATTACHMENTS)
+            log_warning_limit_exceeded<AI_MDL_HL1_MAX_ATTACHMENTS>(header->numattachments, "attachments");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+/*
+    Load textures.
+
+    There are two ways for textures to be stored in a Half-Life model:
+
+    1. Directly in the MDL file (filePath) or
+    2. In an external MDL file.
+
+    Due to the way StudioMDL works (tool used to compile SMDs into MDLs),
+    it is assumed that an external texture file follows the naming
+    convention: <YourModelName>T.mdl. Note the extra (T) at the end of the
+    model name.
+
+    .e.g For a given model named MyModel.mdl
+
+    The external texture file name would be MyModelT.mdl
+*/
+void HL1MDLLoader::load_texture_file() {
+    if (header_->numtextures == 0) {
+        // Load an external MDL texture file.
+        std::string texture_file_path =
+                DefaultIOSystem::absolutePath(file_path_) + io_->getOsSeparator() +
+                DefaultIOSystem::completeBaseName(file_path_) + "T." +
+                BaseImporter::GetExtension(file_path_);
+
+        load_file_into_buffer<Header_HL1>(texture_file_path, texture_buffer_);
+    } else {
+        /* Model has no external texture file. This means the texture
+        is stored inside the main MDL file. */
+        texture_buffer_ = const_cast<unsigned char *>(buffer_);
+    }
+
+    texture_header_ = (const Header_HL1 *)texture_buffer_;
+
+    // Validate texture header.
+    validate_header(texture_header_, true);
+}
+
+// ------------------------------------------------------------------------------------------------
+/*
+    Load sequence group files if any.
+
+    Due to the way StudioMDL works (tool used to compile SMDs into MDLs),
+    it is assumed that a sequence group file follows the naming
+    convention: <YourModelName>0X.mdl. Note the extra (0X) at the end of
+    the model name, where (X) is the sequence group.
+
+    .e.g For a given model named MyModel.mdl
+
+    Sequence group 1 => MyModel01.mdl
+    Sequence group 2 => MyModel02.mdl
+    Sequence group X => MyModel0X.mdl
+
+*/
+void HL1MDLLoader::load_sequence_groups_files() {
+    if (header_->numseqgroups <= 1)
+        return;
+
+    num_sequence_groups_ = header_->numseqgroups;
+
+    anim_buffers_ = new unsigned char *[num_sequence_groups_];
+    anim_headers_ = new SequenceHeader_HL1 *[num_sequence_groups_];
+    for (int i = 0; i < num_sequence_groups_; ++i) {
+        anim_buffers_[i] = NULL;
+        anim_headers_[i] = NULL;
+    }
+
+    std::string file_path_without_extension =
+            DefaultIOSystem::absolutePath(file_path_) +
+            io_->getOsSeparator() +
+            DefaultIOSystem::completeBaseName(file_path_);
+
+    for (int i = 1; i < num_sequence_groups_; ++i) {
+        std::stringstream ss;
+        ss << file_path_without_extension;
+        ss << std::setw(2) << std::setfill('0') << i;
+        ss << '.' << BaseImporter::GetExtension(file_path_);
+
+        std::string sequence_file_path = ss.str();
+
+        load_file_into_buffer<SequenceHeader_HL1>(sequence_file_path, anim_buffers_[i]);
+
+        anim_headers_[i] = (SequenceHeader_HL1 *)anim_buffers_[i];
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+/** @brief Read an MDL texture.
+*
+*   @note This method is taken from HL1 source code.
+*   source: file: studio_utils.c
+*           function(s): UploadTexture
+*/
+void HL1MDLLoader::read_texture(const Texture_HL1 *ptexture,
+        uint8_t *data, uint8_t *pal, aiTexture *pResult,
+        aiColor3D &last_palette_color) {
+    int outwidth, outheight;
+    int i, j;
+    int row1[256], row2[256], col1[256], col2[256];
+    unsigned char *pix1, *pix2, *pix3, *pix4;
+
+    // convert texture to power of 2
+    for (outwidth = 1; outwidth < ptexture->width; outwidth <<= 1)
+        ;
+
+    if (outwidth > 256)
+        outwidth = 256;
+
+    for (outheight = 1; outheight < ptexture->height; outheight <<= 1)
+        ;
+
+    if (outheight > 256)
+        outheight = 256;
+
+    pResult->mFilename = ptexture->name;
+    pResult->mWidth = outwidth;
+    pResult->mHeight = outheight;
+    pResult->achFormatHint[0] = 'b';
+    pResult->achFormatHint[1] = 'g';
+    pResult->achFormatHint[2] = 'r';
+    pResult->achFormatHint[3] = 'a';
+    pResult->achFormatHint[4] = '8';
+    pResult->achFormatHint[5] = '8';
+    pResult->achFormatHint[6] = '8';
+    pResult->achFormatHint[7] = '8';
+    pResult->achFormatHint[8] = '\0';
+
+    aiTexel *out = pResult->pcData = new aiTexel[outwidth * outheight];
+
+    for (i = 0; i < outwidth; i++) {
+        col1[i] = (int)((i + 0.25) * (ptexture->width / (float)outwidth));
+        col2[i] = (int)((i + 0.75) * (ptexture->width / (float)outwidth));
+    }
+
+    for (i = 0; i < outheight; i++) {
+        row1[i] = (int)((i + 0.25) * (ptexture->height / (float)outheight)) * ptexture->width;
+        row2[i] = (int)((i + 0.75) * (ptexture->height / (float)outheight)) * ptexture->width;
+    }
+
+    // scale down and convert to 32bit RGB
+    for (i = 0; i < outheight; i++) {
+        for (j = 0; j < outwidth; j++, out++) {
+            pix1 = &pal[data[row1[i] + col1[j]] * 3];
+            pix2 = &pal[data[row1[i] + col2[j]] * 3];
+            pix3 = &pal[data[row2[i] + col1[j]] * 3];
+            pix4 = &pal[data[row2[i] + col2[j]] * 3];
+
+            out->r = (pix1[0] + pix2[0] + pix3[0] + pix4[0]) >> 2;
+            out->g = (pix1[1] + pix2[1] + pix3[1] + pix4[1]) >> 2;
+            out->b = (pix1[2] + pix2[2] + pix3[2] + pix4[2]) >> 2;
+            out->a = 0xFF;
+        }
+    }
+
+    // Get the last palette color.
+    last_palette_color.r = pal[255 * 3];
+    last_palette_color.g = pal[255 * 3 + 1];
+    last_palette_color.b = pal[255 * 3 + 2];
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_textures() {
+    const Texture_HL1 *ptexture = (const Texture_HL1 *)((uint8_t *)texture_header_ + texture_header_->textureindex);
+    unsigned char *pin = texture_buffer_;
+
+    scene_->mNumTextures = scene_->mNumMaterials = texture_header_->numtextures;
+    scene_->mTextures = new aiTexture *[scene_->mNumTextures];
+    scene_->mMaterials = new aiMaterial *[scene_->mNumMaterials];
+
+    for (int i = 0; i < texture_header_->numtextures; ++i) {
+        scene_->mTextures[i] = new aiTexture();
+
+        aiColor3D last_palette_color;
+        read_texture(&ptexture[i],
+                pin + ptexture[i].index,
+                pin + ptexture[i].width * ptexture[i].height + ptexture[i].index,
+                scene_->mTextures[i],
+                last_palette_color);
+
+        aiMaterial *scene_material = scene_->mMaterials[i] = new aiMaterial();
+
+        const aiTextureType texture_type = aiTextureType_DIFFUSE;
+        aiString texture_name(ptexture[i].name);
+        scene_material->AddProperty(&texture_name, AI_MATKEY_TEXTURE(texture_type, 0));
+
+        // Is this a chrome texture?
+        int chrome = ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_CHROME ? 1 : 0;
+        scene_material->AddProperty(&chrome, 1, AI_MDL_HL1_MATKEY_CHROME(texture_type, 0));
+
+        if (ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_FLATSHADE) {
+            // Flat shading.
+            const aiShadingMode shading_mode = aiShadingMode_Flat;
+            scene_material->AddProperty(&shading_mode, 1, AI_MATKEY_SHADING_MODEL);
+        }
+
+        if (ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_ADDITIVE) {
+            // Additive texture.
+            const aiBlendMode blend_mode = aiBlendMode_Additive;
+            scene_material->AddProperty(&blend_mode, 1, AI_MATKEY_BLEND_FUNC);
+        } else if (ptexture[i].flags & AI_MDL_HL1_STUDIO_NF_MASKED) {
+            // Texture with 1 bit alpha test.
+            const aiTextureFlags use_alpha = aiTextureFlags_UseAlpha;
+            scene_material->AddProperty(&use_alpha, 1, AI_MATKEY_TEXFLAGS(texture_type, 0));
+            scene_material->AddProperty(&last_palette_color, 1, AI_MATKEY_COLOR_TRANSPARENT);
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_skins() {
+    // Read skins, if any.
+    if (texture_header_->numskinfamilies <= 1)
+        return;
+
+    // Pointer to base texture index.
+    short *default_skin_ptr = (short *)((uint8_t *)texture_header_ + texture_header_->skinindex);
+
+    // Start at first replacement skin.
+    short *replacement_skin_ptr = default_skin_ptr + texture_header_->numskinref;
+
+    for (int i = 1; i < texture_header_->numskinfamilies; ++i, replacement_skin_ptr += texture_header_->numskinref) {
+        for (int j = 0; j < texture_header_->numskinref; ++j) {
+            if (default_skin_ptr[j] != replacement_skin_ptr[j]) {
+                // Save replacement textures.
+                aiString skinMaterialId(scene_->mTextures[replacement_skin_ptr[j]]->mFilename);
+                scene_->mMaterials[default_skin_ptr[j]]->AddProperty(&skinMaterialId, AI_MATKEY_TEXTURE_DIFFUSE(i));
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_bones() {
+    const Bone_HL1 *pbone = (const Bone_HL1 *)((uint8_t *)header_ + header_->boneindex);
+
+    std::vector<std::string> unique_bones_names(header_->numbones);
+    for (int i = 0; i < header_->numbones; ++i)
+        unique_bones_names[i] = pbone[i].name;
+
+    // Ensure bones have unique names.
+    unique_name_generator_.set_template_name("Bone");
+    unique_name_generator_.make_unique(unique_bones_names);
+
+    temp_bones_.resize(header_->numbones);
+
+    aiNode *bones_node = new aiNode(AI_MDL_HL1_NODE_BONES);
+    rootnode_children_.push_back(bones_node);
+    bones_node->mNumChildren = static_cast<unsigned int>(header_->numbones);
+    bones_node->mChildren = new aiNode *[bones_node->mNumChildren];
+
+    // Create bone matrices in local space.
+    for (int i = 0; i < header_->numbones; ++i) {
+        aiNode *bone_node = temp_bones_[i].node = bones_node->mChildren[i] = new aiNode(unique_bones_names[i]);
+
+        aiVector3D angles(pbone[i].value[3], pbone[i].value[4], pbone[i].value[5]);
+        temp_bones_[i].absolute_transform = bone_node->mTransformation =
+                aiMatrix4x4(aiVector3D(1), aiQuaternion(angles.y, angles.z, angles.x),
+                        aiVector3D(pbone[i].value[0], pbone[i].value[1], pbone[i].value[2]));
+
+        if (pbone[i].parent == -1) {
+            bone_node->mParent = scene_->mRootNode;
+        } else {
+            bone_node->mParent = bones_node->mChildren[pbone[i].parent];
+
+            temp_bones_[i].absolute_transform =
+                    temp_bones_[pbone[i].parent].absolute_transform * bone_node->mTransformation;
+        }
+
+        temp_bones_[i].offset_matrix = temp_bones_[i].absolute_transform;
+        temp_bones_[i].offset_matrix.Inverse();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+/*
+    Read meshes.
+
+    Half-Life MDLs are structured such that each MDL
+    contains one or more 'bodypart(s)', which contain one
+    or more 'model(s)', which contains one or more mesh(es).
+
+    * Bodyparts are used to group models that may be replaced
+    in the game .e.g a character could have a 'heads' group,
+    'torso' group, 'shoes' group, with each group containing
+    different 'model(s)'.
+
+    * Models, also called 'sub models', contain vertices as
+    well as a reference to each mesh used by the sub model.
+
+    * Meshes contain a list of tris, also known as 'triverts'.
+    Each tris contains the following information:
+
+        1. The index of the position to use for the vertex.
+        2. The index of the normal to use for the vertex.
+        3. The S coordinate to use for the vertex UV.
+        4. The T coordinate ^
+
+    These tris represent the way to represent the triangles
+    for each mesh. Depending on how the tool compiled the MDL,
+    those triangles were saved as strips and or fans.
+
+    NOTE: Each tris is NOT unique. This means that you
+    might encounter the same vertex index but with a different
+    normal index, S coordinate, T coordinate.
+
+    In addition, each mesh contains the texture's index.
+
+    ------------------------------------------------------
+    With the details above, there are several things to
+    take into consideration.
+
+    * The Half-Life models store the vertices by sub model
+    rather than by mesh. Due to Assimp's structure, it
+    is necessary to remap each model vertex to be used
+    per mesh. Unfortunately, this has the consequence
+    to duplicate vertices.
+
+    * Because the mesh triangles are comprised of strips and
+    fans, it is necessary to convert each primitive to
+    triangles, respectively (3 indices per face).
+*/
+void HL1MDLLoader::read_meshes() {
+
+    int total_verts = 0;
+    int total_triangles = 0;
+    total_models_ = 0;
+
+    const Bodypart_HL1 *pbodypart = (const Bodypart_HL1 *)((uint8_t *)header_ + header_->bodypartindex);
+    const Model_HL1 *pmodel = nullptr;
+    const Mesh_HL1 *pmesh = nullptr;
+
+    const Texture_HL1 *ptexture = (const Texture_HL1 *)((uint8_t *)texture_header_ + texture_header_->textureindex);
+    short *pskinref = (short *)((uint8_t *)texture_header_ + texture_header_->skinindex);
+
+    scene_->mNumMeshes = 0;
+
+    std::vector<std::string> unique_bodyparts_names;
+    unique_bodyparts_names.resize(header_->numbodyparts);
+
+    // Count the number of meshes.
+
+    for (int i = 0; i < header_->numbodyparts; ++i, ++pbodypart) {
+        unique_bodyparts_names[i] = pbodypart->name;
+
+        pmodel = (Model_HL1 *)((uint8_t *)header_ + pbodypart->modelindex);
+        for (int j = 0; j < pbodypart->nummodels; ++j, ++pmodel) {
+            scene_->mNumMeshes += pmodel->nummesh;
+            total_verts += pmodel->numverts;
+        }
+
+        total_models_ += pbodypart->nummodels;
+    }
+
+    // Display limit infos.
+    if (total_verts > AI_MDL_HL1_MAX_VERTICES)
+        log_warning_limit_exceeded<AI_MDL_HL1_MAX_VERTICES>(total_verts, "vertices");
+
+    if (scene_->mNumMeshes > AI_MDL_HL1_MAX_MESHES)
+        log_warning_limit_exceeded<AI_MDL_HL1_MAX_MESHES>(scene_->mNumMeshes, "meshes");
+
+    if (total_models_ > AI_MDL_HL1_MAX_MODELS)
+        log_warning_limit_exceeded<AI_MDL_HL1_MAX_MODELS>(total_models_, "models");
+
+    // Ensure bodyparts have unique names.
+    unique_name_generator_.set_template_name("Bodypart");
+    unique_name_generator_.make_unique(unique_bodyparts_names);
+
+    // Now do the same for each model.
+    pbodypart = (const Bodypart_HL1 *)((uint8_t *)header_ + header_->bodypartindex);
+
+    // Prepare template name for bodypart models.
+    std::vector<std::string> unique_models_names;
+    unique_models_names.resize(total_models_);
+
+    unsigned int model_index = 0;
+
+    for (int i = 0; i < header_->numbodyparts; ++i, ++pbodypart) {
+        pmodel = (Model_HL1 *)((uint8_t *)header_ + pbodypart->modelindex);
+        for (int j = 0; j < pbodypart->nummodels; ++j, ++pmodel, ++model_index)
+            unique_models_names[model_index] = pmodel->name;
+    }
+
+    unique_name_generator_.set_template_name("Model");
+    unique_name_generator_.make_unique(unique_models_names);
+
+    unsigned int mesh_index = 0;
+
+    scene_->mMeshes = new aiMesh *[scene_->mNumMeshes];
+
+    pbodypart = (const Bodypart_HL1 *)((uint8_t *)header_ + header_->bodypartindex);
+
+    /* Create a node that will represent the mesh hierarchy.
+
+        <MDL_bodyparts>
+            |
+            +-- bodypart --+-- model -- [mesh index, mesh index, ...]
+            |              |
+            |              +-- model -- [mesh index, mesh index, ...]
+            |              |           
+            |              ...
+            |
+            |-- bodypart -- ...
+            |
+            ...
+     */
+    aiNode *bodyparts_node = new aiNode(AI_MDL_HL1_NODE_BODYPARTS);
+    rootnode_children_.push_back(bodyparts_node);
+    bodyparts_node->mNumChildren = static_cast<unsigned int>(header_->numbodyparts);
+    bodyparts_node->mChildren = new aiNode *[bodyparts_node->mNumChildren];
+    aiNode **bodyparts_node_ptr = bodyparts_node->mChildren;
+
+    // The following variables are defined here so they don't have
+    // to be recreated every iteration.
+
+    // Model_HL1 vertices, in bind pose space.
+    std::vector<aiVector3D> bind_pose_vertices;
+
+    // Model_HL1 normals, in bind pose space.
+    std::vector<aiVector3D> bind_pose_normals;
+
+    // Used to contain temporary information for building a mesh.
+    std::vector<HL1MeshTrivert> triverts;
+
+    std::vector<short> tricmds;
+
+    // Which triverts to use for the mesh.
+    std::vector<short> mesh_triverts_indices;
+
+    std::vector<HL1MeshFace> mesh_faces;
+
+    /* triverts that have the same vertindex, but have different normindex,s,t values.
+       Similar triverts are mapped from vertindex to a list of similar triverts. */
+    std::map<short, std::set<short>> triverts_similars;
+
+    // triverts per bone.
+    std::map<int, std::set<short>> bone_triverts;
+
+    /** This function adds a trivert index to the list of triverts per bone.
+     * \param[in] bone The bone that affects the trivert at index \p trivert_index.
+     * \param[in] trivert_index The trivert index.
+     */
+    auto AddTrivertToBone = [&](int bone, short trivert_index) {
+        if (bone_triverts.count(bone) == 0)
+            bone_triverts.insert({ bone, std::set<short>{ trivert_index }});
+        else
+            bone_triverts[bone].insert(trivert_index);
+    };
+
+    /** This function creates and appends a new trivert to the list of triverts.
+     * \param[in] trivert The trivert to use as a prototype.
+     * \param[in] bone The bone that affects \p trivert.
+     */
+    auto AddSimilarTrivert = [&](const Trivert &trivert, const int bone) {
+        HL1MeshTrivert new_trivert(trivert);
+        new_trivert.localindex = static_cast<short>(mesh_triverts_indices.size());
+
+        short new_trivert_index = static_cast<short>(triverts.size());
+
+        if (triverts_similars.count(trivert.vertindex) == 0)
+            triverts_similars.insert({ trivert.vertindex, std::set<short>{ new_trivert_index }});
+        else
+            triverts_similars[trivert.vertindex].insert(new_trivert_index);
+
+        triverts.push_back(new_trivert);
+
+        mesh_triverts_indices.push_back(new_trivert_index);
+        tricmds.push_back(new_trivert.localindex);
+        AddTrivertToBone(bone, new_trivert.localindex);
+    };
+
+    model_index = 0;
+
+    for (int i = 0; i < header_->numbodyparts; ++i, ++pbodypart, ++bodyparts_node_ptr) {
+        pmodel = (const Model_HL1 *)((uint8_t *)header_ + pbodypart->modelindex);
+
+        // Create bodypart node for the mesh tree hierarchy.
+        aiNode *bodypart_node = (*bodyparts_node_ptr) = new aiNode(unique_bodyparts_names[i]);
+        bodypart_node->mParent = bodyparts_node;
+        bodypart_node->mMetaData = aiMetadata::Alloc(1);
+        bodypart_node->mMetaData->Set(0, "Base", pbodypart->base);
+
+        bodypart_node->mNumChildren = static_cast<unsigned int>(pbodypart->nummodels);
+        bodypart_node->mChildren = new aiNode *[bodypart_node->mNumChildren];
+        aiNode **bodypart_models_ptr = bodypart_node->mChildren;
+
+        for (int j = 0; j < pbodypart->nummodels;
+                ++j, ++pmodel, ++bodypart_models_ptr, ++model_index) {
+
+            pmesh = (const Mesh_HL1 *)((uint8_t *)header_ + pmodel->meshindex);
+
+            uint8_t *pvertbone = ((uint8_t *)header_ + pmodel->vertinfoindex);
+            uint8_t *pnormbone = ((uint8_t *)header_ + pmodel->norminfoindex);
+            vec3_t *pstudioverts = (vec3_t *)((uint8_t *)header_ + pmodel->vertindex);
+            vec3_t *pstudionorms = (vec3_t *)((uint8_t *)header_ + pmodel->normindex);
+
+            // Each vertex and normal is in local space, so transform
+            // each of them to bring them in bind pose.
+            bind_pose_vertices.resize(pmodel->numverts);
+            bind_pose_normals.resize(pmodel->numnorms);
+            for (size_t k = 0; k < bind_pose_vertices.size(); ++k) {
+                const vec3_t &vert = pstudioverts[k];
+                bind_pose_vertices[k] = temp_bones_[pvertbone[k]].absolute_transform * aiVector3D(vert[0], vert[1], vert[2]);
+            }
+            for (size_t k = 0; k < bind_pose_normals.size(); ++k) {
+                const vec3_t &norm = pstudionorms[k];
+                // Compute the normal matrix to transform the normal into bind pose,
+                // without affecting its length.
+                const aiMatrix4x4 normal_matrix = aiMatrix4x4(temp_bones_[pnormbone[k]].absolute_transform).Inverse().Transpose();
+                bind_pose_normals[k] = normal_matrix * aiVector3D(norm[0], norm[1], norm[2]);
+            }
+
+            // Create model node for the mesh tree hierarchy.
+            aiNode *model_node = (*bodypart_models_ptr) = new aiNode(unique_models_names[model_index]);
+            model_node->mParent = bodypart_node;
+            model_node->mNumMeshes = static_cast<unsigned int>(pmodel->nummesh);
+            model_node->mMeshes = new unsigned int[model_node->mNumMeshes];
+            unsigned int *model_meshes_ptr = model_node->mMeshes;
+
+            for (int k = 0; k < pmodel->nummesh; ++k, ++pmesh, ++mesh_index, ++model_meshes_ptr) {
+                *model_meshes_ptr = mesh_index;
+
+                // Read triverts.
+                short *ptricmds = (short *)((uint8_t *)header_ + pmesh->triindex);
+                float texcoords_s_scale = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width;
+                float texcoords_t_scale = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height;
+
+                // Reset the data for the upcoming mesh.
+                triverts.clear();
+                triverts.resize(pmodel->numverts);
+                mesh_triverts_indices.clear();
+                mesh_faces.clear();
+                triverts_similars.clear();
+                bone_triverts.clear();
+
+                int l;
+                while ((l = *(ptricmds++))) {
+                    bool is_triangle_fan = false;
+
+                    if (l < 0) {
+                        l = -l;
+                        is_triangle_fan = true;
+                    }
+
+                    // Clear the list of tris for the upcoming tris.
+                    tricmds.clear();
+
+                    for (; l > 0; l--, ptricmds += 4) {
+                        const Trivert *input_trivert = reinterpret_cast<const Trivert *>(ptricmds);
+                        const int bone = pvertbone[input_trivert->vertindex];
+
+                        HL1MeshTrivert *private_trivert = &triverts[input_trivert->vertindex];
+                        if (private_trivert->localindex == -1) {
+                            // First time referenced.
+                            *private_trivert = *input_trivert;
+                            private_trivert->localindex = static_cast<short>(mesh_triverts_indices.size());
+                            mesh_triverts_indices.push_back(input_trivert->vertindex);
+                            tricmds.push_back(private_trivert->localindex);
+                            AddTrivertToBone(bone, private_trivert->localindex);
+                        } else if (*private_trivert == *input_trivert) {
+                            // Exists and is the same.
+                            tricmds.push_back(private_trivert->localindex);
+                        } else {
+                            // No similar trivert associated to the trivert currently processed.
+                            if (triverts_similars.count(input_trivert->vertindex) == 0)
+                                AddSimilarTrivert(*input_trivert, bone);
+                            else {
+                                // Search in the list of similar triverts to see if the
+                                // trivert in process is already registered.
+                                short similar_index = -1;
+                                for (auto it = triverts_similars[input_trivert->vertindex].cbegin();
+                                        similar_index == -1 && it != triverts_similars[input_trivert->vertindex].cend();
+                                        ++it) {
+                                    if (triverts[*it] == *input_trivert)
+                                        similar_index = *it;
+                                }
+
+                                // If a similar trivert has been found, reuse it.
+                                // Otherwise, add it.
+                                if (similar_index == -1)
+                                    AddSimilarTrivert(*input_trivert, bone);
+                                else
+                                    tricmds.push_back(triverts[similar_index].localindex);
+                            }
+                        }
+                    }
+
+                    // Build mesh faces.
+                    const int num_faces = static_cast<int>(tricmds.size() - 2);
+                    mesh_faces.reserve(num_faces);
+
+                    if (is_triangle_fan) {
+                        for (int i = 0; i < num_faces; ++i) {
+                            mesh_faces.push_back(HL1MeshFace{
+                                    tricmds[0],
+                                    tricmds[i + 1],
+                                    tricmds[i + 2] });
+                        }
+                    } else {
+                        for (int i = 0; i < num_faces; ++i) {
+                            if (i & 1) {
+                                // Preserve winding order.
+                                mesh_faces.push_back(HL1MeshFace{
+                                        tricmds[i + 1],
+                                        tricmds[i],
+                                        tricmds[i + 2] });
+                            } else {
+                                mesh_faces.push_back(HL1MeshFace{
+                                        tricmds[i],
+                                        tricmds[i + 1],
+                                        tricmds[i + 2] });
+                            }
+                        }
+                    }
+
+                    total_triangles += num_faces;
+                }
+
+                // Create the scene mesh.
+                aiMesh *scene_mesh = scene_->mMeshes[mesh_index] = new aiMesh();
+                scene_mesh->mPrimitiveTypes = aiPrimitiveType::aiPrimitiveType_TRIANGLE;
+                scene_mesh->mMaterialIndex = pskinref[pmesh->skinref];
+
+                scene_mesh->mNumVertices = static_cast<unsigned int>(mesh_triverts_indices.size());
+
+                if (scene_mesh->mNumVertices) {
+                    scene_mesh->mVertices = new aiVector3D[scene_mesh->mNumVertices];
+                    scene_mesh->mNormals = new aiVector3D[scene_mesh->mNumVertices];
+
+                    scene_mesh->mNumUVComponents[0] = 2;
+                    scene_mesh->mTextureCoords[0] = new aiVector3D[scene_mesh->mNumVertices];
+
+                    // Add vertices.
+                    for (unsigned int v = 0; v < scene_mesh->mNumVertices; ++v) {
+                        const HL1MeshTrivert *pTrivert = &triverts[mesh_triverts_indices[v]];
+                        scene_mesh->mVertices[v] = bind_pose_vertices[pTrivert->vertindex];
+                        scene_mesh->mNormals[v] = bind_pose_normals[pTrivert->normindex];
+                        scene_mesh->mTextureCoords[0][v] = aiVector3D(
+                                pTrivert->s * texcoords_s_scale,
+                                pTrivert->t * texcoords_t_scale, 0);
+                    }
+
+                    // Add face and indices.
+                    scene_mesh->mNumFaces = static_cast<unsigned int>(mesh_faces.size());
+                    scene_mesh->mFaces = new aiFace[scene_mesh->mNumFaces];
+
+                    for (unsigned int f = 0; f < scene_mesh->mNumFaces; ++f) {
+                        aiFace *face = &scene_mesh->mFaces[f];
+                        face->mNumIndices = 3;
+                        face->mIndices = new unsigned int[3];
+                        face->mIndices[0] = mesh_faces[f].v0;
+                        face->mIndices[1] = mesh_faces[f].v1;
+                        face->mIndices[2] = mesh_faces[f].v2;
+                    }
+
+                    // Add mesh bones.
+                    scene_mesh->mNumBones = static_cast<unsigned int>(bone_triverts.size());
+                    scene_mesh->mBones = new aiBone *[scene_mesh->mNumBones];
+
+                    aiBone **scene_bone_ptr = scene_mesh->mBones;
+
+                    for (auto bone_it = bone_triverts.cbegin();
+                            bone_it != bone_triverts.cend();
+                            ++bone_it, ++scene_bone_ptr) {
+                        const int bone_index = bone_it->first;
+
+                        aiBone *scene_bone = (*scene_bone_ptr) = new aiBone();
+                        scene_bone->mName = temp_bones_[bone_index].node->mName;
+
+                        scene_bone->mOffsetMatrix = temp_bones_[bone_index].offset_matrix;
+
+                        auto vertex_ids = bone_triverts.at(bone_index);
+
+                        // Add vertex weight per bone.
+                        scene_bone->mNumWeights = static_cast<unsigned int>(vertex_ids.size());
+                        aiVertexWeight *vertex_weight_ptr = scene_bone->mWeights = new aiVertexWeight[scene_bone->mNumWeights];
+
+                        for (auto vertex_it = vertex_ids.begin();
+                                vertex_it != vertex_ids.end();
+                                ++vertex_it, ++vertex_weight_ptr) {
+                            vertex_weight_ptr->mVertexId = *vertex_it;
+                            vertex_weight_ptr->mWeight = 1.0f;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    if (total_triangles > AI_MDL_HL1_MAX_TRIANGLES)
+        log_warning_limit_exceeded<AI_MDL_HL1_MAX_TRIANGLES>(total_triangles, "triangles");
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_animations() {
+    if (!header_->numseq)
+        return;
+
+    const SequenceDesc_HL1 *pseqdesc = (const SequenceDesc_HL1 *)((uint8_t *)header_ + header_->seqindex);
+    const SequenceGroup_HL1 *pseqgroup = nullptr;
+    const AnimValueOffset_HL1 *panim = nullptr;
+    const AnimValue_HL1 *panimvalue = nullptr;
+
+    unique_sequence_names_.resize(header_->numseq);
+    for (int i = 0; i < header_->numseq; ++i)
+        unique_sequence_names_[i] = pseqdesc[i].label;
+
+    // Ensure sequences have unique names.
+    unique_name_generator_.set_template_name("Sequence");
+    unique_name_generator_.make_unique(unique_sequence_names_);
+
+    scene_->mNumAnimations = 0;
+
+    int highest_num_blend_animations = SequenceBlendMode_HL1::NoBlend;
+
+    // Count the total number of animations.
+    for (int i = 0; i < header_->numseq; ++i, ++pseqdesc) {
+        scene_->mNumAnimations += pseqdesc->numblends;
+        highest_num_blend_animations = std::max(pseqdesc->numblends, highest_num_blend_animations);
+    }
+
+    // Get the number of available blend controllers for global info.
+    get_num_blend_controllers(highest_num_blend_animations, num_blend_controllers_);
+
+    pseqdesc = (const SequenceDesc_HL1 *)((uint8_t *)header_ + header_->seqindex);
+
+    aiAnimation **scene_animations_ptr = scene_->mAnimations = new aiAnimation *[scene_->mNumAnimations];
+
+    for (int sequence = 0; sequence < header_->numseq; ++sequence, ++pseqdesc) {
+        pseqgroup = (const SequenceGroup_HL1 *)((uint8_t *)header_ + header_->seqgroupindex) + pseqdesc->seqgroup;
+
+        if (pseqdesc->seqgroup == 0)
+            panim = (const AnimValueOffset_HL1 *)((uint8_t *)header_ + pseqgroup->unused2 + pseqdesc->animindex);
+        else
+            panim = (const AnimValueOffset_HL1 *)((uint8_t *)anim_headers_[pseqdesc->seqgroup] + pseqdesc->animindex);
+
+        for (int blend = 0; blend < pseqdesc->numblends; ++blend, ++scene_animations_ptr) {
+
+            const Bone_HL1 *pbone = (const Bone_HL1 *)((uint8_t *)header_ + header_->boneindex);
+
+            aiAnimation *scene_animation = (*scene_animations_ptr) = new aiAnimation();
+
+            scene_animation->mName = unique_sequence_names_[sequence];
+            scene_animation->mTicksPerSecond = pseqdesc->fps;
+            scene_animation->mDuration = static_cast<double>(pseqdesc->fps) * pseqdesc->numframes;
+            scene_animation->mNumChannels = static_cast<unsigned int>(header_->numbones);
+            scene_animation->mChannels = new aiNodeAnim *[scene_animation->mNumChannels];
+
+            for (int bone = 0; bone < header_->numbones; bone++, ++pbone, ++panim) {
+                aiNodeAnim *node_anim = scene_animation->mChannels[bone] = new aiNodeAnim();
+                node_anim->mNodeName = temp_bones_[bone].node->mName;
+
+                node_anim->mNumPositionKeys = pseqdesc->numframes;
+                node_anim->mNumRotationKeys = node_anim->mNumPositionKeys;
+                node_anim->mNumScalingKeys = 0;
+
+                node_anim->mPositionKeys = new aiVectorKey[node_anim->mNumPositionKeys];
+                node_anim->mRotationKeys = new aiQuatKey[node_anim->mNumRotationKeys];
+
+                for (int frame = 0; frame < pseqdesc->numframes; ++frame) {
+                    aiVectorKey *position_key = &node_anim->mPositionKeys[frame];
+                    aiQuatKey *rotation_key = &node_anim->mRotationKeys[frame];
+
+                    aiVector3D angle1;
+                    for (int j = 0; j < 3; ++j) {
+                        if (panim->offset[j + 3] != 0) {
+                            // Read compressed rotation delta.
+                            panimvalue = (const AnimValue_HL1 *)((uint8_t *)panim + panim->offset[j + 3]);
+                            extract_anim_value(panimvalue, frame, pbone->scale[j + 3], angle1[j]);
+                        }
+
+                        // Add the default rotation value.
+                        angle1[j] += pbone->value[j + 3];
+
+                        if (panim->offset[j] != 0) {
+                            // Read compressed position delta.
+                            panimvalue = (const AnimValue_HL1 *)((uint8_t *)panim + panim->offset[j]);
+                            extract_anim_value(panimvalue, frame, pbone->scale[j], position_key->mValue[j]);
+                        }
+
+                        // Add the default position value.
+                        position_key->mValue[j] += pbone->value[j];
+                    }
+
+                    position_key->mTime = rotation_key->mTime = static_cast<double>(frame);
+                    /* The Half-Life engine uses X as forward, Y as left, Z as up. Therefore,
+                       pitch,yaw,roll is represented as (YZX). */
+                    rotation_key->mValue = aiQuaternion(angle1.y, angle1.z, angle1.x);
+                    rotation_key->mValue.Normalize();
+                }
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_sequence_groups_info() {
+
+    aiNode *sequence_groups_node = new aiNode(AI_MDL_HL1_NODE_SEQUENCE_GROUPS);
+    rootnode_children_.push_back(sequence_groups_node);
+
+    sequence_groups_node->mNumChildren = static_cast<unsigned int>(header_->numseqgroups);
+    sequence_groups_node->mChildren = new aiNode *[sequence_groups_node->mNumChildren];
+
+    const SequenceGroup_HL1 *pseqgroup = (const SequenceGroup_HL1 *)((uint8_t *)header_ + header_->seqgroupindex);
+
+    unique_sequence_groups_names_.resize(header_->numseqgroups);
+    for (int i = 0; i < header_->numseqgroups; ++i)
+        unique_sequence_groups_names_[i] = pseqgroup[i].label;
+
+    // Ensure sequence groups have unique names.
+    unique_name_generator_.set_template_name("SequenceGroup");
+    unique_name_generator_.make_unique(unique_sequence_groups_names_);
+
+    for (int i = 0; i < header_->numseqgroups; ++i, ++pseqgroup) {
+        aiNode *sequence_group_node = sequence_groups_node->mChildren[i] = new aiNode(unique_sequence_groups_names_[i]);
+        sequence_group_node->mParent = sequence_groups_node;
+
+        aiMetadata *md = sequence_group_node->mMetaData = aiMetadata::Alloc(1);
+        if (i == 0) {
+            /* StudioMDL does not write the file name for the default sequence group,
+               so we will write it. */
+            md->Set(0, "File", aiString(file_path_));
+        } else {
+            md->Set(0, "File", aiString(pseqgroup->name));
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_sequence_infos() {
+    if (!header_->numseq)
+        return;
+
+    const SequenceDesc_HL1 *pseqdesc = (const SequenceDesc_HL1 *)((uint8_t *)header_ + header_->seqindex);
+
+    aiNode *sequence_infos_node = new aiNode(AI_MDL_HL1_NODE_SEQUENCE_INFOS);
+    rootnode_children_.push_back(sequence_infos_node);
+
+    sequence_infos_node->mNumChildren = static_cast<unsigned int>(header_->numseq);
+    sequence_infos_node->mChildren = new aiNode *[sequence_infos_node->mNumChildren];
+
+    std::vector<aiNode *> sequence_info_node_children;
+
+    int animation_index = 0;
+    for (int i = 0; i < header_->numseq; ++i, ++pseqdesc) {
+        // Clear the list of children for the upcoming sequence info node.
+        sequence_info_node_children.clear();
+
+        aiNode *sequence_info_node = sequence_infos_node->mChildren[i] = new aiNode(unique_sequence_names_[i]);
+        sequence_info_node->mParent = sequence_infos_node;
+
+        // Setup sequence info node Metadata.
+        aiMetadata *md = sequence_info_node->mMetaData = aiMetadata::Alloc(16);
+        md->Set(0, "AnimationIndex", animation_index);
+        animation_index += pseqdesc->numblends;
+
+        // Reference the sequence group by name. This allows us to search a particular
+        // sequence group by name using aiNode(s).
+        md->Set(1, "SequenceGroup", aiString(unique_sequence_groups_names_[pseqdesc->seqgroup]));
+        md->Set(2, "FramesPerSecond", pseqdesc->fps);
+        md->Set(3, "NumFrames", pseqdesc->numframes);
+        md->Set(4, "NumBlends", pseqdesc->numblends);
+        md->Set(5, "Activity", pseqdesc->activity);
+        md->Set(6, "ActivityWeight", pseqdesc->actweight);
+        md->Set(7, "MotionFlags", pseqdesc->motiontype);
+        md->Set(8, "MotionBone", temp_bones_[pseqdesc->motionbone].node->mName);
+        md->Set(9, "LinearMovement", aiVector3D(pseqdesc->linearmovement[0], pseqdesc->linearmovement[1], pseqdesc->linearmovement[2]));
+        md->Set(10, "BBMin", aiVector3D(pseqdesc->bbmin[0], pseqdesc->bbmin[1], pseqdesc->bbmin[2]));
+        md->Set(11, "BBMax", aiVector3D(pseqdesc->bbmax[0], pseqdesc->bbmax[1], pseqdesc->bbmax[2]));
+        md->Set(12, "EntryNode", pseqdesc->entrynode);
+        md->Set(13, "ExitNode", pseqdesc->exitnode);
+        md->Set(14, "NodeFlags", pseqdesc->nodeflags);
+        md->Set(15, "Flags", pseqdesc->flags);
+
+        if (import_settings_.read_blend_controllers) {
+            int num_blend_controllers;
+            if (get_num_blend_controllers(pseqdesc->numblends, num_blend_controllers) && num_blend_controllers) {
+                // Read blend controllers info.
+                aiNode *blend_controllers_node = new aiNode(AI_MDL_HL1_NODE_BLEND_CONTROLLERS);
+                sequence_info_node_children.push_back(blend_controllers_node);
+                blend_controllers_node->mParent = sequence_info_node;
+                blend_controllers_node->mNumChildren = static_cast<unsigned int>(num_blend_controllers);
+                blend_controllers_node->mChildren = new aiNode *[blend_controllers_node->mNumChildren];
+
+                for (unsigned int j = 0; j < blend_controllers_node->mNumChildren; ++j) {
+                    aiNode *blend_controller_node = blend_controllers_node->mChildren[j] = new aiNode();
+                    blend_controller_node->mParent = blend_controllers_node;
+
+                    aiMetadata *md = blend_controller_node->mMetaData = aiMetadata::Alloc(3);
+                    md->Set(0, "Start", pseqdesc->blendstart[j]);
+                    md->Set(1, "End", pseqdesc->blendend[j]);
+                    md->Set(2, "MotionFlags", pseqdesc->blendtype[j]);
+                }
+            }
+        }
+
+        if (import_settings_.read_animation_events && pseqdesc->numevents) {
+            // Read animation events.
+
+            if (pseqdesc->numevents > AI_MDL_HL1_MAX_EVENTS) {
+                log_warning_limit_exceeded<AI_MDL_HL1_MAX_EVENTS>(
+                        "Sequence " + std::string(pseqdesc->label),
+                        pseqdesc->numevents, "animation events");
+            }
+
+            const AnimEvent_HL1 *pevent = (const AnimEvent_HL1 *)((uint8_t *)header_ + pseqdesc->eventindex);
+
+            aiNode *pEventsNode = new aiNode(AI_MDL_HL1_NODE_ANIMATION_EVENTS);
+            sequence_info_node_children.push_back(pEventsNode);
+            pEventsNode->mParent = sequence_info_node;
+            pEventsNode->mNumChildren = static_cast<unsigned int>(pseqdesc->numevents);
+            pEventsNode->mChildren = new aiNode *[pEventsNode->mNumChildren];
+
+            for (unsigned int j = 0; j < pEventsNode->mNumChildren; ++j, ++pevent) {
+                aiNode *pEvent = pEventsNode->mChildren[j] = new aiNode();
+                pEvent->mParent = pEventsNode;
+
+                aiMetadata *md = pEvent->mMetaData = aiMetadata::Alloc(3);
+                md->Set(0, "Frame", pevent->frame);
+                md->Set(1, "ScriptEvent", pevent->event);
+                md->Set(2, "Options", aiString(pevent->options));
+            }
+        }
+
+        if (sequence_info_node_children.size()) {
+            sequence_info_node->addChildren(
+                    static_cast<unsigned int>(sequence_info_node_children.size()),
+                    sequence_info_node_children.data());
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_sequence_transitions() {
+    if (!header_->numtransitions)
+        return;
+
+    // Read sequence transition graph.
+    aiNode *transition_graph_node = new aiNode(AI_MDL_HL1_NODE_SEQUENCE_TRANSITION_GRAPH);
+    rootnode_children_.push_back(transition_graph_node);
+
+    uint8_t *ptransitions = ((uint8_t *)header_ + header_->transitionindex);
+    aiMetadata *md = transition_graph_node->mMetaData = aiMetadata::Alloc(header_->numtransitions * header_->numtransitions);
+    for (unsigned int i = 0; i < md->mNumProperties; ++i)
+        md->Set(i, std::to_string(i), static_cast<int>(ptransitions[i]));
+}
+
+void HL1MDLLoader::read_attachments() {
+    if (!header_->numattachments)
+        return;
+
+    const Attachment_HL1 *pattach = (const Attachment_HL1 *)((uint8_t *)header_ + header_->attachmentindex);
+
+    aiNode *attachments_node = new aiNode(AI_MDL_HL1_NODE_ATTACHMENTS);
+    rootnode_children_.push_back(attachments_node);
+    attachments_node->mNumChildren = static_cast<unsigned int>(header_->numattachments);
+    attachments_node->mChildren = new aiNode *[attachments_node->mNumChildren];
+
+    for (int i = 0; i < header_->numattachments; ++i, ++pattach) {
+        aiNode *attachment_node = attachments_node->mChildren[i] = new aiNode();
+        attachment_node->mParent = attachments_node;
+        attachment_node->mMetaData = aiMetadata::Alloc(2);
+        attachment_node->mMetaData->Set(0, "Position", aiVector3D(pattach->org[0], pattach->org[1], pattach->org[2]));
+        // Reference the bone by name. This allows us to search a particular
+        // bone by name using aiNode(s).
+        attachment_node->mMetaData->Set(1, "Bone", temp_bones_[pattach->bone].node->mName);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_hitboxes() {
+    if (!header_->numhitboxes)
+        return;
+
+    const Hitbox_HL1 *phitbox = (const Hitbox_HL1 *)((uint8_t *)header_ + header_->hitboxindex);
+
+    aiNode *hitboxes_node = new aiNode(AI_MDL_HL1_NODE_HITBOXES);
+    rootnode_children_.push_back(hitboxes_node);
+    hitboxes_node->mNumChildren = static_cast<unsigned int>(header_->numhitboxes);
+    hitboxes_node->mChildren = new aiNode *[hitboxes_node->mNumChildren];
+
+    for (int i = 0; i < header_->numhitboxes; ++i, ++phitbox) {
+        aiNode *hitbox_node = hitboxes_node->mChildren[i] = new aiNode();
+        hitbox_node->mParent = hitboxes_node;
+
+        aiMetadata *md = hitbox_node->mMetaData = aiMetadata::Alloc(4);
+        // Reference the bone by name. This allows us to search a particular
+        // bone by name using aiNode(s).
+        md->Set(0, "Bone", temp_bones_[phitbox->bone].node->mName);
+        md->Set(1, "HitGroup", phitbox->group);
+        md->Set(2, "BBMin", aiVector3D(phitbox->bbmin[0], phitbox->bbmin[1], phitbox->bbmin[2]));
+        md->Set(3, "BBMax", aiVector3D(phitbox->bbmax[0], phitbox->bbmax[1], phitbox->bbmax[2]));
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_bone_controllers() {
+    if (!header_->numbonecontrollers)
+        return;
+
+    const BoneController_HL1 *pbonecontroller = (const BoneController_HL1 *)((uint8_t *)header_ + header_->bonecontrollerindex);
+
+    aiNode *bones_controller_node = new aiNode(AI_MDL_HL1_NODE_BONE_CONTROLLERS);
+    rootnode_children_.push_back(bones_controller_node);
+    bones_controller_node->mNumChildren = static_cast<unsigned int>(header_->numbonecontrollers);
+    bones_controller_node->mChildren = new aiNode *[bones_controller_node->mNumChildren];
+
+    for (int i = 0; i < header_->numbonecontrollers; ++i, ++pbonecontroller) {
+        aiNode *bone_controller_node = bones_controller_node->mChildren[i] = new aiNode();
+        bone_controller_node->mParent = bones_controller_node;
+
+        aiMetadata *md = bone_controller_node->mMetaData = aiMetadata::Alloc(5);
+        // Reference the bone by name. This allows us to search a particular
+        // bone by name using aiNode(s).
+        md->Set(0, "Bone", temp_bones_[pbonecontroller->bone].node->mName);
+        md->Set(1, "MotionFlags", pbonecontroller->type);
+        md->Set(2, "Start", pbonecontroller->start);
+        md->Set(3, "End", pbonecontroller->end);
+        md->Set(4, "Channel", pbonecontroller->index);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void HL1MDLLoader::read_global_info() {
+    aiNode *global_info_node = new aiNode(AI_MDL_HL1_NODE_GLOBAL_INFO);
+    rootnode_children_.push_back(global_info_node);
+
+    aiMetadata *md = global_info_node->mMetaData = aiMetadata::Alloc(import_settings_.read_misc_global_info ? 16 : 11);
+    md->Set(0, "Version", AI_MDL_HL1_VERSION);
+    md->Set(1, "NumBodyparts", header_->numbodyparts);
+    md->Set(2, "NumModels", total_models_);
+    md->Set(3, "NumBones", header_->numbones);
+    md->Set(4, "NumAttachments", import_settings_.read_attachments ? header_->numattachments : 0);
+    md->Set(5, "NumSkinFamilies", texture_header_->numskinfamilies);
+    md->Set(6, "NumHitboxes", import_settings_.read_hitboxes ? header_->numhitboxes : 0);
+    md->Set(7, "NumBoneControllers", import_settings_.read_bone_controllers ? header_->numbonecontrollers : 0);
+    md->Set(8, "NumSequences", import_settings_.read_animations ? header_->numseq : 0);
+    md->Set(9, "NumBlendControllers", import_settings_.read_blend_controllers ? num_blend_controllers_ : 0);
+    md->Set(10, "NumTransitionNodes", import_settings_.read_sequence_transitions ? header_->numtransitions : 0);
+
+    if (import_settings_.read_misc_global_info) {
+        md->Set(11, "EyePosition", aiVector3D(header_->eyeposition[0], header_->eyeposition[1], header_->eyeposition[2]));
+        md->Set(12, "HullMin", aiVector3D(header_->min[0], header_->min[1], header_->min[2]));
+        md->Set(13, "HullMax", aiVector3D(header_->max[0], header_->max[1], header_->max[2]));
+        md->Set(14, "CollisionMin", aiVector3D(header_->bbmin[0], header_->bbmin[1], header_->bbmin[2]));
+        md->Set(15, "CollisionMax", aiVector3D(header_->bbmax[0], header_->bbmax[1], header_->bbmax[2]));
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+/** @brief This method reads a compressed anim value.
+*
+*   @note The structure of this method is taken from HL2 source code.
+*   Although this is from HL2, it's implementation is almost identical
+*   to code found in HL1 SDK. See HL1 and HL2 SDKs for more info.
+*   
+*   source:
+*       HL1 source code.
+*           file: studio_render.cpp
+*           function(s): CalcBoneQuaternion and CalcBonePosition
+*
+*       HL2 source code.
+*           file: bone_setup.cpp
+*           function(s): ExtractAnimValue
+*/
+void HL1MDLLoader::extract_anim_value(
+        const AnimValue_HL1 *panimvalue,
+        int frame, float bone_scale, float &value) {
+    int k = frame;
+
+    // find span of values that includes the frame we want
+    while (panimvalue->num.total <= k) {
+        k -= panimvalue->num.total;
+        panimvalue += panimvalue->num.valid + 1;
+    }
+
+    // Bah, missing blend!
+    if (panimvalue->num.valid > k)
+        value = panimvalue[k + 1].value * bone_scale;
+    else
+        value = panimvalue[panimvalue->num.valid].value * bone_scale;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get the number of blend controllers.
+bool HL1MDLLoader::get_num_blend_controllers(const int num_blend_animations, int &num_blend_controllers) {
+
+    switch (num_blend_animations) {
+        case SequenceBlendMode_HL1::NoBlend:
+            num_blend_controllers = 0;
+            return true;
+        case SequenceBlendMode_HL1::TwoWayBlending:
+            num_blend_controllers = 1;
+            return true;
+        case SequenceBlendMode_HL1::FourWayBlending:
+            num_blend_controllers = 2;
+            return true;
+        default:
+            num_blend_controllers = 0;
+            ASSIMP_LOG_WARN(MDL_HALFLIFE_LOG_HEADER "Unsupported number of blend animations (" + std::to_string(num_blend_animations) + ")");
+            return false;
+    }
+}
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp

+ 241 - 0
code/MDL/HalfLife/HL1MDLLoader.h

@@ -0,0 +1,241 @@
+/*
+---------------------------------------------------------------------------
+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 HL1MDLLoader.h
+ *  @brief Declaration of the Half-Life 1 MDL loader.
+ */
+
+#ifndef AI_HL1MDLLOADER_INCLUDED
+#define AI_HL1MDLLOADER_INCLUDED
+
+#include "HL1FileData.h"
+#include "HL1ImportSettings.h"
+#include "UniqueNameGenerator.h"
+
+#include <memory>
+#include <string>
+
+#include <assimp/types.h>
+#include <assimp/scene.h>
+#include <assimp/texture.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/Exceptional.h>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+class HL1MDLLoader {
+public:
+    HL1MDLLoader() = delete;
+    HL1MDLLoader(const HL1MDLLoader &) = delete;
+
+    /** See variables descriptions at the end for more details. */
+    HL1MDLLoader(
+        aiScene *scene,
+        IOSystem *io,
+        const unsigned char *buffer,
+        const std::string &file_path,
+        const HL1ImportSettings &import_settings);
+
+    ~HL1MDLLoader();
+
+    void load_file();
+
+protected:
+    /** \brief Validate the header data structure of a Half-Life 1 MDL file.
+     * \param[in] header Input header to be validated.
+     * \param[in] is_texture_header Whether or not we are reading an MDL
+     *   texture file.
+     */
+    void validate_header(const Header_HL1 *header, bool is_texture_header);
+
+    void load_texture_file();
+    void load_sequence_groups_files();
+    void read_textures();
+    void read_skins();
+    void read_bones();
+    void read_meshes();
+    void read_animations();
+    void read_sequence_groups_info();
+    void read_sequence_infos();
+    void read_sequence_transitions();
+    void read_attachments();
+    void read_hitboxes();
+    void read_bone_controllers();
+    void read_global_info();
+
+private:
+    void release_resources();
+
+    /** \brief Load a file and copy it's content to a buffer.
+     * \param file_path The path to the file to be loaded.
+     * \param buffer A pointer to a buffer to receive the data.
+     */
+    template <typename MDLFileHeader>
+    void load_file_into_buffer(const std::string &file_path, unsigned char *&buffer);
+
+    /** \brief Read an MDL texture.
+     * \param[in] ptexture A pointer to an MDL texture.
+     * \param[in] data A pointer to the data from \p ptexture.
+     * \param[in] pal A pointer to the texture palette from \p ptexture.
+     * \param[in,out] pResult A pointer to the output resulting Assimp texture.
+     * \param[in,out] last_palette_color The last color from the image palette.
+     */
+    void read_texture(const Texture_HL1 *ptexture,
+            uint8_t *data, uint8_t *pal, aiTexture *pResult,
+            aiColor3D &last_palette_color);
+
+    /** \brief This method reads a compressed anim value.
+    * \param[in] panimvalue A pointer to the animation data.
+    * \param[in] frame The frame to look for.
+    * \param[in] bone_scale The current bone scale to apply to the compressed value.
+    * \param[in,out] value The decompressed anim value at \p frame.
+    */
+    void extract_anim_value(const AnimValue_HL1 *panimvalue,
+            int frame, float bone_scale, float &value);
+
+    /**
+     *  \brief Given the number of blend animations, determine the number of blend controllers.
+     *
+     * \param[in] num_blend_animations The number of blend animations.
+     * \param[out] num_blend_controllers The number of blend controllers.
+     * \return True if the number of blend controllers was determined. False otherwise.
+     */
+    static bool get_num_blend_controllers(const int num_blend_animations, int &num_blend_controllers);
+
+    /** Output scene to be filled */
+    aiScene *scene_;
+
+    /** Output I/O handler. Required for additional IO operations. */
+    IOSystem *io_;
+
+    /** Buffer from MDLLoader class. */
+    const unsigned char *buffer_;
+
+    /** The full file path to the MDL file we are trying to load.
+     * Used to locate other MDL files since MDL may store resources
+     * in external MDL files. */
+    const std::string &file_path_;
+
+    /** Configuration for HL1 MDL */
+    const HL1ImportSettings &import_settings_;
+
+    /** Main MDL header. */
+    const Header_HL1 *header_;
+
+    /** External MDL texture header. */
+    const Header_HL1 *texture_header_;
+
+    /** External MDL animation headers.
+     * One for each loaded animation file. */
+    SequenceHeader_HL1 **anim_headers_;
+
+    /** Texture file data. */
+    unsigned char *texture_buffer_;
+
+    /** Animation files data. */
+    unsigned char **anim_buffers_;
+
+    /** The number of sequence groups. */
+    int num_sequence_groups_;
+
+    /** The list of children to be appended to the scene's root node. */
+    std::vector<aiNode *> rootnode_children_;
+
+    /** A unique name generator. Used to generate names for MDL values
+     * that may have empty/duplicate names. */
+    UniqueNameGenerator unique_name_generator_;
+
+    /** The list of unique sequence names. */
+    std::vector<std::string> unique_sequence_names_;
+
+    /** The list of unique sequence groups names. */
+    std::vector<std::string> unique_sequence_groups_names_;
+
+    /** Structure to store temporary bone information. */
+    struct TempBone {
+
+        TempBone() :
+            node(nullptr),
+            absolute_transform(),
+            offset_matrix() {}
+
+        aiNode *node;
+        aiMatrix4x4 absolute_transform;
+        aiMatrix4x4 offset_matrix;
+    };
+
+    std::vector<TempBone> temp_bones_;
+
+    /** The number of available bone controllers in the model. */
+    int num_blend_controllers_;
+
+    /** Self explanatory. */
+    int total_models_;
+};
+
+// ------------------------------------------------------------------------------------------------
+template <typename MDLFileHeader>
+void HL1MDLLoader::load_file_into_buffer(const std::string &file_path, unsigned char *&buffer) {
+    if (!io_->Exists(file_path))
+        throw DeadlyImportError("Missing file " + DefaultIOSystem::fileName(file_path) + ".");
+
+    std::unique_ptr<IOStream> file(io_->Open(file_path));
+
+    if (file.get() == NULL)
+        throw DeadlyImportError("Failed to open MDL file " + DefaultIOSystem::fileName(file_path) + ".");
+
+    const size_t file_size = file->FileSize();
+    if (file_size < sizeof(MDLFileHeader))
+        throw DeadlyImportError("MDL file is too small.");
+
+    buffer = new unsigned char[1 + file_size];
+    file->Read((void *)buffer, 1, file_size);
+    buffer[file_size] = '\0';
+}
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
+
+#endif // AI_HL1MDLLOADER_INCLUDED

+ 127 - 0
code/MDL/HalfLife/HL1MeshTrivert.h

@@ -0,0 +1,127 @@
+/*
+---------------------------------------------------------------------------
+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 HL1MeshTrivert.h
+ *  @brief This file contains the class declaration for the
+ *         HL1 mesh trivert class.
+ */
+
+#ifndef AI_HL1MESHTRIVERT_INCLUDED
+#define AI_HL1MESHTRIVERT_INCLUDED
+
+#include "HL1FileData.h"
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+/* A class to help map model triverts to mesh triverts. */
+struct HL1MeshTrivert {
+    HL1MeshTrivert() :
+        vertindex(-1),
+        normindex(-1),
+        s(0),
+        t(0),
+        localindex(-1) {
+    }
+
+    HL1MeshTrivert(short vertindex, short normindex, short s, short t, short localindex) :
+        vertindex(vertindex),
+        normindex(normindex),
+        s(s),
+        t(t),
+        localindex() {
+    }
+
+    HL1MeshTrivert(const Trivert &a) :
+        vertindex(a.vertindex),
+        normindex(a.normindex),
+        s(a.s),
+        t(a.t),
+        localindex(-1) {
+    }
+
+    inline bool operator==(const Trivert &a) const {
+        return vertindex == a.vertindex &&
+               normindex == a.normindex &&
+               s == a.s &&
+               t == a.t;
+    }
+
+    inline bool operator!=(const Trivert &a) const {
+        return !(*this == a);
+    }
+
+    inline bool operator==(const HL1MeshTrivert &a) const {
+        return localindex == a.localindex &&
+               vertindex == a.vertindex &&
+               normindex == a.normindex &&
+               s == a.s &&
+               t == a.t;
+    }
+
+    inline bool operator!=(const HL1MeshTrivert &a) const {
+        return !(*this == a);
+    }
+
+    inline HL1MeshTrivert &operator=(const Trivert &other) {
+        vertindex = other.vertindex;
+        normindex = other.normindex;
+        s = other.s;
+        t = other.t;
+        return *this;
+    }
+
+    short vertindex;
+    short normindex;
+    short s, t;
+    short localindex;
+};
+
+struct HL1MeshFace {
+    short v0, v1, v2;
+};
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
+
+#endif // AI_HL1MESHTRIVERT_INCLUDED

+ 25 - 7
include/assimp/Macros.h → code/MDL/HalfLife/HalfLifeMDLBaseHeader.h

@@ -39,11 +39,29 @@ 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)
-#endif
+/** @file HalfLifeMDLBaseHeader.h */
 
 
+#ifndef AI_HALFLIFEMDLBASEHEADER_INCLUDED
+#define AI_HALFLIFEMDLBASEHEADER_INCLUDED
+
+#include <assimp/types.h>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+/** Used to interface different Valve MDL formats. */
+struct HalfLifeMDLBaseHeader
+{
+    //! Magic number: "IDST"/"IDSQ"
+    char ident[4];
+
+    //! The file format version.
+    int32_t version;
+};
+
+}
+}
+}
+
+#endif // AI_HALFLIFEMDLBASEHEADER_INCLUDED

+ 95 - 0
code/MDL/HalfLife/LogFunctions.h

@@ -0,0 +1,95 @@
+/*
+---------------------------------------------------------------------------
+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 LogFunctions.h */
+
+#ifndef AI_MDL_HALFLIFE_LOGFUNCTIONS_INCLUDED
+#define AI_MDL_HALFLIFE_LOGFUNCTIONS_INCLUDED
+
+#include <assimp/Logger.hpp>
+#include <string>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+/**
+ * \brief A function to log precise messages regarding limits exceeded.
+ *
+ * \param[in] subject Subject.
+ * \param[in] current_amount Current amount.
+ * \param[in] direct_object Direct object.
+ *            LIMIT Limit constant.
+ *
+ * Example: Model has 100 textures, which exceeds the limit (50)
+ *
+ *          where \p subject is 'Model'
+ *                \p current_amount is '100'
+ *                \p direct_object is 'textures'
+ *                LIMIT is '50'
+ */
+template <int LIMIT>
+static inline void log_warning_limit_exceeded(
+    const std::string &subject, int current_amount,
+    const std::string &direct_object) {
+
+    ASSIMP_LOG_WARN(MDL_HALFLIFE_LOG_HEADER
+        + subject
+        + " has "
+        + std::to_string(current_amount) + " "
+        + direct_object
+        + ", which exceeds the limit ("
+        + std::to_string(LIMIT)
+        + ")");
+}
+
+/** \brief Same as above, but uses 'Model' as the subject. */
+template <int LIMIT>
+static inline void log_warning_limit_exceeded(int current_amount,
+    const std::string &direct_object) {
+    log_warning_limit_exceeded<LIMIT>("Model", current_amount, direct_object);
+}
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
+
+#endif // AI_MDL_HALFLIFE_LOGFUNCTIONS_INCLUDED

+ 180 - 0
code/MDL/HalfLife/UniqueNameGenerator.cpp

@@ -0,0 +1,180 @@
+/*
+---------------------------------------------------------------------------
+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 UniqueNameGenerator.cpp
+ *  @brief Implementation for the unique name generator.
+ */
+
+#include "UniqueNameGenerator.h"
+#include <algorithm>
+#include <list>
+#include <map>
+#include <numeric>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+UniqueNameGenerator::UniqueNameGenerator() :
+    template_name_("unnamed"),
+    separator_("_") {
+}
+
+UniqueNameGenerator::UniqueNameGenerator(const char *template_name) :
+    template_name_(template_name),
+    separator_("_") {
+}
+
+UniqueNameGenerator::UniqueNameGenerator(const char *template_name, const char *separator) :
+    template_name_(template_name),
+    separator_(separator) {
+}
+
+UniqueNameGenerator::~UniqueNameGenerator() {
+}
+
+void UniqueNameGenerator::make_unique(std::vector<std::string> &names) {
+    struct DuplicateInfo {
+        DuplicateInfo() :
+            indices(),
+            next_id(0) {
+        }
+
+        std::list<size_t> indices;
+        size_t next_id;
+    };
+
+    std::vector<size_t> empty_names_indices;
+    std::vector<size_t> template_name_duplicates;
+    std::map<std::string, DuplicateInfo> names_to_duplicates;
+
+    const std::string template_name_with_separator(template_name_ + separator_);
+
+    auto format_name = [&](const std::string &base_name, size_t id) -> std::string {
+        return base_name + separator_ + std::to_string(id);
+    };
+
+    auto generate_unique_name = [&](const std::string &base_name) -> std::string {
+        auto *duplicate_info = &names_to_duplicates[base_name];
+
+        std::string new_name = "";
+
+        bool found_identical_name;
+        bool tried_with_base_name_only = false;
+        do {
+            // Assume that no identical name exists.
+            found_identical_name = false;
+
+            if (!tried_with_base_name_only) {
+                // First try with only the base name.
+                new_name = base_name;
+            } else {
+                // Create the name expected to be unique.
+                new_name = format_name(base_name, duplicate_info->next_id);
+            }
+
+            // Check in the list of duplicates for an identical name.
+            for (size_t i = 0;
+                    i < names.size() &&
+                    !found_identical_name;
+                    ++i) {
+                if (new_name == names[i])
+                    found_identical_name = true;
+            }
+
+            if (tried_with_base_name_only)
+                ++duplicate_info->next_id;
+
+            tried_with_base_name_only = true;
+
+        } while (found_identical_name);
+
+        return new_name;
+    };
+
+    for (size_t i = 0; i < names.size(); ++i) {
+        // Check for empty names.
+        if (names[i].find_first_not_of(' ') == std::string::npos) {
+            empty_names_indices.push_back(i);
+            continue;
+        }
+
+        /* Check for potential duplicate.
+        a) Either if this name is the same as the template name or
+        b) <template name><separator> is found at the beginning. */
+        if (names[i] == template_name_ ||
+                names[i].substr(0, template_name_with_separator.length()) == template_name_with_separator)
+            template_name_duplicates.push_back(i);
+
+        // Map each unique name to it's duplicate.
+        if (names_to_duplicates.count(names[i]) == 0)
+            names_to_duplicates.insert({ names[i], DuplicateInfo()});
+        else
+            names_to_duplicates[names[i]].indices.push_back(i);
+    }
+
+    // Make every non-empty name unique.
+    for (auto it = names_to_duplicates.begin();
+            it != names_to_duplicates.end(); ++it) {
+        for (auto it2 = it->second.indices.begin();
+                it2 != it->second.indices.end();
+                ++it2)
+            names[*it2] = generate_unique_name(it->first);
+    }
+
+    // Generate a unique name for every empty string.
+    if (template_name_duplicates.size()) {
+        // At least one string ressembles to <template name>.
+        for (auto it = empty_names_indices.begin();
+                it != empty_names_indices.end(); ++it)
+            names[*it] = generate_unique_name(template_name_);
+    } else {
+        // No string alike <template name> exists.
+        size_t i = 0;
+        for (auto it = empty_names_indices.begin();
+                it != empty_names_indices.end(); ++it, ++i)
+            names[*it] = format_name(template_name_, i);
+    }
+}
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp

+ 81 - 0
code/MDL/HalfLife/UniqueNameGenerator.h

@@ -0,0 +1,81 @@
+/*
+---------------------------------------------------------------------------
+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 UniqueNameGenerator.h
+ *  @brief Declaration of the unique name generator.
+ */
+
+#ifndef AI_UNIQUENAMEGENERATOR_INCLUDED
+#define AI_UNIQUENAMEGENERATOR_INCLUDED
+
+#include <string>
+#include <vector>
+
+namespace Assimp {
+namespace MDL {
+namespace HalfLife {
+
+class UniqueNameGenerator {
+public:
+    UniqueNameGenerator();
+    UniqueNameGenerator(const char *template_name);
+    UniqueNameGenerator(const char *template_name, const char *separator);
+    ~UniqueNameGenerator();
+
+    inline void set_template_name(const char *template_name) {
+        template_name_ = template_name;
+    }
+    inline void set_separator(const char *separator) {
+        separator_ = separator;
+    }
+
+    void make_unique(std::vector<std::string> &names);
+
+private:
+    std::string template_name_;
+    std::string separator_;
+};
+
+} // namespace HalfLife
+} // namespace MDL
+} // namespace Assimp
+
+#endif // AI_UNIQUENAMEGENERATOR_INCLUDED

+ 61 - 31
code/MDL/MDLLoader.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2019, assimp team
 Copyright (c) 2006-2019, assimp team
 
 
-
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -53,8 +51,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "MDL/MDLLoader.h"
 #include "MDL/MDLLoader.h"
 #include "MDL/MDLDefaultColorMap.h"
 #include "MDL/MDLDefaultColorMap.h"
 #include "MD2/MD2FileData.h"
 #include "MD2/MD2FileData.h"
+#include "MDL/HalfLife/HL1MDLLoader.h"
 
 
-#include <assimp/Macros.h>
 #include <assimp/qnan.h>
 #include <assimp/qnan.h>
 #include <assimp/StringUtils.h>
 #include <assimp/StringUtils.h>
 #include <assimp/Importer.hpp>
 #include <assimp/Importer.hpp>
@@ -94,23 +92,24 @@ static const aiImporterDesc desc = {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 // Constructor to be privately used by Importer
 MDLImporter::MDLImporter()
 MDLImporter::MDLImporter()
-    : configFrameID(),
-    mBuffer(),
-    iGSFileVersion(),
-    pIOHandler(),
-    pScene(),
-    iFileSize()
-{}
+: configFrameID()
+, mBuffer()
+, iGSFileVersion()
+, pIOHandler()
+, pScene()
+, iFileSize() {
+    // empty
+}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
 // Destructor, private as well
-MDLImporter::~MDLImporter()
-{}
+MDLImporter::~MDLImporter() {
+    // empty
+}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 // 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);
     const std::string extension = GetExtension(pFile);
 
 
     // if check for extension is not enough, check for the magic tokens
     // if check for extension is not enough, check for the magic tokens
@@ -144,6 +143,18 @@ void MDLImporter::SetupProperties(const Importer* pImp)
 
 
     // AI_CONFIG_IMPORT_MDL_COLORMAP - palette file
     // AI_CONFIG_IMPORT_MDL_COLORMAP - palette file
     configPalette =  pImp->GetPropertyString(AI_CONFIG_IMPORT_MDL_COLORMAP,"colormap.lmp");
     configPalette =  pImp->GetPropertyString(AI_CONFIG_IMPORT_MDL_COLORMAP,"colormap.lmp");
+
+    // Read configuration specific to MDL (Half-Life 1).
+    mHL1ImportSettings.read_animations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATIONS, true);
+    if (mHL1ImportSettings.read_animations) {
+        mHL1ImportSettings.read_animation_events = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ANIMATION_EVENTS, true);
+        mHL1ImportSettings.read_blend_controllers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_BLEND_CONTROLLERS, true);
+        mHL1ImportSettings.read_sequence_transitions = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_SEQUENCE_TRANSITIONS, true);
+    }
+    mHL1ImportSettings.read_attachments = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_ATTACHMENTS, true);
+    mHL1ImportSettings.read_bone_controllers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_BONE_CONTROLLERS, true);
+    mHL1ImportSettings.read_hitboxes = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_HITBOXES, true);
+    mHL1ImportSettings.read_misc_global_info = pImp->GetPropertyBool(AI_CONFIG_IMPORT_MDL_HL1_READ_MISC_GLOBAL_INFO, true);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -168,9 +179,9 @@ void MDLImporter::InternReadFile( const std::string& pFile,
     }
     }
 
 
     // This should work for all other types of MDL files, too ...
     // This should work for all other types of MDL files, too ...
-    // the quake header is one of the smallest, afaik
+    // the HL1 sequence group header is one of the smallest, afaik
     iFileSize = (unsigned int)file->FileSize();
     iFileSize = (unsigned int)file->FileSize();
-    if( iFileSize < sizeof(MDL::Header)) {
+    if( iFileSize < sizeof(MDL::HalfLife::SequenceHeader_HL1)) {
         throw DeadlyImportError( "MDL File is too small.");
         throw DeadlyImportError( "MDL File is too small.");
     }
     }
 
 
@@ -226,9 +237,19 @@ void MDLImporter::InternReadFile( const std::string& pFile,
     else if (AI_MDL_MAGIC_NUMBER_BE_HL2a == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2a == iMagicWord ||
     else if (AI_MDL_MAGIC_NUMBER_BE_HL2a == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2a == iMagicWord ||
         AI_MDL_MAGIC_NUMBER_BE_HL2b == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2b == iMagicWord)
         AI_MDL_MAGIC_NUMBER_BE_HL2b == iMagicWord || AI_MDL_MAGIC_NUMBER_LE_HL2b == iMagicWord)
     {
     {
-        ASSIMP_LOG_DEBUG("MDL subtype: Source(tm) Engine, magic word is IDST/IDSQ");
         iGSFileVersion = 0;
         iGSFileVersion = 0;
-        InternReadFile_HL2();
+
+        HalfLife::HalfLifeMDLBaseHeader *pHeader = (HalfLife::HalfLifeMDLBaseHeader *)mBuffer;
+        if (pHeader->version == AI_MDL_HL1_VERSION)
+        {
+            ASSIMP_LOG_DEBUG("MDL subtype: Half-Life 1/Goldsrc Engine, magic word is IDST/IDSQ");
+            InternReadFile_HL1(pFile, iMagicWord);
+        }
+        else
+        {
+            ASSIMP_LOG_DEBUG("MDL subtype: Source(tm) Engine, magic word is IDST/IDSQ");
+            InternReadFile_HL2();
+        }
     }
     }
     else    {
     else    {
         // print the magic word to the log file
         // print the magic word to the log file
@@ -404,23 +425,15 @@ void MDLImporter::InternReadFile_Quake1() {
 
 
     // now get a pointer to the first frame in the file
     // now get a pointer to the first frame in the file
     BE_NCONST MDL::Frame* pcFrames = (BE_NCONST MDL::Frame*)szCurrent;
     BE_NCONST MDL::Frame* pcFrames = (BE_NCONST MDL::Frame*)szCurrent;
-    BE_NCONST MDL::SimpleFrame* pcFirstFrame;
+    MDL::SimpleFrame* pcFirstFrame;
 
 
     if (0 == pcFrames->type) {
     if (0 == pcFrames->type) {
         // get address of single frame
         // get address of single frame
-        pcFirstFrame = &pcFrames->frame;
+        pcFirstFrame =( MDL::SimpleFrame*) &pcFrames->frame;
     } else {
     } else {
         // get the first frame in the group
         // 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));
     BE_NCONST MDL::Vertex* pcVertices = (BE_NCONST MDL::Vertex*) ((pcFirstFrame->name) + sizeof(pcFirstFrame->name));
     VALIDATE_FILE_SIZE((const unsigned char*)(pcVertices + pcHeader->num_verts));
     VALIDATE_FILE_SIZE((const unsigned char*)(pcVertices + pcHeader->num_verts));
@@ -1562,7 +1575,7 @@ void MDLImporter::InternReadFile_3DGS_MDL7( )
 				const size_t maxSize(buffersize - (i*AI_MDL7_MAX_GROUPNAMESIZE));
 				const size_t maxSize(buffersize - (i*AI_MDL7_MAX_GROUPNAMESIZE));
 				pcNode->mName.length = ai_snprintf(szBuffer, maxSize, "Group_%u", p);
 				pcNode->mName.length = ai_snprintf(szBuffer, maxSize, "Group_%u", p);
 			} else {
 			} else {
-				pcNode->mName.length = ::strlen(szBuffer);
+				pcNode->mName.length = (ai_uint32)::strlen(szBuffer);
 			}
 			}
             ::strncpy(pcNode->mName.data,szBuffer,MAXLEN-1);
             ::strncpy(pcNode->mName.data,szBuffer,MAXLEN-1);
             ++p;
             ++p;
@@ -1965,6 +1978,23 @@ void MDLImporter::JoinSkins_3DGS_MDL7(
     }
     }
 }
 }
 
 
+// ------------------------------------------------------------------------------------------------
+// Read a Half-life 1 MDL
+void MDLImporter::InternReadFile_HL1(const std::string& pFile, const uint32_t iMagicWord)
+{
+    // We can't correctly load an MDL from a MDL "sequence" file.
+    if (iMagicWord == AI_MDL_MAGIC_NUMBER_BE_HL2b || iMagicWord == AI_MDL_MAGIC_NUMBER_LE_HL2b)
+        throw DeadlyImportError("Impossible to properly load a model from an MDL sequence file.");
+
+    // Read the MDL file.
+    HalfLife::HL1MDLLoader loader(
+        pScene,
+        pIOHandler,
+        mBuffer,
+        pFile,
+        mHL1ImportSettings);
+}
+
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Read a half-life 2 MDL
 // Read a half-life 2 MDL
 void MDLImporter::InternReadFile_HL2( )
 void MDLImporter::InternReadFile_HL2( )

+ 11 - 17
code/MDL/MDLLoader.h

@@ -51,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/BaseImporter.h>
 #include <assimp/BaseImporter.h>
 #include "MDLFileData.h"
 #include "MDLFileData.h"
 #include "HMP/HalfLifeFileData.h"
 #include "HMP/HalfLifeFileData.h"
+#include "HalfLife/HL1ImportSettings.h"
 
 
 struct aiNode;
 struct aiNode;
 struct aiTexture;
 struct aiTexture;
@@ -77,6 +78,7 @@ using namespace MDL;
  *      <li>3D Game Studio MDL3, MDL4</li>
  *      <li>3D Game Studio MDL3, MDL4</li>
  *      <li>3D Game Studio MDL5</li>
  *      <li>3D Game Studio MDL5</li>
  *      <li>3D Game Studio MDL7</li>
  *      <li>3D Game Studio MDL7</li>
+ *      <li>Halflife 1</li>
  *      <li>Halflife 2</li>
  *      <li>Halflife 2</li>
  *   </ul>
  *   </ul>
  *  These formats are partially identical and it would be possible to load
  *  These formats are partially identical and it would be possible to load
@@ -89,16 +91,12 @@ public:
     MDLImporter();
     MDLImporter();
     ~MDLImporter();
     ~MDLImporter();
 
 
-
-public:
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Returns whether the class can handle the format of the given file.
     /** Returns whether the class can handle the format of the given file.
     * See BaseImporter::CanRead() for details.  */
     * See BaseImporter::CanRead() for details.  */
     bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
     bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
         bool checkSig) const;
         bool checkSig) const;
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Called prior to ReadFile().
     /** Called prior to ReadFile().
     * The function is a request to the importer to update its configuration
     * The function is a request to the importer to update its configuration
@@ -107,8 +105,6 @@ public:
     void SetupProperties(const Importer* pImp);
     void SetupProperties(const Importer* pImp);
 
 
 protected:
 protected:
-
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Return importer meta information.
     /** Return importer meta information.
      * See #BaseImporter::GetInfo for the details
      * See #BaseImporter::GetInfo for the details
@@ -122,8 +118,6 @@ protected:
     void InternReadFile( const std::string& pFile, aiScene* pScene,
     void InternReadFile( const std::string& pFile, aiScene* pScene,
         IOSystem* pIOHandler);
         IOSystem* pIOHandler);
 
 
-protected:
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Import a quake 1 MDL file (IDPO)
     /** Import a quake 1 MDL file (IDPO)
     */
     */
@@ -139,6 +133,11 @@ protected:
     */
     */
     void InternReadFile_3DGS_MDL7( );
     void InternReadFile_3DGS_MDL7( );
 
 
+    // -------------------------------------------------------------------
+    /** Import a Half-Life 1 MDL file
+    */
+    void InternReadFile_HL1(const std::string& pFile, const uint32_t iMagicWord);
+
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Import a CS:S/HL2 MDL file (not fully implemented)
     /** Import a CS:S/HL2 MDL file (not fully implemented)
     */
     */
@@ -154,7 +153,6 @@ protected:
     void SizeCheck(const void* szPos);
     void SizeCheck(const void* szPos);
     void SizeCheck(const void* szPos, const char* szFile, unsigned int iLine);
     void SizeCheck(const void* szPos, const char* szFile, unsigned int iLine);
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Validate the header data structure of a game studio MDL7 file
     /** Validate the header data structure of a game studio MDL7 file
      * \param pcHeader Input header to be validated
      * \param pcHeader Input header to be validated
@@ -167,7 +165,6 @@ protected:
      */
      */
     void ValidateHeader_Quake1(const MDL::Header* pcHeader);
     void ValidateHeader_Quake1(const MDL::Header* pcHeader);
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Try to load a  palette from the current directory (colormap.lmp)
     /** Try to load a  palette from the current directory (colormap.lmp)
      *  If it is not found the default palette of Quake1 is returned
      *  If it is not found the default palette of Quake1 is returned
@@ -179,9 +176,8 @@ protected:
      */
      */
     void FreePalette(const unsigned char* pszColorMap);
     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);
     void CreateTextureARGB8_3DGS_MDL3(const unsigned char* szData);
 
 
@@ -195,7 +191,6 @@ protected:
         unsigned int iType,
         unsigned int iType,
         unsigned int* piSkip);
         unsigned int* piSkip);
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Used to load textures from MDL5
     /** Used to load textures from MDL5
      * \param szData Input data
      * \param szData Input data
@@ -206,7 +201,6 @@ protected:
         unsigned int iType,
         unsigned int iType,
         unsigned int* piSkip);
         unsigned int* piSkip);
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Checks whether a texture can be replaced with a single color
     /** Checks whether a texture can be replaced with a single color
      * This is useful for all file formats before MDL7 (all those
      * This is useful for all file formats before MDL7 (all those
@@ -218,14 +212,12 @@ protected:
     */
     */
     aiColor4D ReplaceTextureWithColor(const aiTexture* pcTexture);
     aiColor4D ReplaceTextureWithColor(const aiTexture* pcTexture);
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Converts the absolute texture coordinates in MDL5 files to
     /** Converts the absolute texture coordinates in MDL5 files to
      *  relative in a range between 0 and 1
      *  relative in a range between 0 and 1
     */
     */
     void CalculateUVCoordinates_MDL5();
     void CalculateUVCoordinates_MDL5();
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Read an UV coordinate from the file. If the file format is not
     /** Read an UV coordinate from the file. If the file format is not
      * MDL5, the function calculates relative texture coordinates
      * MDL5, the function calculates relative texture coordinates
@@ -245,7 +237,6 @@ protected:
      */
      */
     void SetupMaterialProperties_3DGS_MDL5_Quake1( );
     void SetupMaterialProperties_3DGS_MDL5_Quake1( );
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Parse a skin lump in a MDL7/HMP7 file with all of its features
     /** 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
      *  variant 1: Current cursor position is the beginning of the skin header
@@ -452,6 +443,9 @@ protected:
 
 
     /** Size of the input file in bytes */
     /** Size of the input file in bytes */
     unsigned int iFileSize;
     unsigned int iFileSize;
+
+    /* Configuration for HL1 MDL */
+    HalfLife::HL1ImportSettings mHL1ImportSettings;
 };
 };
 
 
 } // end of namespace Assimp
 } // end of namespace Assimp

+ 3 - 3
code/MDL/MDLMaterialLoader.cpp

@@ -541,7 +541,7 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7(
         size_t iLen2 = iLen+1;
         size_t iLen2 = iLen+1;
         iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2;
         iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2;
         memcpy(szFile.data,(const char*)szCurrent,iLen2);
         memcpy(szFile.data,(const char*)szCurrent,iLen2);
-        szFile.length = iLen;
+        szFile.length = (ai_uint32)iLen;
 
 
         szCurrent += iLen2;
         szCurrent += iLen2;
 
 
@@ -710,7 +710,7 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7(
         aiString szFile;
         aiString szFile;
         const size_t iLen = strlen((const char*)szCurrent);
         const size_t iLen = strlen((const char*)szCurrent);
         ::memcpy(szFile.data,(const char*)szCurrent,iLen+1);
         ::memcpy(szFile.data,(const char*)szCurrent,iLen+1);
-        szFile.length = iLen;
+        szFile.length = (ai_uint32)iLen;
 
 
         pcMatOut->AddProperty(&szFile,AI_MATKEY_TEXTURE_DIFFUSE(0));
         pcMatOut->AddProperty(&szFile,AI_MATKEY_TEXTURE_DIFFUSE(0));
 
 
@@ -831,7 +831,7 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7(
         aiString szFile;
         aiString szFile;
         ::memcpy(szFile.data,pcSkin->texture_name,sizeof(pcSkin->texture_name));
         ::memcpy(szFile.data,pcSkin->texture_name,sizeof(pcSkin->texture_name));
         szFile.data[sizeof(pcSkin->texture_name)] = '\0';
         szFile.data[sizeof(pcSkin->texture_name)] = '\0';
-        szFile.length = ::strlen(szFile.data);
+        szFile.length = (ai_uint32)::strlen(szFile.data);
 
 
         pcMatOut->AddProperty(&szFile,AI_MATKEY_NAME);
         pcMatOut->AddProperty(&szFile,AI_MATKEY_NAME);
     }
     }

+ 7 - 24
code/Material/MaterialSystem.cpp

@@ -51,7 +51,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/types.h>
 #include <assimp/types.h>
 #include <assimp/material.h>
 #include <assimp/material.h>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/DefaultLogger.hpp>
-#include <assimp/Macros.h>
 
 
 using namespace Assimp;
 using namespace Assimp;
 
 
@@ -274,14 +273,14 @@ aiReturn aiGetMaterialColor(const aiMaterial* pMat,
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-// Get a aiUVTransform (4 floats) from the material
+// Get a aiUVTransform (5 floats) from the material
 aiReturn aiGetMaterialUVTransform(const aiMaterial* pMat,
 aiReturn aiGetMaterialUVTransform(const aiMaterial* pMat,
     const char* pKey,
     const char* pKey,
     unsigned int type,
     unsigned int type,
     unsigned int index,
     unsigned int index,
     aiUVTransform* pOut)
     aiUVTransform* pOut)
 {
 {
-    unsigned int iMax = 4;
+    unsigned int iMax = 5;
     return aiGetMaterialFloatArray(pMat,pKey,type,index,(ai_real*)pOut,&iMax);
     return aiGetMaterialFloatArray(pMat,pKey,type,index,(ai_real*)pOut,&iMax);
 }
 }
 
 
@@ -472,12 +471,12 @@ aiReturn aiMaterial::AddBinaryProperty (const void* pInput,
     aiPropertyTypeInfo pType
     aiPropertyTypeInfo pType
     )
     )
 {
 {
-    ai_assert( pInput != NULL );
-    ai_assert( pKey != NULL );
+    ai_assert( pInput != nullptr );
+	ai_assert(pKey != nullptr );
     ai_assert( 0 != pSizeInBytes );
     ai_assert( 0 != pSizeInBytes );
 
 
     if ( 0 == pSizeInBytes ) {
     if ( 0 == pSizeInBytes ) {
-
+		return AI_FAILURE;
     }
     }
 
 
     // first search the list whether there is already an entry with this key
     // first search the list whether there is already an entry with this key
@@ -505,7 +504,7 @@ aiReturn aiMaterial::AddBinaryProperty (const void* pInput,
     pcNew->mData = new char[pSizeInBytes];
     pcNew->mData = new char[pSizeInBytes];
     memcpy (pcNew->mData,pInput,pSizeInBytes);
     memcpy (pcNew->mData,pInput,pSizeInBytes);
 
 
-    pcNew->mKey.length = ::strlen(pKey);
+    pcNew->mKey.length = (ai_uint32)::strlen(pKey);
     ai_assert ( MAXLEN > pcNew->mKey.length);
     ai_assert ( MAXLEN > pcNew->mKey.length);
     strcpy( pcNew->mKey.data, pKey );
     strcpy( pcNew->mKey.data, pKey );
 
 
@@ -545,23 +544,7 @@ aiReturn aiMaterial::AddProperty (const aiString* pInput,
     unsigned int type,
     unsigned int type,
     unsigned int index)
     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<uint32_t*>(&copy.length);
-        s[1] = static_cast<uint32_t>(pInput->length);
-
-        return AddBinaryProperty(s+1,
-            static_cast<unsigned int>(pInput->length+1+4),
-            pKey,
-            type,
-            index,
-            aiPTI_String);
-    }
-    ai_assert(sizeof(size_t)==4);
+    ai_assert(sizeof(ai_uint32)==4);
     return AddBinaryProperty(pInput,
     return AddBinaryProperty(pInput,
         static_cast<unsigned int>(pInput->length+1+4),
         static_cast<unsigned int>(pInput->length+1+4),
         pKey,
         pKey,

+ 1 - 4
code/Obj/ObjFileImporter.cpp

@@ -79,10 +79,7 @@ using namespace std;
 ObjFileImporter::ObjFileImporter()
 ObjFileImporter::ObjFileImporter()
 : m_Buffer()
 : m_Buffer()
 , m_pRootObject( nullptr )
 , m_pRootObject( nullptr )
-, m_strAbsPath( "" ) {
-    DefaultIOSystem io;
-    m_strAbsPath = io.getOsSeparator();
-}
+, m_strAbsPath( std::string(1, DefaultIOSystem().getOsSeparator()) ) {}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 //  Destructor.
 //  Destructor.

+ 2 - 2
code/Obj/ObjFileParser.cpp

@@ -244,8 +244,8 @@ void ObjFileParser::copyNextWord(char *pBuffer, size_t length) {
     size_t index = 0;
     size_t index = 0;
     m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
     m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
     if ( *m_DataIt == '\\' ) {
     if ( *m_DataIt == '\\' ) {
-        m_DataIt++;
-        m_DataIt++;
+        ++m_DataIt;
+        ++m_DataIt;
         m_DataIt = getNextWord<DataArrayIt>( m_DataIt, m_DataItEnd );
         m_DataIt = getNextWord<DataArrayIt>( m_DataIt, m_DataItEnd );
     }
     }
     while( m_DataIt != m_DataItEnd && !IsSpaceOrNewLine( *m_DataIt ) ) {
     while( m_DataIt != m_DataItEnd && !IsSpaceOrNewLine( *m_DataIt ) ) {

+ 3 - 2
code/Ply/PlyExporter.cpp

@@ -373,10 +373,11 @@ void PlyExporter::WriteMeshIndices(const aiMesh* m, unsigned int offset)
 {
 {
     for (unsigned int i = 0; i < m->mNumFaces; ++i) {
     for (unsigned int i = 0; i < m->mNumFaces; ++i) {
         const aiFace& f = m->mFaces[i];
         const aiFace& f = m->mFaces[i];
-        mOutput << f.mNumIndices << " ";
+        mOutput << f.mNumIndices;
         for(unsigned int c = 0; c < f.mNumIndices; ++c) {
         for(unsigned int c = 0; c < f.mNumIndices; ++c) {
-            mOutput << (f.mIndices[c] + offset) << (c == f.mNumIndices-1 ? endl : " ");
+            mOutput << " " << (f.mIndices[c] + offset);
         }
         }
+        mOutput << endl;
     }
     }
 }
 }
 
 

+ 0 - 1
code/Ply/PlyLoader.cpp

@@ -49,7 +49,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 // internal headers
 // internal headers
 #include "PlyLoader.h"
 #include "PlyLoader.h"
 #include <assimp/IOStreamBuffer.h>
 #include <assimp/IOStreamBuffer.h>
-#include <assimp/Macros.h>
 #include <memory>
 #include <memory>
 #include <assimp/IOSystem.hpp>
 #include <assimp/IOSystem.hpp>
 #include <assimp/scene.h>
 #include <assimp/scene.h>

+ 268 - 0
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 <assimp/BaseImporter.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <iostream>
+
+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<aiBone *> bones;
+  std::vector<aiNode *> nodes;
+  std::map<aiBone *, aiNode *> 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<aiBone *, aiNode *> 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<aiBone *> &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<aiNode *> &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<aiBone *> &bones,
+                                      std::map<aiBone *, aiNode *> &bone_stack,
+                                      std::vector<aiNode *> &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<aiBone *, aiNode *>(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<aiBone *> &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<aiBone *> &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<aiNode *> &nodes) {
+  std::vector<aiNode *>::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

+ 112 - 0
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 <assimp/BaseImporter.h>
+#include <vector>
+#include <map>
+
+
+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 <[email protected]>
+ * 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<aiBone *> &bone_list);
+
+    static bool IsBoneNode(const aiString &bone_name,
+                              std::vector<aiBone *> &bones);
+
+    static aiNode *GetNodeFromStack(const aiString &node_name,
+                                       std::vector<aiNode *> &nodes);
+
+    static void BuildNodeList(const aiNode *current_node,
+                                 std::vector<aiNode *> &nodes);
+
+    static void BuildBoneList(aiNode *current_node, const aiNode *root_node,
+                                 const aiScene *scene,
+                                 std::vector<aiBone *> &bones);                        
+
+    static void BuildBoneStack(aiNode *current_node, const aiNode *root_node,
+                                  const aiScene *scene,
+                                  const std::vector<aiBone *> &bones,
+                                  std::map<aiBone *, aiNode *> &bone_stack,
+                                  std::vector<aiNode *> &node_stack);
+};
+
+} // Namespace Assimp
+
+
+#endif // SCALE_PROCESS_H_

+ 3 - 3
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
             // project tangent and bitangent into the plane formed by the vertex' normal
             aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]);
             aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]);
             aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * 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.
             // reconstruct tangent/bitangent according to normal and bitangent/tangent when it's infinite or NaN.
             bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z);
             bool invalid_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 != invalid_bitangent) {
                 if (invalid_tangent) {
                 if (invalid_tangent) {
                     localTangent = meshNorm[p] ^ localBitangent;
                     localTangent = meshNorm[p] ^ localBitangent;
-                    localTangent.Normalize();
+                    localTangent.NormalizeSafe();
                 } else {
                 } else {
                     localBitangent = localTangent ^ meshNorm[p];
                     localBitangent = localTangent ^ meshNorm[p];
-                    localBitangent.Normalize();
+                    localBitangent.NormalizeSafe();
                 }
                 }
             }
             }
 
 

+ 3 - 3
code/PostProcessing/ComputeUVMappingProcess.cpp

@@ -354,12 +354,12 @@ void ComputeUVMappingProcess::ComputePlaneMapping(aiMesh* mesh,const aiVector3D&
     }
     }
     else if (axis * base_axis_z >= angle_epsilon)   {
     else if (axis * base_axis_z >= angle_epsilon)   {
         FindMeshCenter(mesh, center, min, max);
         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)  {
         for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
             const aiVector3D& pos = mesh->mVertices[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
     // slower code path in case the mapping axis is not one of the coordinate system axes

+ 3 - 2
code/PostProcessing/ConvertToLHProcess.h

@@ -137,8 +137,9 @@ public:
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     void Execute( aiScene* pScene);
     void Execute( aiScene* pScene);
 
 
-protected:
-    void ProcessMesh( aiMesh* pMesh);
+public:
+    /** Some other types of post-processing require winding order flips */
+    static void ProcessMesh( aiMesh* pMesh);
 };
 };
 
 
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------

+ 0 - 1
code/PostProcessing/FindInvalidDataProcess.cpp

@@ -52,7 +52,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "FindInvalidDataProcess.h"
 #include "FindInvalidDataProcess.h"
 #include "ProcessHelper.h"
 #include "ProcessHelper.h"
 
 
-#include <assimp/Macros.h>
 #include <assimp/Exceptional.h>
 #include <assimp/Exceptional.h>
 #include <assimp/qnan.h>
 #include <assimp/qnan.h>
 
 

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini