Просмотр исходного кода

Merge branch 'master' into pugi_xml

Kim Kulling 6 лет назад
Родитель
Сommit
d48b93cf34
100 измененных файлов с 13680 добавлено и 2022 удалено
  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
 .project
 *.kdev4*
+.DS_Store
+
+# build artefacts
+*.o
+*.a
 
 # Visual Studio
 *.sln

+ 2 - 1
.travis.sh

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

+ 3 - 0
.travis.yml

@@ -30,6 +30,9 @@ env:
     - 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}
 
+git:
+  depth: 1
+
 matrix:
   include:
     - 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
-## Install CMake
+
+## Build on all platforms using vcpkg
+You can download and install assimp using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager:
+```bash
+    git clone https://github.com/Microsoft/vcpkg.git
+    cd vcpkg
+    ./bootstrap-vcpkg.sh
+    ./vcpkg integrate install
+    vcpkg install assimp
+```
+The assimp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
+
+## Manual build instructions
+
+### Install CMake
 Asset-Importer-Lib can be build for a lot of different platforms. We are using cmake to generate the build environment for these via cmake. So you have to make sure that you have a working cmake-installation on your system. You can download it at https://cmake.org/ or for linux install it via
 ```bash
 sudo apt-get install cmake
 ```
 
-## Get the source
+### Get the source
 Make sure you have a working git-installation. Open a command prompt and clone the Asset-Importer-Lib via:
 ```bash
 git clone https://github.com/assimp/assimp.git
 ```
 
-## Build instructions for Windows with Visual-Studio
+### Build instructions for Windows with Visual-Studio
 
 First you have to install Visual-Studio on your windows-system. You can get the Community-Version for free here: https://visualstudio.microsoft.com/de/downloads/
 To generate the build environment for your IDE open a command prompt, navigate to your repo and type:
@@ -20,10 +34,10 @@ cmake CMakeLists.txt
 ```
 This will generate the project files for the visual studio. All dependencies used to build Asset-IMporter-Lib shall be part of the repo. If you want to use you own zlib.installation this is possible as well. Check the options for it.
 
-## Build instructions for Windows with UWP
+### Build instructions for Windows with UWP
 See <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:
 
 ```bash
@@ -34,7 +48,7 @@ The option -j descripes the number of parallel processes for the build. In this
 
 If you want to use a IDE for linux you can try QTCreator for instance. 
 
-## Build instructions for MinGW
+### Build instructions for MinGW
  Older versions of MinGW's compiler (e.g. 5.1.0) do not support the -mbig_obj flag 
 required to compile some of assimp's files, especially for debug builds.
 Version 7.3.0 of g++-mingw-w64 & gcc-mingw-w64 appears to work.
@@ -50,7 +64,7 @@ The following toolchain may or may not be helpful for building assimp using MinG
 
 Besides the toolchain, compilation should be the same as for Linux / Unix.
 
-## CMake build options
+### CMake build options
 The cmake-build-environment provides options to configure the build. The following options can be used:
 - **BUILD_SHARED_LIBS ( default ON )**: Generation of shared libs ( dll for windows, so for Linux ). Set this to OFF to get a static lib.
 - **BUILD_FRAMEWORK ( default OFF, MacOnly)**: Build package as Mac OS X Framework bundle
@@ -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_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_ERROR_MAX( default OFF)**: Enable all warnings.
 - **ASSIMP_WERROR( default OFF )**: Treat warnings as errors.
 - **ASSIMP_ASAN ( default OFF )**: Enable AddressSanitizer.
 - **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."
   OFF
 )
+OPTION ( ASSIMP_ERROR_MAX
+  "Enable all warnings."
+  OFF
+)
 OPTION ( ASSIMP_WERROR
   "Treat warnings as errors."
   OFF
@@ -173,7 +177,6 @@ SET (ASSIMP_VERSION ${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}.${ASSIMP_VER
 SET (ASSIMP_SOVERSION 5)
 
 SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" )
-
 if(NOT HUNTER_ENABLED)
   # Enable C++11 support globally
   set_property( GLOBAL PROPERTY CXX_STANDARD 11 )
@@ -255,6 +258,7 @@ ELSEIF(MSVC)
   IF(MSVC12)
     ADD_COMPILE_OPTIONS(/wd4351)
   ENDIF()
+  SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG /Zi /Od")
 ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" )
   IF(NOT HUNTER_ENABLED)
     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_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}")
   ENDIF()
-  SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long -Wa,-mbig-obj ${CMAKE_CXX_FLAGS}")
+  SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long -Wa,-mbig-obj -O3 ${CMAKE_CXX_FLAGS}")
   SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}")
   ADD_DEFINITIONS( -U__STRICT_ANSI__ )
 ENDIF()
 
 IF ( IOS AND NOT HUNTER_ENABLED)
-
-IF (CMAKE_BUILD_TYPE STREQUAL "Debug")
-  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -Og")
-  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -Og")
-ELSE()
-  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -O3")
-  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3")
-  # Experimental for pdb generation
-ENDIF()
-
+  IF (CMAKE_BUILD_TYPE STREQUAL "Debug")
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -Og")
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -Og")
+  ELSE()
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -O3")
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3")
+    # Experimental for pdb generation
+  ENDIF()
 ENDIF( IOS AND NOT HUNTER_ENABLED)
 
 IF (ASSIMP_COVERALLS)
@@ -297,6 +299,16 @@ IF (ASSIMP_COVERALLS)
   SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
 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)
   MESSAGE(STATUS "Treating warnings as errors")
   IF (MSVC)
@@ -342,7 +354,7 @@ SET( ASSIMP_BIN_INSTALL_DIR "bin" CACHE STRING
 
 get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
 
-IF (is_multi_config OR (CMAKE_BUILD_TYPE STREQUAL "Debug"))
+IF (INJECT_DEBUG_POSTFIX AND (is_multi_config OR CMAKE_BUILD_TYPE STREQUAL "Debug"))
   SET(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "Debug Postfix for lib, samples and tools")
 ELSE()
   SET(CMAKE_DEBUG_POSTFIX "" CACHE STRING "Debug Postfix for lib, samples and tools")
@@ -394,6 +406,11 @@ IF(HUNTER_ENABLED)
   )
 ELSE(HUNTER_ENABLED)
   # cmake configuration files
+  if(${BUILD_SHARED_LIBS})
+    set(BUILD_LIB_TYPE SHARED)
+  else()
+    set(BUILD_LIB_TYPE STATIC)
+  endif()
   CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimp-config.cmake.in"         "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake" @ONLY IMMEDIATE)
   CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimpTargets.cmake.in"         "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets.cmake" @ONLY IMMEDIATE)
   IF (is_multi_config)
@@ -560,17 +577,15 @@ ENDIF(NOT HUNTER_ENABLED)
 
 ADD_SUBDIRECTORY( code/ )
 IF ( ASSIMP_BUILD_ASSIMP_TOOLS )
+  # The viewer for windows only
   IF ( WIN32 AND DirectX_D3DX9_LIBRARY )
     OPTION ( ASSIMP_BUILD_ASSIMP_VIEW "If the Assimp view tool is built. (requires DirectX)" ${DirectX_FOUND} )
     IF ( ASSIMP_BUILD_ASSIMP_VIEW )
       ADD_SUBDIRECTORY( tools/assimp_view/ )
     ENDIF ( ASSIMP_BUILD_ASSIMP_VIEW )
   ENDIF ( WIN32 AND DirectX_D3DX9_LIBRARY )
-
+  # Te command line tool
   ADD_SUBDIRECTORY( tools/assimp_cmd/ )
-IF (NOT IOS)
-  ADD_SUBDIRECTORY( tools/assimp_qt_viewer/ )
-ENDIF (NOT IOS)
 ENDIF ( ASSIMP_BUILD_ASSIMP_TOOLS )
 
 IF ( ASSIMP_BUILD_SAMPLES)

+ 9 - 3
Readme.md

@@ -60,13 +60,19 @@ __Importers__:
 - ENFF
 - [FBX](https://en.wikipedia.org/wiki/FBX)
 - [glTF 1.0](https://en.wikipedia.org/wiki/GlTF#glTF_1.0) + GLB
-- [glTF 2.0](https://en.wikipedia.org/wiki/GlTF#glTF_2.0)
+- [glTF 2.0](https://en.wikipedia.org/wiki/GlTF#glTF_2.0):
+  At the moment for glTF2.0 the following extensions are supported:
+  + KHR_lights_punctual ( 5.0 )
+  + KHR_materials_pbrSpecularGlossiness ( 5.0 )
+  + KHR_materials_unlit ( 5.0 )
+  + KHR_texture_transform ( 5.1 under test )
 - HMB
 - IFC-STEP
 - IRR / IRRMESH
 - [LWO](https://en.wikipedia.org/wiki/LightWave_3D)
 - LWS
 - LXO
+- [M3D](https://bztsrc.gitlab.io/model3d)
 - MD2
 - MD3
 - MD5
@@ -120,12 +126,12 @@ __Exporters__:
 - FBX ( experimental )
 
 ### Building ###
-Take a look into the https://github.com/assimp/assimp/blob/master/Build.md file. Our build system is CMake, if you used CMake before there is a good chance you know what to do.
+Take a look into the https://github.com/assimp/assimp/blob/master/Build.md file. We are available in vcpkg, and our build system is CMake; if you used CMake before there is a good chance you know what to do.
 
 ### Ports ###
 * [Android](port/AndroidJNI/README.md)
 * [Python](port/PyAssimp/README.md)
-* [.NET](https://github.com/kebby/assimp-net)
+* [.NET](https://github.com/assimp/assimp-net)
 * [Pascal](port/AssimpPascal/Readme.md)
 * [Javascript (Alpha)](https://github.com/makc/assimp2json)
 * [Unity 3d Plugin](https://www.assetstore.unity3d.com/en/#!/content/91777)

+ 13 - 5
appveyor.yml

@@ -4,6 +4,8 @@
 # clone directory
 clone_folder: c:\projects\assimp
 
+clone_depth: 1
+
 # branches to build
 branches:
   # whitelist
@@ -17,6 +19,8 @@ image:
   - Visual Studio 2013
   - Visual Studio 2015
   - Visual Studio 2017
+  - Visual Studio 2019
+  - MinGW  
     
 platform:
   - Win32
@@ -27,11 +31,15 @@ configuration: Release
 install:
   - set PATH=C:\Ruby24-x64\bin;%PATH%
   - set CMAKE_DEFINES -DASSIMP_WERROR=ON
+  - if [%COMPILER%]==[MinGW] set PATH=C:\MinGW\bin;%PATH%
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2013" set CMAKE_GENERATOR_NAME=Visual Studio 12 2013
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" set CMAKE_GENERATOR_NAME=Visual Studio 15 2017
-  - if "%platform%"=="x64" set CMAKE_GENERATOR_NAME=%CMAKE_GENERATOR_NAME% Win64
-  - cmake %CMAKE_DEFINES% -G "%CMAKE_GENERATOR_NAME%" .
+  - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2019" set CMAKE_GENERATOR_NAME=Visual Studio 16 2019
+  - cmake %CMAKE_DEFINES% -G "%CMAKE_GENERATOR_NAME%" -A %platform% .
+  # Rename sh.exe as sh.exe in PATH interferes with MinGW  - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015
+
+  - rename "C:\Program Files\Git\usr\bin\sh.exe" "sh2.exe"
   - set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5"
   - ps: Invoke-WebRequest -Uri https://download.microsoft.com/download/5/7/b/57b2947c-7221-4f33-b35e-2fc78cb10df4/vc_redist.x64.exe -OutFile .\packaging\windows-innosetup\vc_redist.x64.exe
   - ps: Invoke-WebRequest -Uri https://download.microsoft.com/download/1/d/8/1d8137db-b5bb-4925-8c5d-927424a2e4de/vc_redist.x86.exe -OutFile .\packaging\windows-innosetup\vc_redist.x86.exe
@@ -46,11 +54,11 @@ cache:
   - bin\.mtime_cache
   
 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
   
-build:
-  parallel: true
-  project: Assimp.sln
+build_script:
+  cmake --build . --config Release -- /maxcpucount:2
   
 after_build:
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" (

+ 10 - 1
assimpTargets-debug.cmake.in

@@ -35,6 +35,8 @@ if(MSVC)
   endif()
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" )
 
+  file(TO_NATIVE_PATH ${_IMPORT_PREFIX} _IMPORT_PREFIX)
+
   if(ASSIMP_BUILD_SHARED_LIBS)
     set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@")
     set(importLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_IMPORT_LIBRARY_SUFFIX@")
@@ -63,7 +65,14 @@ if(MSVC)
 else()
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" )
   if(ASSIMP_BUILD_SHARED_LIBS)
-    set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@")
+    if(WIN32)
+      # Handle MinGW compiler.
+      set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@@CMAKE_STATIC_LIBRARY_SUFFIX@")
+    elseif(APPLE)
+      set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@.@ASSIMP_VERSION_MAJOR@@CMAKE_SHARED_LIBRARY_SUFFIX@")
+    else()
+      set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@")
+    endif()
     set_target_properties(assimp::assimp PROPERTIES
       IMPORTED_SONAME_DEBUG "${sharedLibraryName}"
       IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/${sharedLibraryName}"

+ 11 - 1
assimpTargets-release.cmake.in

@@ -34,6 +34,8 @@ if(MSVC)
     endif()
   endif()
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" )
+  	
+  file(TO_NATIVE_PATH ${_IMPORT_PREFIX} _IMPORT_PREFIX)
 
   if(ASSIMP_BUILD_SHARED_LIBS)
     set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@")
@@ -63,9 +65,17 @@ if(MSVC)
 else()
   set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" )
   if(ASSIMP_BUILD_SHARED_LIBS)
-    set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@")
+    if(WIN32)
+      # Handle MinGW compiler.
+      set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@@CMAKE_STATIC_LIBRARY_SUFFIX@")
+    elseif(APPLE)
+      set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}.@ASSIMP_VERSION_MAJOR@@CMAKE_SHARED_LIBRARY_SUFFIX@")
+    else()
+      set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@")
+    endif()
     set_target_properties(assimp::assimp PROPERTIES
       IMPORTED_SONAME_RELEASE "${sharedLibraryName}"
+
       IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/${sharedLibraryName}"
     )
     list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp )

+ 4 - 5
assimpTargets.cmake.in

@@ -5,6 +5,9 @@ if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.5)
 endif()
 cmake_policy(PUSH)
 cmake_policy(VERSION 2.6)
+# Required for the evaluation of "if(@BUILD_SHARED_LIBS@)" below to function
+cmake_policy(SET CMP0012 NEW)
+
 #----------------------------------------------------------------
 # Generated CMake target import file.
 #----------------------------------------------------------------
@@ -51,11 +54,7 @@ if(_IMPORT_PREFIX STREQUAL "/")
 endif()
 
 # Create imported target assimp::assimp
-if(@BUILD_SHARED_LIBS@)
-  add_library(assimp::assimp SHARED IMPORTED)
-else()
-  add_library(assimp::assimp STATIC IMPORTED)
-endif()
+add_library(assimp::assimp @BUILD_LIB_TYPE@ IMPORTED)
 
 set_target_properties(assimp::assimp PROPERTIES
   COMPATIBLE_INTERFACE_STRING "assimp_MAJOR_VERSION"

+ 7 - 3
cmake-modules/Findassimp.cmake

@@ -54,14 +54,18 @@ else(WIN32)
 
 	find_path(
 	  assimp_INCLUDE_DIRS
-	  NAMES postprocess.h scene.h version.h config.h cimport.h
-	  PATHS /usr/local/include/
+	  NAMES assimp/postprocess.h assimp/scene.h assimp/version.h assimp/config.h assimp/cimport.h
+	  PATHS /usr/local/include
+	  PATHS /usr/include/
+
 	)
 
 	find_library(
 	  assimp_LIBRARIES
 	  NAMES assimp
 	  PATHS /usr/local/lib/
+	  PATHS /usr/lib64/
+	  PATHS /usr/lib/
 	)
 
 	if (assimp_INCLUDE_DIRS AND assimp_LIBRARIES)
@@ -78,4 +82,4 @@ else(WIN32)
 	  endif (assimp_FIND_REQUIRED)
 	endif (assimp_FOUND)
 	
-endif(WIN32)
+endif(WIN32)

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

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

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

+ 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/DefaultLogger.hpp>
 #include <assimp/ai_assert.h>
+#include <assimp/ZipArchiveIOSystem.h>
 
 #include <cstdlib>
 #include <memory>
@@ -56,344 +57,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <map>
 #include <algorithm>
 #include <cassert>
-#ifdef ASSIMP_USE_HUNTER
-#  include <minizip/unzip.h>
-#else
-#  include <unzip.h>
-#endif
 #include "3MFXmlTags.h"
 
 namespace Assimp {
 
 namespace D3MF {
-
-class IOSystem2Unzip {
-public:
-    static voidpf open(voidpf opaque, const char* filename, int mode);
-    static uLong read(voidpf opaque, voidpf stream, void* buf, uLong size);
-    static uLong write(voidpf opaque, voidpf stream, const void* buf, uLong size);
-    static long tell(voidpf opaque, voidpf stream);
-    static long seek(voidpf opaque, voidpf stream, uLong offset, int origin);
-    static int close(voidpf opaque, voidpf stream);
-    static int testerror(voidpf opaque, voidpf stream);
-    static zlib_filefunc_def get(IOSystem* pIOHandler);
-};
-
-voidpf IOSystem2Unzip::open(voidpf opaque, const char* filename, int mode) {
-    IOSystem* io_system = reinterpret_cast<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;
@@ -453,7 +121,7 @@ public:
 D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
 : mRootStream(nullptr)
 , mZipArchive() {    
-    mZipArchive.reset( new D3MF::D3MFZipArchive( pIOHandler, rFile ) );    
+    mZipArchive.reset( new ZipArchiveIOSystem( pIOHandler, rFile ) );
     if(!mZipArchive->isOpen()) {
         throw DeadlyImportError("Failed to open file " + rFile+ ".");
     }
@@ -481,14 +149,14 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
 
             ASSIMP_LOG_DEBUG(rootFile);
 
+            mZipArchive->Close(fileStream);
+
             mRootStream = mZipArchive->Open(rootFile.c_str());
             ai_assert( mRootStream != nullptr );
             if ( nullptr == mRootStream ) {
                 throw DeadlyExportError( "Cannot open root-file in archive : " + rootFile );
             }
 
-            mZipArchive->Close( fileStream );
-
         } else if( file == D3MF::XmlTag::CONTENT_TYPES_ARCHIVE) {
             ASSIMP_LOG_WARN_F("Ignored file of unsupported type CONTENT_TYPES_ARCHIVES",file);
         } else {
@@ -499,7 +167,7 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
 }
 
 D3MFOpcPackage::~D3MFOpcPackage() {
-    // empty
+    mZipArchive->Close(mRootStream);
 }
 
 IOStream* D3MFOpcPackage::RootStream() const {
@@ -516,15 +184,6 @@ bool D3MFOpcPackage::validate() {
     return mZipArchive->Exists( ModelRef.c_str() );
 }
 
-bool D3MFOpcPackage::isZipArchive( IOSystem* pIOHandler, const std::string& rFile ) {
-    D3MF::D3MFZipArchive ar( pIOHandler, rFile );
-    if ( !ar.isOpen() ) {
-        return false;
-    }
-
-    return true;
-}
-
 std::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream* stream) {
     std::unique_ptr<CIrrXML_IOStreamReader> xmlStream(new CIrrXML_IOStreamReader(stream));
     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>
 
 namespace Assimp {
+    class ZipArchiveIOSystem;
+
 namespace D3MF {
 
 using XmlReader = irr::io::IrrXMLReader ;
@@ -60,22 +62,19 @@ struct OpcPackageRelationship {
     std::string target;
 };
 
-class D3MFZipArchive;
-
 class D3MFOpcPackage {
 public:
     D3MFOpcPackage( IOSystem* pIOHandler, const std::string& rFile );
     ~D3MFOpcPackage();
     IOStream* RootStream() const;
     bool validate();
-    static bool isZipArchive( IOSystem* pIOHandler, const std::string& rFile );
 
 protected:
     std::string ReadPackageRootRelationship(IOStream* stream);
 
 private:
     IOStream* mRootStream;
-    std::unique_ptr<D3MFZipArchive> mZipArchive;
+    std::unique_ptr<ZipArchiveIOSystem> mZipArchive;
 };
 
 } // Namespace D3MF

+ 1 - 1
code/AMF/AMFImporter.cpp

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

+ 13 - 13
code/AMF/AMFImporter_Postprocess.cpp

@@ -66,7 +66,7 @@ aiColor4D AMFImporter::SPP_Material::GetColor(const float /*pX*/, const float /*
     aiColor4D tcol;
 
 	// Check if stored data are supported.
-	if(Composition.size() != 0)
+	if(!Composition.empty())
 	{
 		throw DeadlyImportError("IME. GetColor for composition");
 	}
@@ -321,7 +321,7 @@ void AMFImporter::PostprocessHelper_SplitFacesByTextureID(std::list<SComplexFace
     };
 
 	pOutputList_Separated.clear();
-	if(pInputList.size() == 0) return;
+	if(pInputList.empty()) return;
 
 	do
 	{
@@ -334,19 +334,19 @@ void AMFImporter::PostprocessHelper_SplitFacesByTextureID(std::list<SComplexFace
 			{
 				auto it_old = it;
 
-				it++;
+				++it;
 				face_list_cur.push_back(*it_old);
 				pInputList.erase(it_old);
 			}
 			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
@@ -712,7 +712,7 @@ std::list<unsigned int> mesh_idx;
 	}// for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child)
 
 	// if meshes was created then assign new indices with current aiNode
-	if(mesh_idx.size() > 0)
+	if(!mesh_idx.empty())
 	{
 		std::list<unsigned int>::const_iterator mit = mesh_idx.begin();
 
@@ -787,7 +787,7 @@ std::list<aiNode*> ch_node;
 	}// for(const CAMFImporter_NodeElement* ne: pConstellation.Child)
 
 	// 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;
 
@@ -883,13 +883,13 @@ nl_clean_loop:
 	if(node_list.size() > 1)
 	{
 		// 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.
 			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)
 				{
@@ -907,7 +907,7 @@ nl_clean_loop:
 	//
 	//
 	// Nodes
-	if(node_list.size() > 0)
+	if(!node_list.empty())
 	{
 		std::list<aiNode*>::const_iterator nl_it = node_list.begin();
 
@@ -924,7 +924,7 @@ nl_clean_loop:
 
 	//
 	// Meshes
-	if(mesh_list.size() > 0)
+	if(!mesh_list.empty())
 	{
 		std::list<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->step = step_A;
-				return codechar - code_out;
+				return (int)(codechar - code_out);
 			}
 			fragment = *plainchar++;
 			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->step = step_B;
-				return codechar - code_out;
+				return (int)(codechar - code_out);
 			}
 			fragment = *plainchar++;
 			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->step = step_C;
-				return codechar - code_out;
+				return (int)(codechar - code_out);
 			}
 			fragment = *plainchar++;
 			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 */
-	return codechar - code_out;
+	return (int)(codechar - code_out);
 }
 
 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';
 	
-	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*);
 
-Exporter::ExportFormatEntry Assimp2Json_desc = Assimp::Exporter::ExportFormatEntry(
-    "json",
-    "Plain JSON representation of the Assimp scene data structure",
-    "json",
-    &ExportAssimp2Json,
-    0u
-);
-
-
 // small utility class to simplify serializing the aiScene to Json
 class JSONWriter {
 public:

+ 94 - 93
code/B3D/B3DImporter.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2019, assimp team
 
-
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -78,7 +76,6 @@ static const aiImporterDesc desc = {
     "b3d"
 };
 
-// (fixme, Aramis) quick workaround to get rid of all those signed to unsigned warnings
 #ifdef _MSC_VER
 #	pragma warning (disable: 4018)
 #endif
@@ -86,10 +83,8 @@ static const aiImporterDesc desc = {
 //#define DEBUG_B3D
 
 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;
     }
 }
@@ -102,10 +97,14 @@ B3DImporter::~B3DImporter()
 bool B3DImporter::CanRead( const std::string& pFile, IOSystem* /*pIOHandler*/, bool /*checkSig*/) const{
 
     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 );
-    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');
 }
@@ -117,30 +116,21 @@ const aiImporterDesc* B3DImporter::GetInfo () const
     return &desc;
 }
 
-#ifdef DEBUG_B3D
-    extern "C"{ void _stdcall AllocConsole(); }
-#endif
 // ------------------------------------------------------------------------------------------------
 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));
 
     // Check whether we can read from the file
-    if( file.get() == NULL)
+    if( file.get() == nullptr) {
         throw DeadlyImportError( "Failed to open B3D file " + pFile + ".");
+    }
 
     // check whether the .b3d file is large enough to contain
     // at least one chunk.
     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;
     _buf.resize( fileSize );
@@ -158,14 +148,17 @@ AI_WONT_RETURN void B3DImporter::Oops(){
 // ------------------------------------------------------------------------------------------------
 AI_WONT_RETURN void B3DImporter::Fail( string str ){
 #ifdef DEBUG_B3D
-    cout<<"Error in B3D file data: "<<str<<endl;
+    ASSIMP_LOG_ERROR_F("Error in B3D file data: ", str);
 #endif
     throw DeadlyImportError( "B3D Importer - error in B3D file data: "+str );
 }
 
 // ------------------------------------------------------------------------------------------------
 int B3DImporter::ReadByte(){
-    if( _pos<_buf.size() ) return _buf[_pos++];
+    if( _pos<_buf.size() ) {
+        return _buf[_pos++];
+    }
+    
     Fail( "EOF" );
     return 0;
 }
@@ -224,7 +217,9 @@ string B3DImporter::ReadString(){
     string str;
     while( _pos<_buf.size() ){
         char c=(char)ReadByte();
-        if( !c ) return str;
+        if( !c ) {
+            return str;
+        }
         str+=c;
     }
     Fail( "EOF" );
@@ -238,7 +233,7 @@ string B3DImporter::ReadChunk(){
         tag+=char( ReadByte() );
     }
 #ifdef DEBUG_B3D
-//	cout<<"ReadChunk:"<<tag<<endl;
+    ASSIMP_LOG_DEBUG_F("ReadChunk: ", tag);
 #endif
     unsigned sz=(unsigned)ReadInt();
     _stack.push_back( _pos+sz );
@@ -269,7 +264,6 @@ T *B3DImporter::to_array( const vector<T> &v ){
     return p;
 }
 
-
 // ------------------------------------------------------------------------------------------------
 template<class T>
 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;
 }
 
-
 // ------------------------------------------------------------------------------------------------
 void B3DImporter::ReadTEXS(){
     while( ChunkSize() ){
@@ -376,9 +369,13 @@ void B3DImporter::ReadVRTS(){
 
         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 ){
             float t[4]={0,0,0,0};
@@ -386,53 +383,55 @@ void B3DImporter::ReadVRTS(){
                 t[j]=ReadFloat();
             }
             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
-        cout<<"material id="<<matid<<endl;
+		ASSIMP_LOG_ERROR_F("material id=", matid);
 #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
-            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
-            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();
 
-    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 ){
         aiNode *node=_nodes[i];
 
@@ -648,8 +645,12 @@ void B3DImporter::ReadBB3D( aiScene *scene ){
             int n_verts=mesh->mNumVertices=n_tris * 3;
 
             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;
 

+ 35 - 4
code/CMakeLists.txt

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

+ 1 - 1
code/CSM/CSMLoader.cpp

@@ -178,7 +178,7 @@ void CSMImporter::InternReadFile( const std::string& pFile,
                         *ot++ = *buffer++;
 
                     *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());

+ 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 <assimp/Bitmap.h>
+#include <assimp/commonMetaData.h>
+#include <assimp/MathFunctions.h>
 #include <assimp/fast_atof.h>
 #include <assimp/SceneCombiner.h>
 #include <assimp/StringUtils.h>
@@ -91,6 +93,36 @@ void ExportSceneCollada(const char* pFile, IOSystem* pIOSystem, const aiScene* p
 
 } // end of namespace Assimp
 
+// ------------------------------------------------------------------------------------------------
+// Encodes a string into a valid XML ID using the xsd:ID schema qualifications.
+static const std::string XMLIDEncode(const std::string& name) {
+    const char XML_ID_CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.";
+    const unsigned int XML_ID_CHARS_COUNT = sizeof(XML_ID_CHARS) / sizeof(char);
+
+    if (name.length() == 0) {
+        return name;
+    }
+
+    std::stringstream idEncoded;
+
+    // xsd:ID must start with letter or underscore
+    if (!((name[0] >= 'A' && name[0] <= 'z') || name[0] == '_')) {
+        idEncoded << '_';
+    }
+
+    for (std::string::const_iterator it = name.begin(); it != name.end(); ++it) {
+        // xsd:ID can only contain letters, digits, underscores, hyphens and periods
+        if (strchr(XML_ID_CHARS, *it) != nullptr) {
+            idEncoded << *it;
+        } else {
+            // Select placeholder character based on invalid character to prevent name collisions 
+            idEncoded << XML_ID_CHARS[(*it) % XML_ID_CHARS_COUNT];
+        }
+    }
+
+    return idEncoded.str();
+}
+
 // ------------------------------------------------------------------------------------------------
 // Constructor for a specific scene to export
 ColladaExporter::ColladaExporter( const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file) 
@@ -145,7 +177,7 @@ void ColladaExporter::WriteFile() {
     // useless Collada fu at the end, just in case we haven't had enough indirections, yet.
     mOutput << startstr << "<scene>" << endstr;
     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();
     mOutput << startstr << "</scene>" << endstr;
     PopTag();
@@ -155,7 +187,7 @@ void ColladaExporter::WriteFile() {
 // ------------------------------------------------------------------------------------------------
 // Writes the asset header
 void ColladaExporter::WriteHeader() {
-    static const ai_real epsilon = ai_real( 0.00001 );
+    static const ai_real epsilon = Math::getEpsilon<ai_real>();
     static const aiQuaternion x_rot(aiMatrix3x3(
         0, -1,  0,
         1,  0,  0,
@@ -246,7 +278,7 @@ void ColladaExporter::WriteHeader() {
         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;
     } else {
         mOutput << startstr << "<authoring_tool>" << XMLEscape(value.C_Str()) << "</authoring_tool>" << endstr;
@@ -256,7 +288,7 @@ void ColladaExporter::WriteHeader() {
         if (meta->Get("Comments", value)) {
             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;
         }
         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::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + name, "wb"));
+            std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + mIOSystem->getOsSeparator() + name, "wb"));
             if(outfile == NULL) {
                 throw DeadlyExportError("could not open output texture file: " + mPath + name);
             }
@@ -355,9 +387,10 @@ void ColladaExporter::WriteCamerasLibrary() {
 void ColladaExporter::WriteCamera(size_t pIndex){
 
     const aiCamera *cam = mScene->mCameras[pIndex];
-    const std::string idstrEscaped = XMLEscape(cam->mName.C_Str());
+    const std::string cameraName = XMLEscape(cam->mName.C_Str());
+    const std::string cameraId = XMLIDEncode(cam->mName.C_Str());
 
-    mOutput << startstr << "<camera id=\"" << idstrEscaped << "-camera\" name=\"" << idstrEscaped << "_name\" >" << endstr;
+    mOutput << startstr << "<camera id=\"" << cameraId << "-camera\" name=\"" << cameraName << "\" >" << endstr;
     PushTag();
     mOutput << startstr << "<optics>" << endstr;
     PushTag();
@@ -411,10 +444,11 @@ void ColladaExporter::WriteLightsLibrary() {
 void ColladaExporter::WriteLight(size_t pIndex){
 
     const aiLight *light = mScene->mLights[pIndex];
-    const std::string idstrEscaped = XMLEscape(light->mName.C_Str());
+    const std::string lightName = XMLEscape(light->mName.C_Str());
+    const std::string lightId = XMLIDEncode(light->mName.C_Str());
 
-    mOutput << startstr << "<light id=\"" << idstrEscaped << "-light\" name=\""
-            << idstrEscaped << "_name\" >" << endstr;
+    mOutput << startstr << "<light id=\"" << lightId << "-light\" name=\""
+            << lightName << "\" >" << endstr;
     PushTag();
     mOutput << startstr << "<technique_common>" << endstr;
     PushTag();
@@ -585,7 +619,7 @@ static bool isalnum_C(char c) {
 void ColladaExporter::WriteImageEntry( const Surface& pSurface, const std::string& pNameAdd) {
   if( !pSurface.texture.empty() )
   {
-    mOutput << startstr << "<image id=\"" << XMLEscape(pNameAdd) << "\">" << endstr;
+    mOutput << startstr << "<image id=\"" << XMLIDEncode(pNameAdd) << "\">" << endstr;
     PushTag();
     mOutput << startstr << "<init_from>";
 
@@ -618,7 +652,7 @@ void ColladaExporter::WriteTextureColorEntry( const Surface& pSurface, const std
     }
     else
     {
-      mOutput << startstr << "<texture texture=\"" << XMLEscape(pImageName) << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
+      mOutput << startstr << "<texture texture=\"" << XMLIDEncode(pImageName) << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
     }
     PopTag();
     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( !pSurface.texture.empty() )
   {
-    mOutput << startstr << "<newparam sid=\"" << XMLEscape(pMatName) << "-" << pTypeName << "-surface\">" << endstr;
+    mOutput << startstr << "<newparam sid=\"" << XMLIDEncode(pMatName) << "-" << pTypeName << "-surface\">" << endstr;
     PushTag();
     mOutput << startstr << "<surface type=\"2D\">" << endstr;
     PushTag();
-    mOutput << startstr << "<init_from>" << XMLEscape(pMatName) << "-" << pTypeName << "-image</init_from>" << endstr;
+    mOutput << startstr << "<init_from>" << XMLIDEncode(pMatName) << "-" << pTypeName << "-image</init_from>" << endstr;
     PopTag();
     mOutput << startstr << "</surface>" << endstr;
     PopTag();
     mOutput << startstr << "</newparam>" << endstr;
 
-    mOutput << startstr << "<newparam sid=\"" << XMLEscape(pMatName) << "-" << pTypeName << "-sampler\">" << endstr;
+    mOutput << startstr << "<newparam sid=\"" << XMLIDEncode(pMatName) << "-" << pTypeName << "-sampler\">" << endstr;
     PushTag();
     mOutput << startstr << "<sampler2D>" << endstr;
     PushTag();
-    mOutput << startstr << "<source>" << XMLEscape(pMatName) << "-" << pTypeName << "-surface</source>" << endstr;
+    mOutput << startstr << "<source>" << XMLIDEncode(pMatName) << "-" << pTypeName << "-surface</source>" << endstr;
     PopTag();
     mOutput << startstr << "</sampler2D>" << endstr;
     PopTag();
@@ -698,11 +732,6 @@ void ColladaExporter::WriteMaterials()
         materials[a].name = std::string(name.C_Str()) + to_string(materialCountWithThisName);
       }
     }
-    for( std::string::iterator it = materials[a].name.begin(); it != materials[a].name.end(); ++it ) {
-      if( !isalnum_C( *it ) ) {
-        *it = '_';
-      }
-    }
 
     aiShadingMode shading = aiShadingMode_Flat;
     materials[a].shading_model = "phong";
@@ -767,7 +796,7 @@ void ColladaExporter::WriteMaterials()
     {
       const Material& mat = *it;
       // 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();
       mOutput << startstr << "<profile_COMMON>" << endstr;
       PushTag();
@@ -818,9 +847,9 @@ void ColladaExporter::WriteMaterials()
     for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++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();
-      mOutput << startstr << "<instance_effect url=\"#" << XMLEscape(mat.name) << "-fx\"/>" << endstr;
+      mOutput << startstr << "<instance_effect url=\"#" << XMLIDEncode(mat.name) << "-fx\"/>" << endstr;
       PopTag();
       mOutput << startstr << "</material>" << endstr;
     }
@@ -849,8 +878,8 @@ void ColladaExporter::WriteControllerLibrary()
 void ColladaExporter::WriteController( size_t pIndex)
 {
     const aiMesh* mesh = mScene->mMeshes[pIndex];
-    const std::string idstr = GetMeshId( pIndex);
-    const std::string idstrEscaped = XMLEscape(idstr);
+    const std::string idstr = mesh->mName.length == 0 ? GetMeshId(pIndex) : mesh->mName.C_Str();
+    const std::string idstrEscaped = XMLIDEncode(idstr);
 
     if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
         return;
@@ -885,7 +914,7 @@ void ColladaExporter::WriteController( size_t pIndex)
     mOutput << startstr << "<Name_array id=\"" << idstrEscaped << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">";
 
     for( size_t i = 0; i < mesh->mNumBones; ++i )
-        mOutput << XMLEscape(mesh->mBones[i]->mName.C_Str()) << " ";
+        mOutput << XMLIDEncode(mesh->mBones[i]->mName.C_Str()) << " ";
 
     mOutput << "</Name_array>" << endstr;
 
@@ -1020,14 +1049,15 @@ void ColladaExporter::WriteGeometryLibrary()
 void ColladaExporter::WriteGeometry( size_t pIndex)
 {
     const aiMesh* mesh = mScene->mMeshes[pIndex];
-    const std::string idstr = GetMeshId( pIndex);
-    const std::string idstrEscaped = XMLEscape(idstr);
+    const std::string idstr = mesh->mName.length == 0 ? GetMeshId(pIndex) : mesh->mName.C_Str();
+    const std::string geometryName = XMLEscape(idstr);
+    const std::string geometryId = XMLIDEncode(idstr);
 
     if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
         return;
 
     // opening tag
-    mOutput << startstr << "<geometry id=\"" << idstrEscaped << "\" name=\"" << idstrEscaped << "_name\" >" << endstr;
+    mOutput << startstr << "<geometry id=\"" << geometryId << "\" name=\"" << geometryName << "\" >" << endstr;
     PushTag();
 
     mOutput << startstr << "<mesh>" << endstr;
@@ -1058,9 +1088,9 @@ void ColladaExporter::WriteGeometry( size_t pIndex)
 
     // assemble vertex structure
     // Only write input for POSITION since we will write other as shared inputs in polygon definition
-    mOutput << startstr << "<vertices id=\"" << idstrEscaped << "-vertices" << "\">" << endstr;
+    mOutput << startstr << "<vertices id=\"" << geometryId << "-vertices" << "\">" << endstr;
     PushTag();
-    mOutput << startstr << "<input semantic=\"POSITION\" source=\"#" << idstrEscaped << "-positions\" />" << endstr;
+    mOutput << startstr << "<input semantic=\"POSITION\" source=\"#" << geometryId << "-positions\" />" << endstr;
     PopTag();
     mOutput << startstr << "</vertices>" << endstr;
 
@@ -1078,18 +1108,18 @@ void ColladaExporter::WriteGeometry( size_t pIndex)
     {
         mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr;
         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() )
-            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 )
         {
             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 )
         {
             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>";
@@ -1112,18 +1142,18 @@ void ColladaExporter::WriteGeometry( size_t pIndex)
     {
         mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr;
         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() )
-            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 )
         {
             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 )
         {
             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>";
@@ -1172,13 +1202,13 @@ void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataTy
             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();
 
     // source array
-    mOutput << startstr << "<float_array id=\"" << XMLEscape(arrayId) << "\" count=\"" << pElementCount * floatsPerElement << "\"> ";
+    mOutput << startstr << "<float_array id=\"" << arrayId << "\" count=\"" << pElementCount * floatsPerElement << "\"> ";
     PushTag();
 
     if( pType == FloatType_TexCoord2 )
@@ -1264,11 +1294,12 @@ void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataTy
 // Writes the scene library
 void ColladaExporter::WriteSceneLibrary()
 {
-    const std::string scene_name_escaped = XMLEscape(mScene->mRootNode->mName.C_Str());
+    const std::string sceneName = XMLEscape(mScene->mRootNode->mName.C_Str());
+    const std::string sceneId = XMLIDEncode(mScene->mRootNode->mName.C_Str());
 
     mOutput << startstr << "<library_visual_scenes>" << endstr;
     PushTag();
-    mOutput << startstr << "<visual_scene id=\"" + scene_name_escaped + "\" name=\"" + scene_name_escaped + "\">" << endstr;
+    mOutput << startstr << "<visual_scene id=\"" + sceneId + "\" name=\"" + sceneName + "\">" << endstr;
     PushTag();
 
     // start recursive write at the root node
@@ -1299,7 +1330,7 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 		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;
 	PushTag();
@@ -1371,13 +1402,13 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 			}
 			
 			const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-interpolation");
-			std::string arrayId = node_idstr + "-array";
+            std::string arrayId = XMLIDEncode(node_idstr) + "-array";
 			
-			mOutput << startstr << "<source id=\"" << XMLEscape(node_idstr) << "\">" << endstr;
+			mOutput << startstr << "<source id=\"" << XMLIDEncode(node_idstr) << "\">" << endstr;
 			PushTag();
 			
 			// 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 ) {
 				mOutput << names[a] << " ";
             }
@@ -1386,7 +1417,7 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 			mOutput << startstr << "<technique_common>" << endstr;
 			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();
 			
 			mOutput << startstr << "<param name=\"INTERPOLATION\" type=\"name\"></param>" << endstr;
@@ -1408,12 +1439,12 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 		{
 		// samplers
 			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();
 			
-			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();
 			mOutput << startstr << "</sampler>" << endstr;
@@ -1425,7 +1456,7 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 		
 		{
 		// 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()
 {
-	const std::string scene_name_escaped = XMLEscape(mScene->mRootNode->mName.C_Str());
-	
 	if ( mScene->mNumAnimations > 0 ) {
 		mOutput << startstr << "<library_animations>" << endstr;
 		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 ";
 	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 {
-		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
             << "\">" << endstr;
     PushTag();
@@ -1593,14 +1623,14 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
         //check if it is a camera node
         for(size_t i=0; i<mScene->mNumCameras; i++){
             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;
             }
         }
         //check if it is a light node
         for(size_t i=0; i<mScene->mNumLights; i++){
             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;
             }
         }
@@ -1614,15 +1644,17 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
         if( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
             continue;
 
+        const std::string meshName = mesh->mName.length == 0 ? GetMeshId(pNode->mMeshes[a]) : mesh->mName.C_Str();
+
         if( mesh->mNumBones == 0 )
         {
-            mOutput << startstr << "<instance_geometry url=\"#" << XMLEscape(GetMeshId( pNode->mMeshes[a])) << "\">" << endstr;
+            mOutput << startstr << "<instance_geometry url=\"#" << XMLIDEncode(meshName) << "\">" << endstr;
             PushTag();
         }
         else
         {
             mOutput << startstr
-                    << "<instance_controller url=\"#" << XMLEscape(GetMeshId( pNode->mMeshes[a])) << "-skin\">"
+                    << "<instance_controller url=\"#" << XMLIDEncode(meshName) << "-skin\">"
                     << endstr;
             PushTag();
 
@@ -1630,7 +1662,7 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
 			// use the first bone to find skeleton root
 			const aiNode * skeletonRootBoneNode = findSkeletonRootNode( pScene, mesh );
 			if ( skeletonRootBoneNode ) {
-				mFoundSkeletonRootNodeID = XMLEscape( skeletonRootBoneNode->mName.C_Str() );
+				mFoundSkeletonRootNodeID = XMLIDEncode( skeletonRootBoneNode->mName.C_Str() );
 			}
             mOutput << startstr << "<skeleton>#" << mFoundSkeletonRootNodeID << "</skeleton>" << endstr;
         }
@@ -1638,7 +1670,7 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
         PushTag();
         mOutput << startstr << "<technique_common>" << endstr;
         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();
         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

+ 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 <vector>
+#include <set>
 #include <stdint.h>
 #include <assimp/light.h>
 #include <assimp/mesh.h>
@@ -104,6 +105,17 @@ enum MorphMethod
     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 */
 struct Transform
@@ -580,15 +592,11 @@ struct Image
 {
     std::string mFileName;
 
-    /** If image file name is zero, embedded image data
-     */
+    /** Embedded image data */
     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;
-
 };
 
 /** An animation channel. */
@@ -651,23 +659,37 @@ struct Animation
 
 	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();)
 		{
 			Animation *anim = *it;
-
 			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]);
 
 				it = pParent->mSubAnims.erase(it);
 
 				delete anim;
-			}
-			else
-			{
-				++it;
+				continue;
 			}
 		}
 	}

Разница между файлами не показана из-за своего большого размера
+ 256 - 208
code/Collada/ColladaLoader.cpp


+ 5 - 8
code/Collada/ColladaLoader.h

@@ -94,20 +94,20 @@ public:
 public:
     /** Returns whether the class can handle the format of the given file.
      * See BaseImporter::CanRead() for details. */
-    bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const;
+    bool CanRead(const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override;
 
 protected:
     /** Return importer meta information.
      * See #BaseImporter::GetInfo for the details
      */
-    const aiImporterDesc* GetInfo () const;
+    const aiImporterDesc* GetInfo () const override;
 
-    void SetupProperties(const Importer* pImp);
+    void SetupProperties(const Importer* pImp) override;
 
     /** Imports the given file into the given scene structure.
      * See BaseImporter::InternReadFile() for details
      */
-    void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler);
+    void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) override;
 
     /** Recursively constructs a scene node for the given parser node and returns it. */
     aiNode* BuildHierarchy( const ColladaParser& pParser, const Collada::Node* pNode);
@@ -120,7 +120,7 @@ protected:
     void BuildMeshesForNode( const ColladaParser& pParser, const Collada::Node* pNode,
         aiNode* pTarget);
 		
-    aiMesh *findMesh(std::string meshid);
+    aiMesh *findMesh(const std::string& meshid);
 
     /** Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh */
     aiMesh* CreateMesh( const ColladaParser& pParser, const Collada::Mesh* pSrcMesh, const Collada::SubMesh& pSubMesh,
@@ -184,9 +184,6 @@ protected:
     aiString FindFilenameForEffectTexture( const ColladaParser& pParser,
         const Collada::Effect& pEffect, const std::string& pName);
 
-    /** Converts a path read from a collada file to the usual representation */
-    void ConvertPath( aiString& ss);
-
     /** Reads a float value from an accessor and its data array.
      * @param pAccessor The accessor to use for reading
      * @param pData The data array to read from

Разница между файлами не показана из-за своего большого размера
+ 411 - 275
code/Collada/ColladaParser.cpp


+ 12 - 5
code/Collada/ColladaParser.h

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

+ 39 - 3
code/Common/BaseImporter.cpp

@@ -67,7 +67,20 @@ using namespace Assimp;
 // Constructor to be privately used by Importer
 BaseImporter::BaseImporter() AI_NO_EXCEPT
 : m_progress() {
-    // nothing to do here
+    /**
+    * Assimp Importer
+    * unit conversions available
+    * if you need another measurment unit add it below.
+    * it's currently defined in assimp that we prefer meters.
+    *
+    * NOTE: Initialised here rather than in the header file
+    * to workaround a VS2013 bug with brace initialisers
+    * */
+    importerUnits[ImporterUnits::M] = 1.0;
+    importerUnits[ImporterUnits::CM] = 0.01;
+    importerUnits[ImporterUnits::MM] = 0.001;
+    importerUnits[ImporterUnits::INCHES] = 0.0254;
+    importerUnits[ImporterUnits::FEET] = 0.3048;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -76,9 +89,25 @@ BaseImporter::~BaseImporter() {
     // nothing to do here
 }
 
+void BaseImporter::UpdateImporterScale( Importer* pImp )
+{
+    ai_assert(pImp != nullptr);
+    ai_assert(importerScale != 0.0);
+    ai_assert(fileScale != 0.0);
+
+    double activeScale = importerScale * fileScale;
+
+    // Set active scaling
+    pImp->SetPropertyFloat( AI_CONFIG_APP_SCALE_KEY, static_cast<float>( activeScale) );
+
+    ASSIMP_LOG_DEBUG_F("UpdateImporterScale scale set: %f", activeScale );
+}
+
 // ------------------------------------------------------------------------------------------------
 // Imports the given file and returns the imported data.
-aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile, IOSystem* pIOHandler) {
+aiScene* BaseImporter::ReadFile(Importer* pImp, const std::string& pFile, IOSystem* pIOHandler) {
+
+
     m_progress = pImp->GetProgressHandler();
     if (nullptr == m_progress) {
         return nullptr;
@@ -100,6 +129,11 @@ aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile,
     {
         InternReadFile( pFile, sc.get(), &filter);
 
+        // Calculate import scale hook - required because pImp not available anywhere else
+        // passes scale into ScaleProcess
+        UpdateImporterScale(pImp);
+
+
     } catch( const std::exception& err )    {
         // extract error description
         m_ErrorText = err.what();
@@ -112,7 +146,7 @@ aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile,
 }
 
 // ------------------------------------------------------------------------------------------------
-void BaseImporter::SetupProperties(const Importer* /*pImp*/)
+void BaseImporter::SetupProperties(const Importer* pImp)
 {
     // the default implementation does nothing
 }
@@ -588,6 +622,8 @@ aiScene* BatchLoader::GetImport( unsigned int which )
     return nullptr;
 }
 
+
+
 // ------------------------------------------------------------------------------------------------
 void BatchLoader::LoadAll()
 {

+ 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;
 
+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()
 {
@@ -93,7 +122,7 @@ aiReturn DefaultIOStream::Seek(size_t pOffset,
         aiOrigin_END == SEEK_END && aiOrigin_SET == SEEK_SET");
 
     // do the seek
-    return (0 == ::fseek(mFile, (long)pOffset,(int)pOrigin) ? AI_SUCCESS : AI_FAILURE);
+    return (0 == select_fseek<sizeof(void*)>(mFile, (int64_t)pOffset,(int)pOrigin) ? AI_SUCCESS : AI_FAILURE);
 }
 
 // ----------------------------------------------------------------------------------
@@ -102,7 +131,7 @@ size_t DefaultIOStream::Tell() const
     if (!mFile) {
         return 0;
     }
-    return ::ftell(mFile);
+    return select_ftell<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;
 
-// 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
 
 // ------------------------------------------------------------------------------------------------
 // Tests for the existence of a file at the given path.
-bool DefaultIOSystem::Exists( const char* pFile) const
+bool DefaultIOSystem::Exists(const char* pFile) const
 {
 #ifdef _WIN32
-    wchar_t fileName16[PATHLIMIT];
-
-#ifndef WindowsStore
-    bool isUnicode = IsTextUnicode(pFile, static_cast<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
-    FILE* file = ::fopen( pFile, "rb");
-    if( !file)
+    FILE* file = ::fopen(pFile, "rb");
+    if (!file)
         return false;
 
-    ::fclose( file);
+    ::fclose(file);
 #endif
     return true;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Open a new file with a given path.
-IOStream* DefaultIOSystem::Open( const char* strFile, const char* strMode)
+IOStream* DefaultIOSystem::Open(const char* strFile, const char* strMode)
 {
-    ai_assert(NULL != strFile);
-    ai_assert(NULL != strMode);
+    ai_assert(strFile != nullptr);
+    ai_assert(strMode != nullptr);
     FILE* file;
 #ifdef _WIN32
-    wchar_t fileName16[PATHLIMIT];
-#ifndef WindowsStore
-    bool isUnicode = IsTextUnicode(strFile, static_cast<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
     file = ::fopen(strFile, strMode);
 #endif
-    if (nullptr == file)
+    if (!file)
         return nullptr;
 
-    return new DefaultIOStream(file, (std::string) strFile);
+    return new DefaultIOStream(file, strFile);
 }
 
 // ------------------------------------------------------------------------------------------------
 // Closes the given file and releases all resources associated with it.
-void DefaultIOSystem::Close( IOStream* pFile)
+void DefaultIOSystem::Close(IOStream* pFile)
 {
     delete pFile;
 }
@@ -155,78 +138,56 @@ char DefaultIOSystem::getOsSeparator() const
 
 // ------------------------------------------------------------------------------------------------
 // IOSystem default implementation (ComparePaths isn't a pure virtual function)
-bool IOSystem::ComparePaths (const char* one, const char* second) const
+bool IOSystem::ComparePaths(const char* one, const char* second) const
 {
-    return !ASSIMP_stricmp(one,second);
+    return !ASSIMP_stricmp(one, second);
 }
 
 // ------------------------------------------------------------------------------------------------
 // Convert a relative path into an absolute path
-inline static void MakeAbsolutePath (const char* in, char* _out)
+inline static std::string MakeAbsolutePath(const char* in)
 {
-    ai_assert(in && _out);
-#if defined( _MSC_VER ) || defined( __MINGW32__ )
-#ifndef WindowsStore
-    bool isUnicode = IsTextUnicode(in, static_cast<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
-    // 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
         // the path before it is accessed (e.g. our file system filter)
         ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
-        strcpy(_out,in);
+        out = in;
     }
-#endif
+    return out;
 }
 
 // ------------------------------------------------------------------------------------------------
 // DefaultIOSystem's more specialized implementation
-bool DefaultIOSystem::ComparePaths (const char* one, const char* second) const
+bool DefaultIOSystem::ComparePaths(const char* one, const char* second) const
 {
     // chances are quite good both paths are formatted identically,
     // so we can hopefully return here already
-    if( !ASSIMP_stricmp(one,second) )
+    if (!ASSIMP_stricmp(one, second))
         return true;
 
-    char temp1[PATHLIMIT];
-    char temp2[PATHLIMIT];
-
-    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::size_t last = ret.find_last_of("\\/");
@@ -235,16 +196,16 @@ std::string DefaultIOSystem::fileName( const std::string &path )
 }
 
 // ------------------------------------------------------------------------------------------------
-std::string DefaultIOSystem::completeBaseName( const std::string &path )
+std::string DefaultIOSystem::completeBaseName(const std::string& path)
 {
     std::string ret = fileName(path);
     std::size_t pos = ret.find_last_of('.');
-    if(pos != ret.npos) ret = ret.substr(0, pos);
+    if (pos != std::string::npos) ret = ret.substr(0, pos);
     return ret;
 }
 
 // ------------------------------------------------------------------------------------------------
-std::string DefaultIOSystem::absolutePath( const std::string &path )
+std::string DefaultIOSystem::absolutePath(const std::string& path)
 {
     std::string ret = path;
     std::size_t last = ret.find_last_of("\\/");
@@ -253,5 +214,3 @@ std::string DefaultIOSystem::absolutePath( const std::string &path )
 }
 
 // ------------------------------------------------------------------------------------------------
-
-#undef PATHLIMIT

+ 2 - 2
code/Common/DefaultLogger.cpp

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

+ 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 ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* );
+void ExportSceneM3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneM3DA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportAssimp2Json(const char* , IOSystem*, const aiScene* , const Assimp::ExportProperties*);
 
-// ------------------------------------------------------------------------------------------------
-// global array of all export formats which Assimp supports in its current build
-Exporter::ExportFormatEntry gExporters[] =
-{
+
+static void setupExporterArray(std::vector<Exporter::ExportFormatEntry> &exporters) {
 #ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER
-    Exporter::ExportFormatEntry( "collada", "COLLADA - Digital Asset Exchange Schema", "dae", &ExportSceneCollada ),
+	exporters.push_back(Exporter::ExportFormatEntry("collada", "COLLADA - Digital Asset Exchange Schema", "dae", &ExportSceneCollada));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_X_EXPORTER
-    Exporter::ExportFormatEntry( "x", "X Files", "x", &ExportSceneXFile,
-        aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs ),
+	exporters.push_back(Exporter::ExportFormatEntry("x", "X Files", "x", &ExportSceneXFile,
+			aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_STEP_EXPORTER
-    Exporter::ExportFormatEntry( "stp", "Step Files", "stp", &ExportSceneStep, 0 ),
+	exporters.push_back(Exporter::ExportFormatEntry("stp", "Step Files", "stp", &ExportSceneStep, 0));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER
-    Exporter::ExportFormatEntry( "obj", "Wavefront OBJ format", "obj", &ExportSceneObj,
-        aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */ ),
-    Exporter::ExportFormatEntry( "objnomtl", "Wavefront OBJ format without material file", "obj", &ExportSceneObjNoMtl,
-        aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */ ),
+	exporters.push_back(Exporter::ExportFormatEntry("obj", "Wavefront OBJ format", "obj", &ExportSceneObj,
+			aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */));
+	exporters.push_back(Exporter::ExportFormatEntry("objnomtl", "Wavefront OBJ format without material file", "obj", &ExportSceneObjNoMtl,
+			aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_STL_EXPORTER
-    Exporter::ExportFormatEntry( "stl", "Stereolithography", "stl" , &ExportSceneSTL,
-        aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices
-    ),
-    Exporter::ExportFormatEntry( "stlb", "Stereolithography (binary)", "stl" , &ExportSceneSTLBinary,
-        aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices
-    ),
+	exporters.push_back(Exporter::ExportFormatEntry("stl", "Stereolithography", "stl", &ExportSceneSTL,
+			aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices));
+	exporters.push_back(Exporter::ExportFormatEntry("stlb", "Stereolithography (binary)", "stl", &ExportSceneSTLBinary,
+			aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_PLY_EXPORTER
-    Exporter::ExportFormatEntry( "ply", "Stanford Polygon Library", "ply" , &ExportScenePly,
-        aiProcess_PreTransformVertices
-    ),
-    Exporter::ExportFormatEntry( "plyb", "Stanford Polygon Library (binary)", "ply", &ExportScenePlyBinary,
-        aiProcess_PreTransformVertices
-    ),
+	exporters.push_back(Exporter::ExportFormatEntry("ply", "Stanford Polygon Library", "ply", &ExportScenePly,
+			aiProcess_PreTransformVertices));
+	exporters.push_back(Exporter::ExportFormatEntry("plyb", "Stanford Polygon Library (binary)", "ply", &ExportScenePlyBinary,
+			aiProcess_PreTransformVertices));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_3DS_EXPORTER
-    Exporter::ExportFormatEntry( "3ds", "Autodesk 3DS (legacy)", "3ds" , &ExportScene3DS,
-        aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_JoinIdenticalVertices ),
+	exporters.push_back(Exporter::ExportFormatEntry("3ds", "Autodesk 3DS (legacy)", "3ds", &ExportScene3DS,
+			aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_JoinIdenticalVertices));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER
-    Exporter::ExportFormatEntry( "gltf2", "GL Transmission Format v. 2", "gltf", &ExportSceneGLTF2,
-        aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ),
-    Exporter::ExportFormatEntry( "glb2", "GL Transmission Format v. 2 (binary)", "glb", &ExportSceneGLB2,
-        aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ),
-    Exporter::ExportFormatEntry( "gltf", "GL Transmission Format", "gltf", &ExportSceneGLTF,
-        aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ),
-    Exporter::ExportFormatEntry( "glb", "GL Transmission Format (binary)", "glb", &ExportSceneGLB,
-        aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ),
+	exporters.push_back(Exporter::ExportFormatEntry("gltf2", "GL Transmission Format v. 2", "gltf", &ExportSceneGLTF2,
+			aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType));
+	exporters.push_back(Exporter::ExportFormatEntry("glb2", "GL Transmission Format v. 2 (binary)", "glb", &ExportSceneGLB2,
+			aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType));
+	exporters.push_back(Exporter::ExportFormatEntry("gltf", "GL Transmission Format", "gltf", &ExportSceneGLTF,
+			aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType));
+	exporters.push_back(Exporter::ExportFormatEntry("glb", "GL Transmission Format (binary)", "glb", &ExportSceneGLB,
+			aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER
-    Exporter::ExportFormatEntry( "assbin", "Assimp Binary", "assbin" , &ExportSceneAssbin, 0 ),
+	exporters.push_back(Exporter::ExportFormatEntry("assbin", "Assimp Binary File", "assbin", &ExportSceneAssbin, 0));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER
-    Exporter::ExportFormatEntry( "assxml", "Assxml Document", "assxml" , &ExportSceneAssxml, 0 ),
+	exporters.push_back(Exporter::ExportFormatEntry("assxml", "Assimp XML Document", "assxml", &ExportSceneAssxml, 0));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_X3D_EXPORTER
-    Exporter::ExportFormatEntry( "x3d", "Extensible 3D", "x3d" , &ExportSceneX3D, 0 ),
+	exporters.push_back(Exporter::ExportFormatEntry("x3d", "Extensible 3D", "x3d", &ExportSceneX3D, 0));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
-    Exporter::ExportFormatEntry( "fbx", "Autodesk FBX (binary)", "fbx", &ExportSceneFBX, 0 ),
-    Exporter::ExportFormatEntry( "fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0 ),
+	exporters.push_back(Exporter::ExportFormatEntry("fbx", "Autodesk FBX (binary)", "fbx", &ExportSceneFBX, 0));
+	exporters.push_back(Exporter::ExportFormatEntry("fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0));
+#endif
+
+#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER
+	exporters.push_back(Exporter::ExportFormatEntry("m3d", "Model 3D (binary)", "m3d", &ExportSceneM3D, 0));
+	exporters.push_back(Exporter::ExportFormatEntry("m3da", "Model 3D (ascii)", "a3d", &ExportSceneM3DA, 0));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_3MF_EXPORTER
-    Exporter::ExportFormatEntry( "3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0 ),
+	exporters.push_back(Exporter::ExportFormatEntry("3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0));
 #endif
 
 #ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER
-    Exporter::ExportFormatEntry("json", "Plain JSON representation of the Assimp scene data structure", "json", &ExportAssimp2Json, 0)
+	exporters.push_back(Exporter::ExportFormatEntry("assjson", "Assimp JSON Document", "json", &ExportAssimp2Json, 0));
 #endif
-};
-
-#define ASSIMP_NUM_EXPORTERS (sizeof(gExporters)/sizeof(gExporters[0]))
-
+}
 
 class ExporterPimpl {
 public:
@@ -205,10 +203,7 @@ public:
         GetPostProcessingStepInstanceList(mPostProcessingSteps);
 
         // grab all built-in exporters
-        if ( 0 != ( ASSIMP_NUM_EXPORTERS ) ) {
-            mExporters.resize( ASSIMP_NUM_EXPORTERS );
-            std::copy( gExporters, gExporters + ASSIMP_NUM_EXPORTERS, mExporters.begin() );
-        }
+		setupExporterArray(mExporters);
     }
 
     ~ExporterPimpl() {
@@ -252,24 +247,28 @@ Exporter :: Exporter()
 
 // ------------------------------------------------------------------------------------------------
 Exporter::~Exporter() {
-    FreeBlob();
+	ai_assert(nullptr != pimpl);
+	FreeBlob();
     delete pimpl;
 }
 
 // ------------------------------------------------------------------------------------------------
 void Exporter::SetIOHandler( IOSystem* pIOHandler) {
-    pimpl->mIsDefaultIOHandler = !pIOHandler;
+	ai_assert(nullptr != pimpl);
+	pimpl->mIsDefaultIOHandler = !pIOHandler;
     pimpl->mIOSystem.reset(pIOHandler);
 }
 
 // ------------------------------------------------------------------------------------------------
 IOSystem* Exporter::GetIOHandler() const {
-    return pimpl->mIOSystem.get();
+	ai_assert(nullptr != pimpl);
+	return pimpl->mIOSystem.get();
 }
 
 // ------------------------------------------------------------------------------------------------
 bool Exporter::IsDefaultIOHandler() const {
-    return pimpl->mIsDefaultIOHandler;
+	ai_assert(nullptr != pimpl);
+	return pimpl->mIsDefaultIOHandler;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -295,6 +294,7 @@ void Exporter::SetProgressHandler(ProgressHandler* pHandler) {
 // ------------------------------------------------------------------------------------------------
 const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const char* pFormatId,
                                                 unsigned int pPreprocessing, const ExportProperties* pProperties) {
+	ai_assert(nullptr != pimpl);
     if (pimpl->blob) {
         delete pimpl->blob;
         pimpl->blob = nullptr;
@@ -315,44 +315,16 @@ const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const cha
     return pimpl->blob;
 }
 
-// ------------------------------------------------------------------------------------------------
-bool IsVerboseFormat(const aiMesh* mesh) {
-    // avoid slow vector<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,
         unsigned int pPreprocessing, const ExportProperties* pProperties) {
     ASSIMP_BEGIN_EXCEPTION_REGION();
-
+	ai_assert(nullptr != pimpl);
     // when they create scenes from scratch, users will likely create them not in verbose
     // format. They will likely not be aware that there is a flag in the scene to indicate
     // this, however. To avoid surprises and bug reports, we check for duplicates in
     // meshes upfront.
-    const bool is_verbose_format = !(pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) || IsVerboseFormat(pScene);
+    const bool is_verbose_format = !(pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) || MakeVerboseFormatProcess::IsVerboseFormat(pScene);
 
     pimpl->mProgressHandler->UpdateFileWrite(0, 4);
 
@@ -472,7 +444,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.
-                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);
             } catch (DeadlyExportError& err) {
@@ -491,11 +465,13 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
 
 // ------------------------------------------------------------------------------------------------
 const char* Exporter::GetErrorString() const {
+	ai_assert(nullptr != pimpl);
     return pimpl->mError.c_str();
 }
 
 // ------------------------------------------------------------------------------------------------
 void Exporter::FreeBlob() {
+	ai_assert(nullptr != pimpl);
     delete pimpl->blob;
     pimpl->blob = nullptr;
 
@@ -504,30 +480,34 @@ void Exporter::FreeBlob() {
 
 // ------------------------------------------------------------------------------------------------
 const aiExportDataBlob* Exporter::GetBlob() const {
-    return pimpl->blob;
+	ai_assert(nullptr != pimpl);
+	return pimpl->blob;
 }
 
 // ------------------------------------------------------------------------------------------------
 const aiExportDataBlob* Exporter::GetOrphanedBlob() const {
-    const aiExportDataBlob* tmp = pimpl->blob;
+	ai_assert(nullptr != pimpl);
+	const aiExportDataBlob *tmp = pimpl->blob;
     pimpl->blob = nullptr;
     return tmp;
 }
 
 // ------------------------------------------------------------------------------------------------
 size_t Exporter::GetExportFormatCount() const {
+	ai_assert(nullptr != pimpl);
     return pimpl->mExporters.size();
 }
 
 // ------------------------------------------------------------------------------------------------
 const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) const {
-    if (index >= GetExportFormatCount()) {
+	ai_assert(nullptr != pimpl);
+	if (index >= GetExportFormatCount()) {
         return nullptr;
     }
 
     // Return from static storage if the requested index is built-in.
-    if (index < sizeof(gExporters) / sizeof(gExporters[0])) {
-        return &gExporters[index].mDescription;
+	if (index < pimpl->mExporters.size()) {
+		return &pimpl->mExporters[index].mDescription;
     }
 
     return &pimpl->mExporters[index].mDescription;
@@ -535,7 +515,8 @@ const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) c
 
 // ------------------------------------------------------------------------------------------------
 aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) {
-    for(const ExportFormatEntry& e : pimpl->mExporters) {
+	ai_assert(nullptr != pimpl);
+	for (const ExportFormatEntry &e : pimpl->mExporters) {
         if (!strcmp(e.mDescription.id,desc.mDescription.id)) {
             return aiReturn_FAILURE;
         }
@@ -547,7 +528,8 @@ aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) {
 
 // ------------------------------------------------------------------------------------------------
 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) {
         if (!strcmp((*it).mDescription.id,id)) {
             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
 
-
-
 All rights reserved.
 
 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/Exceptional.h>
 #include <assimp/Profiler.h>
+#include <assimp/commonMetaData.h>
+
 #include <set>
 #include <memory>
 #include <cctype>
@@ -119,7 +119,7 @@ void* AllocateFromAssimpHeap::operator new ( size_t num_bytes, const std::nothro
         return AllocateFromAssimpHeap::operator new( num_bytes );
     }
     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() {
     try {
         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::Importer()
  : pimpl( new ImporterPimpl ) {
-    pimpl->mScene = NULL;
+    pimpl->mScene = nullptr;
     pimpl->mErrorString = "";
 
     // Allocate a default IO handler
@@ -174,14 +173,14 @@ Importer::Importer()
 
 // ------------------------------------------------------------------------------------------------
 // Destructor of Importer
-Importer::~Importer()
-{
+Importer::~Importer() {
     // Delete all import plugins
 	DeleteImporterInstanceList(pimpl->mImporter);
 
     // 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 the assigned IO and progress handler
     delete pimpl->mIOHandler;
@@ -199,9 +198,9 @@ Importer::~Importer()
 
 // ------------------------------------------------------------------------------------------------
 // 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();
 
         pimpl->mPostProcessingSteps.push_back(pImp);
@@ -213,9 +212,9 @@ aiReturn Importer::RegisterPPStep(BaseProcess* pImp)
 
 // ------------------------------------------------------------------------------------------------
 // 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();
 
     // --------------------------------------------------------------------
@@ -242,13 +241,13 @@ aiReturn Importer::RegisterLoader(BaseImporter* pImp)
     pimpl->mImporter.push_back(pImp);
     ASSIMP_LOG_INFO_F("Registering custom importer for these file extensions: ", baked);
     ASSIMP_END_EXCEPTION_REGION(aiReturn);
+    
     return AI_SUCCESS;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Unregister a custom loader plugin
-aiReturn Importer::UnregisterLoader(BaseImporter* pImp)
-{
+aiReturn Importer::UnregisterLoader(BaseImporter* pImp) {
     if(!pImp) {
         // unregistering a NULL importer is no problem for us ... really!
         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_END_EXCEPTION_REGION(aiReturn);
+
     return AI_FAILURE;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Unregister a custom loader plugin
-aiReturn Importer::UnregisterPPStep(BaseProcess* pImp)
-{
+aiReturn Importer::UnregisterPPStep(BaseProcess* pImp) {
     if(!pImp) {
         // unregistering a NULL ppstep is no problem for us ... really!
         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_END_EXCEPTION_REGION(aiReturn);
+
     return AI_FAILURE;
 }
 
 // ------------------------------------------------------------------------------------------------
 // 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();
     // If the new handler is zero, allocate a default IO implementation.
-    if (!pIOHandler)
-    {
+    if (!pIOHandler) {
         // Release pointer in the possession of the caller
         pimpl->mIOHandler = new DefaultIOSystem();
         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;
         pimpl->mIOHandler = pIOHandler;
         pimpl->mIsDefaultHandler = false;
@@ -316,29 +313,32 @@ void Importer::SetIOHandler( IOSystem* pIOHandler)
 // ------------------------------------------------------------------------------------------------
 // Get the currently set IO handler
 IOSystem* Importer::GetIOHandler() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mIOHandler;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Check whether a custom IO handler is currently set
 bool Importer::IsDefaultIOHandler() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mIsDefaultHandler;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Supplies a custom progress handler to get regular callbacks during importing
 void Importer::SetProgressHandler ( ProgressHandler* pHandler ) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
+    
     // If the new handler is zero, allocate a default implementation.
-    if (!pHandler)
-    {
+    if (!pHandler) {
         // Release pointer in the possession of the caller
         pimpl->mProgressHandler = new DefaultProgressHandler();
         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;
         pimpl->mProgressHandler = pHandler;
         pimpl->mIsDefaultProgressHandler = false;
@@ -349,19 +349,22 @@ void Importer::SetProgressHandler ( ProgressHandler* pHandler ) {
 // ------------------------------------------------------------------------------------------------
 // Get the currently set progress handler
 ProgressHandler* Importer::GetProgressHandler() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mProgressHandler;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Check whether a custom progress handler is currently set
 bool Importer::IsDefaultProgressHandler() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mIsDefaultProgressHandler;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Validate post process step flags
-bool _ValidateFlags(unsigned int pFlags)
-{
+bool _ValidateFlags(unsigned int pFlags) {
     if (pFlags & aiProcess_GenSmoothNormals && pFlags & aiProcess_GenNormals)   {
         ASSIMP_LOG_ERROR("#aiProcess_GenSmoothNormals and #aiProcess_GenNormals are incompatible");
         return false;
@@ -375,12 +378,13 @@ bool _ValidateFlags(unsigned int pFlags)
 
 // ------------------------------------------------------------------------------------------------
 // Free the current scene
-void Importer::FreeScene( )
-{
+void Importer::FreeScene( ) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
 
     delete pimpl->mScene;
-    pimpl->mScene = NULL;
+    pimpl->mScene = nullptr;
 
     pimpl->mErrorString = "";
     ASSIMP_END_EXCEPTION_REGION(void);
@@ -388,44 +392,48 @@ void Importer::FreeScene( )
 
 // ------------------------------------------------------------------------------------------------
 // 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();
 }
 
 // ------------------------------------------------------------------------------------------------
 // Enable extra-verbose mode
-void Importer::SetExtraVerbose(bool bDo)
-{
+void Importer::SetExtraVerbose(bool bDo) {
+    ai_assert(nullptr != pimpl);
+    
     pimpl->bExtraVerbose = bDo;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Get the current scene
-const aiScene* Importer::GetScene() const
-{
+const aiScene* Importer::GetScene() const {
+    ai_assert(nullptr != pimpl);
+    
     return pimpl->mScene;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Orphan the current scene and return it.
-aiScene* Importer::GetOrphanedScene()
-{
+aiScene* Importer::GetOrphanedScene() {
+    ai_assert(nullptr != pimpl);
+    
     aiScene* s = pimpl->mScene;
 
     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*);
+    
     return s;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Validate post-processing flags
-bool Importer::ValidateFlags(unsigned int pFlags) const
-{
+bool Importer::ValidateFlags(unsigned int pFlags) const {
     ASSIMP_BEGIN_EXCEPTION_REGION();
     // run basic checks for mutually exclusive flags
     if(!_ValidateFlags(pFlags)) {
@@ -467,8 +475,9 @@ bool Importer::ValidateFlags(unsigned int pFlags) const
 const aiScene* Importer::ReadFileFromMemory( const void* pBuffer,
     size_t pLength,
     unsigned int pFlags,
-    const char* pHint /*= ""*/)
-{
+    const char* pHint /*= ""*/) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     if (!pHint) {
         pHint = "";
@@ -476,12 +485,12 @@ const aiScene* Importer::ReadFileFromMemory( const void* pBuffer,
 
     if (!pBuffer || !pLength || strlen(pHint) > MaxLenHint ) {
         pimpl->mErrorString = "Invalid parameters passed to ReadFileFromMemory()";
-        return NULL;
+        return nullptr;
     }
 
     // prevent deletion of the previous IOHandler
     IOSystem* io = pimpl->mIOHandler;
-    pimpl->mIOHandler = NULL;
+    pimpl->mIOHandler = nullptr;
 
     SetIOHandler(new MemoryIOSystem((const uint8_t*)pBuffer,pLength,io));
 
@@ -493,13 +502,13 @@ const aiScene* Importer::ReadFileFromMemory( const void* pBuffer,
     ReadFile(fbuff,pFlags);
     SetIOHandler(io);
 
-    ASSIMP_END_EXCEPTION_REGION(const aiScene*);
+    ASSIMP_END_EXCEPTION_REGION_WITH_ERROR_STRING(const aiScene*, pimpl->mErrorString);
     return pimpl->mScene;
 }
 
 // ------------------------------------------------------------------------------------------------
-void WriteLogOpening(const std::string& file)
-{
+void WriteLogOpening(const std::string& file) {
+    
     ASSIMP_LOG_INFO_F("Load ", file);
 
     // 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.
-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();
     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 + "\".";
             ASSIMP_LOG_ERROR(pimpl->mErrorString);
-            return NULL;
+            return nullptr;
         }
 
         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
-        BaseImporter* imp = NULL;
+        BaseImporter* imp = nullptr;
         SetPropertyInteger("importerIndex", -1);
         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)   {
                 pimpl->mErrorString = "No suitable reader found for the file format of file \"" + pFile + "\".";
                 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
         const aiImporterDesc *desc( imp->GetInfo() );
         std::string ext( "unknown" );
-        if ( NULL != desc ) {
+        if ( nullptr != desc ) {
             ext = desc->mName;
         }
         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( 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
             // 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;
                 ds.ExecuteOnScene (this);
                 if (!pimpl->mScene) {
-                    return NULL;
+                    return nullptr;
                 }
             }
 #endif // no validation
@@ -695,8 +710,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
         }
     }
 #ifdef ASSIMP_CATCH_GLOBAL_EXCEPTIONS
-    catch (std::exception &e)
-    {
+    catch (std::exception &e) {
 #if (defined _MSC_VER) &&   (defined _CPPRTTI)
         // if we have RTTI get the full name of the exception that occurred
         pimpl->mErrorString = std::string(typeid( e ).name()) + ": " + e.what();
@@ -705,24 +719,26 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
 #endif
 
         ASSIMP_LOG_ERROR(pimpl->mErrorString);
-        delete pimpl->mScene; pimpl->mScene = NULL;
+        delete pimpl->mScene; pimpl->mScene = nullptr;
     }
 #endif // ! ASSIMP_CATCH_GLOBAL_EXCEPTIONS
 
     // 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;
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // 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();
     // Return immediately if no scene is active
     if (!pimpl->mScene) {
-        return NULL;
+        return nullptr;
     }
 
     // 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
     // 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.
-    if (pFlags & aiProcess_ValidateDataStructure)
-    {
+    if (pFlags & aiProcess_ValidateDataStructure) {
         ValidateDSProcess ds;
         ds.ExecuteOnScene (this);
         if (!pimpl->mScene) {
-            return NULL;
+            return nullptr;
         }
     }
 #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);
     for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++)   {
-
         BaseProcess* process = pimpl->mPostProcessingSteps[a];
         pimpl->mProgressHandler->UpdatePostProcess(static_cast<int>(a), static_cast<int>(pimpl->mPostProcessingSteps.size()) );
         if( process->IsActive( pFlags)) {
-
             if (profiler) {
                 profiler->BeginRegion("postprocess");
             }
@@ -803,24 +816,28 @@ const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags)
         static_cast<int>(pimpl->mPostProcessingSteps.size()) );
 
     // update private scene flags
-    if( pimpl->mScene )
+    if( pimpl->mScene ) {
       ScenePriv(pimpl->mScene)->mPPStepsApplied |= pFlags;
+    }
 
     // clear any data allocated by post-process steps
     pimpl->mPPShared->Clean();
     ASSIMP_LOG_INFO("Leaving post processing pipeline");
 
     ASSIMP_END_EXCEPTION_REGION(const aiScene*);
+    
     return pimpl->mScene;
 }
 
 // ------------------------------------------------------------------------------------------------
 const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess, bool requestValidation ) {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
 
     // 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
@@ -839,7 +856,7 @@ const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess
         ValidateDSProcess ds;
         ds.ExecuteOnScene( this );
         if ( !pimpl->mScene ) {
-            return NULL;
+            return nullptr;
         }
     }
 #endif // no validation
@@ -890,46 +907,50 @@ const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess
 
 // ------------------------------------------------------------------------------------------------
 // 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);
 }
 
 // ------------------------------------------------------------------------------------------------
-size_t Importer::GetImporterCount() const
-{
+size_t Importer::GetImporterCount() const {
+    ai_assert(nullptr != pimpl);
+    
     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()) {
-        return NULL;
+        return nullptr;
     }
     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()) {
-        return NULL;
+        return nullptr;
     }
     return pimpl->mImporter[index];
 }
 
 // ------------------------------------------------------------------------------------------------
 // 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));
 }
 
 // ------------------------------------------------------------------------------------------------
 // Find a loader plugin for a given file extension
 size_t Importer::GetImporterIndex (const char* szExtension) const {
+    ai_assert(nullptr != pimpl);
     ai_assert(nullptr != szExtension);
 
     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
-void Importer::GetExtensionList(aiString& szOut) const
-{
+void Importer::GetExtensionList(aiString& szOut) const {
+    ai_assert(nullptr != pimpl);
+    
     ASSIMP_BEGIN_EXCEPTION_REGION();
     std::set<std::string> str;
     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
-bool Importer::SetPropertyInteger(const char* szName, int iValue)
-{
+bool Importer::SetPropertyInteger(const char* szName, int iValue) {
+    ai_assert(nullptr != pimpl);
+    
     bool existing;
     ASSIMP_BEGIN_EXCEPTION_REGION();
         existing = SetGenericProperty<int>(pimpl->mIntProperties, szName,iValue);
@@ -996,8 +1019,9 @@ bool Importer::SetPropertyInteger(const char* szName, int iValue)
 
 // ------------------------------------------------------------------------------------------------
 // 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;
     ASSIMP_BEGIN_EXCEPTION_REGION();
         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
-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;
     ASSIMP_BEGIN_EXCEPTION_REGION();
         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
-bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value)
-{
+bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value) {
+    ai_assert(nullptr != pimpl);
+    
     bool existing;
     ASSIMP_BEGIN_EXCEPTION_REGION();
         existing = SetGenericProperty<aiMatrix4x4>(pimpl->mMatrixProperties, szName,value);
@@ -1029,40 +1055,43 @@ bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value)
 
 // ------------------------------------------------------------------------------------------------
 // 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);
 }
 
 // ------------------------------------------------------------------------------------------------
 // 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);
 }
 
 // ------------------------------------------------------------------------------------------------
 // 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);
 }
 
 // ------------------------------------------------------------------------------------------------
 // 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);
 }
 
 // ------------------------------------------------------------------------------------------------
 // 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(unsigned int) * pcNode->mNumMeshes;
     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
-void Importer::GetMemoryRequirements(aiMemoryInfo& in) const
-{
+void Importer::GetMemoryRequirements(aiMemoryInfo& in) const {
+    ai_assert(nullptr != pimpl);
+    
     in = aiMemoryInfo();
     aiScene* mScene = pimpl->mScene;
 
@@ -1087,8 +1117,7 @@ void Importer::GetMemoryRequirements(aiMemoryInfo& in) const
     in.total = sizeof(aiScene);
 
     // 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);
         if (mScene->mMeshes[i]->HasPositions()) {
             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) {
             if (mScene->mMeshes[i]->HasVertexColors(a)) {
                 in.meshes += sizeof(aiColor4D) * mScene->mMeshes[i]->mNumVertices;
+            } else {
+                break;
             }
-            else break;
         }
         for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS;++a) {
             if (mScene->mMeshes[i]->HasTextureCoords(a)) {
                 in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices;
+            } else {
+                break;
             }
-            else break;
         }
         if (mScene->mMeshes[i]->HasBones()) {
             in.meshes += sizeof(void*) * mScene->mMeshes[i]->mNumBones;
@@ -1131,8 +1162,9 @@ void Importer::GetMemoryRequirements(aiMemoryInfo& in) const
         in.textures += sizeof(aiTexture);
         if (pc->mHeight) {
             in.textures += 4 * pc->mHeight * pc->mWidth;
+        } else {
+            in.textures += pc->mWidth;
         }
-        else in.textures += pc->mWidth;
     }
     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
 #   include "MMD/MMDImporter.h"
 #endif
+#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
+#   include "M3D/M3DImporter.h"
+#endif
 #ifndef ASSIMP_BUILD_NO_STEP_IMPORTER
 #   include "Importer/StepFile/StepFileImporter.h"
 #endif
@@ -223,6 +226,9 @@ void GetImporterInstanceList(std::vector< BaseImporter* >& out)
 #if (!defined ASSIMP_BUILD_NO_3DS_IMPORTER)
     out.push_back( new Discreet3DSImporter());
 #endif
+#if (!defined ASSIMP_BUILD_NO_M3D_IMPORTER)
+    out.push_back( new M3DImporter());
+#endif
 #if (!defined ASSIMP_BUILD_NO_MD3_IMPORTER)
     out.push_back( new MD3Importer());
 #endif

+ 7 - 0
code/Common/PostStepRegistry.cpp

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

+ 50 - 0
code/Common/SceneCombiner.cpp

@@ -1091,6 +1091,35 @@ void SceneCombiner::Copy( aiMesh** _dest, const aiMesh* src ) {
         aiFace& f = dest->mFaces[i];
         GetArrayCopy(f.mIndices,f.mNumIndices);
     }
+
+    // make a deep copy of all blend shapes
+    CopyPtrArray(dest->mAnimMeshes, dest->mAnimMeshes, dest->mNumAnimMeshes);
+}
+
+// ------------------------------------------------------------------------------------------------
+void SceneCombiner::Copy(aiAnimMesh** _dest, const aiAnimMesh* src) {
+    if (nullptr == _dest || nullptr == src) {
+        return;
+    }
+
+    aiAnimMesh* dest = *_dest = new aiAnimMesh();
+
+    // get a flat copy
+    ::memcpy(dest, src, sizeof(aiAnimMesh));
+
+    // and reallocate all arrays
+    GetArrayCopy(dest->mVertices, dest->mNumVertices);
+    GetArrayCopy(dest->mNormals, dest->mNumVertices);
+    GetArrayCopy(dest->mTangents, dest->mNumVertices);
+    GetArrayCopy(dest->mBitangents, dest->mNumVertices);
+
+    unsigned int n = 0;
+    while (dest->HasTextureCoords(n))
+        GetArrayCopy(dest->mTextureCoords[n++], dest->mNumVertices);
+
+    n = 0;
+    while (dest->HasVertexColors(n))
+        GetArrayCopy(dest->mColors[n++], dest->mNumVertices);
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -1167,6 +1196,7 @@ void SceneCombiner::Copy( aiAnimation** _dest, const aiAnimation* src ) {
 
     // and reallocate all arrays
     CopyPtrArray( dest->mChannels, src->mChannels, dest->mNumChannels );
+    CopyPtrArray( dest->mMorphMeshChannels, src->mMorphMeshChannels, dest->mNumMorphMeshChannels );
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -1186,6 +1216,26 @@ void SceneCombiner::Copy(aiNodeAnim** _dest, const aiNodeAnim* src) {
     GetArrayCopy( dest->mRotationKeys, dest->mNumRotationKeys );
 }
 
+void SceneCombiner::Copy(aiMeshMorphAnim** _dest, const aiMeshMorphAnim* src) {
+    if ( nullptr == _dest || nullptr == src ) {
+        return;
+    }
+
+    aiMeshMorphAnim* dest = *_dest = new aiMeshMorphAnim();
+
+    // get a flat copy
+    ::memcpy(dest,src,sizeof(aiMeshMorphAnim));
+
+    // and reallocate all arrays
+    GetArrayCopy( dest->mKeys, dest->mNumKeys );
+    for (ai_uint i = 0; i < dest->mNumKeys;++i) {
+        dest->mKeys[i].mValues = new unsigned int[dest->mKeys[i].mNumValuesAndWeights];
+        dest->mKeys[i].mWeights = new double[dest->mKeys[i].mNumValuesAndWeights];
+        ::memcpy(dest->mKeys[i].mValues, src->mKeys[i].mValues, dest->mKeys[i].mNumValuesAndWeights * sizeof(unsigned int));
+        ::memcpy(dest->mKeys[i].mWeights, src->mKeys[i].mWeights, dest->mKeys[i].mNumValuesAndWeights * sizeof(double));
+    }
+}
+
 // ------------------------------------------------------------------------------------------------
 void SceneCombiner::Copy( aiCamera** _dest,const  aiCamera* src) {
     if ( nullptr == _dest || nullptr == src ) {

+ 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 "ScenePrivate.h"
 
-static const unsigned int MajorVersion = 4;
-static const unsigned int MinorVersion = 1;
+#include "revision.h"
 
 // --------------------------------------------------------------------------------
 // Legal information string - don't remove this.
@@ -56,9 +55,9 @@ static const char* LEGAL_INFORMATION =
 "Open Asset Import Library (Assimp).\n"
 "A free C/C++ library to import various 3D file formats into applications\n\n"
 
-"(c) 2008-2017, assimp team\n"
+"(c) 2006-2019, assimp team\n"
 "License under the terms and conditions of the 3-clause BSD license\n"
-"http://assimp.sourceforge.net\n"
+"http://assimp.org\n"
 ;
 
 // ------------------------------------------------------------------------------------------------
@@ -67,16 +66,22 @@ ASSIMP_API const char*  aiGetLegalString  ()    {
     return LEGAL_INFORMATION;
 }
 
+// ------------------------------------------------------------------------------------------------
+// Get Assimp patch version
+ASSIMP_API unsigned int aiGetVersionPatch() {
+	return VER_PATCH;
+}
+
 // ------------------------------------------------------------------------------------------------
 // Get Assimp minor version
 ASSIMP_API unsigned int aiGetVersionMinor ()    {
-    return MinorVersion;
+    return VER_MINOR;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Get Assimp major version
 ASSIMP_API unsigned int aiGetVersionMajor ()    {
-    return MajorVersion;
+    return VER_MAJOR;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -104,9 +109,6 @@ ASSIMP_API unsigned int aiGetCompileFlags ()    {
     return flags;
 }
 
-// include current build revision, which is even updated from time to time -- :-)
-#include "revision.h"
-
 // ------------------------------------------------------------------------------------------------
 ASSIMP_API unsigned int aiGetVersionRevision() {
     return GitVersion;

+ 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
     const aiFace* const pcFaceEnd = pcFaces + iNumFaces;
-    if (!iNumVertices)  {
+    if (0 == iNumVertices)  {
         for (aiFace* pcFace = pcFaces; pcFace != pcFaceEnd; ++pcFace)   {
             ai_assert( nullptr != pcFace );
             ai_assert(3 == pcFace->mNumIndices);
@@ -68,7 +68,7 @@ VertexTriangleAdjacency::VertexTriangleAdjacency(aiFace *pcFaces,
         }
     }
 
-    mNumVertices = iNumVertices;
+    mNumVertices = iNumVertices + 1;
 
     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()
 : mName("")
-, mParent(NULL)
+, mParent(nullptr)
 , mNumChildren(0)
-, mChildren(NULL)
+, mChildren(nullptr)
 , mNumMeshes(0)
-, mMeshes(NULL)
-, mMetaData(NULL) {
+, mMeshes(nullptr)
+, mMetaData(nullptr) {
     // empty
 }
 
 aiNode::aiNode(const std::string& name)
 : mName(name)
-, mParent(NULL)
+, mParent(nullptr)
 , mNumChildren(0)
-, mChildren(NULL)
+, mChildren(nullptr)
 , mNumMeshes(0)
-, mMeshes(NULL)
-, mMetaData(NULL) {
+, mMeshes(nullptr)
+, mMetaData(nullptr) {
     // empty
 }
 
@@ -68,7 +68,7 @@ aiNode::aiNode(const std::string& name)
 aiNode::~aiNode() {
     // delete all children recursively
     // to make sure we won't crash if the data is invalid ...
-    if (mChildren && mNumChildren)
+    if (mNumChildren && mChildren)
     {
         for (unsigned int a = 0; a < mNumChildren; a++)
             delete mChildren[a];

+ 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 FBX
 {
-    const std::string NULL_RECORD = { // 13 null bytes
-        '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'
-    }; // who knows why
+    const std::string NULL_RECORD = { // 25 null bytes in 64-bit and 13 null bytes in 32-bit
+        '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',
+        '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'
+    }; // who knows why, it looks like two integers 32/64 bit (compressed and uncompressed sizes?) + 1 byte (might be compression type?)
     const std::string SEPARATOR = {'\x00', '\x01'}; // for use inside strings
     const std::string MAGIC_NODE_TAG = "_$AssimpFbx$"; // from import
     const int64_t SECOND = 46186158000; // FBX's kTime unit

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

+ 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 <assimp/StringComparison.h>
+#include <assimp/MathFunctions.h>
 
 #include <assimp/scene.h>
 
 #include <assimp/CreateAnimMesh.h>
+#include <assimp/commonMetaData.h>
+#include <assimp/StringUtils.h>
 
 #include <tuple>
 #include <memory>
@@ -66,7 +69,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <vector>
 #include <sstream>
 #include <iomanip>
-
+#include <cstdint>
+#include <iostream>
+#include <stdlib.h>
 
 namespace Assimp {
     namespace FBX {
@@ -75,9 +80,9 @@ namespace Assimp {
 
 #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()
         , lights()
         , cameras()
@@ -89,13 +94,19 @@ namespace Assimp {
         , mNodeNames()
         , anim_fps()
         , out(out)
-        , doc(doc)
-        , mRemoveEmptyBones( removeEmptyBones )
-        , mCurrentUnit(FbxUnit::cm) {
+        , doc(doc) {
             // animations need to be converted first since this will
             // populate the node_anim_chain_bits map, which is needed
             // to determine which nodes need to be generated.
             ConvertAnimations();
+            // Embedded textures in FBX could be connected to nothing but to itself,
+            // for instance Texture -> Video connection only but not to the main graph,
+            // The idea here is to traverse all objects to find these Textures and convert them,
+            // so later during material conversion it will find converted texture in the textures_converted array.
+            if (doc.Settings().readTextures)
+            {
+                ConvertOrphantEmbeddedTextures();
+            }
             ConvertRootNode();
 
             if (doc.Settings().readAllMaterials) {
@@ -119,7 +130,6 @@ namespace Assimp {
 
             ConvertGlobalSettings();
             TransferDataToScene();
-            ConvertToUnitScale(unit);
 
             // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE
             // to make sure the scene passes assimp's validation. FBX files
@@ -146,7 +156,7 @@ namespace Assimp {
             out->mRootNode->mName.Set(unique_name);
 
             // root has ID 0
-            ConvertNodes(0L, *out->mRootNode);
+            ConvertNodes(0L, out->mRootNode, out->mRootNode);
         }
 
         static std::string getAncestorBaseName(const aiNode* node)
@@ -180,8 +190,11 @@ namespace Assimp {
             GetUniqueName(original_name, unique_name);
             return unique_name;
         }
-
-        void FBXConverter::ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform) {
+        /// todo: pre-build node hierarchy
+        /// todo: get bone from stack
+        /// todo: make map of aiBone* to aiNode*
+        /// then update convert clusters to the new format
+        void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) {
             const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(id, "Model");
 
             std::vector<aiNode*> nodes;
@@ -192,62 +205,69 @@ namespace Assimp {
 
             try {
                 for (const Connection* con : conns) {
-
                     // ignore object-property links
                     if (con->PropertyName().length()) {
-                        continue;
+                        // really important we document why this is ignored.
+                        FBXImporter::LogInfo("ignoring property link - no docs on why this is ignored");
+                        continue; //?
                     }
 
+                    // convert connection source object into Object base class
                     const Object* const object = con->SourceObject();
                     if (nullptr == object) {
-                        FBXImporter::LogWarn("failed to convert source object for Model link");
+                        FBXImporter::LogError("failed to convert source object for Model link");
                         continue;
                     }
 
+                    // FBX Model::Cube, Model::Bone001, etc elements
+                    // This detects if we can cast the object into this model structure.
                     const Model* const model = dynamic_cast<const Model*>(object);
 
                     if (nullptr != model) {
                         nodes_chain.clear();
                         post_nodes_chain.clear();
 
-                        aiMatrix4x4 new_abs_transform = parent_transform;
-
-                        std::string unique_name = MakeUniqueNodeName(model, parent);
-
+                        aiMatrix4x4 new_abs_transform = parent->mTransformation;
+                        std::string node_name = FixNodeName(model->Name());
                         // even though there is only a single input node, the design of
                         // assimp (or rather: the complicated transformation chain that
                         // is employed by fbx) means that we may need multiple aiNode's
                         // to represent a fbx node's transformation.
-                        const bool need_additional_node = GenerateTransformationNodeChain(*model, unique_name, nodes_chain, post_nodes_chain);
 
+
+                        // generate node transforms - this includes pivot data
+                        // if need_additional_node is true then you t
+                        const bool need_additional_node = GenerateTransformationNodeChain(*model, node_name, nodes_chain, post_nodes_chain);
+
+                        // assert that for the current node we must have at least a single transform
                         ai_assert(nodes_chain.size());
 
                         if (need_additional_node) {
-                            nodes_chain.push_back(new aiNode(unique_name));
+                            nodes_chain.push_back(new aiNode(node_name));
                         }
 
                         //setup metadata on newest node
                         SetupNodeMetadata(*model, *nodes_chain.back());
 
                         // link all nodes in a row
-                        aiNode* last_parent = &parent;
-                        for (aiNode* prenode : nodes_chain) {
-                            ai_assert(prenode);
+                        aiNode* last_parent = parent;
+                        for (aiNode* child : nodes_chain) {
+                            ai_assert(child);
 
-                            if (last_parent != &parent) {
+                            if (last_parent != parent) {
                                 last_parent->mNumChildren = 1;
                                 last_parent->mChildren = new aiNode*[1];
-                                last_parent->mChildren[0] = prenode;
+                                last_parent->mChildren[0] = child;
                             }
 
-                            prenode->mParent = last_parent;
-                            last_parent = prenode;
+                            child->mParent = last_parent;
+                            last_parent = child;
 
-                            new_abs_transform *= prenode->mTransformation;
+                            new_abs_transform *= child->mTransformation;
                         }
 
                         // attach geometry
-                        ConvertModel(*model, *nodes_chain.back(), new_abs_transform);
+                        ConvertModel(*model, nodes_chain.back(), root_node, new_abs_transform);
 
                         // check if there will be any child nodes
                         const std::vector<const Connection*>& child_conns
@@ -259,7 +279,7 @@ namespace Assimp {
                             for (aiNode* postnode : post_nodes_chain) {
                                 ai_assert(postnode);
 
-                                if (last_parent != &parent) {
+                                if (last_parent != parent) {
                                     last_parent->mNumChildren = 1;
                                     last_parent->mChildren = new aiNode*[1];
                                     last_parent->mChildren[0] = postnode;
@@ -281,15 +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) {
-                            ConvertLights(*model, unique_name);
+                            ConvertLights(*model, node_name);
                         }
 
                         if (doc.Settings().readCameras) {
-                            ConvertCameras(*model, unique_name);
+                            ConvertCameras(*model, node_name);
                         }
 
                         nodes.push_back(nodes_chain.front());
@@ -298,11 +318,17 @@ namespace Assimp {
                 }
 
                 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&) {
                 Util::delete_fun<aiNode> deleter;
@@ -555,7 +581,7 @@ namespace Assimp {
                 return;
             }
 
-            const float angle_epsilon = 1e-6f;
+            const float angle_epsilon = Math::getEpsilon<float>();
 
             out = aiMatrix4x4();
 
@@ -685,30 +711,37 @@ namespace Assimp {
             bool ok;
 
             aiMatrix4x4 chain[TransformationComp_MAXIMUM];
+
+            ai_assert(TransformationComp_MAXIMUM < 32);
+            std::uint32_t chainBits = 0;
+            // A node won't need a node chain if it only has these.
+            const std::uint32_t chainMaskSimple = (1 << TransformationComp_Translation) + (1 << TransformationComp_Scaling) + (1 << TransformationComp_Rotation);
+            // A node will need a node chain if it has any of these.
+            const std::uint32_t chainMaskComplex = ((1 << (TransformationComp_MAXIMUM)) - 1) - chainMaskSimple;
+
             std::fill_n(chain, static_cast<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4());
 
             // 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);
-            bool is_complex = false;
 
             const aiVector3D& PreRotation = PropertyGet<aiVector3D>(props, "PreRotation", ok);
             if (ok && PreRotation.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_PreRotation);
 
                 GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PreRotation, chain[TransformationComp_PreRotation]);
             }
 
             const aiVector3D& PostRotation = PropertyGet<aiVector3D>(props, "PostRotation", ok);
             if (ok && PostRotation.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_PostRotation);
 
                 GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PostRotation, chain[TransformationComp_PostRotation]);
             }
 
             const aiVector3D& RotationPivot = PropertyGet<aiVector3D>(props, "RotationPivot", ok);
             if (ok && RotationPivot.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_RotationPivot) | (1 << TransformationComp_RotationPivotInverse);
 
                 aiMatrix4x4::Translation(RotationPivot, chain[TransformationComp_RotationPivot]);
                 aiMatrix4x4::Translation(-RotationPivot, chain[TransformationComp_RotationPivotInverse]);
@@ -716,21 +749,21 @@ namespace Assimp {
 
             const aiVector3D& RotationOffset = PropertyGet<aiVector3D>(props, "RotationOffset", ok);
             if (ok && RotationOffset.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_RotationOffset);
 
                 aiMatrix4x4::Translation(RotationOffset, chain[TransformationComp_RotationOffset]);
             }
 
             const aiVector3D& ScalingOffset = PropertyGet<aiVector3D>(props, "ScalingOffset", ok);
             if (ok && ScalingOffset.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_ScalingOffset);
 
                 aiMatrix4x4::Translation(ScalingOffset, chain[TransformationComp_ScalingOffset]);
             }
 
             const aiVector3D& ScalingPivot = PropertyGet<aiVector3D>(props, "ScalingPivot", ok);
             if (ok && ScalingPivot.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_ScalingPivot) | (1 << TransformationComp_ScalingPivotInverse);
 
                 aiMatrix4x4::Translation(ScalingPivot, chain[TransformationComp_ScalingPivot]);
                 aiMatrix4x4::Translation(-ScalingPivot, chain[TransformationComp_ScalingPivotInverse]);
@@ -738,22 +771,28 @@ namespace Assimp {
 
             const aiVector3D& Translation = PropertyGet<aiVector3D>(props, "Lcl Translation", ok);
             if (ok && Translation.SquareLength() > zero_epsilon) {
+                chainBits = chainBits | (1 << TransformationComp_Translation);
+
                 aiMatrix4x4::Translation(Translation, chain[TransformationComp_Translation]);
             }
 
             const aiVector3D& Scaling = PropertyGet<aiVector3D>(props, "Lcl Scaling", ok);
             if (ok && (Scaling - all_ones).SquareLength() > zero_epsilon) {
+                chainBits = chainBits | (1 << TransformationComp_Scaling);
+
                 aiMatrix4x4::Scaling(Scaling, chain[TransformationComp_Scaling]);
             }
 
             const aiVector3D& Rotation = PropertyGet<aiVector3D>(props, "Lcl Rotation", ok);
             if (ok && Rotation.SquareLength() > zero_epsilon) {
+                chainBits = chainBits | (1 << TransformationComp_Rotation);
+
                 GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]);
             }
 
             const aiVector3D& GeometricScaling = PropertyGet<aiVector3D>(props, "GeometricScaling", ok);
             if (ok && (GeometricScaling - all_ones).SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_GeometricScaling);
                 aiMatrix4x4::Scaling(GeometricScaling, chain[TransformationComp_GeometricScaling]);
                 aiVector3D GeometricScalingInverse = GeometricScaling;
                 bool canscale = true;
@@ -768,13 +807,14 @@ namespace Assimp {
                     }
                 }
                 if (canscale) {
+                    chainBits = chainBits | (1 << TransformationComp_GeometricScalingInverse);
                     aiMatrix4x4::Scaling(GeometricScalingInverse, chain[TransformationComp_GeometricScalingInverse]);
                 }
             }
 
             const aiVector3D& GeometricRotation = PropertyGet<aiVector3D>(props, "GeometricRotation", ok);
             if (ok && GeometricRotation.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_GeometricRotation) | (1 << TransformationComp_GeometricRotationInverse);
                 GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotation]);
                 GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotationInverse]);
                 chain[TransformationComp_GeometricRotationInverse].Inverse();
@@ -782,7 +822,7 @@ namespace Assimp {
 
             const aiVector3D& GeometricTranslation = PropertyGet<aiVector3D>(props, "GeometricTranslation", ok);
             if (ok && GeometricTranslation.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_GeometricTranslation) | (1 << TransformationComp_GeometricTranslationInverse);
                 aiMatrix4x4::Translation(GeometricTranslation, chain[TransformationComp_GeometricTranslation]);
                 aiMatrix4x4::Translation(-GeometricTranslation, chain[TransformationComp_GeometricTranslationInverse]);
             }
@@ -790,12 +830,12 @@ namespace Assimp {
             // is_complex needs to be consistent with NeedsComplexTransformationChain()
             // or the interplay between this code and the animation converter would
             // not be guaranteed.
-            ai_assert(NeedsComplexTransformationChain(model) == is_complex);
+            //ai_assert(NeedsComplexTransformationChain(model) == ((chainBits & chainMaskComplex) != 0));
 
             // now, if we have more than just Translation, Scaling and Rotation,
             // we need to generate a full node chain to accommodate for assimp's
             // lack to express pivots and offsets.
-            if (is_complex && doc.Settings().preservePivots) {
+            if ((chainBits & chainMaskComplex) && doc.Settings().preservePivots) {
                 FBXImporter::LogInfo("generating full transformation chain for node: " + name);
 
                 // query the anim_chain_bits dictionary to find out which chain elements
@@ -808,7 +848,7 @@ namespace Assimp {
                 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) {
                     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;
                     }
 
@@ -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();
 
@@ -904,11 +945,12 @@ namespace Assimp {
                 const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*>(geo);
                 const LineGeometry* const line = dynamic_cast<const LineGeometry*>(geo);
                 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));
                 }
                 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));
                 }
                 else {
@@ -917,15 +959,16 @@ namespace Assimp {
             }
 
             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;
 
@@ -949,18 +992,18 @@ namespace Assimp {
                 const MatIndexArray::value_type base = mindices[0];
                 for (MatIndexArray::value_type index : mindices) {
                     if (index != base) {
-                        return ConvertMeshMultiMaterial(mesh, model, node_global_transform, nd);
+                        return ConvertMeshMultiMaterial(mesh, model, parent, root_node, absolute_transform);
                     }
                 }
             }
 
             // faster code-path, just copy the data
-            temp.push_back(ConvertMeshSingleMaterial(mesh, model, node_global_transform, nd));
+            temp.push_back(ConvertMeshSingleMaterial(mesh, model, absolute_transform, parent, root_node));
             return temp;
         }
 
         std::vector<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;
 
@@ -971,7 +1014,7 @@ namespace Assimp {
                 return temp;
             }
 
-            aiMesh* const out_mesh = SetupEmptyMesh(line, nd);
+            aiMesh* const out_mesh = SetupEmptyMesh(line, root_node);
             out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
 
             // copy vertices
@@ -1006,7 +1049,7 @@ namespace Assimp {
             return temp;
         }
 
-        aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode& nd)
+        aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode *parent)
         {
             aiMesh* const out_mesh = new aiMesh();
             meshes.push_back(out_mesh);
@@ -1023,17 +1066,18 @@ namespace Assimp {
             }
             else
             {
-                out_mesh->mName = nd.mName;
+                out_mesh->mName = parent->mName;
             }
 
             return out_mesh;
         }
 
-        unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model,
-            const aiMatrix4x4& node_global_transform, aiNode& nd)
+        unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model,
+                                                             const aiMatrix4x4 &absolute_transform, aiNode *parent,
+                                                             aiNode *root_node)
         {
             const MatIndexArray& mindices = mesh.GetMaterialIndices();
-            aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd);
+            aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent);
 
             const std::vector<aiVector3D>& vertices = mesh.GetVertices();
             const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
@@ -1100,7 +1144,7 @@ namespace Assimp {
                         binormals = &tempBinormals;
                     }
                     else {
-                        binormals = NULL;
+                        binormals = nullptr;
                     }
                 }
 
@@ -1150,8 +1194,9 @@ namespace Assimp {
                 ConvertMaterialForMesh(out_mesh, model, mesh, mindices[0]);
             }
 
-            if (doc.Settings().readWeights && mesh.DeformerSkin() != NULL) {
-                ConvertWeights(out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION);
+            if (doc.Settings().readWeights && mesh.DeformerSkin() != nullptr) {
+                ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, NO_MATERIAL_SEPARATION,
+                               nullptr);
             }
 
             std::vector<aiAnimMesh*> animMeshes;
@@ -1196,8 +1241,10 @@ namespace Assimp {
             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();
             ai_assert(mindices.size());
@@ -1208,7 +1255,7 @@ namespace Assimp {
             for (MatIndexArray::value_type index : mindices) {
                 if (had.find(index) == had.end()) {
 
-                    indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, node_global_transform, nd));
+                    indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, parent, root_node, absolute_transform));
                     had.insert(index);
                 }
             }
@@ -1216,18 +1263,18 @@ namespace Assimp {
             return indices;
         }
 
-        unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
-            MatIndexArray::value_type index,
-            const aiMatrix4x4& node_global_transform,
-            aiNode& nd)
+        unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model,
+                                                            MatIndexArray::value_type index,
+                                                            aiNode *parent, aiNode *root_node,
+                                                            const aiMatrix4x4 &absolute_transform)
         {
-            aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd);
+            aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent);
 
             const MatIndexArray& mindices = mesh.GetMaterialIndices();
             const std::vector<aiVector3D>& vertices = mesh.GetVertices();
             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_vertices = 0;
@@ -1287,7 +1334,7 @@ namespace Assimp {
                         binormals = &tempBinormals;
                     }
                     else {
-                        binormals = NULL;
+                        binormals = nullptr;
                     }
                 }
 
@@ -1386,7 +1433,7 @@ namespace Assimp {
             ConvertMaterialForMesh(out_mesh, model, mesh, index);
 
             if (process_weights) {
-                ConvertWeights(out_mesh, model, mesh, node_global_transform, index, &reverseMapping);
+                ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, index, &reverseMapping);
             }
 
             std::vector<aiAnimMesh*> animMeshes;
@@ -1436,10 +1483,10 @@ namespace Assimp {
             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());
 
@@ -1450,41 +1497,35 @@ namespace Assimp {
             const Skin& sk = *geo.DeformerSkin();
 
             std::vector<aiBone*> bones;
-            bones.reserve(sk.Clusters().size());
 
             const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION;
             ai_assert(no_mat_check || outputVertStartIndices);
 
             try {
-
+                // iterate over the sub deformers
                 for (const Cluster* cluster : sk.Clusters()) {
                     ai_assert(cluster);
 
                     const WeightIndexArray& indices = cluster->GetIndices();
 
-                    if (indices.empty() && mRemoveEmptyBones ) {
-                        continue;
-                    }
-
                     const MatIndexArray& mats = geo.GetMaterialIndices();
 
-                    bool ok = false;
-
                     const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
 
                     count_out_indices.clear();
                     index_out_indices.clear();
                     out_indices.clear();
 
+
                     // now check if *any* of these weights is contained in the output mesh,
                     // taking notes so we don't need to do it twice.
                     for (WeightIndexArray::value_type index : indices) {
 
                         unsigned int count = 0;
                         const unsigned int* const out_idx = geo.ToOutputVertexIndex(index, count);
-                        // ToOutputVertexIndex only returns NULL if index is out of bounds
+                        // ToOutputVertexIndex only returns nullptr if index is out of bounds
                         // which should never happen
-                        ai_assert(out_idx != NULL);
+                        ai_assert(out_idx != nullptr);
 
                         index_out_indices.push_back(no_index_sentinel);
                         count_out_indices.push_back(0);
@@ -1509,75 +1550,110 @@ namespace Assimp {
                                     out_indices.push_back(std::distance(outputVertStartIndices->begin(), it));
                                 }
 
-                                ++count_out_indices.back();
-                                ok = true;
+                                ++count_out_indices.back();                               
                             }
                         }
                     }
-                    
+
                     // if we found at least one, generate the output bones
                     // XXX this could be heavily simplified by collecting the bone
                     // data in a single step.
-                    if (ok && mRemoveEmptyBones) {
-                        ConvertCluster(bones, model, *cluster, out_indices, index_out_indices,
-                            count_out_indices, node_global_transform);
-                    }
+                    ConvertCluster(bones, cluster, out_indices, index_out_indices,
+                                   count_out_indices, absolute_transform, parent, root_node);
                 }
+
+                bone_map.clear();
             }
-            catch (std::exception&) {
+            catch (std::exception&e) {
                 std::for_each(bones.begin(), bones.end(), Util::delete_fun<aiBone>());
                 throw;
             }
 
             if (bones.empty()) {
+                out->mBones = nullptr;
+                out->mNumBones = 0;
                 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,
@@ -1707,7 +1783,7 @@ namespace Assimp {
                 bool textureReady = false; //tells if our texture is ready (if it was loaded or if it was found)
                 unsigned int index;
 
-                VideoMap::const_iterator it = textures_converted.find(media);
+                VideoMap::const_iterator it = textures_converted.find(*media);
                 if (it != textures_converted.end()) {
                     index = (*it).second;
                     textureReady = true;
@@ -1715,7 +1791,7 @@ namespace Assimp {
                 else {
                     if (media->ContentLength() > 0) {
                         index = ConvertVideo(*media);
-                        textures_converted[media] = index;
+                        textures_converted[*media] = index;
                         textureReady = true;
                     }
                 }
@@ -1998,6 +2074,28 @@ namespace Assimp {
             TrySetTextureProperties(out_mat, textures, "Maya|SpecularTexture", aiTextureType_SPECULAR, mesh);
             TrySetTextureProperties(out_mat, textures, "Maya|FalloffTexture", aiTextureType_OPACITY, mesh);
             TrySetTextureProperties(out_mat, textures, "Maya|ReflectionMapTexture", aiTextureType_REFLECTION, mesh);
+            
+            // Maya PBR
+            TrySetTextureProperties(out_mat, textures, "Maya|baseColor|file", aiTextureType_BASE_COLOR, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|normalCamera|file", aiTextureType_NORMAL_CAMERA, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|emissionColor|file", aiTextureType_EMISSION_COLOR, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|metalness|file", aiTextureType_METALNESS, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|diffuseRoughness|file", aiTextureType_DIFFUSE_ROUGHNESS, mesh);
+            
+            // Maya stingray
+            TrySetTextureProperties(out_mat, textures, "Maya|TEX_color_map|file", aiTextureType_BASE_COLOR, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|TEX_normal_map|file", aiTextureType_NORMAL_CAMERA, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|TEX_emissive_map|file", aiTextureType_EMISSION_COLOR, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|TEX_metallic_map|file", aiTextureType_METALNESS, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|TEX_roughness_map|file", aiTextureType_DIFFUSE_ROUGHNESS, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|TEX_ao_map|file", aiTextureType_AMBIENT_OCCLUSION, mesh);
+
+            // 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)
@@ -2224,13 +2322,13 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
             if (media != nullptr && media->ContentLength() > 0) {
                 unsigned int index;
 
-                VideoMap::const_iterator it = textures_converted.find(media);
+                VideoMap::const_iterator it = textures_converted.find(*media);
                 if (it != textures_converted.end()) {
                     index = (*it).second;
                 }
                 else {
                     index = ConvertVideo(*media);
-                    textures_converted[media] = index;
+                    textures_converted[*media] = index;
                 }
 
                 // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture)
@@ -2658,7 +2756,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
         // sanity check whether the input is ok
         static void validateAnimCurveNodes(const std::vector<const AnimationCurveNode*>& curves,
             bool strictMode) {
-            const Object* target(NULL);
+            const Object* target(nullptr);
             for (const AnimationCurveNode* node : curves) {
                 if (!target) {
                     target = node->Target();
@@ -2689,7 +2787,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
 #ifdef ASSIMP_BUILD_DEBUG
             validateAnimCurveNodes(curves, doc.Settings().strictMode);
 #endif
-            const AnimationCurveNode* curve_node = NULL;
+            const AnimationCurveNode* curve_node = nullptr;
             for (const AnimationCurveNode* node : curves) {
                 ai_assert(node);
 
@@ -2949,7 +3047,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
                 TransformationCompDefaultValue(comp)
                 );
 
-            const float epsilon = 1e-6f;
+            const float epsilon = Math::getEpsilon<float>();
             return (dyn_val - static_val).SquareLength() < epsilon;
         }
 
@@ -3514,7 +3612,9 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
                 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(1, "UpAxisSign", doc.GlobalSettings().UpAxisSign());
             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(13, "TimeSpanStop", doc.GlobalSettings().TimeSpanStop());
             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->mNumMeshes);
 
-            // note: the trailing () ensures initialization with NULL - not
+            // note: the trailing () ensures initialization with nullptr - not
             // many C++ users seem to know this, so pointing it out to avoid
             // confusion why this code works.
 
@@ -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

+ 57 - 46
code/FBX/FBXConverter.h

@@ -76,23 +76,13 @@ namespace Assimp {
 namespace FBX {
 
 class Document;
-
-enum class FbxUnit {
-    cm = 0,
-    m,
-    km,
-    NumUnits,
-
-    Undefined
-};
-
 /** 
  *  Convert a FBX #Document to #aiScene
  *  @param out Empty scene to be populated
  *  @param doc Parsed FBX document
  *  @param removeEmptyBones Will remove bones, which do not have any references to vertices.
  */
-void ConvertToAssimpScene(aiScene* out, const Document& doc, bool removeEmptyBones, FbxUnit unit);
+void ConvertToAssimpScene(aiScene* out, const Document& doc, bool removeEmptyBones);
 
 /** Dummy class to encapsulate the conversion process */
 class FBXConverter {
@@ -123,7 +113,7 @@ public:
     };
 
 public:
-    FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones, FbxUnit unit);
+    FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones);
     ~FBXConverter();
 
 private:
@@ -133,7 +123,7 @@ private:
 
     // ------------------------------------------------------------------------------------------------
     // collect and assign child nodes
-    void ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4());
+    void ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node);
 
     // ------------------------------------------------------------------------------------------------
     void ConvertLights(const Model& model, const std::string &orig_name );
@@ -189,32 +179,35 @@ private:
     void SetupNodeMetadata(const Model& model, aiNode& nd);
 
     // ------------------------------------------------------------------------------------------------
-    void ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform);
+    void ConvertModel(const Model &model, aiNode *parent, aiNode *root_node,
+                      const aiMatrix4x4 &absolute_transform);
     
     // ------------------------------------------------------------------------------------------------
     // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed
-    std::vector<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,
-        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() */
@@ -227,17 +220,17 @@ private:
     *  - outputVertStartIndices is only used when a material index is specified, it gives for
     *    each output vertex the DOM index it maps to.
     */
-    void ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo,
-        const aiMatrix4x4& node_global_transform = aiMatrix4x4(),
-        unsigned int materialIndex = NO_MATERIAL_SEPARATION,
-        std::vector<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,
@@ -428,16 +421,18 @@ private:
         double& minTime,
         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
     void TransferDataToScene();
 
+    // ------------------------------------------------------------------------------------------------
+    // FBX file could have embedded textures not connected to anything
+    void ConvertOrphantEmbeddedTextures();
+
 private:
     // 0: not assigned yet, others: index is value - 1
     unsigned int defaultMaterialIndex;
@@ -449,31 +444,47 @@ private:
     std::vector<aiCamera*> cameras;
     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;
 
-    using VideoMap = std::map<const Video*, unsigned int>;
+    using VideoMap = std::fbx_unordered_map<const Video, unsigned int>;
     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;
 
     // 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;
 
     // 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;
 
+    // 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;
 
     aiScene* const out;
     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();
     }
 
-    // if this is the root object, we return a dummy since there
-    // is no root object int he fbx file - it is just referenced
-    // with id 0.
-    if(id == 0L) {
-        object.reset(new Object(id, element, "Model::RootNode"));
-        return object.get();
-    }
-
     const Token& key = element.KeyToken();
     const TokenList& tokens = element.Tokens();
 

+ 37 - 2
code/FBX/FBXDocument.h

@@ -637,6 +637,20 @@ public:
         return ptr;
     }
 
+    bool operator==(const Video& other) const
+    {
+        return (
+               type == other.type
+            && relativeFileName == other.relativeFileName
+            && fileName == other.fileName
+        );
+    }
+
+    bool operator<(const Video& other) const
+    {
+        return std::tie(type, relativeFileName, fileName) < std::tie(other.type, other.relativeFileName, other.fileName);
+    }
+
 private:
     std::string type;
     std::string relativeFileName;
@@ -1005,10 +1019,10 @@ public:
 // during their entire lifetime (Document). FBX files have
 // up to many thousands of objects (most of which we never use),
 // so the memory overhead for them should be kept at a minimum.
-typedef std::map<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::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
  *  be accessed via Document.Globals(). */
@@ -1177,4 +1191,25 @@ private:
 } // Namespace FBX
 } // 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

+ 7 - 7
code/FBX/FBXExportNode.cpp

@@ -325,9 +325,9 @@ void FBX::Node::BeginBinary(Assimp::StreamWriterLE &s)
     this->start_pos = s.Tell();
 
     // placeholders for end pos and property section info
-    s.PutU4(0); // end pos
-    s.PutU4(0); // number of properties
-    s.PutU4(0); // total property section length
+    s.PutU8(0); // end pos
+    s.PutU8(0); // number of properties
+    s.PutU8(0); // total property section length
 
     // node name
     s.PutU1(uint8_t(name.size())); // length of node name
@@ -352,9 +352,9 @@ void FBX::Node::EndPropertiesBinary(
     size_t pos = s.Tell();
     ai_assert(pos > property_start);
     size_t property_section_size = pos - property_start;
-    s.Seek(start_pos + 4);
-    s.PutU4(uint32_t(num_properties));
-    s.PutU4(uint32_t(property_section_size));
+    s.Seek(start_pos + 8); // 8 bytes of uint64_t of end_pos
+    s.PutU8(num_properties);
+    s.PutU8(property_section_size);
     s.Seek(pos);
 }
 
@@ -375,7 +375,7 @@ void FBX::Node::EndBinary(
     // now go back and write initial pos
     this->end_pos = s.Tell();
     s.Seek(start_pos);
-    s.PutU4(uint32_t(end_pos));
+    s.PutU8(end_pos);
     s.Seek(end_pos);
 }
 

+ 1 - 5
code/FBX/FBXExportProperty.cpp

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

+ 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 <array>
 #include <unordered_set>
+#include <numeric>
 
 // RESOURCES:
 // https://code.blender.org/2013/08/fbx-binary-file-format-specification/
@@ -80,8 +81,8 @@ using namespace Assimp::FBX;
 // some constants that we'll use for writing metadata
 namespace Assimp {
 namespace FBX {
-    const std::string EXPORT_VERSION_STR = "7.4.0";
-    const uint32_t EXPORT_VERSION_INT = 7400; // 7.4 == 2014/2015
+    const std::string EXPORT_VERSION_STR = "7.5.0";
+    const uint32_t EXPORT_VERSION_INT = 7500; // 7.5 == 2016+
     // FBX files have some hashed values that depend on the creation time field,
     // but for now we don't actually know how to generate these.
     // what we can do is set them to a known-working version.
@@ -1005,6 +1006,9 @@ void FBXExporter::WriteObjects ()
     object_node.EndProperties(outstream, binary, indent);
     object_node.BeginChildren(outstream, binary, indent);
 
+    bool bJoinIdenticalVertices = mProperties->GetPropertyBool("bJoinIdenticalVertices", true);
+    std::vector<std::vector<int32_t>> vVertexIndice;//save vertex_indices as it is needed later
+
     // geometry (aiMesh)
     mesh_uids.clear();
     indent = 1;
@@ -1031,21 +1035,35 @@ void FBXExporter::WriteObjects ()
         std::vector<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;
-                flattened_vertices.push_back(vtx[0]);
-                flattened_vertices.push_back(vtx[1]);
-                flattened_vertices.push_back(vtx[2]);
-                ++index;
-            } else {
-                vertex_indices.push_back(int32_t(elem->second));
+        if(bJoinIdenticalVertices){
+            int32_t index = 0;
+            for (size_t vi = 0; vi < m->mNumVertices; ++vi) {
+                aiVector3D vtx = m->mVertices[vi];
+                auto elem = index_by_vertex_value.find(vtx);
+                if (elem == index_by_vertex_value.end()) {
+                    vertex_indices.push_back(index);
+                    index_by_vertex_value[vtx] = index;
+                    flattened_vertices.push_back(vtx[0]);
+                    flattened_vertices.push_back(vtx[1]);
+                    flattened_vertices.push_back(vtx[2]);
+                    ++index;
+                } else {
+                    vertex_indices.push_back(int32_t(elem->second));
+                }
             }
         }
+        else { // do not join vertex, respect the export flag
+            vertex_indices.resize(m->mNumVertices);
+            std::iota(vertex_indices.begin(), vertex_indices.end(), 0);
+            for(unsigned int v = 0; v < m->mNumVertices; ++ v) {
+                aiVector3D vtx = m->mVertices[v];
+                flattened_vertices.push_back(vtx.x);
+                flattened_vertices.push_back(vtx.y);
+                flattened_vertices.push_back(vtx.z);
+            }
+        }
+        vVertexIndice.push_back(vertex_indices);
+
         FBX::Node::WritePropertyNode(
             "Vertices", flattened_vertices, outstream, binary, indent
         );
@@ -1116,6 +1134,51 @@ void FBXExporter::WriteObjects ()
             normals.End(outstream, binary, indent, true);
         }
 
+        // colors, if any
+        // TODO only one color channel currently
+        const int32_t colorChannelIndex = 0;
+        if (m->HasVertexColors(colorChannelIndex)) {
+            FBX::Node vertexcolors("LayerElementColor", int32_t(colorChannelIndex));
+            vertexcolors.Begin(outstream, binary, indent);
+            vertexcolors.DumpProperties(outstream, binary, indent);
+            vertexcolors.EndProperties(outstream, binary, indent);
+            vertexcolors.BeginChildren(outstream, binary, indent);
+            indent = 3;
+            FBX::Node::WritePropertyNode(
+                "Version", int32_t(101), outstream, binary, indent
+            );
+            char layerName[8];
+            sprintf(layerName, "COLOR_%d", colorChannelIndex);
+            FBX::Node::WritePropertyNode(
+                "Name", (const char*)layerName, outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "MappingInformationType", "ByPolygonVertex",
+                outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "ReferenceInformationType", "Direct",
+                outstream, binary, indent
+            );
+            std::vector<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
         for (size_t uvi = 0; uvi < m->GetNumUVChannels(); ++uvi) {
             if (m->mNumUVComponents[uvi] > 2) {
@@ -1209,6 +1272,11 @@ void FBXExporter::WriteObjects ()
         le.AddChild("Type", "LayerElementNormal");
         le.AddChild("TypedIndex", int32_t(0));
         layer.AddChild(le);
+        // TODO only 1 color channel currently
+        le = FBX::Node("LayerElement");
+        le.AddChild("Type", "LayerElementColor");
+        le.AddChild("TypedIndex", int32_t(0));
+        layer.AddChild(le);
         le = FBX::Node("LayerElement");
         le.AddChild("Type", "LayerElementMaterial");
         le.AddChild("TypedIndex", int32_t(0));
@@ -1219,6 +1287,16 @@ void FBXExporter::WriteObjects ()
         layer.AddChild(le);
         layer.Dump(outstream, binary, indent);
 
+        for(unsigned int lr = 1; lr < m->GetNumUVChannels(); ++ lr)
+        {
+            FBX::Node layerExtra("Layer", int32_t(lr));
+            layerExtra.AddChild("Version", int32_t(100));
+            FBX::Node leExtra("LayerElement");
+            leExtra.AddChild("Type", "LayerElementUV");
+            leExtra.AddChild("TypedIndex", int32_t(lr));
+            layerExtra.AddChild(leExtra);
+            layerExtra.Dump(outstream, binary, indent);
+        }
         // finish the node record
         indent = 1;
         n.End(outstream, binary, indent, true);
@@ -1617,6 +1695,17 @@ void FBXExporter::WriteObjects ()
     // at the same time we can build a list of all the skeleton nodes,
     // which will be used later to mark them as type "limbNode".
     std::unordered_set<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.
     std::map<std::string,aiNode*> node_by_bone;
     for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
@@ -1660,6 +1749,11 @@ void FBXExporter::WriteObjects ()
                 if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) {
                     continue;
                 }
+                //not a bone in scene && no effect in transform
+                if(setAllBoneNamesInScene.find(node_name)==setAllBoneNamesInScene.end()
+                   && parent->mTransformation == mxTransIdentity) {
+                        continue;
+                }
                 // otherwise check if this is the root of the skeleton
                 bool end = false;
                 // is the mesh part of this node?
@@ -1680,8 +1774,7 @@ void FBXExporter::WriteObjects ()
                     }
                     if (end) { break; }
                 }
-                limbnodes.insert(parent);
-                skeleton.insert(parent);
+                
                 // if it was the skeleton root we can finish here
                 if (end) { break; }
             }
@@ -1723,28 +1816,8 @@ void FBXExporter::WriteObjects ()
         // connect it
         connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]);
 
-        // we will be indexing by vertex...
-        // but there might be a different number of "vertices"
-        // between assimp and our output FBX.
-        // this code is cut-and-pasted from the geometry section above...
-        // ideally this should not be so.
-        // ---
-        // index of original vertex in vertex data vector
-        std::vector<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.
         // 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("UserData", "", "");
 
+            std::set<int32_t> setWeightedVertex;
             // add indices and weights, if any
             if (b) {
                 std::vector<int32_t> subdef_indices;
@@ -1794,7 +1868,8 @@ void FBXExporter::WriteObjects ()
                 int32_t last_index = -1;
                 for (size_t wi = 0; wi < b->mNumWeights; ++wi) {
                     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
                         // TODO, FIXME: this assumes identically-located vertices
                         // will always deform in the same way.
@@ -1804,6 +1879,7 @@ void FBXExporter::WriteObjects ()
                         // identical vertex.
                         continue;
                     }
+                    setWeightedVertex.insert(vi);
                     subdef_indices.push_back(vi);
                     subdef_weights.push_back(b->mWeights[wi].mWeight);
                     last_index = vi;
@@ -1822,41 +1898,10 @@ void FBXExporter::WriteObjects ()
             inverse_bone_xform.Inverse();
             aiMatrix4x4 tr = inverse_bone_xform * mesh_xform;
 
-            // this should be the same as the bone's mOffsetMatrix.
-            // if it's not the same, the skeleton isn't in the bind pose.
-            const float epsilon = 1e-4f; // some error is to be expected
-            bool bone_xform_okay = true;
-            if (b && ! tr.Equal(b->mOffsetMatrix, epsilon)) {
-                not_in_bind_pose.insert(b);
-                bone_xform_okay = false;
-            }
+            sdnode.AddChild("Transform", tr);
 
-            // if we have a bone we should use the mOffsetMatrix,
-            // otherwise try to just use the calculated transform.
-            if (b) {
-                sdnode.AddChild("Transform", b->mOffsetMatrix);
-            } else {
-                sdnode.AddChild("Transform", tr);
-            }
-            // note: it doesn't matter if we mix these,
-            // because if they disagree we'll throw an exception later.
-            // it could be that the skeleton is not in the bone pose
-            // but all bones are still defined,
-            // in which case this would use the mOffsetMatrix for everything
-            // and a correct skeleton would still be output.
-
-            // transformlink should be the position of the bone in world space.
-            // if the bone is in the bind pose (or nonexistent),
-            // we can just use the matrix we already calculated
-            if (bone_xform_okay) {
-                sdnode.AddChild("TransformLink", bone_xform);
-            // otherwise we can only work it out using the mesh position.
-            } else {
-                aiMatrix4x4 trl = b->mOffsetMatrix;
-                trl.Inverse();
-                trl *= mesh_xform;
-                sdnode.AddChild("TransformLink", trl);
-            }
+
+            sdnode.AddChild("TransformLink", bone_xform);
             // note: this means we ALWAYS rely on the mesh node transform
             // being unchanged from the time the skeleton was bound.
             // there's not really any way around this at the moment.
@@ -2441,7 +2486,7 @@ void FBXExporter::WriteModelNodes(
 void FBXExporter::WriteAnimationCurveNode(
     StreamWriterLE& outstream,
     int64_t uid,
-    std::string name, // "T", "R", or "S"
+    const std::string& name, // "T", "R", or "S"
     aiVector3D default_value,
     std::string property_name, // "Lcl Translation" etc
     int64_t layer_uid,

+ 1 - 1
code/FBX/FBXExporter.h

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

+ 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 "FBXTokenizer.h"
+#include "FBXConverter.h"
+#include "FBXDocument.h"
 #include "FBXParser.h"
+#include "FBXTokenizer.h"
 #include "FBXUtil.h"
-#include "FBXDocument.h"
-#include "FBXConverter.h"
 
-#include <assimp/StreamReader.h>
 #include <assimp/MemoryIOWrapper.h>
-#include <assimp/Importer.hpp>
+#include <assimp/StreamReader.h>
 #include <assimp/importerdesc.h>
+#include <assimp/Importer.hpp>
 
 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::Formatter;
@@ -76,128 +76,123 @@ using namespace Assimp::FBX;
 namespace {
 
 static const aiImporterDesc desc = {
-    "Autodesk FBX Importer",
-    "",
-    "",
-    "",
-    aiImporterFlags_SupportTextFlavour,
-    0,
-    0,
-    0,
-    0,
-    "fbx"
+	"Autodesk FBX Importer",
+	"",
+	"",
+	"",
+	aiImporterFlags_SupportTextFlavour,
+	0,
+	0,
+	0,
+	0,
+	"fbx"
 };
 }
 
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by #Importer
-FBXImporter::FBXImporter()
-{
+FBXImporter::FBXImporter() {
 }
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
-FBXImporter::~FBXImporter()
-{
+FBXImporter::~FBXImporter() {
 }
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool FBXImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
-{
-    const std::string& extension = GetExtension(pFile);
-    if (extension == std::string( desc.mFileExtensions ) ) {
-        return true;
-    }
-
-    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
-const aiImporterDesc* FBXImporter::GetInfo () const
-{
-    return &desc;
+const aiImporterDesc *FBXImporter::GetInfo() const {
+	return &desc;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Setup configuration properties for the loader
-void FBXImporter::SetupProperties(const Importer* pImp)
-{
-    settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true);
-    settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false);
-    settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true);
-    settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true);
-    settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true);
-    settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true);
-    settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true);
-    settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false);
-    settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true);
-    settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
-    settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false);
-    settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true);
-    settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false);
+void FBXImporter::SetupProperties(const Importer *pImp) {
+	settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true);
+	settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false);
+	settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true);
+	settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true);
+	settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true);
+	settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true);
+	settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true);
+	settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false);
+	settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true);
+	settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
+	settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false);
+	settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true);
+	settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false);
 }
 
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure.
-void FBXImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
-{
-    std::unique_ptr<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

+ 11 - 14
code/FBX/FBXMeshGeometry.cpp

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

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

@@ -311,10 +311,9 @@ class TrimmedCurve : public BoundedCurve {
 public:
     // --------------------------------------------------
     TrimmedCurve(const Schema_2x3::IfcTrimmedCurve& entity, ConversionData& conv)
-        : BoundedCurve(entity,conv)
+        : BoundedCurve(entity,conv),
+          base(std::shared_ptr<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;
 
         // for some reason, trimmed curves can either specify a parametric value
@@ -500,7 +499,7 @@ bool Curve::InRange(IfcFloat u) const {
     if (IsClosed()) {
         return true;
     }
-    const IfcFloat epsilon = 1e-5;
+    const IfcFloat epsilon = Math::getEpsilon<float>();
     return u - range.first > -epsilon && range.second - u > -epsilon;
 }
 #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;
     }
     else {
-        for(iit = begin; iit != end; iit++) {
+        for(iit = begin; iit != end; ++iit) {
             // find the polygon with the largest area and take it as the outer bound.
             IfcVector3& n = normals[std::distance(begin,iit)];
             const IfcFloat area = n.SquareLength();
@@ -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 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)
 {
     // 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) ||
         (std::fabs(bb.first.x - ibb.second.x) < epsilon && ibb.first.y <= bb.second.y && ibb.second.y >= bb.first.y) ||
         (std::fabs(bb.second.y - ibb.first.y) < epsilon && bb.first.x <= ibb.second.x && bb.second.x >= ibb.first.x) ||
@@ -681,7 +681,7 @@ bool IntersectingLineSegments(const IfcVector2& n0, const IfcVector2& n1,
 // ------------------------------------------------------------------------------------------------
 void FindAdjacentContours(ContourVector::iterator current, const ContourVector& contours)
 {
-    const IfcFloat sqlen_epsilon = static_cast<IfcFloat>(1e-8);
+    const IfcFloat sqlen_epsilon = static_cast<IfcFloat>(Math::getEpsilon<float>());
     const BoundingBox& bb = (*current).bb;
 
     // What is to be done here is to populate the skip lists for the contour
@@ -758,7 +758,7 @@ void FindAdjacentContours(ContourVector::iterator current, const ContourVector&
 // ------------------------------------------------------------------------------------------------
 AI_FORCE_INLINE bool LikelyBorder(const IfcVector2& vdelta)
 {
-    const IfcFloat dot_point_epsilon = static_cast<IfcFloat>(1e-5);
+    const IfcFloat dot_point_epsilon = static_cast<IfcFloat>(Math::getEpsilon<float>());
     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/scene.h>
 #include <assimp/importerdesc.h>
-#include <assimp/Macros.h>
 
 using namespace Assimp;
 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>");
 
     //Set parent of all children, inserting pivots
-    //std::cout << "Set parent of all children" << std::endl;
     std::map<uint16_t, aiNode*> mapPivot;
     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
-    //std::cout << "Merge pivot map into node map" << std::endl;
     for (auto itMapPivot = mapPivot.begin(); itMapPivot != mapPivot.end(); ++itMapPivot) {
         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])
         {
             aiString szString;
-            const size_t iLen = ::strlen(pcSkins->name);
+            const ai_uint32 iLen = (ai_uint32) ::strlen(pcSkins->name);
             ::memcpy(szString.data,pcSkins->name,iLen);
             szString.data[iLen] = '\0';
             szString.length = iLen;

+ 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_SWAP4( pcUVs[i].U );
-            AI_SWAP4( pcUVs[i].U );
+            AI_SWAP4( pcUVs[i].V );
         }
         for (uint32_t i = 0; i < pcSurfaces->NUM_TRIANGLES;++i) {
             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 <assimp/StringComparison.h>
 #include <assimp/fast_atof.h>
+#include <assimp/MathFunctions.h>
 #include <assimp/SkeletonMeshBuilder.h>
 #include <assimp/Importer.hpp>
 #include <assimp/scene.h>
@@ -64,7 +65,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 using namespace Assimp;
 
 // Minimum weight value. Weights inside [-n ... n] are ignored
-#define AI_MD5_WEIGHT_EPSILON 1e-5f
+#define AI_MD5_WEIGHT_EPSILON Math::getEpsilon<float>()
 
 
 static const aiImporterDesc desc = {

+ 1 - 1
code/MD5/MD5Parser.cpp

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

+ 1 - 1
code/MDC/MDCLoader.cpp

@@ -355,7 +355,7 @@ void MDCImporter::InternReadFile(
         // swap all texture coordinates
         for (unsigned int i = 0; i < pcSurface->ulNumVertices;++i)
         {
-            AI_SWAP4( pcUVs->v );
+            AI_SWAP4( pcUVs->u );
             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
 
-
-
 All rights reserved.
 
 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/MDLDefaultColorMap.h"
 #include "MD2/MD2FileData.h"
+#include "MDL/HalfLife/HL1MDLLoader.h"
 
-#include <assimp/Macros.h>
 #include <assimp/qnan.h>
 #include <assimp/StringUtils.h>
 #include <assimp/Importer.hpp>
@@ -94,23 +92,24 @@ static const aiImporterDesc desc = {
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 MDLImporter::MDLImporter()
-    : configFrameID(),
-    mBuffer(),
-    iGSFileVersion(),
-    pIOHandler(),
-    pScene(),
-    iFileSize()
-{}
+: configFrameID()
+, mBuffer()
+, iGSFileVersion()
+, pIOHandler()
+, pScene()
+, iFileSize() {
+    // empty
+}
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
-MDLImporter::~MDLImporter()
-{}
+MDLImporter::~MDLImporter() {
+    // empty
+}
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool MDLImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
-{
+bool MDLImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
     const std::string extension = GetExtension(pFile);
 
     // if check for extension is not enough, check for the magic tokens
@@ -144,6 +143,18 @@ void MDLImporter::SetupProperties(const Importer* pImp)
 
     // AI_CONFIG_IMPORT_MDL_COLORMAP - palette file
     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 ...
-    // 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();
-    if( iFileSize < sizeof(MDL::Header)) {
+    if( iFileSize < sizeof(MDL::HalfLife::SequenceHeader_HL1)) {
         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 ||
         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;
-        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    {
         // 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
     BE_NCONST MDL::Frame* pcFrames = (BE_NCONST MDL::Frame*)szCurrent;
-    BE_NCONST MDL::SimpleFrame* pcFirstFrame;
+    MDL::SimpleFrame* pcFirstFrame;
 
     if (0 == pcFrames->type) {
         // get address of single frame
-        pcFirstFrame = &pcFrames->frame;
+        pcFirstFrame =( MDL::SimpleFrame*) &pcFrames->frame;
     } else {
         // get the first frame in the group
-
-#if 1
-        // FIXME: the cast is wrong and cause a warning on clang 5.0
-        // disable this code for now, fix it later
-        ai_assert(false && "Bad pointer cast");
-        pcFirstFrame = nullptr; // Workaround: msvc++ C4703 error
-#else
-        BE_NCONST MDL::GroupFrame* pcFrames2 = (BE_NCONST MDL::GroupFrame*)pcFrames;
-        pcFirstFrame = (BE_NCONST MDL::SimpleFrame*)(&pcFrames2->time + pcFrames->type);
-#endif
+        BE_NCONST MDL::GroupFrame* pcFrames2 = (BE_NCONST MDL::GroupFrame*) pcFrames;
+        pcFirstFrame = &(pcFrames2->frames[0]);
     }
     BE_NCONST MDL::Vertex* pcVertices = (BE_NCONST MDL::Vertex*) ((pcFirstFrame->name) + sizeof(pcFirstFrame->name));
     VALIDATE_FILE_SIZE((const unsigned char*)(pcVertices + pcHeader->num_verts));
@@ -1562,7 +1575,7 @@ void MDLImporter::InternReadFile_3DGS_MDL7( )
 				const size_t maxSize(buffersize - (i*AI_MDL7_MAX_GROUPNAMESIZE));
 				pcNode->mName.length = ai_snprintf(szBuffer, maxSize, "Group_%u", p);
 			} else {
-				pcNode->mName.length = ::strlen(szBuffer);
+				pcNode->mName.length = (ai_uint32)::strlen(szBuffer);
 			}
             ::strncpy(pcNode->mName.data,szBuffer,MAXLEN-1);
             ++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
 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 "MDLFileData.h"
 #include "HMP/HalfLifeFileData.h"
+#include "HalfLife/HL1ImportSettings.h"
 
 struct aiNode;
 struct aiTexture;
@@ -77,6 +78,7 @@ using namespace MDL;
  *      <li>3D Game Studio MDL3, MDL4</li>
  *      <li>3D Game Studio MDL5</li>
  *      <li>3D Game Studio MDL7</li>
+ *      <li>Halflife 1</li>
  *      <li>Halflife 2</li>
  *   </ul>
  *  These formats are partially identical and it would be possible to load
@@ -89,16 +91,12 @@ public:
     MDLImporter();
     ~MDLImporter();
 
-
-public:
-
     // -------------------------------------------------------------------
     /** Returns whether the class can handle the format of the given file.
     * See BaseImporter::CanRead() for details.  */
     bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
         bool checkSig) const;
 
-
     // -------------------------------------------------------------------
     /** Called prior to ReadFile().
     * The function is a request to the importer to update its configuration
@@ -107,8 +105,6 @@ public:
     void SetupProperties(const Importer* pImp);
 
 protected:
-
-
     // -------------------------------------------------------------------
     /** Return importer meta information.
      * See #BaseImporter::GetInfo for the details
@@ -122,8 +118,6 @@ protected:
     void InternReadFile( const std::string& pFile, aiScene* pScene,
         IOSystem* pIOHandler);
 
-protected:
-
     // -------------------------------------------------------------------
     /** Import a quake 1 MDL file (IDPO)
     */
@@ -139,6 +133,11 @@ protected:
     */
     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)
     */
@@ -154,7 +153,6 @@ protected:
     void SizeCheck(const void* szPos);
     void SizeCheck(const void* szPos, const char* szFile, unsigned int iLine);
 
-
     // -------------------------------------------------------------------
     /** Validate the header data structure of a game studio MDL7 file
      * \param pcHeader Input header to be validated
@@ -167,7 +165,6 @@ protected:
      */
     void ValidateHeader_Quake1(const MDL::Header* pcHeader);
 
-
     // -------------------------------------------------------------------
     /** Try to load a  palette from the current directory (colormap.lmp)
      *  If it is not found the default palette of Quake1 is returned
@@ -179,9 +176,8 @@ protected:
      */
     void FreePalette(const unsigned char* pszColorMap);
 
-
     // -------------------------------------------------------------------
-    /** Load a paletized texture from the file and convert it to 32bpp
+    /** Load a palletized texture from the file and convert it to 32bpp
     */
     void CreateTextureARGB8_3DGS_MDL3(const unsigned char* szData);
 
@@ -195,7 +191,6 @@ protected:
         unsigned int iType,
         unsigned int* piSkip);
 
-
     // -------------------------------------------------------------------
     /** Used to load textures from MDL5
      * \param szData Input data
@@ -206,7 +201,6 @@ protected:
         unsigned int iType,
         unsigned int* piSkip);
 
-
     // -------------------------------------------------------------------
     /** Checks whether a texture can be replaced with a single color
      * This is useful for all file formats before MDL7 (all those
@@ -218,14 +212,12 @@ protected:
     */
     aiColor4D ReplaceTextureWithColor(const aiTexture* pcTexture);
 
-
     // -------------------------------------------------------------------
     /** Converts the absolute texture coordinates in MDL5 files to
      *  relative in a range between 0 and 1
     */
     void CalculateUVCoordinates_MDL5();
 
-
     // -------------------------------------------------------------------
     /** Read an UV coordinate from the file. If the file format is not
      * MDL5, the function calculates relative texture coordinates
@@ -245,7 +237,6 @@ protected:
      */
     void SetupMaterialProperties_3DGS_MDL5_Quake1( );
 
-
     // -------------------------------------------------------------------
     /** Parse a skin lump in a MDL7/HMP7 file with all of its features
      *  variant 1: Current cursor position is the beginning of the skin header
@@ -452,6 +443,9 @@ protected:
 
     /** Size of the input file in bytes */
     unsigned int iFileSize;
+
+    /* Configuration for HL1 MDL */
+    HalfLife::HL1ImportSettings mHL1ImportSettings;
 };
 
 } // end of namespace Assimp

+ 3 - 3
code/MDL/MDLMaterialLoader.cpp

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

+ 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/material.h>
 #include <assimp/DefaultLogger.hpp>
-#include <assimp/Macros.h>
 
 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,
     const char* pKey,
     unsigned int type,
     unsigned int index,
     aiUVTransform* pOut)
 {
-    unsigned int iMax = 4;
+    unsigned int iMax = 5;
     return aiGetMaterialFloatArray(pMat,pKey,type,index,(ai_real*)pOut,&iMax);
 }
 
@@ -472,12 +471,12 @@ aiReturn aiMaterial::AddBinaryProperty (const void* pInput,
     aiPropertyTypeInfo pType
     )
 {
-    ai_assert( pInput != NULL );
-    ai_assert( pKey != NULL );
+    ai_assert( pInput != nullptr );
+	ai_assert(pKey != nullptr );
     ai_assert( 0 != pSizeInBytes );
 
     if ( 0 == pSizeInBytes ) {
-
+		return AI_FAILURE;
     }
 
     // first search the list whether there is already an entry with this key
@@ -505,7 +504,7 @@ aiReturn aiMaterial::AddBinaryProperty (const void* pInput,
     pcNew->mData = new char[pSizeInBytes];
     memcpy (pcNew->mData,pInput,pSizeInBytes);
 
-    pcNew->mKey.length = ::strlen(pKey);
+    pcNew->mKey.length = (ai_uint32)::strlen(pKey);
     ai_assert ( MAXLEN > pcNew->mKey.length);
     strcpy( pcNew->mKey.data, pKey );
 
@@ -545,23 +544,7 @@ aiReturn aiMaterial::AddProperty (const aiString* pInput,
     unsigned int type,
     unsigned int index)
 {
-    // We don't want to add the whole buffer .. write a 32 bit length
-    // prefix followed by the zero-terminated UTF8 string.
-    // (HACK) I don't want to break the ABI now, but we definitely
-    // ought to change aiString::mLength to uint32_t one day.
-    if (sizeof(size_t) == 8) {
-        aiString copy = *pInput;
-        uint32_t* s = reinterpret_cast<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,
         static_cast<unsigned int>(pInput->length+1+4),
         pKey,

+ 1 - 4
code/Obj/ObjFileImporter.cpp

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

+ 2 - 2
code/Obj/ObjFileParser.cpp

@@ -244,8 +244,8 @@ void ObjFileParser::copyNextWord(char *pBuffer, size_t length) {
     size_t index = 0;
     m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
     if ( *m_DataIt == '\\' ) {
-        m_DataIt++;
-        m_DataIt++;
+        ++m_DataIt;
+        ++m_DataIt;
         m_DataIt = getNextWord<DataArrayIt>( m_DataIt, m_DataItEnd );
     }
     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) {
         const aiFace& f = m->mFaces[i];
-        mOutput << f.mNumIndices << " ";
+        mOutput << f.mNumIndices;
         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
 #include "PlyLoader.h"
 #include <assimp/IOStreamBuffer.h>
-#include <assimp/Macros.h>
 #include <memory>
 #include <assimp/IOSystem.hpp>
 #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
             aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]);
             aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]);
-            localTangent.Normalize(); localBitangent.Normalize();
+            localTangent.NormalizeSafe(); localBitangent.NormalizeSafe();
 
             // reconstruct tangent/bitangent according to normal and bitangent/tangent when it's infinite or NaN.
             bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z);
@@ -220,10 +220,10 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
             if (invalid_tangent != invalid_bitangent) {
                 if (invalid_tangent) {
                     localTangent = meshNorm[p] ^ localBitangent;
-                    localTangent.Normalize();
+                    localTangent.NormalizeSafe();
                 } else {
                     localBitangent = localTangent ^ meshNorm[p];
-                    localBitangent.Normalize();
+                    localBitangent.NormalizeSafe();
                 }
             }
 

+ 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)   {
         FindMeshCenter(mesh, center, min, max);
-        diffu = max.y - min.y;
-        diffv = max.z - min.z;
+        diffu = max.x - min.x;
+        diffv = max.y - min.y;
 
         for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
             const aiVector3D& pos = mesh->mVertices[pnt];
-            out[pnt].Set((pos.y - min.y) / diffu,(pos.x - min.x) / diffv,0.0);
+            out[pnt].Set((pos.x - min.x) / diffu,(pos.y - min.y) / diffv,0.0);
         }
     }
     // slower code path in case the mapping axis is not one of the coordinate system axes

+ 3 - 2
code/PostProcessing/ConvertToLHProcess.h

@@ -137,8 +137,9 @@ public:
     // -------------------------------------------------------------------
     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 "ProcessHelper.h"
 
-#include <assimp/Macros.h>
 #include <assimp/Exceptional.h>
 #include <assimp/qnan.h>
 

Некоторые файлы не были показаны из-за большого количества измененных файлов