浏览代码

Merge pull request #16 from assimp/master

Update
Madrich 5 年之前
父节点
当前提交
a413774e24
共有 100 个文件被更改,包括 10434 次插入2510 次删除
  1. 127 0
      .clang-format
  2. 2 0
      .github/FUNDING.yml
  3. 5 0
      .gitignore
  4. 2 1
      .travis.sh
  5. 26 0
      BUILDBINARIES_EXAMPLE.bat
  6. 21 7
      Build.md
  7. 18 17
      CMakeLists.txt
  8. 8 2
      Readme.md
  9. 8 2
      appveyor.yml
  10. 10 1
      assimpTargets-debug.cmake.in
  11. 11 1
      assimpTargets-release.cmake.in
  12. 4 5
      assimpTargets.cmake.in
  13. 7 3
      cmake-modules/Findassimp.cmake
  14. 1 1
      code/3DS/3DSConverter.cpp
  15. 0 2
      code/3DS/3DSLoader.cpp
  16. 2 6
      code/3MF/D3MFImporter.cpp
  17. 5 346
      code/3MF/D3MFOpcPackage.cpp
  18. 3 4
      code/3MF/D3MFOpcPackage.h
  19. 1 1
      code/AMF/AMFImporter.cpp
  20. 13 13
      code/AMF/AMFImporter_Postprocess.cpp
  21. 0 9
      code/Assjson/json_exporter.cpp
  22. 21 4
      code/CMakeLists.txt
  23. 98 67
      code/Collada/ColladaExporter.cpp
  24. 2 6
      code/Collada/ColladaHelper.h
  25. 297 278
      code/Collada/ColladaLoader.cpp
  26. 5 8
      code/Collada/ColladaLoader.h
  27. 407 277
      code/Collada/ColladaParser.cpp
  28. 11 1
      code/Collada/ColladaParser.h
  29. 39 3
      code/Common/BaseImporter.cpp
  30. 31 2
      code/Common/DefaultIOStream.cpp
  31. 60 101
      code/Common/DefaultIOSystem.cpp
  32. 2 2
      code/Common/DefaultLogger.cpp
  33. 74 91
      code/Common/Exporter.cpp
  34. 6 0
      code/Common/ImporterRegistry.cpp
  35. 7 0
      code/Common/PostStepRegistry.cpp
  36. 50 0
      code/Common/SceneCombiner.cpp
  37. 11 9
      code/Common/Version.cpp
  38. 539 0
      code/Common/ZipArchiveIOSystem.cpp
  39. 9 9
      code/Common/scene.cpp
  40. 4 3
      code/FBX/FBXCommon.h
  41. 8 0
      code/FBX/FBXCompileConfig.h
  42. 291 188
      code/FBX/FBXConverter.cpp
  43. 55 46
      code/FBX/FBXConverter.h
  44. 0 8
      code/FBX/FBXDocument.cpp
  45. 37 2
      code/FBX/FBXDocument.h
  46. 7 7
      code/FBX/FBXExportNode.cpp
  47. 1 5
      code/FBX/FBXExportProperty.cpp
  48. 116 74
      code/FBX/FBXExporter.cpp
  49. 1 1
      code/FBX/FBXExporter.h
  50. 102 107
      code/FBX/FBXImporter.cpp
  51. 11 14
      code/FBX/FBXMeshGeometry.cpp
  52. 3 4
      code/Importer/IFC/IFCCurve.cpp
  53. 1 1
      code/Importer/IFC/IFCGeometry.cpp
  54. 3 3
      code/Importer/IFC/IFCOpenings.cpp
  55. 0 1
      code/Irr/IRRMeshLoader.cpp
  56. 420 0
      code/M3D/M3DExporter.cpp
  57. 100 0
      code/M3D/M3DExporter.h
  58. 766 0
      code/M3D/M3DImporter.cpp
  59. 106 0
      code/M3D/M3DImporter.h
  60. 106 0
      code/M3D/M3DMaterials.h
  61. 5568 0
      code/M3D/m3d.h
  62. 1 1
      code/MD2/MD2Loader.cpp
  63. 2 1
      code/MD5/MD5Loader.cpp
  64. 1 1
      code/MD5/MD5Parser.cpp
  65. 16 26
      code/MDL/MDLLoader.cpp
  66. 1 17
      code/MDL/MDLLoader.h
  67. 4 21
      code/Material/MaterialSystem.cpp
  68. 1 4
      code/Obj/ObjFileImporter.cpp
  69. 3 3
      code/Obj/ObjFileParser.cpp
  70. 0 1
      code/Ply/PlyLoader.cpp
  71. 268 0
      code/PostProcessing/ArmaturePopulate.cpp
  72. 112 0
      code/PostProcessing/ArmaturePopulate.h
  73. 3 3
      code/PostProcessing/CalcTangentsProcess.cpp
  74. 3 3
      code/PostProcessing/ComputeUVMappingProcess.cpp
  75. 0 1
      code/PostProcessing/FindInvalidDataProcess.cpp
  76. 0 25
      code/PostProcessing/JoinVerticesProcess.cpp
  77. 29 0
      code/PostProcessing/MakeVerboseFormat.cpp
  78. 7 0
      code/PostProcessing/MakeVerboseFormat.h
  79. 115 10
      code/PostProcessing/ScaleProcess.cpp
  80. 11 2
      code/PostProcessing/ScaleProcess.h
  81. 67 9
      code/PostProcessing/ValidateDataStructure.cpp
  82. 8 0
      code/PostProcessing/ValidateDataStructure.h
  83. 8 8
      code/Q3BSP/Q3BSPFileImporter.cpp
  84. 6 6
      code/Q3BSP/Q3BSPFileImporter.h
  85. 3 2
      code/Q3BSP/Q3BSPFileParser.cpp
  86. 6 7
      code/Q3BSP/Q3BSPFileParser.h
  87. 0 325
      code/Q3BSP/Q3BSPZipArchive.cpp
  88. 0 135
      code/Q3BSP/Q3BSPZipArchive.h
  89. 1 1
      code/STL/STLLoader.cpp
  90. 2 1
      code/Unreal/UnrealLoader.h
  91. 3 3
      code/X/XFileParser.cpp
  92. 1 1
      code/X3D/X3DExporter.cpp
  93. 37 31
      code/X3D/X3DImporter.cpp
  94. 3 3
      code/X3D/X3DImporter_Geometry2D.cpp
  95. 7 7
      code/X3D/X3DImporter_Geometry3D.cpp
  96. 1 1
      code/X3D/X3DImporter_Networking.cpp
  97. 40 40
      code/X3D/X3DImporter_Postprocess.cpp
  98. 6 6
      code/X3D/X3DImporter_Rendering.cpp
  99. 1 1
      code/X3D/X3DImporter_Texturing.cpp
  100. 8 61
      code/glTF/glTFAsset.h

+ 127 - 0
.clang-format

@@ -0,0 +1,127 @@
+# Commented out parameters are those with the same value as base LLVM style
+# We can uncomment them if we want to change their value, or enforce the
+# chosen value in case the base style changes (last sync: Clang 6.0.1).
+---
+### General config, applies to all languages ###
+BasedOnStyle:  LLVM
+AccessModifierOffset: -4
+AlignAfterOpenBracket: DontAlign
+# AlignConsecutiveAssignments: false
+# AlignConsecutiveDeclarations: false
+# AlignEscapedNewlines: Right
+# AlignOperands:   true
+AlignTrailingComments: false
+AllowAllParametersOfDeclarationOnNextLine: false
+# AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: true
+# AllowShortLoopsOnASingleLine: false
+# AlwaysBreakAfterDefinitionReturnType: None
+# AlwaysBreakAfterReturnType: None
+# AlwaysBreakBeforeMultilineStrings: false
+# AlwaysBreakTemplateDeclarations: false
+# BinPackArguments: true
+# BinPackParameters: true
+# BraceWrapping:
+#   AfterClass:      false
+#   AfterControlStatement: false
+#   AfterEnum:       false
+#   AfterFunction:   false
+#   AfterNamespace:  false
+#   AfterObjCDeclaration: false
+#   AfterStruct:     false
+#   AfterUnion:      false
+#   AfterExternBlock: false
+#   BeforeCatch:     false
+#   BeforeElse:      false
+#   IndentBraces:    false
+#   SplitEmptyFunction: true
+#   SplitEmptyRecord: true
+#   SplitEmptyNamespace: true
+# BreakBeforeBinaryOperators: None
+# BreakBeforeBraces: Attach
+# BreakBeforeInheritanceComma: false
+BreakBeforeTernaryOperators: false
+# BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: AfterColon
+# BreakStringLiterals: true
+ColumnLimit:     0
+# CommentPragmas:  '^ IWYU pragma:'
+# CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 8
+ContinuationIndentWidth: 8
+Cpp11BracedListStyle: false
+# DerivePointerAlignment: false
+# DisableFormat:   false
+# ExperimentalAutoDetectBinPacking: false
+# FixNamespaceComments: true
+# ForEachMacros:
+#   - foreach
+#   - Q_FOREACH
+#   - BOOST_FOREACH
+# IncludeBlocks:   Preserve
+IncludeCategories:
+  - Regex:           '".*"'
+    Priority:        1
+  - Regex:           '^<.*\.h>'
+    Priority:        2
+  - Regex:           '^<.*'
+    Priority:        3
+# IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: true
+# IndentPPDirectives: None
+IndentWidth:     4
+# IndentWrappedFunctionNames: false
+# JavaScriptQuotes: Leave
+# JavaScriptWrapImports: true
+# KeepEmptyLinesAtTheStartOfBlocks: true
+# MacroBlockBegin: ''
+# MacroBlockEnd:   ''
+# MaxEmptyLinesToKeep: 1
+# NamespaceIndentation: None
+# PenaltyBreakAssignment: 2
+# PenaltyBreakBeforeFirstCallParameter: 19
+# PenaltyBreakComment: 300
+# PenaltyBreakFirstLessLess: 120
+# PenaltyBreakString: 1000
+# PenaltyExcessCharacter: 1000000
+# PenaltyReturnTypeOnItsOwnLine: 60
+# PointerAlignment: Right
+# RawStringFormats:
+#   - Delimiter:       pb
+#     Language:        TextProto
+#     BasedOnStyle:    google
+# ReflowComments:  true
+# SortIncludes:    true
+# SortUsingDeclarations: true
+# SpaceAfterCStyleCast: false
+# SpaceAfterTemplateKeyword: true
+# SpaceBeforeAssignmentOperators: true
+# SpaceBeforeParens: ControlStatements
+# SpaceInEmptyParentheses: false
+# SpacesBeforeTrailingComments: 1
+# SpacesInAngles:  false
+# SpacesInContainerLiterals: true
+# SpacesInCStyleCastParentheses: false
+# SpacesInParentheses: false
+# SpacesInSquareBrackets: false
+TabWidth:        4
+UseTab:          Always
+---
+### C++ specific config ###
+Language:        Cpp
+Standard:        Cpp11
+---
+### ObjC specific config ###
+Language:        ObjC
+Standard:        Cpp11
+ObjCBlockIndentWidth: 4
+# ObjCSpaceAfterProperty: false
+# ObjCSpaceBeforeProtocolList: true
+---
+### Java specific config ###
+Language:        Java
+# BreakAfterJavaFieldAnnotations: false
+...

+ 2 - 0
.github/FUNDING.yml

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

+ 5 - 0
.gitignore

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

+ 2 - 1
.travis.sh

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

+ 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

+ 21 - 7
Build.md

@@ -1,17 +1,31 @@
 # Build Instructions
 # Build Instructions
-## Install CMake
+
+## Build on all platforms using vcpkg
+You can download and install assimp using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager:
+```bash
+    git clone https://github.com/Microsoft/vcpkg.git
+    cd vcpkg
+    ./bootstrap-vcpkg.sh
+    ./vcpkg integrate install
+    vcpkg install assimp
+```
+The assimp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
+
+## Manual build instructions
+
+### Install CMake
 Asset-Importer-Lib can be build for a lot of different platforms. We are using cmake to generate the build environment for these via cmake. So you have to make sure that you have a working cmake-installation on your system. You can download it at https://cmake.org/ or for linux install it via
 Asset-Importer-Lib can be build for a lot of different platforms. We are using cmake to generate the build environment for these via cmake. So you have to make sure that you have a working cmake-installation on your system. You can download it at https://cmake.org/ or for linux install it via
 ```bash
 ```bash
 sudo apt-get install cmake
 sudo apt-get install cmake
 ```
 ```
 
 
-## Get the source
+### Get the source
 Make sure you have a working git-installation. Open a command prompt and clone the Asset-Importer-Lib via:
 Make sure you have a working git-installation. Open a command prompt and clone the Asset-Importer-Lib via:
 ```bash
 ```bash
 git clone https://github.com/assimp/assimp.git
 git clone https://github.com/assimp/assimp.git
 ```
 ```
 
 
-## Build instructions for Windows with Visual-Studio
+### Build instructions for Windows with Visual-Studio
 
 
 First you have to install Visual-Studio on your windows-system. You can get the Community-Version for free here: https://visualstudio.microsoft.com/de/downloads/
 First you have to install Visual-Studio on your windows-system. You can get the Community-Version for free here: https://visualstudio.microsoft.com/de/downloads/
 To generate the build environment for your IDE open a command prompt, navigate to your repo and type:
 To generate the build environment for your IDE open a command prompt, navigate to your repo and type:
@@ -20,10 +34,10 @@ cmake CMakeLists.txt
 ```
 ```
 This will generate the project files for the visual studio. All dependencies used to build Asset-IMporter-Lib shall be part of the repo. If you want to use you own zlib.installation this is possible as well. Check the options for it.
 This will generate the project files for the visual studio. All dependencies used to build Asset-IMporter-Lib shall be part of the repo. If you want to use you own zlib.installation this is possible as well. Check the options for it.
 
 
-## Build instructions for Windows with UWP
+### Build instructions for Windows with UWP
 See <https://stackoverflow.com/questions/40803170/cmake-uwp-using-cmake-to-build-universal-windows-app>
 See <https://stackoverflow.com/questions/40803170/cmake-uwp-using-cmake-to-build-universal-windows-app>
 
 
-## Build instructions for Linux / Unix
+### Build instructions for Linux / Unix
 Open a terminal and got to your repository. You can generate the makefiles and build the library via:
 Open a terminal and got to your repository. You can generate the makefiles and build the library via:
 
 
 ```bash
 ```bash
@@ -34,7 +48,7 @@ The option -j descripes the number of parallel processes for the build. In this
 
 
 If you want to use a IDE for linux you can try QTCreator for instance. 
 If you want to use a IDE for linux you can try QTCreator for instance. 
 
 
-## Build instructions for MinGW
+### Build instructions for MinGW
  Older versions of MinGW's compiler (e.g. 5.1.0) do not support the -mbig_obj flag 
  Older versions of MinGW's compiler (e.g. 5.1.0) do not support the -mbig_obj flag 
 required to compile some of assimp's files, especially for debug builds.
 required to compile some of assimp's files, especially for debug builds.
 Version 7.3.0 of g++-mingw-w64 & gcc-mingw-w64 appears to work.
 Version 7.3.0 of g++-mingw-w64 & gcc-mingw-w64 appears to work.
@@ -50,7 +64,7 @@ The following toolchain may or may not be helpful for building assimp using MinG
 
 
 Besides the toolchain, compilation should be the same as for Linux / Unix.
 Besides the toolchain, compilation should be the same as for Linux / Unix.
 
 
-## CMake build options
+### CMake build options
 The cmake-build-environment provides options to configure the build. The following options can be used:
 The cmake-build-environment provides options to configure the build. The following options can be used:
 - **BUILD_SHARED_LIBS ( default ON )**: Generation of shared libs ( dll for windows, so for Linux ). Set this to OFF to get a static lib.
 - **BUILD_SHARED_LIBS ( default ON )**: Generation of shared libs ( dll for windows, so for Linux ). Set this to OFF to get a static lib.
 - **BUILD_FRAMEWORK ( default OFF, MacOnly)**: Build package as Mac OS X Framework bundle
 - **BUILD_FRAMEWORK ( default OFF, MacOnly)**: Build package as Mac OS X Framework bundle

+ 18 - 17
CMakeLists.txt

@@ -173,7 +173,6 @@ SET (ASSIMP_VERSION ${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}.${ASSIMP_VER
 SET (ASSIMP_SOVERSION 5)
 SET (ASSIMP_SOVERSION 5)
 
 
 SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" )
 SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" )
-
 if(NOT HUNTER_ENABLED)
 if(NOT HUNTER_ENABLED)
   # Enable C++11 support globally
   # Enable C++11 support globally
   set_property( GLOBAL PROPERTY CXX_STANDARD 11 )
   set_property( GLOBAL PROPERTY CXX_STANDARD 11 )
@@ -254,6 +253,7 @@ ELSEIF(MSVC)
   IF(MSVC12)
   IF(MSVC12)
     ADD_COMPILE_OPTIONS(/wd4351)
     ADD_COMPILE_OPTIONS(/wd4351)
   ENDIF()
   ENDIF()
+  SET(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Ob2 /Zi")
 ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" )
 ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" )
   IF(NOT HUNTER_ENABLED)
   IF(NOT HUNTER_ENABLED)
     SET(CMAKE_CXX_FLAGS "-fPIC -std=c++11 ${CMAKE_CXX_FLAGS}")
     SET(CMAKE_CXX_FLAGS "-fPIC -std=c++11 ${CMAKE_CXX_FLAGS}")
@@ -271,22 +271,20 @@ ELSEIF( CMAKE_COMPILER_IS_MINGW )
     SET(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
     SET(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
     SET(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}")
     SET(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}")
   ENDIF()
   ENDIF()
-  SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long -Wa,-mbig-obj ${CMAKE_CXX_FLAGS}")
+  SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long -Wa,-mbig-obj -O3 ${CMAKE_CXX_FLAGS}")
   SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}")
   SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}")
   ADD_DEFINITIONS( -U__STRICT_ANSI__ )
   ADD_DEFINITIONS( -U__STRICT_ANSI__ )
 ENDIF()
 ENDIF()
 
 
 IF ( IOS AND NOT HUNTER_ENABLED)
 IF ( IOS AND NOT HUNTER_ENABLED)
-
-IF (CMAKE_BUILD_TYPE STREQUAL "Debug")
-  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -Og")
-  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -Og")
-ELSE()
-  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -O3")
-  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3")
-  # Experimental for pdb generation
-ENDIF()
-
+  IF (CMAKE_BUILD_TYPE STREQUAL "Debug")
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -Og")
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -Og")
+  ELSE()
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -O3")
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3")
+    # Experimental for pdb generation
+  ENDIF()
 ENDIF( IOS AND NOT HUNTER_ENABLED)
 ENDIF( IOS AND NOT HUNTER_ENABLED)
 
 
 IF (ASSIMP_COVERALLS)
 IF (ASSIMP_COVERALLS)
@@ -341,7 +339,7 @@ SET( ASSIMP_BIN_INSTALL_DIR "bin" CACHE STRING
 
 
 get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
 get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
 
 
-IF (is_multi_config OR (CMAKE_BUILD_TYPE STREQUAL "Debug"))
+IF (INJECT_DEBUG_POSTFIX AND (is_multi_config OR CMAKE_BUILD_TYPE STREQUAL "Debug"))
   SET(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "Debug Postfix for lib, samples and tools")
   SET(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "Debug Postfix for lib, samples and tools")
 ELSE()
 ELSE()
   SET(CMAKE_DEBUG_POSTFIX "" CACHE STRING "Debug Postfix for lib, samples and tools")
   SET(CMAKE_DEBUG_POSTFIX "" CACHE STRING "Debug Postfix for lib, samples and tools")
@@ -393,6 +391,11 @@ IF(HUNTER_ENABLED)
   )
   )
 ELSE(HUNTER_ENABLED)
 ELSE(HUNTER_ENABLED)
   # cmake configuration files
   # cmake configuration files
+  if(${BUILD_SHARED_LIBS})
+    set(BUILD_LIB_TYPE SHARED)
+  else()
+    set(BUILD_LIB_TYPE STATIC)
+  endif()
   CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimp-config.cmake.in"         "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake" @ONLY IMMEDIATE)
   CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimp-config.cmake.in"         "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake" @ONLY IMMEDIATE)
   CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimpTargets.cmake.in"         "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets.cmake" @ONLY IMMEDIATE)
   CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimpTargets.cmake.in"         "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets.cmake" @ONLY IMMEDIATE)
   IF (is_multi_config)
   IF (is_multi_config)
@@ -559,17 +562,15 @@ ENDIF(NOT HUNTER_ENABLED)
 
 
 ADD_SUBDIRECTORY( code/ )
 ADD_SUBDIRECTORY( code/ )
 IF ( ASSIMP_BUILD_ASSIMP_TOOLS )
 IF ( ASSIMP_BUILD_ASSIMP_TOOLS )
+  # The viewer for windows only
   IF ( WIN32 AND DirectX_D3DX9_LIBRARY )
   IF ( WIN32 AND DirectX_D3DX9_LIBRARY )
     OPTION ( ASSIMP_BUILD_ASSIMP_VIEW "If the Assimp view tool is built. (requires DirectX)" ${DirectX_FOUND} )
     OPTION ( ASSIMP_BUILD_ASSIMP_VIEW "If the Assimp view tool is built. (requires DirectX)" ${DirectX_FOUND} )
     IF ( ASSIMP_BUILD_ASSIMP_VIEW )
     IF ( ASSIMP_BUILD_ASSIMP_VIEW )
       ADD_SUBDIRECTORY( tools/assimp_view/ )
       ADD_SUBDIRECTORY( tools/assimp_view/ )
     ENDIF ( ASSIMP_BUILD_ASSIMP_VIEW )
     ENDIF ( ASSIMP_BUILD_ASSIMP_VIEW )
   ENDIF ( WIN32 AND DirectX_D3DX9_LIBRARY )
   ENDIF ( WIN32 AND DirectX_D3DX9_LIBRARY )
-
+  # Te command line tool
   ADD_SUBDIRECTORY( tools/assimp_cmd/ )
   ADD_SUBDIRECTORY( tools/assimp_cmd/ )
-IF (NOT IOS)
-  ADD_SUBDIRECTORY( tools/assimp_qt_viewer/ )
-ENDIF (NOT IOS)
 ENDIF ( ASSIMP_BUILD_ASSIMP_TOOLS )
 ENDIF ( ASSIMP_BUILD_ASSIMP_TOOLS )
 
 
 IF ( ASSIMP_BUILD_SAMPLES)
 IF ( ASSIMP_BUILD_SAMPLES)

+ 8 - 2
Readme.md

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

+ 8 - 2
appveyor.yml

@@ -17,6 +17,8 @@ image:
   - Visual Studio 2013
   - Visual Studio 2013
   - Visual Studio 2015
   - Visual Studio 2015
   - Visual Studio 2017
   - Visual Studio 2017
+  - Visual Studio 2019
+  - MinGW  
     
     
 platform:
 platform:
   - Win32
   - Win32
@@ -27,11 +29,15 @@ configuration: Release
 install:
 install:
   - set PATH=C:\Ruby24-x64\bin;%PATH%
   - set PATH=C:\Ruby24-x64\bin;%PATH%
   - set CMAKE_DEFINES -DASSIMP_WERROR=ON
   - set CMAKE_DEFINES -DASSIMP_WERROR=ON
+  - if [%COMPILER%]==[MinGW] set PATH=C:\MinGW\bin;%PATH%
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2013" set CMAKE_GENERATOR_NAME=Visual Studio 12 2013
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2013" set CMAKE_GENERATOR_NAME=Visual Studio 12 2013
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" set CMAKE_GENERATOR_NAME=Visual Studio 15 2017
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" set CMAKE_GENERATOR_NAME=Visual Studio 15 2017
-  - if "%platform%"=="x64" set CMAKE_GENERATOR_NAME=%CMAKE_GENERATOR_NAME% Win64
-  - cmake %CMAKE_DEFINES% -G "%CMAKE_GENERATOR_NAME%" .
+  - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2019" set CMAKE_GENERATOR_NAME=Visual Studio 16 2019
+  - cmake %CMAKE_DEFINES% -G "%CMAKE_GENERATOR_NAME%" -A %platform% .
+  # Rename sh.exe as sh.exe in PATH interferes with MinGW  - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015
+
+  - rename "C:\Program Files\Git\usr\bin\sh.exe" "sh2.exe"
   - set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5"
   - set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5"
   - ps: Invoke-WebRequest -Uri https://download.microsoft.com/download/5/7/b/57b2947c-7221-4f33-b35e-2fc78cb10df4/vc_redist.x64.exe -OutFile .\packaging\windows-innosetup\vc_redist.x64.exe
   - ps: Invoke-WebRequest -Uri https://download.microsoft.com/download/5/7/b/57b2947c-7221-4f33-b35e-2fc78cb10df4/vc_redist.x64.exe -OutFile .\packaging\windows-innosetup\vc_redist.x64.exe
   - ps: Invoke-WebRequest -Uri https://download.microsoft.com/download/1/d/8/1d8137db-b5bb-4925-8c5d-927424a2e4de/vc_redist.x86.exe -OutFile .\packaging\windows-innosetup\vc_redist.x86.exe
   - ps: Invoke-WebRequest -Uri https://download.microsoft.com/download/1/d/8/1d8137db-b5bb-4925-8c5d-927424a2e4de/vc_redist.x86.exe -OutFile .\packaging\windows-innosetup\vc_redist.x86.exe

+ 10 - 1
assimpTargets-debug.cmake.in

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

+ 11 - 1
assimpTargets-release.cmake.in

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

+ 4 - 5
assimpTargets.cmake.in

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

+ 7 - 3
cmake-modules/Findassimp.cmake

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

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

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

+ 0 - 2
code/3DS/3DSLoader.cpp

@@ -50,9 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
 #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
 
 
-// internal headers
 #include "3DSLoader.h"
 #include "3DSLoader.h"
-#include <assimp/Macros.h>
 #include <assimp/IOSystem.hpp>
 #include <assimp/IOSystem.hpp>
 #include <assimp/scene.h>
 #include <assimp/scene.h>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/DefaultLogger.hpp>

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

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

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

@@ -49,6 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/IOSystem.hpp>
 #include <assimp/IOSystem.hpp>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/ai_assert.h>
 #include <assimp/ai_assert.h>
+#include <assimp/ZipArchiveIOSystem.h>
 
 
 #include <cstdlib>
 #include <cstdlib>
 #include <memory>
 #include <memory>
@@ -56,344 +57,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <map>
 #include <map>
 #include <algorithm>
 #include <algorithm>
 #include <cassert>
 #include <cassert>
-#ifdef ASSIMP_USE_HUNTER
-#  include <minizip/unzip.h>
-#else
-#  include <unzip.h>
-#endif
 #include "3MFXmlTags.h"
 #include "3MFXmlTags.h"
 
 
 namespace Assimp {
 namespace Assimp {
 
 
 namespace D3MF {
 namespace D3MF {
-
-class IOSystem2Unzip {
-public:
-    static voidpf open(voidpf opaque, const char* filename, int mode);
-    static uLong read(voidpf opaque, voidpf stream, void* buf, uLong size);
-    static uLong write(voidpf opaque, voidpf stream, const void* buf, uLong size);
-    static long tell(voidpf opaque, voidpf stream);
-    static long seek(voidpf opaque, voidpf stream, uLong offset, int origin);
-    static int close(voidpf opaque, voidpf stream);
-    static int testerror(voidpf opaque, voidpf stream);
-    static zlib_filefunc_def get(IOSystem* pIOHandler);
-};
-
-voidpf IOSystem2Unzip::open(voidpf opaque, const char* filename, int mode) {
-    IOSystem* io_system = reinterpret_cast<IOSystem*>(opaque);
-
-    const char* mode_fopen = NULL;
-    if((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) {
-        mode_fopen = "rb";
-    } else {
-        if(mode & ZLIB_FILEFUNC_MODE_EXISTING) {
-            mode_fopen = "r+b";
-        } else {
-            if(mode & ZLIB_FILEFUNC_MODE_CREATE) {
-                mode_fopen = "wb";
-            }
-        }
-    }
-
-    return (voidpf) io_system->Open(filename, mode_fopen);
-}
-
-uLong IOSystem2Unzip::read(voidpf /*opaque*/, voidpf stream, void* buf, uLong size) {
-    IOStream* io_stream = (IOStream*) stream;
-
-    return static_cast<uLong>(io_stream->Read(buf, 1, size));
-}
-
-uLong IOSystem2Unzip::write(voidpf /*opaque*/, voidpf stream, const void* buf, uLong size) {
-    IOStream* io_stream = (IOStream*) stream;
-
-    return static_cast<uLong>(io_stream->Write(buf, 1, size));
-}
-
-long IOSystem2Unzip::tell(voidpf /*opaque*/, voidpf stream) {
-    IOStream* io_stream = (IOStream*) stream;
-
-    return static_cast<long>(io_stream->Tell());
-}
-
-long IOSystem2Unzip::seek(voidpf /*opaque*/, voidpf stream, uLong offset, int origin) {
-    IOStream* io_stream = (IOStream*) stream;
-
-    aiOrigin assimp_origin;
-    switch (origin) {
-        default:
-        case ZLIB_FILEFUNC_SEEK_CUR:
-            assimp_origin = aiOrigin_CUR;
-            break;
-        case ZLIB_FILEFUNC_SEEK_END:
-            assimp_origin = aiOrigin_END;
-            break;
-        case ZLIB_FILEFUNC_SEEK_SET:
-            assimp_origin = aiOrigin_SET;
-            break;
-    }
-
-    return (io_stream->Seek(offset, assimp_origin) == aiReturn_SUCCESS ? 0 : -1);
-}
-
-int IOSystem2Unzip::close(voidpf opaque, voidpf stream) {
-    IOSystem* io_system = (IOSystem*) opaque;
-    IOStream* io_stream = (IOStream*) stream;
-
-    io_system->Close(io_stream);
-
-    return 0;
-}
-
-int IOSystem2Unzip::testerror(voidpf /*opaque*/, voidpf /*stream*/) {
-    return 0;
-}
-
-zlib_filefunc_def IOSystem2Unzip::get(IOSystem* pIOHandler) {
-    zlib_filefunc_def mapping;
-
-#ifdef ASSIMP_USE_HUNTER
-    mapping.zopen_file = (open_file_func)open;
-    mapping.zread_file = (read_file_func)read;
-    mapping.zwrite_file = (write_file_func)write;
-    mapping.ztell_file = (tell_file_func)tell;
-    mapping.zseek_file = (seek_file_func)seek;
-    mapping.zclose_file = (close_file_func)close;
-    mapping.zerror_file = (error_file_func)testerror;
-#else
-    mapping.zopen_file = open;
-    mapping.zread_file = read;
-    mapping.zwrite_file = write;
-    mapping.ztell_file = tell;
-    mapping.zseek_file = seek;
-    mapping.zclose_file = close;
-    mapping.zerror_file = testerror;
-#endif
-    mapping.opaque = reinterpret_cast<voidpf>(pIOHandler);
-
-    return mapping;
-}
-
-class ZipFile : public IOStream {
-    friend class D3MFZipArchive;
-
-public:
-    explicit ZipFile(size_t size);
-    virtual ~ZipFile();
-    size_t Read(void* pvBuffer, size_t pSize, size_t pCount );
-    size_t Write(const void* /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/);
-    size_t FileSize() const;
-    aiReturn Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/);
-    size_t Tell() const;
-    void Flush();
-
-private:
-    void *m_Buffer;
-    size_t m_Size;
-};
-
-ZipFile::ZipFile(size_t size)
-: m_Buffer( nullptr )
-, m_Size(size) {
-    ai_assert(m_Size != 0);
-    m_Buffer = ::malloc(m_Size);
-}
-
-ZipFile::~ZipFile() {
-    ::free(m_Buffer);
-    m_Buffer = NULL;
-}
-
-size_t ZipFile::Read(void* pvBuffer, size_t pSize, size_t pCount) {
-    const size_t size = pSize * pCount;
-    ai_assert(size <= m_Size);
-
-    std::memcpy(pvBuffer, m_Buffer, size);
-
-    return size;
-}
-
-size_t ZipFile::Write(const void* pvBuffer, size_t size, size_t pCount ) {
-    const size_t size_to_write( size * pCount );
-    if ( 0 == size_to_write ) {
-        return 0U;
-    }
-    return 0U;
-}
-
-size_t ZipFile::FileSize() const {
-    return m_Size;
-}
-
-aiReturn ZipFile::Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/) {
-    return aiReturn_FAILURE;
-}
-
-size_t ZipFile::Tell() const {
-    return 0;
-}
-
-void ZipFile::Flush() {
-    // empty
-}
-
-class D3MFZipArchive : public IOSystem {
-public:
-    static const unsigned int FileNameSize = 256;
-
-    D3MFZipArchive(IOSystem* pIOHandler, const std::string & rFile);
-    ~D3MFZipArchive();
-    bool Exists(const char* pFile) const;
-    char getOsSeparator() const;
-    IOStream* Open(const char* pFile, const char* pMode = "rb");
-    void Close(IOStream* pFile);
-    bool isOpen() const;
-    void getFileList(std::vector<std::string> &rFileList);
-
-private:
-    bool mapArchive();
-
-private:
-    unzFile m_ZipFileHandle;
-    std::map<std::string, ZipFile*> m_ArchiveMap;
-};
-
-// ------------------------------------------------------------------------------------------------
-//  Constructor.
-D3MFZipArchive::D3MFZipArchive(IOSystem* pIOHandler, const std::string& rFile)
-: m_ZipFileHandle( nullptr )
-, m_ArchiveMap() {
-    if (! rFile.empty()) {                
-        zlib_filefunc_def mapping = IOSystem2Unzip::get(pIOHandler);            
-
-        m_ZipFileHandle = unzOpen2(rFile.c_str(), &mapping);
-        if(m_ZipFileHandle != nullptr ) {
-            mapArchive();
-        }
-    }
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Destructor.
-D3MFZipArchive::~D3MFZipArchive() {
-    for(auto &file : m_ArchiveMap) {
-        delete file.second;
-    }
-    m_ArchiveMap.clear();
-
-    if(m_ZipFileHandle != nullptr) {
-        unzClose(m_ZipFileHandle);
-        m_ZipFileHandle = nullptr;
-    }
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Returns true, if the archive is already open.
-bool D3MFZipArchive::isOpen() const {
-    return (m_ZipFileHandle != nullptr );
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Returns true, if the filename is part of the archive.
-bool D3MFZipArchive::Exists(const char* pFile) const {
-    ai_assert(pFile != nullptr );
-
-    if ( pFile == nullptr ) {
-        return false;
-    }
-
-    std::string filename(pFile);
-    std::map<std::string, ZipFile*>::const_iterator it = m_ArchiveMap.find(filename);
-    bool exist( false );
-    if(it != m_ArchiveMap.end()) {
-        exist = true;
-    }
-
-    return exist;
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Returns the separator delimiter.
-char D3MFZipArchive::getOsSeparator() const {
-#ifndef _WIN32
-    return '/';
-#else
-    return '\\';
-#endif
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Opens a file, which is part of the archive.
-IOStream *D3MFZipArchive::Open(const char* pFile, const char* /*pMode*/) {
-    ai_assert(pFile != NULL);
-
-    IOStream* result = NULL;
-
-    std::map<std::string, ZipFile*>::iterator it = m_ArchiveMap.find(pFile);
-
-    if(it != m_ArchiveMap.end()) {
-        result = static_cast<IOStream*>(it->second);
-    }
-
-    return result;
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Close a filestream.
-void D3MFZipArchive::Close(IOStream *pFile) {
-    (void)(pFile);
-    ai_assert(pFile != NULL);
-
-    // We don't do anything in case the file would be opened again in the future
-}
-// ------------------------------------------------------------------------------------------------
-//  Returns the file-list of the archive.
-void D3MFZipArchive::getFileList(std::vector<std::string> &rFileList) {
-    rFileList.clear();
-
-    for(const auto &file : m_ArchiveMap) {
-        rFileList.push_back(file.first);
-    }
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Maps the archive content.
-bool D3MFZipArchive::mapArchive() {
-    bool success = false;
-
-    if(m_ZipFileHandle != NULL) {
-        if(m_ArchiveMap.empty()) {
-            //  At first ensure file is already open
-            if(unzGoToFirstFile(m_ZipFileHandle) == UNZ_OK) {
-                // Loop over all files
-                do {
-                    char filename[FileNameSize];
-                    unz_file_info fileInfo;
-
-                    if(unzGetCurrentFileInfo(m_ZipFileHandle, &fileInfo, filename, FileNameSize, NULL, 0, NULL, 0) == UNZ_OK) {
-                        // The file has EXACTLY the size of uncompressed_size. In C
-                        // you need to mark the last character with '\0', so add
-                        // another character
-                        if(fileInfo.uncompressed_size != 0 && unzOpenCurrentFile(m_ZipFileHandle) == UNZ_OK) {
-                            std::pair<std::map<std::string, ZipFile*>::iterator, bool> result = m_ArchiveMap.insert(std::make_pair(filename, new ZipFile(fileInfo.uncompressed_size)));
-
-                            if(unzReadCurrentFile(m_ZipFileHandle, result.first->second->m_Buffer, fileInfo.uncompressed_size) == (long int) fileInfo.uncompressed_size) {
-                                if(unzCloseCurrentFile(m_ZipFileHandle) == UNZ_OK) {
-                                    // Nothing to do anymore...
-                                }
-                            }
-                        }
-                    }
-                } while(unzGoToNextFile(m_ZipFileHandle) != UNZ_END_OF_LIST_OF_FILE);
-            }
-        }
-
-        success = true;
-    }
-
-    return success;
-}
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 
 
 typedef std::shared_ptr<OpcPackageRelationship> OpcPackageRelationshipPtr;
 typedef std::shared_ptr<OpcPackageRelationship> OpcPackageRelationshipPtr;
@@ -453,7 +121,7 @@ public:
 D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
 D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
 : mRootStream(nullptr)
 : mRootStream(nullptr)
 , mZipArchive() {    
 , mZipArchive() {    
-    mZipArchive.reset( new D3MF::D3MFZipArchive( pIOHandler, rFile ) );    
+    mZipArchive.reset( new ZipArchiveIOSystem( pIOHandler, rFile ) );
     if(!mZipArchive->isOpen()) {
     if(!mZipArchive->isOpen()) {
         throw DeadlyImportError("Failed to open file " + rFile+ ".");
         throw DeadlyImportError("Failed to open file " + rFile+ ".");
     }
     }
@@ -481,14 +149,14 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
 
 
             ASSIMP_LOG_DEBUG(rootFile);
             ASSIMP_LOG_DEBUG(rootFile);
 
 
+            mZipArchive->Close(fileStream);
+
             mRootStream = mZipArchive->Open(rootFile.c_str());
             mRootStream = mZipArchive->Open(rootFile.c_str());
             ai_assert( mRootStream != nullptr );
             ai_assert( mRootStream != nullptr );
             if ( nullptr == mRootStream ) {
             if ( nullptr == mRootStream ) {
                 throw DeadlyExportError( "Cannot open root-file in archive : " + rootFile );
                 throw DeadlyExportError( "Cannot open root-file in archive : " + rootFile );
             }
             }
 
 
-            mZipArchive->Close( fileStream );
-
         } else if( file == D3MF::XmlTag::CONTENT_TYPES_ARCHIVE) {
         } else if( file == D3MF::XmlTag::CONTENT_TYPES_ARCHIVE) {
             ASSIMP_LOG_WARN_F("Ignored file of unsupported type CONTENT_TYPES_ARCHIVES",file);
             ASSIMP_LOG_WARN_F("Ignored file of unsupported type CONTENT_TYPES_ARCHIVES",file);
         } else {
         } else {
@@ -499,7 +167,7 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
 }
 }
 
 
 D3MFOpcPackage::~D3MFOpcPackage() {
 D3MFOpcPackage::~D3MFOpcPackage() {
-    // empty
+    mZipArchive->Close(mRootStream);
 }
 }
 
 
 IOStream* D3MFOpcPackage::RootStream() const {
 IOStream* D3MFOpcPackage::RootStream() const {
@@ -516,15 +184,6 @@ bool D3MFOpcPackage::validate() {
     return mZipArchive->Exists( ModelRef.c_str() );
     return mZipArchive->Exists( ModelRef.c_str() );
 }
 }
 
 
-bool D3MFOpcPackage::isZipArchive( IOSystem* pIOHandler, const std::string& rFile ) {
-    D3MF::D3MFZipArchive ar( pIOHandler, rFile );
-    if ( !ar.isOpen() ) {
-        return false;
-    }
-
-    return true;
-}
-
 std::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream* stream) {
 std::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream* stream) {
     std::unique_ptr<CIrrXML_IOStreamReader> xmlStream(new CIrrXML_IOStreamReader(stream));
     std::unique_ptr<CIrrXML_IOStreamReader> xmlStream(new CIrrXML_IOStreamReader(stream));
     std::unique_ptr<XmlReader> xml(irr::io::createIrrXMLReader(xmlStream.get()));
     std::unique_ptr<XmlReader> xml(irr::io::createIrrXMLReader(xmlStream.get()));

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

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

+ 1 - 1
code/AMF/AMFImporter.cpp

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

+ 13 - 13
code/AMF/AMFImporter_Postprocess.cpp

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

+ 0 - 9
code/Assjson/json_exporter.cpp

@@ -34,15 +34,6 @@ namespace Assimp {
 
 
 void ExportAssimp2Json(const char*, Assimp::IOSystem*, const aiScene*, const Assimp::ExportProperties*);
 void ExportAssimp2Json(const char*, Assimp::IOSystem*, const aiScene*, const Assimp::ExportProperties*);
 
 
-Exporter::ExportFormatEntry Assimp2Json_desc = Assimp::Exporter::ExportFormatEntry(
-    "json",
-    "Plain JSON representation of the Assimp scene data structure",
-    "json",
-    &ExportAssimp2Json,
-    0u
-);
-
-
 // small utility class to simplify serializing the aiScene to Json
 // small utility class to simplify serializing the aiScene to Json
 class JSONWriter {
 class JSONWriter {
 public:
 public:

+ 21 - 4
code/CMakeLists.txt

@@ -104,6 +104,7 @@ SET( PUBLIC_HEADERS
   ${HEADER_PATH}/Exporter.hpp
   ${HEADER_PATH}/Exporter.hpp
   ${HEADER_PATH}/DefaultIOStream.h
   ${HEADER_PATH}/DefaultIOStream.h
   ${HEADER_PATH}/DefaultIOSystem.h
   ${HEADER_PATH}/DefaultIOSystem.h
+  ${HEADER_PATH}/ZipArchiveIOSystem.h
   ${HEADER_PATH}/SceneCombiner.h
   ${HEADER_PATH}/SceneCombiner.h
   ${HEADER_PATH}/fast_atof.h
   ${HEADER_PATH}/fast_atof.h
   ${HEADER_PATH}/qnan.h
   ${HEADER_PATH}/qnan.h
@@ -136,7 +137,6 @@ SET( PUBLIC_HEADERS
   ${HEADER_PATH}/irrXMLWrapper.h
   ${HEADER_PATH}/irrXMLWrapper.h
   ${HEADER_PATH}/BlobIOSystem.h
   ${HEADER_PATH}/BlobIOSystem.h
   ${HEADER_PATH}/MathFunctions.h
   ${HEADER_PATH}/MathFunctions.h
-  ${HEADER_PATH}/Macros.h
   ${HEADER_PATH}/Exceptional.h
   ${HEADER_PATH}/Exceptional.h
   ${HEADER_PATH}/ByteSwapper.h
   ${HEADER_PATH}/ByteSwapper.h
 )
 )
@@ -172,6 +172,7 @@ SET( Common_SRCS
   Common/DefaultProgressHandler.h
   Common/DefaultProgressHandler.h
   Common/DefaultIOStream.cpp
   Common/DefaultIOStream.cpp
   Common/DefaultIOSystem.cpp
   Common/DefaultIOSystem.cpp
+  Common/ZipArchiveIOSystem.cpp
   Common/PolyTools.h
   Common/PolyTools.h
   Common/Importer.cpp
   Common/Importer.cpp
   Common/IFF.h
   Common/IFF.h
@@ -406,6 +407,18 @@ ADD_ASSIMP_IMPORTER( LWS
   LWS/LWSLoader.h
   LWS/LWSLoader.h
 )
 )
 
 
+ADD_ASSIMP_IMPORTER( M3D
+  M3D/M3DMaterials.h
+  M3D/M3DImporter.h
+  M3D/M3DImporter.cpp
+  M3D/m3d.h
+)
+
+ADD_ASSIMP_EXPORTER( M3D
+  M3D/M3DExporter.h
+  M3D/M3DExporter.cpp
+)
+
 ADD_ASSIMP_IMPORTER( MD2
 ADD_ASSIMP_IMPORTER( MD2
   MD2/MD2FileData.h
   MD2/MD2FileData.h
   MD2/MD2Loader.cpp
   MD2/MD2Loader.cpp
@@ -669,6 +682,8 @@ SET( PostProcessing_SRCS
   PostProcessing/MakeVerboseFormat.h
   PostProcessing/MakeVerboseFormat.h
   PostProcessing/ScaleProcess.cpp
   PostProcessing/ScaleProcess.cpp
   PostProcessing/ScaleProcess.h
   PostProcessing/ScaleProcess.h
+  PostProcessing/ArmaturePopulate.cpp
+  PostProcessing/ArmaturePopulate.h
   PostProcessing/GenBoundingBoxesProcess.cpp
   PostProcessing/GenBoundingBoxesProcess.cpp
   PostProcessing/GenBoundingBoxesProcess.h
   PostProcessing/GenBoundingBoxesProcess.h
 )
 )
@@ -688,8 +703,6 @@ ADD_ASSIMP_IMPORTER( Q3BSP
   Q3BSP/Q3BSPFileParser.cpp
   Q3BSP/Q3BSPFileParser.cpp
   Q3BSP/Q3BSPFileImporter.h
   Q3BSP/Q3BSPFileImporter.h
   Q3BSP/Q3BSPFileImporter.cpp
   Q3BSP/Q3BSPFileImporter.cpp
-  Q3BSP/Q3BSPZipArchive.h
-  Q3BSP/Q3BSPZipArchive.cpp
 )
 )
 
 
 ADD_ASSIMP_IMPORTER( RAW
 ADD_ASSIMP_IMPORTER( RAW
@@ -766,6 +779,8 @@ ADD_ASSIMP_EXPORTER( X3D
 )
 )
 
 
 ADD_ASSIMP_IMPORTER( GLTF
 ADD_ASSIMP_IMPORTER( GLTF
+  glTF/glTFCommon.h
+  glTF/glTFCommon.cpp
   glTF/glTFAsset.h
   glTF/glTFAsset.h
   glTF/glTFAsset.inl
   glTF/glTFAsset.inl
   glTF/glTFAssetWriter.h
   glTF/glTFAssetWriter.h
@@ -810,7 +825,7 @@ ADD_ASSIMP_IMPORTER( MMD
   MMD/MMDVmdParser.h
   MMD/MMDVmdParser.h
 )
 )
 
 
-ADD_ASSIMP_EXPORTER( Assjson
+ADD_ASSIMP_EXPORTER( ASSJSON
   Assjson/cencode.c
   Assjson/cencode.c
   Assjson/cencode.h
   Assjson/cencode.h
   Assjson/json_exporter.cpp
   Assjson/json_exporter.cpp
@@ -1057,6 +1072,8 @@ MESSAGE(STATUS "Disabled importer formats:${ASSIMP_IMPORTERS_DISABLED}")
 MESSAGE(STATUS "Enabled exporter formats:${ASSIMP_EXPORTERS_ENABLED}")
 MESSAGE(STATUS "Enabled exporter formats:${ASSIMP_EXPORTERS_ENABLED}")
 MESSAGE(STATUS "Disabled exporter formats:${ASSIMP_EXPORTERS_DISABLED}")
 MESSAGE(STATUS "Disabled exporter formats:${ASSIMP_EXPORTERS_DISABLED}")
 
 
+SOURCE_GROUP( include\\assimp    FILES ${PUBLIC_HEADERS} )
+
 SET( assimp_src
 SET( assimp_src
   # Assimp Files
   # Assimp Files
   ${Core_SRCS}
   ${Core_SRCS}

+ 98 - 67
code/Collada/ColladaExporter.cpp

@@ -45,6 +45,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 #include "ColladaExporter.h"
 #include "ColladaExporter.h"
 #include <assimp/Bitmap.h>
 #include <assimp/Bitmap.h>
+#include <assimp/MathFunctions.h>
 #include <assimp/fast_atof.h>
 #include <assimp/fast_atof.h>
 #include <assimp/SceneCombiner.h>
 #include <assimp/SceneCombiner.h>
 #include <assimp/StringUtils.h>
 #include <assimp/StringUtils.h>
@@ -91,6 +92,36 @@ void ExportSceneCollada(const char* pFile, IOSystem* pIOSystem, const aiScene* p
 
 
 } // end of namespace Assimp
 } // end of namespace Assimp
 
 
+// ------------------------------------------------------------------------------------------------
+// Encodes a string into a valid XML ID using the xsd:ID schema qualifications.
+static const std::string XMLIDEncode(const std::string& name) {
+    const char XML_ID_CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.";
+    const unsigned int XML_ID_CHARS_COUNT = sizeof(XML_ID_CHARS) / sizeof(char);
+
+    if (name.length() == 0) {
+        return name;
+    }
+
+    std::stringstream idEncoded;
+
+    // xsd:ID must start with letter or underscore
+    if (!((name[0] >= 'A' && name[0] <= 'z') || name[0] == '_')) {
+        idEncoded << '_';
+    }
+
+    for (std::string::const_iterator it = name.begin(); it != name.end(); ++it) {
+        // xsd:ID can only contain letters, digits, underscores, hyphens and periods
+        if (strchr(XML_ID_CHARS, *it) != nullptr) {
+            idEncoded << *it;
+        } else {
+            // Select placeholder character based on invalid character to prevent name collisions 
+            idEncoded << XML_ID_CHARS[(*it) % XML_ID_CHARS_COUNT];
+        }
+    }
+
+    return idEncoded.str();
+}
+
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor for a specific scene to export
 // Constructor for a specific scene to export
 ColladaExporter::ColladaExporter( const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file) 
 ColladaExporter::ColladaExporter( const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file) 
@@ -145,7 +176,7 @@ void ColladaExporter::WriteFile() {
     // useless Collada fu at the end, just in case we haven't had enough indirections, yet.
     // useless Collada fu at the end, just in case we haven't had enough indirections, yet.
     mOutput << startstr << "<scene>" << endstr;
     mOutput << startstr << "<scene>" << endstr;
     PushTag();
     PushTag();
-    mOutput << startstr << "<instance_visual_scene url=\"#" + XMLEscape(mScene->mRootNode->mName.C_Str()) + "\" />" << endstr;
+    mOutput << startstr << "<instance_visual_scene url=\"#" + XMLIDEncode(mScene->mRootNode->mName.C_Str()) + "\" />" << endstr;
     PopTag();
     PopTag();
     mOutput << startstr << "</scene>" << endstr;
     mOutput << startstr << "</scene>" << endstr;
     PopTag();
     PopTag();
@@ -155,7 +186,7 @@ void ColladaExporter::WriteFile() {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Writes the asset header
 // Writes the asset header
 void ColladaExporter::WriteHeader() {
 void ColladaExporter::WriteHeader() {
-    static const ai_real epsilon = ai_real( 0.00001 );
+    static const ai_real epsilon = Math::getEpsilon<ai_real>();
     static const aiQuaternion x_rot(aiMatrix3x3(
     static const aiQuaternion x_rot(aiMatrix3x3(
         0, -1,  0,
         0, -1,  0,
         1,  0,  0,
         1,  0,  0,
@@ -317,7 +348,7 @@ void ColladaExporter::WriteTextures() {
 
 
             std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char*) texture->achFormatHint);
             std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char*) texture->achFormatHint);
 
 
-            std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + name, "wb"));
+            std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + mIOSystem->getOsSeparator() + name, "wb"));
             if(outfile == NULL) {
             if(outfile == NULL) {
                 throw DeadlyExportError("could not open output texture file: " + mPath + name);
                 throw DeadlyExportError("could not open output texture file: " + mPath + name);
             }
             }
@@ -355,9 +386,10 @@ void ColladaExporter::WriteCamerasLibrary() {
 void ColladaExporter::WriteCamera(size_t pIndex){
 void ColladaExporter::WriteCamera(size_t pIndex){
 
 
     const aiCamera *cam = mScene->mCameras[pIndex];
     const aiCamera *cam = mScene->mCameras[pIndex];
-    const std::string idstrEscaped = XMLEscape(cam->mName.C_Str());
+    const std::string cameraName = XMLEscape(cam->mName.C_Str());
+    const std::string cameraId = XMLIDEncode(cam->mName.C_Str());
 
 
-    mOutput << startstr << "<camera id=\"" << idstrEscaped << "-camera\" name=\"" << idstrEscaped << "_name\" >" << endstr;
+    mOutput << startstr << "<camera id=\"" << cameraId << "-camera\" name=\"" << cameraName << "\" >" << endstr;
     PushTag();
     PushTag();
     mOutput << startstr << "<optics>" << endstr;
     mOutput << startstr << "<optics>" << endstr;
     PushTag();
     PushTag();
@@ -411,10 +443,11 @@ void ColladaExporter::WriteLightsLibrary() {
 void ColladaExporter::WriteLight(size_t pIndex){
 void ColladaExporter::WriteLight(size_t pIndex){
 
 
     const aiLight *light = mScene->mLights[pIndex];
     const aiLight *light = mScene->mLights[pIndex];
-    const std::string idstrEscaped = XMLEscape(light->mName.C_Str());
+    const std::string lightName = XMLEscape(light->mName.C_Str());
+    const std::string lightId = XMLIDEncode(light->mName.C_Str());
 
 
-    mOutput << startstr << "<light id=\"" << idstrEscaped << "-light\" name=\""
-            << idstrEscaped << "_name\" >" << endstr;
+    mOutput << startstr << "<light id=\"" << lightId << "-light\" name=\""
+            << lightName << "\" >" << endstr;
     PushTag();
     PushTag();
     mOutput << startstr << "<technique_common>" << endstr;
     mOutput << startstr << "<technique_common>" << endstr;
     PushTag();
     PushTag();
@@ -585,7 +618,7 @@ static bool isalnum_C(char c) {
 void ColladaExporter::WriteImageEntry( const Surface& pSurface, const std::string& pNameAdd) {
 void ColladaExporter::WriteImageEntry( const Surface& pSurface, const std::string& pNameAdd) {
   if( !pSurface.texture.empty() )
   if( !pSurface.texture.empty() )
   {
   {
-    mOutput << startstr << "<image id=\"" << XMLEscape(pNameAdd) << "\">" << endstr;
+    mOutput << startstr << "<image id=\"" << XMLIDEncode(pNameAdd) << "\">" << endstr;
     PushTag();
     PushTag();
     mOutput << startstr << "<init_from>";
     mOutput << startstr << "<init_from>";
 
 
@@ -618,7 +651,7 @@ void ColladaExporter::WriteTextureColorEntry( const Surface& pSurface, const std
     }
     }
     else
     else
     {
     {
-      mOutput << startstr << "<texture texture=\"" << XMLEscape(pImageName) << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
+      mOutput << startstr << "<texture texture=\"" << XMLIDEncode(pImageName) << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
     }
     }
     PopTag();
     PopTag();
     mOutput << startstr << "</" << pTypeName << ">" << endstr;
     mOutput << startstr << "</" << pTypeName << ">" << endstr;
@@ -632,21 +665,21 @@ void ColladaExporter::WriteTextureParamEntry( const Surface& pSurface, const std
   // if surface is a texture, write out the sampler and the surface parameters necessary to reference the texture
   // if surface is a texture, write out the sampler and the surface parameters necessary to reference the texture
   if( !pSurface.texture.empty() )
   if( !pSurface.texture.empty() )
   {
   {
-    mOutput << startstr << "<newparam sid=\"" << XMLEscape(pMatName) << "-" << pTypeName << "-surface\">" << endstr;
+    mOutput << startstr << "<newparam sid=\"" << XMLIDEncode(pMatName) << "-" << pTypeName << "-surface\">" << endstr;
     PushTag();
     PushTag();
     mOutput << startstr << "<surface type=\"2D\">" << endstr;
     mOutput << startstr << "<surface type=\"2D\">" << endstr;
     PushTag();
     PushTag();
-    mOutput << startstr << "<init_from>" << XMLEscape(pMatName) << "-" << pTypeName << "-image</init_from>" << endstr;
+    mOutput << startstr << "<init_from>" << XMLIDEncode(pMatName) << "-" << pTypeName << "-image</init_from>" << endstr;
     PopTag();
     PopTag();
     mOutput << startstr << "</surface>" << endstr;
     mOutput << startstr << "</surface>" << endstr;
     PopTag();
     PopTag();
     mOutput << startstr << "</newparam>" << endstr;
     mOutput << startstr << "</newparam>" << endstr;
 
 
-    mOutput << startstr << "<newparam sid=\"" << XMLEscape(pMatName) << "-" << pTypeName << "-sampler\">" << endstr;
+    mOutput << startstr << "<newparam sid=\"" << XMLIDEncode(pMatName) << "-" << pTypeName << "-sampler\">" << endstr;
     PushTag();
     PushTag();
     mOutput << startstr << "<sampler2D>" << endstr;
     mOutput << startstr << "<sampler2D>" << endstr;
     PushTag();
     PushTag();
-    mOutput << startstr << "<source>" << XMLEscape(pMatName) << "-" << pTypeName << "-surface</source>" << endstr;
+    mOutput << startstr << "<source>" << XMLIDEncode(pMatName) << "-" << pTypeName << "-surface</source>" << endstr;
     PopTag();
     PopTag();
     mOutput << startstr << "</sampler2D>" << endstr;
     mOutput << startstr << "</sampler2D>" << endstr;
     PopTag();
     PopTag();
@@ -698,11 +731,6 @@ void ColladaExporter::WriteMaterials()
         materials[a].name = std::string(name.C_Str()) + to_string(materialCountWithThisName);
         materials[a].name = std::string(name.C_Str()) + to_string(materialCountWithThisName);
       }
       }
     }
     }
-    for( std::string::iterator it = materials[a].name.begin(); it != materials[a].name.end(); ++it ) {
-      if( !isalnum_C( *it ) ) {
-        *it = '_';
-      }
-    }
 
 
     aiShadingMode shading = aiShadingMode_Flat;
     aiShadingMode shading = aiShadingMode_Flat;
     materials[a].shading_model = "phong";
     materials[a].shading_model = "phong";
@@ -767,7 +795,7 @@ void ColladaExporter::WriteMaterials()
     {
     {
       const Material& mat = *it;
       const Material& mat = *it;
       // this is so ridiculous it must be right
       // this is so ridiculous it must be right
-      mOutput << startstr << "<effect id=\"" << XMLEscape(mat.name) << "-fx\" name=\"" << XMLEscape(mat.name) << "\">" << endstr;
+      mOutput << startstr << "<effect id=\"" << XMLIDEncode(mat.name) << "-fx\" name=\"" << XMLEscape(mat.name) << "\">" << endstr;
       PushTag();
       PushTag();
       mOutput << startstr << "<profile_COMMON>" << endstr;
       mOutput << startstr << "<profile_COMMON>" << endstr;
       PushTag();
       PushTag();
@@ -818,9 +846,9 @@ void ColladaExporter::WriteMaterials()
     for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it )
     for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it )
     {
     {
       const Material& mat = *it;
       const Material& mat = *it;
-      mOutput << startstr << "<material id=\"" << XMLEscape(mat.name) << "\" name=\"" << mat.name << "\">" << endstr;
+      mOutput << startstr << "<material id=\"" << XMLIDEncode(mat.name) << "\" name=\"" << XMLEscape(mat.name) << "\">" << endstr;
       PushTag();
       PushTag();
-      mOutput << startstr << "<instance_effect url=\"#" << XMLEscape(mat.name) << "-fx\"/>" << endstr;
+      mOutput << startstr << "<instance_effect url=\"#" << XMLIDEncode(mat.name) << "-fx\"/>" << endstr;
       PopTag();
       PopTag();
       mOutput << startstr << "</material>" << endstr;
       mOutput << startstr << "</material>" << endstr;
     }
     }
@@ -849,8 +877,8 @@ void ColladaExporter::WriteControllerLibrary()
 void ColladaExporter::WriteController( size_t pIndex)
 void ColladaExporter::WriteController( size_t pIndex)
 {
 {
     const aiMesh* mesh = mScene->mMeshes[pIndex];
     const aiMesh* mesh = mScene->mMeshes[pIndex];
-    const std::string idstr = GetMeshId( pIndex);
-    const std::string idstrEscaped = XMLEscape(idstr);
+    const std::string idstr = mesh->mName.length == 0 ? GetMeshId(pIndex) : mesh->mName.C_Str();
+    const std::string idstrEscaped = XMLIDEncode(idstr);
 
 
     if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
     if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
         return;
         return;
@@ -885,7 +913,7 @@ void ColladaExporter::WriteController( size_t pIndex)
     mOutput << startstr << "<Name_array id=\"" << idstrEscaped << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">";
     mOutput << startstr << "<Name_array id=\"" << idstrEscaped << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">";
 
 
     for( size_t i = 0; i < mesh->mNumBones; ++i )
     for( size_t i = 0; i < mesh->mNumBones; ++i )
-        mOutput << XMLEscape(mesh->mBones[i]->mName.C_Str()) << " ";
+        mOutput << XMLIDEncode(mesh->mBones[i]->mName.C_Str()) << " ";
 
 
     mOutput << "</Name_array>" << endstr;
     mOutput << "</Name_array>" << endstr;
 
 
@@ -1020,14 +1048,15 @@ void ColladaExporter::WriteGeometryLibrary()
 void ColladaExporter::WriteGeometry( size_t pIndex)
 void ColladaExporter::WriteGeometry( size_t pIndex)
 {
 {
     const aiMesh* mesh = mScene->mMeshes[pIndex];
     const aiMesh* mesh = mScene->mMeshes[pIndex];
-    const std::string idstr = GetMeshId( pIndex);
-    const std::string idstrEscaped = XMLEscape(idstr);
+    const std::string idstr = mesh->mName.length == 0 ? GetMeshId(pIndex) : mesh->mName.C_Str();
+    const std::string geometryName = XMLEscape(idstr);
+    const std::string geometryId = XMLIDEncode(idstr);
 
 
     if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
     if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
         return;
         return;
 
 
     // opening tag
     // opening tag
-    mOutput << startstr << "<geometry id=\"" << idstrEscaped << "\" name=\"" << idstrEscaped << "_name\" >" << endstr;
+    mOutput << startstr << "<geometry id=\"" << geometryId << "\" name=\"" << geometryName << "\" >" << endstr;
     PushTag();
     PushTag();
 
 
     mOutput << startstr << "<mesh>" << endstr;
     mOutput << startstr << "<mesh>" << endstr;
@@ -1058,9 +1087,9 @@ void ColladaExporter::WriteGeometry( size_t pIndex)
 
 
     // assemble vertex structure
     // assemble vertex structure
     // Only write input for POSITION since we will write other as shared inputs in polygon definition
     // Only write input for POSITION since we will write other as shared inputs in polygon definition
-    mOutput << startstr << "<vertices id=\"" << idstrEscaped << "-vertices" << "\">" << endstr;
+    mOutput << startstr << "<vertices id=\"" << geometryId << "-vertices" << "\">" << endstr;
     PushTag();
     PushTag();
-    mOutput << startstr << "<input semantic=\"POSITION\" source=\"#" << idstrEscaped << "-positions\" />" << endstr;
+    mOutput << startstr << "<input semantic=\"POSITION\" source=\"#" << geometryId << "-positions\" />" << endstr;
     PopTag();
     PopTag();
     mOutput << startstr << "</vertices>" << endstr;
     mOutput << startstr << "</vertices>" << endstr;
 
 
@@ -1078,18 +1107,18 @@ void ColladaExporter::WriteGeometry( size_t pIndex)
     {
     {
         mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr;
         mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr;
         PushTag();
         PushTag();
-        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << idstrEscaped << "-vertices\" />" << endstr;
+        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
         if( mesh->HasNormals() )
         if( mesh->HasNormals() )
-            mOutput << startstr << "<input semantic=\"NORMAL\" source=\"#" << idstrEscaped << "-normals\" />" << endstr;
+            mOutput << startstr << "<input semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
         {
         {
             if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
             if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
-                mOutput << startstr << "<input semantic=\"TEXCOORD\" source=\"#" << idstrEscaped << "-tex" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+                mOutput << startstr << "<input semantic=\"TEXCOORD\" source=\"#" << geometryId << "-tex" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
         }
         }
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a )
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a )
         {
         {
             if( mesh->HasVertexColors(static_cast<unsigned int>(a) ) )
             if( mesh->HasVertexColors(static_cast<unsigned int>(a) ) )
-                mOutput << startstr << "<input semantic=\"COLOR\" source=\"#" << idstrEscaped << "-color" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+                mOutput << startstr << "<input semantic=\"COLOR\" source=\"#" << geometryId << "-color" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
         }
         }
 
 
         mOutput << startstr << "<p>";
         mOutput << startstr << "<p>";
@@ -1112,18 +1141,18 @@ void ColladaExporter::WriteGeometry( size_t pIndex)
     {
     {
         mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr;
         mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr;
         PushTag();
         PushTag();
-        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << idstrEscaped << "-vertices\" />" << endstr;
+        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
         if( mesh->HasNormals() )
         if( mesh->HasNormals() )
-            mOutput << startstr << "<input offset=\"0\" semantic=\"NORMAL\" source=\"#" << idstrEscaped << "-normals\" />" << endstr;
+            mOutput << startstr << "<input offset=\"0\" semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
         {
         {
             if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
             if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
-                mOutput << startstr << "<input offset=\"0\" semantic=\"TEXCOORD\" source=\"#" << idstrEscaped << "-tex" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+                mOutput << startstr << "<input offset=\"0\" semantic=\"TEXCOORD\" source=\"#" << geometryId << "-tex" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
         }
         }
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a )
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a )
         {
         {
             if( mesh->HasVertexColors(static_cast<unsigned int>(a) ) )
             if( mesh->HasVertexColors(static_cast<unsigned int>(a) ) )
-                mOutput << startstr << "<input offset=\"0\" semantic=\"COLOR\" source=\"#" << idstrEscaped << "-color" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+                mOutput << startstr << "<input offset=\"0\" semantic=\"COLOR\" source=\"#" << geometryId << "-color" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
         }
         }
 
 
         mOutput << startstr << "<vcount>";
         mOutput << startstr << "<vcount>";
@@ -1172,13 +1201,13 @@ void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataTy
             return;
             return;
     }
     }
 
 
-    std::string arrayId = pIdString + "-array";
+    std::string arrayId = XMLIDEncode(pIdString) + "-array";
 
 
-    mOutput << startstr << "<source id=\"" << XMLEscape(pIdString) << "\" name=\"" << XMLEscape(pIdString) << "\">" << endstr;
+    mOutput << startstr << "<source id=\"" << XMLIDEncode(pIdString) << "\" name=\"" << XMLEscape(pIdString) << "\">" << endstr;
     PushTag();
     PushTag();
 
 
     // source array
     // source array
-    mOutput << startstr << "<float_array id=\"" << XMLEscape(arrayId) << "\" count=\"" << pElementCount * floatsPerElement << "\"> ";
+    mOutput << startstr << "<float_array id=\"" << arrayId << "\" count=\"" << pElementCount * floatsPerElement << "\"> ";
     PushTag();
     PushTag();
 
 
     if( pType == FloatType_TexCoord2 )
     if( pType == FloatType_TexCoord2 )
@@ -1264,11 +1293,12 @@ void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataTy
 // Writes the scene library
 // Writes the scene library
 void ColladaExporter::WriteSceneLibrary()
 void ColladaExporter::WriteSceneLibrary()
 {
 {
-    const std::string scene_name_escaped = XMLEscape(mScene->mRootNode->mName.C_Str());
+    const std::string sceneName = XMLEscape(mScene->mRootNode->mName.C_Str());
+    const std::string sceneId = XMLIDEncode(mScene->mRootNode->mName.C_Str());
 
 
     mOutput << startstr << "<library_visual_scenes>" << endstr;
     mOutput << startstr << "<library_visual_scenes>" << endstr;
     PushTag();
     PushTag();
-    mOutput << startstr << "<visual_scene id=\"" + scene_name_escaped + "\" name=\"" + scene_name_escaped + "\">" << endstr;
+    mOutput << startstr << "<visual_scene id=\"" + sceneId + "\" name=\"" + sceneName + "\">" << endstr;
     PushTag();
     PushTag();
 
 
     // start recursive write at the root node
     // start recursive write at the root node
@@ -1299,7 +1329,7 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 		idstr = idstr + ending;
 		idstr = idstr + ending;
 	}
 	}
 
 
-	const std::string idstrEscaped = XMLEscape(idstr);
+	const std::string idstrEscaped = XMLIDEncode(idstr);
 	
 	
 	mOutput << startstr << "<animation id=\"" + idstrEscaped + "\" name=\"" + animation_name_escaped + "\">" << endstr;
 	mOutput << startstr << "<animation id=\"" + idstrEscaped + "\" name=\"" + animation_name_escaped + "\">" << endstr;
 	PushTag();
 	PushTag();
@@ -1371,13 +1401,13 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 			}
 			}
 			
 			
 			const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-interpolation");
 			const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-interpolation");
-			std::string arrayId = node_idstr + "-array";
+            std::string arrayId = XMLIDEncode(node_idstr) + "-array";
 			
 			
-			mOutput << startstr << "<source id=\"" << XMLEscape(node_idstr) << "\">" << endstr;
+			mOutput << startstr << "<source id=\"" << XMLIDEncode(node_idstr) << "\">" << endstr;
 			PushTag();
 			PushTag();
 			
 			
 			// source array
 			// source array
-			mOutput << startstr << "<Name_array id=\"" << XMLEscape(arrayId) << "\" count=\"" << names.size() << "\"> ";
+			mOutput << startstr << "<Name_array id=\"" << arrayId << "\" count=\"" << names.size() << "\"> ";
 			for( size_t a = 0; a < names.size(); ++a ) {
 			for( size_t a = 0; a < names.size(); ++a ) {
 				mOutput << names[a] << " ";
 				mOutput << names[a] << " ";
             }
             }
@@ -1386,7 +1416,7 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 			mOutput << startstr << "<technique_common>" << endstr;
 			mOutput << startstr << "<technique_common>" << endstr;
 			PushTag();
 			PushTag();
 
 
-			mOutput << startstr << "<accessor source=\"#" << XMLEscape(arrayId) << "\" count=\"" << names.size() << "\" stride=\"" << 1 << "\">" << endstr;
+			mOutput << startstr << "<accessor source=\"#" << arrayId << "\" count=\"" << names.size() << "\" stride=\"" << 1 << "\">" << endstr;
 			PushTag();
 			PushTag();
 			
 			
 			mOutput << startstr << "<param name=\"INTERPOLATION\" type=\"name\"></param>" << endstr;
 			mOutput << startstr << "<param name=\"INTERPOLATION\" type=\"name\"></param>" << endstr;
@@ -1408,12 +1438,12 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 		{
 		{
 		// samplers
 		// samplers
 			const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-sampler");
 			const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-sampler");
-			mOutput << startstr << "<sampler id=\"" << XMLEscape(node_idstr) << "\">" << endstr;
+			mOutput << startstr << "<sampler id=\"" << XMLIDEncode(node_idstr) << "\">" << endstr;
 			PushTag();
 			PushTag();
 			
 			
-			mOutput << startstr << "<input semantic=\"INPUT\" source=\"#" << XMLEscape( nodeAnim->mNodeName.data + std::string("_matrix-input") ) << "\"/>" << endstr;
-			mOutput << startstr << "<input semantic=\"OUTPUT\" source=\"#" << XMLEscape( nodeAnim->mNodeName.data + std::string("_matrix-output") ) << "\"/>" << endstr;
-			mOutput << startstr << "<input semantic=\"INTERPOLATION\" source=\"#" << XMLEscape( nodeAnim->mNodeName.data + std::string("_matrix-interpolation") ) << "\"/>" << endstr;
+			mOutput << startstr << "<input semantic=\"INPUT\" source=\"#" << XMLIDEncode( nodeAnim->mNodeName.data + std::string("_matrix-input") ) << "\"/>" << endstr;
+			mOutput << startstr << "<input semantic=\"OUTPUT\" source=\"#" << XMLIDEncode( nodeAnim->mNodeName.data + std::string("_matrix-output") ) << "\"/>" << endstr;
+			mOutput << startstr << "<input semantic=\"INTERPOLATION\" source=\"#" << XMLIDEncode( nodeAnim->mNodeName.data + std::string("_matrix-interpolation") ) << "\"/>" << endstr;
 			
 			
 			PopTag();
 			PopTag();
 			mOutput << startstr << "</sampler>" << endstr;
 			mOutput << startstr << "</sampler>" << endstr;
@@ -1425,7 +1455,7 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 		
 		
 		{
 		{
 		// channels
 		// channels
-			mOutput << startstr << "<channel source=\"#" << XMLEscape( nodeAnim->mNodeName.data + std::string("_matrix-sampler") ) << "\" target=\"" << XMLEscape(nodeAnim->mNodeName.data) << "/matrix\"/>" << endstr;
+			mOutput << startstr << "<channel source=\"#" << XMLIDEncode( nodeAnim->mNodeName.data + std::string("_matrix-sampler") ) << "\" target=\"" << XMLIDEncode(nodeAnim->mNodeName.data) << "/matrix\"/>" << endstr;
 		}
 		}
 	}
 	}
 	
 	
@@ -1436,8 +1466,6 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void ColladaExporter::WriteAnimationsLibrary()
 void ColladaExporter::WriteAnimationsLibrary()
 {
 {
-	const std::string scene_name_escaped = XMLEscape(mScene->mRootNode->mName.C_Str());
-	
 	if ( mScene->mNumAnimations > 0 ) {
 	if ( mScene->mNumAnimations > 0 ) {
 		mOutput << startstr << "<library_animations>" << endstr;
 		mOutput << startstr << "<library_animations>" << endstr;
 		PushTag();
 		PushTag();
@@ -1545,16 +1573,17 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
         }
         }
     }
     }
 
 
-    const std::string node_name_escaped = XMLEscape(pNode->mName.data);
+    const std::string node_id = XMLIDEncode(pNode->mName.data);
+    const std::string node_name = XMLEscape(pNode->mName.data);
 	mOutput << startstr << "<node ";
 	mOutput << startstr << "<node ";
 	if(is_skeleton_root) {
 	if(is_skeleton_root) {
-		mOutput << "id=\"" << node_name_escaped << "\" " << (is_joint ? "sid=\"" + node_name_escaped +"\"" : "") ; // For now, only support one skeleton in a scene.
-		mFoundSkeletonRootNodeID = node_name_escaped;
+		mOutput << "id=\"" << node_id << "\" " << (is_joint ? "sid=\"" + node_id +"\"" : "") ; // For now, only support one skeleton in a scene.
+		mFoundSkeletonRootNodeID = node_id;
 	} else {
 	} else {
-		mOutput << "id=\"" << node_name_escaped << "\" " << (is_joint ? "sid=\"" + node_name_escaped +"\"": "") ;
+		mOutput << "id=\"" << node_id << "\" " << (is_joint ? "sid=\"" + node_id +"\"": "") ;
 	}
 	}
 	
 	
-    mOutput << " name=\"" << node_name_escaped
+    mOutput << " name=\"" << node_name
             << "\" type=\"" << node_type
             << "\" type=\"" << node_type
             << "\">" << endstr;
             << "\">" << endstr;
     PushTag();
     PushTag();
@@ -1593,14 +1622,14 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
         //check if it is a camera node
         //check if it is a camera node
         for(size_t i=0; i<mScene->mNumCameras; i++){
         for(size_t i=0; i<mScene->mNumCameras; i++){
             if(mScene->mCameras[i]->mName == pNode->mName){
             if(mScene->mCameras[i]->mName == pNode->mName){
-                mOutput << startstr <<"<instance_camera url=\"#" << node_name_escaped << "-camera\"/>" << endstr;
+                mOutput << startstr <<"<instance_camera url=\"#" << node_id << "-camera\"/>" << endstr;
                 break;
                 break;
             }
             }
         }
         }
         //check if it is a light node
         //check if it is a light node
         for(size_t i=0; i<mScene->mNumLights; i++){
         for(size_t i=0; i<mScene->mNumLights; i++){
             if(mScene->mLights[i]->mName == pNode->mName){
             if(mScene->mLights[i]->mName == pNode->mName){
-                mOutput << startstr <<"<instance_light url=\"#" << node_name_escaped << "-light\"/>" << endstr;
+                mOutput << startstr <<"<instance_light url=\"#" << node_id << "-light\"/>" << endstr;
                 break;
                 break;
             }
             }
         }
         }
@@ -1614,15 +1643,17 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
         if( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
         if( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
             continue;
             continue;
 
 
+        const std::string meshName = mesh->mName.length == 0 ? GetMeshId(pNode->mMeshes[a]) : mesh->mName.C_Str();
+
         if( mesh->mNumBones == 0 )
         if( mesh->mNumBones == 0 )
         {
         {
-            mOutput << startstr << "<instance_geometry url=\"#" << XMLEscape(GetMeshId( pNode->mMeshes[a])) << "\">" << endstr;
+            mOutput << startstr << "<instance_geometry url=\"#" << XMLIDEncode(meshName) << "\">" << endstr;
             PushTag();
             PushTag();
         }
         }
         else
         else
         {
         {
             mOutput << startstr
             mOutput << startstr
-                    << "<instance_controller url=\"#" << XMLEscape(GetMeshId( pNode->mMeshes[a])) << "-skin\">"
+                    << "<instance_controller url=\"#" << XMLIDEncode(meshName) << "-skin\">"
                     << endstr;
                     << endstr;
             PushTag();
             PushTag();
 
 
@@ -1630,7 +1661,7 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
 			// use the first bone to find skeleton root
 			// use the first bone to find skeleton root
 			const aiNode * skeletonRootBoneNode = findSkeletonRootNode( pScene, mesh );
 			const aiNode * skeletonRootBoneNode = findSkeletonRootNode( pScene, mesh );
 			if ( skeletonRootBoneNode ) {
 			if ( skeletonRootBoneNode ) {
-				mFoundSkeletonRootNodeID = XMLEscape( skeletonRootBoneNode->mName.C_Str() );
+				mFoundSkeletonRootNodeID = XMLIDEncode( skeletonRootBoneNode->mName.C_Str() );
 			}
 			}
             mOutput << startstr << "<skeleton>#" << mFoundSkeletonRootNodeID << "</skeleton>" << endstr;
             mOutput << startstr << "<skeleton>#" << mFoundSkeletonRootNodeID << "</skeleton>" << endstr;
         }
         }
@@ -1638,7 +1669,7 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
         PushTag();
         PushTag();
         mOutput << startstr << "<technique_common>" << endstr;
         mOutput << startstr << "<technique_common>" << endstr;
         PushTag();
         PushTag();
-        mOutput << startstr << "<instance_material symbol=\"defaultMaterial\" target=\"#" << XMLEscape(materials[mesh->mMaterialIndex].name) << "\">" << endstr;
+        mOutput << startstr << "<instance_material symbol=\"defaultMaterial\" target=\"#" << XMLIDEncode(materials[mesh->mMaterialIndex].name) << "\">" << endstr;
         PushTag();
         PushTag();
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
         for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
         {
         {
@@ -1671,4 +1702,4 @@ void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
 }
 }
 
 
 #endif
 #endif
-#endif
+#endif

+ 2 - 6
code/Collada/ColladaHelper.h

@@ -580,15 +580,11 @@ struct Image
 {
 {
     std::string mFileName;
     std::string mFileName;
 
 
-    /** If image file name is zero, embedded image data
-     */
+    /** Embedded image data */
     std::vector<uint8_t> mImageData;
     std::vector<uint8_t> mImageData;
 
 
-    /** If image file name is zero, file format of
-     *  embedded image data.
-     */
+    /** File format hint of embedded image data */
     std::string mEmbeddedFormat;
     std::string mEmbeddedFormat;
-
 };
 };
 
 
 /** An animation channel. */
 /** An animation channel. */

文件差异内容过多而无法显示
+ 297 - 278
code/Collada/ColladaLoader.cpp


+ 5 - 8
code/Collada/ColladaLoader.h

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

文件差异内容过多而无法显示
+ 407 - 277
code/Collada/ColladaParser.cpp


+ 11 - 1
code/Collada/ColladaParser.h

@@ -54,6 +54,7 @@
 
 
 namespace Assimp
 namespace Assimp
 {
 {
+    class ZipArchiveIOSystem;
 
 
     // ------------------------------------------------------------------------------------------
     // ------------------------------------------------------------------------------------------
     /** Parser helper class for the Collada loader.
     /** Parser helper class for the Collada loader.
@@ -65,16 +66,22 @@ namespace Assimp
     {
     {
         friend class ColladaLoader;
         friend class ColladaLoader;
 
 
+        /** Converts a path read from a collada file to the usual representation */
+        static void UriDecodePath(aiString& ss);
+
     protected:
     protected:
         /** Map for generic metadata as aiString */
         /** Map for generic metadata as aiString */
         typedef std::map<std::string, aiString> StringMetaData;
         typedef std::map<std::string, aiString> StringMetaData;
 
 
         /** Constructor from XML file */
         /** Constructor from XML file */
-        ColladaParser( IOSystem* pIOHandler, const std::string& pFile);
+        ColladaParser(IOSystem* pIOHandler, const std::string& pFile);
 
 
         /** Destructor */
         /** Destructor */
         ~ColladaParser();
         ~ColladaParser();
 
 
+        /** Attempts to read the ZAE manifest and returns the DAE to open */
+        static std::string ReadZaeManifest(ZipArchiveIOSystem &zip_archive);
+
         /** Reads the contents of the file */
         /** Reads the contents of the file */
         void ReadContents();
         void ReadContents();
 
 
@@ -235,6 +242,9 @@ namespace Assimp
         // Processes bind_vertex_input and bind elements
         // Processes bind_vertex_input and bind elements
         void ReadMaterialVertexInputBinding( Collada::SemanticMappingTable& tbl);
         void ReadMaterialVertexInputBinding( Collada::SemanticMappingTable& tbl);
 
 
+        /** Reads embedded textures from a ZAE archive*/
+        void ReadEmbeddedTextures(ZipArchiveIOSystem &zip_archive);
+
     protected:
     protected:
         /** Aborts the file reading with an exception */
         /** Aborts the file reading with an exception */
         AI_WONT_RETURN void ThrowException( const std::string& pError) const AI_WONT_RETURN_SUFFIX;
         AI_WONT_RETURN void ThrowException( const std::string& pError) const AI_WONT_RETURN_SUFFIX;

+ 39 - 3
code/Common/BaseImporter.cpp

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

+ 31 - 2
code/Common/DefaultIOStream.cpp

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

+ 60 - 101
code/Common/DefaultIOSystem.cpp

@@ -61,83 +61,66 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 using namespace Assimp;
 using namespace Assimp;
 
 
-// maximum path length
-// XXX http://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
-#ifdef PATH_MAX
-#   define PATHLIMIT PATH_MAX
-#else
-#   define PATHLIMIT 4096
+#ifdef _WIN32
+static std::wstring Utf8ToWide(const char* in)
+{
+    int size = MultiByteToWideChar(CP_UTF8, 0, in, -1, nullptr, 0);
+    // size includes terminating null; std::wstring adds null automatically
+    std::wstring out(static_cast<size_t>(size) - 1, L'\0');
+    MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], size);
+    return out;
+}
+
+static std::string WideToUtf8(const wchar_t* in)
+{
+    int size = WideCharToMultiByte(CP_UTF8, 0, in, -1, nullptr, 0, nullptr, nullptr);
+    // size includes terminating null; std::string adds null automatically
+    std::string out(static_cast<size_t>(size) - 1, '\0');
+    WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], size, nullptr, nullptr);
+    return out;
+}
 #endif
 #endif
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Tests for the existence of a file at the given path.
 // Tests for the existence of a file at the given path.
-bool DefaultIOSystem::Exists( const char* pFile) const
+bool DefaultIOSystem::Exists(const char* pFile) const
 {
 {
 #ifdef _WIN32
 #ifdef _WIN32
-    wchar_t fileName16[PATHLIMIT];
-
-#ifndef WindowsStore
-    bool isUnicode = IsTextUnicode(pFile, static_cast<int>(strlen(pFile)), NULL) != 0;
-    if (isUnicode) {
-
-        MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, pFile, -1, fileName16, PATHLIMIT);
-        struct __stat64 filestat;
-        if (0 != _wstat64(fileName16, &filestat)) {
-            return false;
-        }
-    } else {
-#endif
-        FILE* file = ::fopen(pFile, "rb");
-        if (!file)
-            return false;
-
-        ::fclose(file);
-#ifndef WindowsStore
+    struct __stat64 filestat;
+    if (_wstat64(Utf8ToWide(pFile).c_str(), &filestat) != 0) {
+        return false;
     }
     }
-#endif
 #else
 #else
-    FILE* file = ::fopen( pFile, "rb");
-    if( !file)
+    FILE* file = ::fopen(pFile, "rb");
+    if (!file)
         return false;
         return false;
 
 
-    ::fclose( file);
+    ::fclose(file);
 #endif
 #endif
     return true;
     return true;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Open a new file with a given path.
 // Open a new file with a given path.
-IOStream* DefaultIOSystem::Open( const char* strFile, const char* strMode)
+IOStream* DefaultIOSystem::Open(const char* strFile, const char* strMode)
 {
 {
-    ai_assert(NULL != strFile);
-    ai_assert(NULL != strMode);
+    ai_assert(strFile != nullptr);
+    ai_assert(strMode != nullptr);
     FILE* file;
     FILE* file;
 #ifdef _WIN32
 #ifdef _WIN32
-    wchar_t fileName16[PATHLIMIT];
-#ifndef WindowsStore
-    bool isUnicode = IsTextUnicode(strFile, static_cast<int>(strlen(strFile)), NULL) != 0;
-    if (isUnicode) {
-        MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, strFile, -1, fileName16, PATHLIMIT);
-        std::string mode8(strMode);
-        file = ::_wfopen(fileName16, std::wstring(mode8.begin(), mode8.end()).c_str());
-    } else {
-#endif
-        file = ::fopen(strFile, strMode);
-#ifndef WindowsStore
-    }
-#endif
+    file = ::_wfopen(Utf8ToWide(strFile).c_str(), Utf8ToWide(strMode).c_str());
 #else
 #else
     file = ::fopen(strFile, strMode);
     file = ::fopen(strFile, strMode);
 #endif
 #endif
-    if (nullptr == file)
+    if (!file)
         return nullptr;
         return nullptr;
 
 
-    return new DefaultIOStream(file, (std::string) strFile);
+    return new DefaultIOStream(file, strFile);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Closes the given file and releases all resources associated with it.
 // Closes the given file and releases all resources associated with it.
-void DefaultIOSystem::Close( IOStream* pFile)
+void DefaultIOSystem::Close(IOStream* pFile)
 {
 {
     delete pFile;
     delete pFile;
 }
 }
@@ -155,78 +138,56 @@ char DefaultIOSystem::getOsSeparator() const
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // IOSystem default implementation (ComparePaths isn't a pure virtual function)
 // IOSystem default implementation (ComparePaths isn't a pure virtual function)
-bool IOSystem::ComparePaths (const char* one, const char* second) const
+bool IOSystem::ComparePaths(const char* one, const char* second) const
 {
 {
-    return !ASSIMP_stricmp(one,second);
+    return !ASSIMP_stricmp(one, second);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Convert a relative path into an absolute path
 // Convert a relative path into an absolute path
-inline static void MakeAbsolutePath (const char* in, char* _out)
+inline static std::string MakeAbsolutePath(const char* in)
 {
 {
-    ai_assert(in && _out);
-#if defined( _MSC_VER ) || defined( __MINGW32__ )
-#ifndef WindowsStore
-    bool isUnicode = IsTextUnicode(in, static_cast<int>(strlen(in)), NULL) != 0;
-    if (isUnicode) {
-        wchar_t out16[PATHLIMIT];
-        wchar_t in16[PATHLIMIT];
-        MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, in, -1, out16, PATHLIMIT);
-        wchar_t* ret = ::_wfullpath(out16, in16, PATHLIMIT);
-        if (ret) {
-            WideCharToMultiByte(CP_UTF8, MB_PRECOMPOSED, out16, -1, _out, PATHLIMIT, nullptr, nullptr);
-        }
-        if (!ret) {
-            // preserve the input path, maybe someone else is able to fix
-            // the path before it is accessed (e.g. our file system filter)
-            ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
-            strcpy(_out, in);
-        }
-
-    } else {
-#endif
-        char* ret = :: _fullpath(_out, in, PATHLIMIT);
-        if (!ret) {
-            // preserve the input path, maybe someone else is able to fix
-            // the path before it is accessed (e.g. our file system filter)
-            ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
-            strcpy(_out, in);
-        }
-#ifndef WindowsStore
+    ai_assert(in);
+    std::string out;
+#ifdef _WIN32
+    wchar_t* ret = ::_wfullpath(nullptr, Utf8ToWide(in).c_str(), 0);
+    if (ret) {
+        out = WideToUtf8(ret);
+        free(ret);
     }
     }
-#endif
 #else
 #else
-    // use realpath
-    char* ret = realpath(in, _out);
-    if(!ret) {
+    char* ret = realpath(in, nullptr);
+    if (ret) {
+        out = ret;
+        free(ret);
+    }
+#endif
+    if (!ret) {
         // preserve the input path, maybe someone else is able to fix
         // preserve the input path, maybe someone else is able to fix
         // the path before it is accessed (e.g. our file system filter)
         // the path before it is accessed (e.g. our file system filter)
         ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
         ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
-        strcpy(_out,in);
+        out = in;
     }
     }
-#endif
+    return out;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // DefaultIOSystem's more specialized implementation
 // DefaultIOSystem's more specialized implementation
-bool DefaultIOSystem::ComparePaths (const char* one, const char* second) const
+bool DefaultIOSystem::ComparePaths(const char* one, const char* second) const
 {
 {
     // chances are quite good both paths are formatted identically,
     // chances are quite good both paths are formatted identically,
     // so we can hopefully return here already
     // so we can hopefully return here already
-    if( !ASSIMP_stricmp(one,second) )
+    if (!ASSIMP_stricmp(one, second))
         return true;
         return true;
 
 
-    char temp1[PATHLIMIT];
-    char temp2[PATHLIMIT];
-
-    MakeAbsolutePath (one, temp1);
-    MakeAbsolutePath (second, temp2);
+    std::string temp1 = MakeAbsolutePath(one);
+    std::string temp2 = MakeAbsolutePath(second);
 
 
-    return !ASSIMP_stricmp(temp1,temp2);
+    return !ASSIMP_stricmp(temp1, temp2);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-std::string DefaultIOSystem::fileName( const std::string &path )
+std::string DefaultIOSystem::fileName(const std::string& path)
 {
 {
     std::string ret = path;
     std::string ret = path;
     std::size_t last = ret.find_last_of("\\/");
     std::size_t last = ret.find_last_of("\\/");
@@ -235,16 +196,16 @@ std::string DefaultIOSystem::fileName( const std::string &path )
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-std::string DefaultIOSystem::completeBaseName( const std::string &path )
+std::string DefaultIOSystem::completeBaseName(const std::string& path)
 {
 {
     std::string ret = fileName(path);
     std::string ret = fileName(path);
     std::size_t pos = ret.find_last_of('.');
     std::size_t pos = ret.find_last_of('.');
-    if(pos != ret.npos) ret = ret.substr(0, pos);
+    if (pos != std::string::npos) ret = ret.substr(0, pos);
     return ret;
     return ret;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-std::string DefaultIOSystem::absolutePath( const std::string &path )
+std::string DefaultIOSystem::absolutePath(const std::string& path)
 {
 {
     std::string ret = path;
     std::string ret = path;
     std::size_t last = ret.find_last_of("\\/");
     std::size_t last = ret.find_last_of("\\/");
@@ -253,5 +214,3 @@ std::string DefaultIOSystem::absolutePath( const std::string &path )
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-
-#undef PATHLIMIT

+ 2 - 2
code/Common/DefaultLogger.cpp

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

+ 74 - 91
code/Common/Exporter.cpp

@@ -102,94 +102,92 @@ void ExportSceneX3D(const char*, IOSystem*, const aiScene*, const ExportProperti
 void ExportSceneFBX(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneFBX(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* );
 void ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* );
+void ExportSceneM3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneA3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportAssimp2Json(const char* , IOSystem*, const aiScene* , const Assimp::ExportProperties*);
 void ExportAssimp2Json(const char* , IOSystem*, const aiScene* , const Assimp::ExportProperties*);
 
 
-// ------------------------------------------------------------------------------------------------
-// global array of all export formats which Assimp supports in its current build
-Exporter::ExportFormatEntry gExporters[] =
-{
+
+static void setupExporterArray(std::vector<Exporter::ExportFormatEntry> &exporters) {
 #ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER
 #ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER
-    Exporter::ExportFormatEntry( "collada", "COLLADA - Digital Asset Exchange Schema", "dae", &ExportSceneCollada ),
+	exporters.push_back(Exporter::ExportFormatEntry("collada", "COLLADA - Digital Asset Exchange Schema", "dae", &ExportSceneCollada));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_X_EXPORTER
 #ifndef ASSIMP_BUILD_NO_X_EXPORTER
-    Exporter::ExportFormatEntry( "x", "X Files", "x", &ExportSceneXFile,
-        aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs ),
+	exporters.push_back(Exporter::ExportFormatEntry("x", "X Files", "x", &ExportSceneXFile,
+			aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_STEP_EXPORTER
 #ifndef ASSIMP_BUILD_NO_STEP_EXPORTER
-    Exporter::ExportFormatEntry( "stp", "Step Files", "stp", &ExportSceneStep, 0 ),
+	exporters.push_back(Exporter::ExportFormatEntry("stp", "Step Files", "stp", &ExportSceneStep, 0));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER
 #ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER
-    Exporter::ExportFormatEntry( "obj", "Wavefront OBJ format", "obj", &ExportSceneObj,
-        aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */ ),
-    Exporter::ExportFormatEntry( "objnomtl", "Wavefront OBJ format without material file", "obj", &ExportSceneObjNoMtl,
-        aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */ ),
+	exporters.push_back(Exporter::ExportFormatEntry("obj", "Wavefront OBJ format", "obj", &ExportSceneObj,
+			aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */));
+	exporters.push_back(Exporter::ExportFormatEntry("objnomtl", "Wavefront OBJ format without material file", "obj", &ExportSceneObjNoMtl,
+			aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_STL_EXPORTER
 #ifndef ASSIMP_BUILD_NO_STL_EXPORTER
-    Exporter::ExportFormatEntry( "stl", "Stereolithography", "stl" , &ExportSceneSTL,
-        aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices
-    ),
-    Exporter::ExportFormatEntry( "stlb", "Stereolithography (binary)", "stl" , &ExportSceneSTLBinary,
-        aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices
-    ),
+	exporters.push_back(Exporter::ExportFormatEntry("stl", "Stereolithography", "stl", &ExportSceneSTL,
+			aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices));
+	exporters.push_back(Exporter::ExportFormatEntry("stlb", "Stereolithography (binary)", "stl", &ExportSceneSTLBinary,
+			aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_PLY_EXPORTER
 #ifndef ASSIMP_BUILD_NO_PLY_EXPORTER
-    Exporter::ExportFormatEntry( "ply", "Stanford Polygon Library", "ply" , &ExportScenePly,
-        aiProcess_PreTransformVertices
-    ),
-    Exporter::ExportFormatEntry( "plyb", "Stanford Polygon Library (binary)", "ply", &ExportScenePlyBinary,
-        aiProcess_PreTransformVertices
-    ),
+	exporters.push_back(Exporter::ExportFormatEntry("ply", "Stanford Polygon Library", "ply", &ExportScenePly,
+			aiProcess_PreTransformVertices));
+	exporters.push_back(Exporter::ExportFormatEntry("plyb", "Stanford Polygon Library (binary)", "ply", &ExportScenePlyBinary,
+			aiProcess_PreTransformVertices));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_3DS_EXPORTER
 #ifndef ASSIMP_BUILD_NO_3DS_EXPORTER
-    Exporter::ExportFormatEntry( "3ds", "Autodesk 3DS (legacy)", "3ds" , &ExportScene3DS,
-        aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_JoinIdenticalVertices ),
+	exporters.push_back(Exporter::ExportFormatEntry("3ds", "Autodesk 3DS (legacy)", "3ds", &ExportScene3DS,
+			aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_JoinIdenticalVertices));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER
 #ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER
-    Exporter::ExportFormatEntry( "gltf2", "GL Transmission Format v. 2", "gltf", &ExportSceneGLTF2,
-        aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ),
-    Exporter::ExportFormatEntry( "glb2", "GL Transmission Format v. 2 (binary)", "glb", &ExportSceneGLB2,
-        aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ),
-    Exporter::ExportFormatEntry( "gltf", "GL Transmission Format", "gltf", &ExportSceneGLTF,
-        aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ),
-    Exporter::ExportFormatEntry( "glb", "GL Transmission Format (binary)", "glb", &ExportSceneGLB,
-        aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ),
+	exporters.push_back(Exporter::ExportFormatEntry("gltf2", "GL Transmission Format v. 2", "gltf", &ExportSceneGLTF2,
+			aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType));
+	exporters.push_back(Exporter::ExportFormatEntry("glb2", "GL Transmission Format v. 2 (binary)", "glb", &ExportSceneGLB2,
+			aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType));
+	exporters.push_back(Exporter::ExportFormatEntry("gltf", "GL Transmission Format", "gltf", &ExportSceneGLTF,
+			aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType));
+	exporters.push_back(Exporter::ExportFormatEntry("glb", "GL Transmission Format (binary)", "glb", &ExportSceneGLB,
+			aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER
 #ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER
-    Exporter::ExportFormatEntry( "assbin", "Assimp Binary", "assbin" , &ExportSceneAssbin, 0 ),
+	exporters.push_back(Exporter::ExportFormatEntry("assbin", "Assimp Binary File", "assbin", &ExportSceneAssbin, 0));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER
 #ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER
-    Exporter::ExportFormatEntry( "assxml", "Assxml Document", "assxml" , &ExportSceneAssxml, 0 ),
+	exporters.push_back(Exporter::ExportFormatEntry("assxml", "Assimp XML Document", "assxml", &ExportSceneAssxml, 0));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_X3D_EXPORTER
 #ifndef ASSIMP_BUILD_NO_X3D_EXPORTER
-    Exporter::ExportFormatEntry( "x3d", "Extensible 3D", "x3d" , &ExportSceneX3D, 0 ),
+	exporters.push_back(Exporter::ExportFormatEntry("x3d", "Extensible 3D", "x3d", &ExportSceneX3D, 0));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
 #ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
-    Exporter::ExportFormatEntry( "fbx", "Autodesk FBX (binary)", "fbx", &ExportSceneFBX, 0 ),
-    Exporter::ExportFormatEntry( "fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0 ),
+	exporters.push_back(Exporter::ExportFormatEntry("fbx", "Autodesk FBX (binary)", "fbx", &ExportSceneFBX, 0));
+	exporters.push_back(Exporter::ExportFormatEntry("fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0));
+#endif
+
+#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER
+	exporters.push_back(Exporter::ExportFormatEntry("m3d", "Model 3D (binary)", "m3d", &ExportSceneM3D, 0));
+	exporters.push_back(Exporter::ExportFormatEntry("a3d", "Model 3D (ascii)", "m3d", &ExportSceneA3D, 0));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_3MF_EXPORTER
 #ifndef ASSIMP_BUILD_NO_3MF_EXPORTER
-    Exporter::ExportFormatEntry( "3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0 ),
+	exporters.push_back(Exporter::ExportFormatEntry("3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0));
 #endif
 #endif
 
 
 #ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER
 #ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER
-    Exporter::ExportFormatEntry("json", "Plain JSON representation of the Assimp scene data structure", "json", &ExportAssimp2Json, 0)
+	exporters.push_back(Exporter::ExportFormatEntry("assjson", "Assimp JSON Document", "json", &ExportAssimp2Json, 0));
 #endif
 #endif
-};
-
-#define ASSIMP_NUM_EXPORTERS (sizeof(gExporters)/sizeof(gExporters[0]))
-
+}
 
 
 class ExporterPimpl {
 class ExporterPimpl {
 public:
 public:
@@ -205,10 +203,7 @@ public:
         GetPostProcessingStepInstanceList(mPostProcessingSteps);
         GetPostProcessingStepInstanceList(mPostProcessingSteps);
 
 
         // grab all built-in exporters
         // grab all built-in exporters
-        if ( 0 != ( ASSIMP_NUM_EXPORTERS ) ) {
-            mExporters.resize( ASSIMP_NUM_EXPORTERS );
-            std::copy( gExporters, gExporters + ASSIMP_NUM_EXPORTERS, mExporters.begin() );
-        }
+		setupExporterArray(mExporters);
     }
     }
 
 
     ~ExporterPimpl() {
     ~ExporterPimpl() {
@@ -252,24 +247,28 @@ Exporter :: Exporter()
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 Exporter::~Exporter() {
 Exporter::~Exporter() {
-    FreeBlob();
+	ai_assert(nullptr != pimpl);
+	FreeBlob();
     delete pimpl;
     delete pimpl;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Exporter::SetIOHandler( IOSystem* pIOHandler) {
 void Exporter::SetIOHandler( IOSystem* pIOHandler) {
-    pimpl->mIsDefaultIOHandler = !pIOHandler;
+	ai_assert(nullptr != pimpl);
+	pimpl->mIsDefaultIOHandler = !pIOHandler;
     pimpl->mIOSystem.reset(pIOHandler);
     pimpl->mIOSystem.reset(pIOHandler);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 IOSystem* Exporter::GetIOHandler() const {
 IOSystem* Exporter::GetIOHandler() const {
-    return pimpl->mIOSystem.get();
+	ai_assert(nullptr != pimpl);
+	return pimpl->mIOSystem.get();
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 bool Exporter::IsDefaultIOHandler() const {
 bool Exporter::IsDefaultIOHandler() const {
-    return pimpl->mIsDefaultIOHandler;
+	ai_assert(nullptr != pimpl);
+	return pimpl->mIsDefaultIOHandler;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -295,6 +294,7 @@ void Exporter::SetProgressHandler(ProgressHandler* pHandler) {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const char* pFormatId,
 const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const char* pFormatId,
                                                 unsigned int pPreprocessing, const ExportProperties* pProperties) {
                                                 unsigned int pPreprocessing, const ExportProperties* pProperties) {
+	ai_assert(nullptr != pimpl);
     if (pimpl->blob) {
     if (pimpl->blob) {
         delete pimpl->blob;
         delete pimpl->blob;
         pimpl->blob = nullptr;
         pimpl->blob = nullptr;
@@ -315,44 +315,16 @@ const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const cha
     return pimpl->blob;
     return pimpl->blob;
 }
 }
 
 
-// ------------------------------------------------------------------------------------------------
-bool IsVerboseFormat(const aiMesh* mesh) {
-    // avoid slow vector<bool> specialization
-    std::vector<unsigned int> seen(mesh->mNumVertices,0);
-    for(unsigned int i = 0; i < mesh->mNumFaces; ++i) {
-        const aiFace& f = mesh->mFaces[i];
-        for(unsigned int j = 0; j < f.mNumIndices; ++j) {
-            if(++seen[f.mIndices[j]] == 2) {
-                // found a duplicate index
-                return false;
-            }
-        }
-    }
-
-    return true;
-}
-
-// ------------------------------------------------------------------------------------------------
-bool IsVerboseFormat(const aiScene* pScene) {
-    for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
-        if(!IsVerboseFormat(pScene->mMeshes[i])) {
-            return false;
-        }
-    }
-
-    return true;
-}
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const char* pPath,
 aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const char* pPath,
         unsigned int pPreprocessing, const ExportProperties* pProperties) {
         unsigned int pPreprocessing, const ExportProperties* pProperties) {
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
-
+	ai_assert(nullptr != pimpl);
     // when they create scenes from scratch, users will likely create them not in verbose
     // when they create scenes from scratch, users will likely create them not in verbose
     // format. They will likely not be aware that there is a flag in the scene to indicate
     // format. They will likely not be aware that there is a flag in the scene to indicate
     // this, however. To avoid surprises and bug reports, we check for duplicates in
     // this, however. To avoid surprises and bug reports, we check for duplicates in
     // meshes upfront.
     // meshes upfront.
-    const bool is_verbose_format = !(pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) || IsVerboseFormat(pScene);
+    const bool is_verbose_format = !(pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) || MakeVerboseFormatProcess::IsVerboseFormat(pScene);
 
 
     pimpl->mProgressHandler->UpdateFileWrite(0, 4);
     pimpl->mProgressHandler->UpdateFileWrite(0, 4);
 
 
@@ -472,7 +444,10 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
                 }
                 }
 
 
                 ExportProperties emptyProperties;  // Never pass NULL ExportProperties so Exporters don't have to worry.
                 ExportProperties emptyProperties;  // Never pass NULL ExportProperties so Exporters don't have to worry.
-                exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProperties ? pProperties : &emptyProperties);
+                ExportProperties* pProp = pProperties ? (ExportProperties*)pProperties : &emptyProperties;
+                                pProp->SetPropertyBool("bJoinIdenticalVertices", must_join_again);
+                                exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProp);
+                exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProp);
 
 
                 pimpl->mProgressHandler->UpdateFileWrite(4, 4);
                 pimpl->mProgressHandler->UpdateFileWrite(4, 4);
             } catch (DeadlyExportError& err) {
             } catch (DeadlyExportError& err) {
@@ -491,11 +466,13 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 const char* Exporter::GetErrorString() const {
 const char* Exporter::GetErrorString() const {
+	ai_assert(nullptr != pimpl);
     return pimpl->mError.c_str();
     return pimpl->mError.c_str();
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Exporter::FreeBlob() {
 void Exporter::FreeBlob() {
+	ai_assert(nullptr != pimpl);
     delete pimpl->blob;
     delete pimpl->blob;
     pimpl->blob = nullptr;
     pimpl->blob = nullptr;
 
 
@@ -504,30 +481,34 @@ void Exporter::FreeBlob() {
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 const aiExportDataBlob* Exporter::GetBlob() const {
 const aiExportDataBlob* Exporter::GetBlob() const {
-    return pimpl->blob;
+	ai_assert(nullptr != pimpl);
+	return pimpl->blob;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 const aiExportDataBlob* Exporter::GetOrphanedBlob() const {
 const aiExportDataBlob* Exporter::GetOrphanedBlob() const {
-    const aiExportDataBlob* tmp = pimpl->blob;
+	ai_assert(nullptr != pimpl);
+	const aiExportDataBlob *tmp = pimpl->blob;
     pimpl->blob = nullptr;
     pimpl->blob = nullptr;
     return tmp;
     return tmp;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 size_t Exporter::GetExportFormatCount() const {
 size_t Exporter::GetExportFormatCount() const {
+	ai_assert(nullptr != pimpl);
     return pimpl->mExporters.size();
     return pimpl->mExporters.size();
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) const {
 const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) const {
-    if (index >= GetExportFormatCount()) {
+	ai_assert(nullptr != pimpl);
+	if (index >= GetExportFormatCount()) {
         return nullptr;
         return nullptr;
     }
     }
 
 
     // Return from static storage if the requested index is built-in.
     // Return from static storage if the requested index is built-in.
-    if (index < sizeof(gExporters) / sizeof(gExporters[0])) {
-        return &gExporters[index].mDescription;
+	if (index < pimpl->mExporters.size()) {
+		return &pimpl->mExporters[index].mDescription;
     }
     }
 
 
     return &pimpl->mExporters[index].mDescription;
     return &pimpl->mExporters[index].mDescription;
@@ -535,7 +516,8 @@ const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) c
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) {
 aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) {
-    for(const ExportFormatEntry& e : pimpl->mExporters) {
+	ai_assert(nullptr != pimpl);
+	for (const ExportFormatEntry &e : pimpl->mExporters) {
         if (!strcmp(e.mDescription.id,desc.mDescription.id)) {
         if (!strcmp(e.mDescription.id,desc.mDescription.id)) {
             return aiReturn_FAILURE;
             return aiReturn_FAILURE;
         }
         }
@@ -547,7 +529,8 @@ aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) {
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Exporter::UnregisterExporter(const char* id) {
 void Exporter::UnregisterExporter(const char* id) {
-    for(std::vector<ExportFormatEntry>::iterator it = pimpl->mExporters.begin();
+	ai_assert(nullptr != pimpl);
+	for (std::vector<ExportFormatEntry>::iterator it = pimpl->mExporters.begin();
             it != pimpl->mExporters.end(); ++it) {
             it != pimpl->mExporters.end(); ++it) {
         if (!strcmp((*it).mDescription.id,id)) {
         if (!strcmp((*it).mDescription.id,id)) {
             pimpl->mExporters.erase(it);
             pimpl->mExporters.erase(it);

+ 6 - 0
code/Common/ImporterRegistry.cpp

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

+ 7 - 0
code/Common/PostStepRegistry.cpp

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

+ 50 - 0
code/Common/SceneCombiner.cpp

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

+ 11 - 9
code/Common/Version.cpp

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

+ 539 - 0
code/Common/ZipArchiveIOSystem.cpp

@@ -0,0 +1,539 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  ZipArchiveIOSystem.cpp
+ *  @brief Zip File I/O implementation for #Importer
+ */
+
+#include <assimp/ZipArchiveIOSystem.h>
+#include <assimp/BaseImporter.h>
+
+#include <assimp/ai_assert.h>
+
+#include <map>
+#include <memory>
+
+#ifdef ASSIMP_USE_HUNTER
+#  include <minizip/unzip.h>
+#else
+#  include <unzip.h>
+#endif
+
+namespace Assimp {
+    // ----------------------------------------------------------------
+    // Wraps an existing Assimp::IOSystem for unzip
+    class IOSystem2Unzip {
+    public:
+        static voidpf open(voidpf opaque, const char* filename, int mode);
+        static uLong read(voidpf opaque, voidpf stream, void* buf, uLong size);
+        static uLong write(voidpf opaque, voidpf stream, const void* buf, uLong size);
+        static long tell(voidpf opaque, voidpf stream);
+        static long seek(voidpf opaque, voidpf stream, uLong offset, int origin);
+        static int close(voidpf opaque, voidpf stream);
+        static int testerror(voidpf opaque, voidpf stream);
+        static zlib_filefunc_def get(IOSystem* pIOHandler);
+    };
+
+    voidpf IOSystem2Unzip::open(voidpf opaque, const char* filename, int mode) {
+        IOSystem* io_system = reinterpret_cast<IOSystem*>(opaque);
+
+        const char* mode_fopen = nullptr;
+        if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) {
+            mode_fopen = "rb";
+        }
+        else {
+            if (mode & ZLIB_FILEFUNC_MODE_EXISTING) {
+                mode_fopen = "r+b";
+            }
+            else {
+                if (mode & ZLIB_FILEFUNC_MODE_CREATE) {
+                    mode_fopen = "wb";
+                }
+            }
+        }
+
+        return (voidpf)io_system->Open(filename, mode_fopen);
+    }
+
+    uLong IOSystem2Unzip::read(voidpf /*opaque*/, voidpf stream, void* buf, uLong size) {
+        IOStream* io_stream = (IOStream*)stream;
+
+        return static_cast<uLong>(io_stream->Read(buf, 1, size));
+    }
+
+    uLong IOSystem2Unzip::write(voidpf /*opaque*/, voidpf stream, const void* buf, uLong size) {
+        IOStream* io_stream = (IOStream*)stream;
+
+        return static_cast<uLong>(io_stream->Write(buf, 1, size));
+    }
+
+    long IOSystem2Unzip::tell(voidpf /*opaque*/, voidpf stream) {
+        IOStream* io_stream = (IOStream*)stream;
+
+        return static_cast<long>(io_stream->Tell());
+    }
+
+    long IOSystem2Unzip::seek(voidpf /*opaque*/, voidpf stream, uLong offset, int origin) {
+        IOStream* io_stream = (IOStream*)stream;
+
+        aiOrigin assimp_origin;
+        switch (origin) {
+        default:
+        case ZLIB_FILEFUNC_SEEK_CUR:
+            assimp_origin = aiOrigin_CUR;
+            break;
+        case ZLIB_FILEFUNC_SEEK_END:
+            assimp_origin = aiOrigin_END;
+            break;
+        case ZLIB_FILEFUNC_SEEK_SET:
+            assimp_origin = aiOrigin_SET;
+            break;
+        }
+
+        return (io_stream->Seek(offset, assimp_origin) == aiReturn_SUCCESS ? 0 : -1);
+    }
+
+    int IOSystem2Unzip::close(voidpf opaque, voidpf stream) {
+        IOSystem* io_system = (IOSystem*)opaque;
+        IOStream* io_stream = (IOStream*)stream;
+
+        io_system->Close(io_stream);
+
+        return 0;
+    }
+
+    int IOSystem2Unzip::testerror(voidpf /*opaque*/, voidpf /*stream*/) {
+        return 0;
+    }
+
+    zlib_filefunc_def IOSystem2Unzip::get(IOSystem* pIOHandler) {
+        zlib_filefunc_def mapping;
+
+#ifdef ASSIMP_USE_HUNTER
+        mapping.zopen_file = (open_file_func)open;
+        mapping.zread_file = (read_file_func)read;
+        mapping.zwrite_file = (write_file_func)write;
+        mapping.ztell_file = (tell_file_func)tell;
+        mapping.zseek_file = (seek_file_func)seek;
+        mapping.zclose_file = (close_file_func)close;
+        mapping.zerror_file = (error_file_func)testerror;
+#else
+        mapping.zopen_file = open;
+        mapping.zread_file = read;
+        mapping.zwrite_file = write;
+        mapping.ztell_file = tell;
+        mapping.zseek_file = seek;
+        mapping.zclose_file = close;
+        mapping.zerror_file = testerror;
+#endif
+        mapping.opaque = reinterpret_cast<voidpf>(pIOHandler);
+
+        return mapping;
+    }
+
+    // ----------------------------------------------------------------
+    // A read-only file inside a ZIP
+
+    class ZipFile : public IOStream {
+        friend class ZipFileInfo;
+        explicit ZipFile(size_t size);
+    public:
+        virtual ~ZipFile();
+
+        // IOStream interface
+        size_t Read(void* pvBuffer, size_t pSize, size_t pCount) override;
+        size_t Write(const void* /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/) override { return 0; }
+        size_t FileSize() const override;
+        aiReturn Seek(size_t pOffset, aiOrigin pOrigin) override;
+        size_t Tell() const override;
+        void Flush() override {}
+
+    private:
+        size_t m_Size = 0;
+        size_t m_SeekPtr = 0;
+        std::unique_ptr<uint8_t[]> m_Buffer;
+    };
+
+
+    // ----------------------------------------------------------------
+    // Info about a read-only file inside a ZIP
+    class ZipFileInfo
+    {
+    public:
+        explicit ZipFileInfo(unzFile zip_handle, size_t size);
+
+        // Allocate and Extract data from the ZIP
+        ZipFile * Extract(unzFile zip_handle) const;
+
+    private:
+        size_t m_Size = 0;
+        unz_file_pos_s m_ZipFilePos;
+    };
+
+    ZipFileInfo::ZipFileInfo(unzFile zip_handle, size_t size)
+        : m_Size(size) {
+        ai_assert(m_Size != 0);
+        // Workaround for MSVC 2013 - C2797
+        m_ZipFilePos.num_of_file = 0;
+        m_ZipFilePos.pos_in_zip_directory = 0;
+        unzGetFilePos(zip_handle, &(m_ZipFilePos));
+    }
+
+    ZipFile * ZipFileInfo::Extract(unzFile zip_handle) const {
+        // Find in the ZIP. This cannot fail
+        unz_file_pos_s *filepos = const_cast<unz_file_pos_s*>(&(m_ZipFilePos));
+        if (unzGoToFilePos(zip_handle, filepos) != UNZ_OK)
+            return nullptr;
+
+        if (unzOpenCurrentFile(zip_handle) != UNZ_OK)
+            return nullptr;
+
+        ZipFile *zip_file = new ZipFile(m_Size);
+
+        if (unzReadCurrentFile(zip_handle, zip_file->m_Buffer.get(), static_cast<unsigned int>(m_Size)) != static_cast<int>(m_Size))
+        {
+            // Failed, release the memory
+            delete zip_file;
+            zip_file = nullptr;
+        }
+
+        ai_assert(unzCloseCurrentFile(zip_handle) == UNZ_OK);
+        return zip_file;
+    }
+
+    ZipFile::ZipFile(size_t size)
+        : m_Size(size) {
+        ai_assert(m_Size != 0);
+        m_Buffer = std::unique_ptr<uint8_t[]>(new uint8_t[m_Size]);
+    }
+
+    ZipFile::~ZipFile() {
+    }
+
+    size_t ZipFile::Read(void* pvBuffer, size_t pSize, size_t pCount) {
+        // Should be impossible
+        ai_assert(m_Buffer != nullptr);
+        ai_assert(NULL != pvBuffer && 0 != pSize && 0 != pCount);
+
+        // Clip down to file size
+        size_t byteSize = pSize * pCount;
+        if ((byteSize + m_SeekPtr) > m_Size)
+        {
+            pCount = (m_Size - m_SeekPtr) / pSize;
+            byteSize = pSize * pCount;
+            if (byteSize == 0)
+                return 0;
+        }
+
+        std::memcpy(pvBuffer, m_Buffer.get() + m_SeekPtr, byteSize);
+
+        m_SeekPtr += byteSize;
+
+        return pCount;
+    }
+
+    size_t ZipFile::FileSize() const {
+        return m_Size;
+    }
+
+    aiReturn ZipFile::Seek(size_t pOffset, aiOrigin pOrigin) {
+        switch (pOrigin)
+        {
+        case aiOrigin_SET: {
+            if (pOffset > m_Size) return aiReturn_FAILURE;
+            m_SeekPtr = pOffset;
+            return aiReturn_SUCCESS;
+        }
+
+        case aiOrigin_CUR: {
+            if ((pOffset + m_SeekPtr) > m_Size) return aiReturn_FAILURE;
+            m_SeekPtr += pOffset;
+            return aiReturn_SUCCESS;
+        }
+
+        case aiOrigin_END: {
+            if (pOffset > m_Size) return aiReturn_FAILURE;
+            m_SeekPtr = m_Size - pOffset;
+            return aiReturn_SUCCESS;
+        }
+        default:;
+        }
+
+        return aiReturn_FAILURE;
+    }
+
+    size_t ZipFile::Tell() const {
+        return m_SeekPtr;
+    }
+
+    // ----------------------------------------------------------------
+    // pImpl of the Zip Archive IO
+    class ZipArchiveIOSystem::Implement {
+    public:
+        static const unsigned int FileNameSize = 256;
+
+        Implement(IOSystem* pIOHandler, const char* pFilename, const char* pMode);
+        ~Implement();
+
+        bool isOpen() const;
+        void getFileList(std::vector<std::string>& rFileList);
+        void getFileListExtension(std::vector<std::string>& rFileList, const std::string& extension);
+        bool Exists(std::string& filename);
+        IOStream* OpenFile(std::string& filename);
+
+        static void SimplifyFilename(std::string& filename);
+
+    private:
+        void MapArchive();
+
+    private:
+        typedef std::map<std::string, ZipFileInfo> ZipFileInfoMap;
+
+        unzFile m_ZipFileHandle = nullptr;
+        ZipFileInfoMap m_ArchiveMap;
+    };
+
+    ZipArchiveIOSystem::Implement::Implement(IOSystem* pIOHandler, const char* pFilename, const char* pMode) {
+        ai_assert(strcmp(pMode, "r") == 0);
+        ai_assert(pFilename != nullptr);
+        if (pFilename[0] == 0)
+            return;
+
+        zlib_filefunc_def mapping = IOSystem2Unzip::get(pIOHandler);
+        m_ZipFileHandle = unzOpen2(pFilename, &mapping);
+    }
+
+    ZipArchiveIOSystem::Implement::~Implement() {
+        m_ArchiveMap.clear();
+
+        if (m_ZipFileHandle != nullptr) {
+            unzClose(m_ZipFileHandle);
+            m_ZipFileHandle = nullptr;
+        }
+    }
+
+    void ZipArchiveIOSystem::Implement::MapArchive() {
+        if (m_ZipFileHandle == nullptr)
+            return;
+
+        if (!m_ArchiveMap.empty())
+            return;
+
+        //  At first ensure file is already open
+        if (unzGoToFirstFile(m_ZipFileHandle) != UNZ_OK)
+            return;
+
+        // Loop over all files
+        do {
+            char filename[FileNameSize];
+            unz_file_info fileInfo;
+
+            if (unzGetCurrentFileInfo(m_ZipFileHandle, &fileInfo, filename, FileNameSize, nullptr, 0, nullptr, 0) == UNZ_OK) {
+                if (fileInfo.uncompressed_size != 0) {
+                    std::string filename_string(filename, fileInfo.size_filename);
+                    SimplifyFilename(filename_string);
+                    m_ArchiveMap.emplace(filename_string, ZipFileInfo(m_ZipFileHandle, fileInfo.uncompressed_size));
+                }
+            }
+        } while (unzGoToNextFile(m_ZipFileHandle) != UNZ_END_OF_LIST_OF_FILE);
+    }
+
+    bool ZipArchiveIOSystem::Implement::isOpen() const {
+        return (m_ZipFileHandle != nullptr);
+    }
+
+    void ZipArchiveIOSystem::Implement::getFileList(std::vector<std::string>& rFileList) {
+        MapArchive();
+        rFileList.clear();
+
+        for (const auto &file : m_ArchiveMap) {
+            rFileList.push_back(file.first);
+        }
+    }
+
+    void ZipArchiveIOSystem::Implement::getFileListExtension(std::vector<std::string>& rFileList, const std::string& extension) {
+        MapArchive();
+        rFileList.clear();
+
+        for (const auto &file : m_ArchiveMap) {
+            if (extension == BaseImporter::GetExtension(file.first))
+                rFileList.push_back(file.first);
+        }
+    }
+
+    bool ZipArchiveIOSystem::Implement::Exists(std::string& filename) {
+        MapArchive();
+
+        ZipFileInfoMap::const_iterator it = m_ArchiveMap.find(filename);
+        return (it != m_ArchiveMap.end());
+    }
+
+    IOStream * ZipArchiveIOSystem::Implement::OpenFile(std::string& filename) {
+        MapArchive();
+
+        SimplifyFilename(filename);
+
+        // Find in the map
+        ZipFileInfoMap::const_iterator zip_it = m_ArchiveMap.find(filename);
+        if (zip_it == m_ArchiveMap.cend())
+            return nullptr;
+
+        const ZipFileInfo &zip_file = (*zip_it).second;
+        return zip_file.Extract(m_ZipFileHandle);
+    }
+
+    inline void ReplaceAll(std::string& data, const std::string& before, const std::string& after) {
+        size_t pos = data.find(before);
+        while (pos != std::string::npos)
+        {
+            data.replace(pos, before.size(), after);
+            pos = data.find(before, pos + after.size());
+        }
+    }
+
+    inline void ReplaceAllChar(std::string& data, const char before, const char after) {
+        size_t pos = data.find(before);
+        while (pos != std::string::npos)
+        {
+            data[pos] = after;
+            pos = data.find(before, pos + 1);
+        }
+    }
+
+    void ZipArchiveIOSystem::Implement::SimplifyFilename(std::string& filename)
+    {
+        ReplaceAllChar(filename, '\\', '/');
+
+        // Remove all . and / from the beginning of the path
+        size_t pos = filename.find_first_not_of("./");
+        if (pos != 0)
+            filename.erase(0, pos);
+
+        // Simplify "my/folder/../file.png" constructions, if any
+        static const std::string relative("/../");
+        const size_t relsize = relative.size() - 1;
+        pos = filename.find(relative);
+        while (pos != std::string::npos)
+        {
+            // Previous slash
+            size_t prevpos = filename.rfind('/', pos - 1);
+            if (prevpos == pos)
+                filename.erase(0, pos + relative.size());
+            else
+                filename.erase(prevpos, pos + relsize - prevpos);
+
+            pos = filename.find(relative);
+        }
+    }
+
+    ZipArchiveIOSystem::ZipArchiveIOSystem(IOSystem* pIOHandler, const char* pFilename, const char* pMode)
+        : pImpl(new Implement(pIOHandler, pFilename, pMode)) {
+    }
+
+    // ----------------------------------------------------------------
+    // The ZipArchiveIO
+    ZipArchiveIOSystem::ZipArchiveIOSystem(IOSystem* pIOHandler, const std::string& rFilename, const char* pMode)
+        : pImpl(new Implement(pIOHandler, rFilename.c_str(), pMode))
+    {
+    }
+
+    ZipArchiveIOSystem::~ZipArchiveIOSystem() {
+        delete pImpl;
+    }
+
+    bool ZipArchiveIOSystem::Exists(const char* pFilename) const {
+        ai_assert(pFilename != nullptr);
+
+        if (pFilename == nullptr) {
+            return false;
+        }
+
+        std::string filename(pFilename);
+        return pImpl->Exists(filename);
+    }
+
+    // This is always '/' in a ZIP
+    char ZipArchiveIOSystem::getOsSeparator() const {
+        return '/';
+    }
+
+    // Only supports Reading
+    IOStream * ZipArchiveIOSystem::Open(const char* pFilename, const char* pMode) {
+        ai_assert(pFilename != nullptr);
+
+        for (size_t i = 0; pMode[i] != 0; ++i)
+        {
+            ai_assert(pMode[i] != 'w');
+            if (pMode[i] == 'w')
+                return nullptr;
+        }
+
+        std::string filename(pFilename);
+        return pImpl->OpenFile(filename);
+    }
+
+    void ZipArchiveIOSystem::Close(IOStream* pFile) {
+        delete pFile;
+    }
+
+    bool ZipArchiveIOSystem::isOpen() const {
+        return (pImpl->isOpen());
+    }
+
+    void ZipArchiveIOSystem::getFileList(std::vector<std::string>& rFileList) const {
+        return pImpl->getFileList(rFileList);
+    }
+
+    void ZipArchiveIOSystem::getFileListExtension(std::vector<std::string>& rFileList, const std::string& extension) const {
+        return pImpl->getFileListExtension(rFileList, extension);
+    }
+
+    bool ZipArchiveIOSystem::isZipArchive(IOSystem* pIOHandler, const char* pFilename) {
+        Implement tmp(pIOHandler, pFilename, "r");
+        return tmp.isOpen();
+    }
+
+    bool ZipArchiveIOSystem::isZipArchive(IOSystem* pIOHandler, const std::string& rFilename) {
+        return isZipArchive(pIOHandler, rFilename.c_str());
+    }
+
+}

+ 9 - 9
code/Common/scene.cpp

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

+ 4 - 3
code/FBX/FBXCommon.h

@@ -50,9 +50,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp {
 namespace Assimp {
 namespace FBX
 namespace FBX
 {
 {
-    const std::string NULL_RECORD = { // 13 null bytes
-        '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'
-    }; // who knows why
+    const std::string NULL_RECORD = { // 25 null bytes in 64-bit and 13 null bytes in 32-bit
+        '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',
+        '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'
+    }; // who knows why, it looks like two integers 32/64 bit (compressed and uncompressed sizes?) + 1 byte (might be compression type?)
     const std::string SEPARATOR = {'\x00', '\x01'}; // for use inside strings
     const std::string SEPARATOR = {'\x00', '\x01'}; // for use inside strings
     const std::string MAGIC_NODE_TAG = "_$AssimpFbx$"; // from import
     const std::string MAGIC_NODE_TAG = "_$AssimpFbx$"; // from import
     const int64_t SECOND = 46186158000; // FBX's kTime unit
     const int64_t SECOND = 46186158000; // FBX's kTime unit

+ 8 - 0
code/FBX/FBXCompileConfig.h

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

+ 291 - 188
code/FBX/FBXConverter.cpp

@@ -55,6 +55,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "FBXImporter.h"
 #include "FBXImporter.h"
 
 
 #include <assimp/StringComparison.h>
 #include <assimp/StringComparison.h>
+#include <assimp/MathFunctions.h>
 
 
 #include <assimp/scene.h>
 #include <assimp/scene.h>
 
 
@@ -66,7 +67,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <vector>
 #include <vector>
 #include <sstream>
 #include <sstream>
 #include <iomanip>
 #include <iomanip>
-
+#include <cstdint>
+#include <iostream>
+#include <stdlib.h>
 
 
 namespace Assimp {
 namespace Assimp {
     namespace FBX {
     namespace FBX {
@@ -75,9 +78,9 @@ namespace Assimp {
 
 
 #define MAGIC_NODE_TAG "_$AssimpFbx$"
 #define MAGIC_NODE_TAG "_$AssimpFbx$"
 
 
-#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000L
+#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000LL
 
 
-        FBXConverter::FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones, FbxUnit unit )
+        FBXConverter::FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones )
         : defaultMaterialIndex()
         : defaultMaterialIndex()
         , lights()
         , lights()
         , cameras()
         , cameras()
@@ -89,13 +92,19 @@ namespace Assimp {
         , mNodeNames()
         , mNodeNames()
         , anim_fps()
         , anim_fps()
         , out(out)
         , out(out)
-        , doc(doc)
-        , mRemoveEmptyBones( removeEmptyBones )
-        , mCurrentUnit(FbxUnit::cm) {
+        , doc(doc) {
             // animations need to be converted first since this will
             // animations need to be converted first since this will
             // populate the node_anim_chain_bits map, which is needed
             // populate the node_anim_chain_bits map, which is needed
             // to determine which nodes need to be generated.
             // to determine which nodes need to be generated.
             ConvertAnimations();
             ConvertAnimations();
+            // Embedded textures in FBX could be connected to nothing but to itself,
+            // for instance Texture -> Video connection only but not to the main graph,
+            // The idea here is to traverse all objects to find these Textures and convert them,
+            // so later during material conversion it will find converted texture in the textures_converted array.
+            if (doc.Settings().readTextures)
+            {
+                ConvertOrphantEmbeddedTextures();
+            }
             ConvertRootNode();
             ConvertRootNode();
 
 
             if (doc.Settings().readAllMaterials) {
             if (doc.Settings().readAllMaterials) {
@@ -119,7 +128,6 @@ namespace Assimp {
 
 
             ConvertGlobalSettings();
             ConvertGlobalSettings();
             TransferDataToScene();
             TransferDataToScene();
-            ConvertToUnitScale(unit);
 
 
             // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE
             // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE
             // to make sure the scene passes assimp's validation. FBX files
             // to make sure the scene passes assimp's validation. FBX files
@@ -146,7 +154,7 @@ namespace Assimp {
             out->mRootNode->mName.Set(unique_name);
             out->mRootNode->mName.Set(unique_name);
 
 
             // root has ID 0
             // root has ID 0
-            ConvertNodes(0L, *out->mRootNode);
+            ConvertNodes(0L, out->mRootNode, out->mRootNode);
         }
         }
 
 
         static std::string getAncestorBaseName(const aiNode* node)
         static std::string getAncestorBaseName(const aiNode* node)
@@ -180,8 +188,11 @@ namespace Assimp {
             GetUniqueName(original_name, unique_name);
             GetUniqueName(original_name, unique_name);
             return unique_name;
             return unique_name;
         }
         }
-
-        void FBXConverter::ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform) {
+        /// todo: pre-build node hierarchy
+        /// todo: get bone from stack
+        /// todo: make map of aiBone* to aiNode*
+        /// then update convert clusters to the new format
+        void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) {
             const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(id, "Model");
             const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(id, "Model");
 
 
             std::vector<aiNode*> nodes;
             std::vector<aiNode*> nodes;
@@ -192,62 +203,69 @@ namespace Assimp {
 
 
             try {
             try {
                 for (const Connection* con : conns) {
                 for (const Connection* con : conns) {
-
                     // ignore object-property links
                     // ignore object-property links
                     if (con->PropertyName().length()) {
                     if (con->PropertyName().length()) {
-                        continue;
+                        // really important we document why this is ignored.
+                        FBXImporter::LogInfo("ignoring property link - no docs on why this is ignored");
+                        continue; //?
                     }
                     }
 
 
+                    // convert connection source object into Object base class
                     const Object* const object = con->SourceObject();
                     const Object* const object = con->SourceObject();
                     if (nullptr == object) {
                     if (nullptr == object) {
-                        FBXImporter::LogWarn("failed to convert source object for Model link");
+                        FBXImporter::LogError("failed to convert source object for Model link");
                         continue;
                         continue;
                     }
                     }
 
 
+                    // FBX Model::Cube, Model::Bone001, etc elements
+                    // This detects if we can cast the object into this model structure.
                     const Model* const model = dynamic_cast<const Model*>(object);
                     const Model* const model = dynamic_cast<const Model*>(object);
 
 
                     if (nullptr != model) {
                     if (nullptr != model) {
                         nodes_chain.clear();
                         nodes_chain.clear();
                         post_nodes_chain.clear();
                         post_nodes_chain.clear();
 
 
-                        aiMatrix4x4 new_abs_transform = parent_transform;
-
-                        std::string unique_name = MakeUniqueNodeName(model, parent);
-
+                        aiMatrix4x4 new_abs_transform = parent->mTransformation;
+                        std::string node_name = FixNodeName(model->Name());
                         // even though there is only a single input node, the design of
                         // even though there is only a single input node, the design of
                         // assimp (or rather: the complicated transformation chain that
                         // assimp (or rather: the complicated transformation chain that
                         // is employed by fbx) means that we may need multiple aiNode's
                         // is employed by fbx) means that we may need multiple aiNode's
                         // to represent a fbx node's transformation.
                         // to represent a fbx node's transformation.
-                        const bool need_additional_node = GenerateTransformationNodeChain(*model, unique_name, nodes_chain, post_nodes_chain);
 
 
+
+                        // generate node transforms - this includes pivot data
+                        // if need_additional_node is true then you t
+                        const bool need_additional_node = GenerateTransformationNodeChain(*model, node_name, nodes_chain, post_nodes_chain);
+
+                        // assert that for the current node we must have at least a single transform
                         ai_assert(nodes_chain.size());
                         ai_assert(nodes_chain.size());
 
 
                         if (need_additional_node) {
                         if (need_additional_node) {
-                            nodes_chain.push_back(new aiNode(unique_name));
+                            nodes_chain.push_back(new aiNode(node_name));
                         }
                         }
 
 
                         //setup metadata on newest node
                         //setup metadata on newest node
                         SetupNodeMetadata(*model, *nodes_chain.back());
                         SetupNodeMetadata(*model, *nodes_chain.back());
 
 
                         // link all nodes in a row
                         // link all nodes in a row
-                        aiNode* last_parent = &parent;
-                        for (aiNode* prenode : nodes_chain) {
-                            ai_assert(prenode);
+                        aiNode* last_parent = parent;
+                        for (aiNode* child : nodes_chain) {
+                            ai_assert(child);
 
 
-                            if (last_parent != &parent) {
+                            if (last_parent != parent) {
                                 last_parent->mNumChildren = 1;
                                 last_parent->mNumChildren = 1;
                                 last_parent->mChildren = new aiNode*[1];
                                 last_parent->mChildren = new aiNode*[1];
-                                last_parent->mChildren[0] = prenode;
+                                last_parent->mChildren[0] = child;
                             }
                             }
 
 
-                            prenode->mParent = last_parent;
-                            last_parent = prenode;
+                            child->mParent = last_parent;
+                            last_parent = child;
 
 
-                            new_abs_transform *= prenode->mTransformation;
+                            new_abs_transform *= child->mTransformation;
                         }
                         }
 
 
                         // attach geometry
                         // attach geometry
-                        ConvertModel(*model, *nodes_chain.back(), new_abs_transform);
+                        ConvertModel(*model, nodes_chain.back(), root_node, new_abs_transform);
 
 
                         // check if there will be any child nodes
                         // check if there will be any child nodes
                         const std::vector<const Connection*>& child_conns
                         const std::vector<const Connection*>& child_conns
@@ -259,7 +277,7 @@ namespace Assimp {
                             for (aiNode* postnode : post_nodes_chain) {
                             for (aiNode* postnode : post_nodes_chain) {
                                 ai_assert(postnode);
                                 ai_assert(postnode);
 
 
-                                if (last_parent != &parent) {
+                                if (last_parent != parent) {
                                     last_parent->mNumChildren = 1;
                                     last_parent->mNumChildren = 1;
                                     last_parent->mChildren = new aiNode*[1];
                                     last_parent->mChildren = new aiNode*[1];
                                     last_parent->mChildren[0] = postnode;
                                     last_parent->mChildren[0] = postnode;
@@ -281,15 +299,15 @@ namespace Assimp {
                             );
                             );
                         }
                         }
 
 
-                        // attach sub-nodes (if any)
-                        ConvertNodes(model->ID(), *last_parent, new_abs_transform);
+                        // recursion call - child nodes
+                        ConvertNodes(model->ID(), last_parent, root_node);
 
 
                         if (doc.Settings().readLights) {
                         if (doc.Settings().readLights) {
-                            ConvertLights(*model, unique_name);
+                            ConvertLights(*model, node_name);
                         }
                         }
 
 
                         if (doc.Settings().readCameras) {
                         if (doc.Settings().readCameras) {
-                            ConvertCameras(*model, unique_name);
+                            ConvertCameras(*model, node_name);
                         }
                         }
 
 
                         nodes.push_back(nodes_chain.front());
                         nodes.push_back(nodes_chain.front());
@@ -298,11 +316,17 @@ namespace Assimp {
                 }
                 }
 
 
                 if (nodes.size()) {
                 if (nodes.size()) {
-                    parent.mChildren = new aiNode*[nodes.size()]();
-                    parent.mNumChildren = static_cast<unsigned int>(nodes.size());
+                    parent->mChildren = new aiNode*[nodes.size()]();
+                    parent->mNumChildren = static_cast<unsigned int>(nodes.size());
 
 
-                    std::swap_ranges(nodes.begin(), nodes.end(), parent.mChildren);
+                    std::swap_ranges(nodes.begin(), nodes.end(), parent->mChildren);
+                }
+                else
+                {
+                    parent->mNumChildren = 0;
+                    parent->mChildren = nullptr;
                 }
                 }
+                
             }
             }
             catch (std::exception&) {
             catch (std::exception&) {
                 Util::delete_fun<aiNode> deleter;
                 Util::delete_fun<aiNode> deleter;
@@ -555,7 +579,7 @@ namespace Assimp {
                 return;
                 return;
             }
             }
 
 
-            const float angle_epsilon = 1e-6f;
+            const float angle_epsilon = Math::getEpsilon<float>();
 
 
             out = aiMatrix4x4();
             out = aiMatrix4x4();
 
 
@@ -685,30 +709,37 @@ namespace Assimp {
             bool ok;
             bool ok;
 
 
             aiMatrix4x4 chain[TransformationComp_MAXIMUM];
             aiMatrix4x4 chain[TransformationComp_MAXIMUM];
+
+            ai_assert(TransformationComp_MAXIMUM < 32);
+            std::uint32_t chainBits = 0;
+            // A node won't need a node chain if it only has these.
+            const std::uint32_t chainMaskSimple = (1 << TransformationComp_Translation) + (1 << TransformationComp_Scaling) + (1 << TransformationComp_Rotation);
+            // A node will need a node chain if it has any of these.
+            const std::uint32_t chainMaskComplex = ((1 << (TransformationComp_MAXIMUM)) - 1) - chainMaskSimple;
+
             std::fill_n(chain, static_cast<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4());
             std::fill_n(chain, static_cast<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4());
 
 
             // generate transformation matrices for all the different transformation components
             // generate transformation matrices for all the different transformation components
-            const float zero_epsilon = 1e-6f;
+            const float zero_epsilon = Math::getEpsilon<float>();
             const aiVector3D all_ones(1.0f, 1.0f, 1.0f);
             const aiVector3D all_ones(1.0f, 1.0f, 1.0f);
-            bool is_complex = false;
 
 
             const aiVector3D& PreRotation = PropertyGet<aiVector3D>(props, "PreRotation", ok);
             const aiVector3D& PreRotation = PropertyGet<aiVector3D>(props, "PreRotation", ok);
             if (ok && PreRotation.SquareLength() > zero_epsilon) {
             if (ok && PreRotation.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_PreRotation);
 
 
                 GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PreRotation, chain[TransformationComp_PreRotation]);
                 GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PreRotation, chain[TransformationComp_PreRotation]);
             }
             }
 
 
             const aiVector3D& PostRotation = PropertyGet<aiVector3D>(props, "PostRotation", ok);
             const aiVector3D& PostRotation = PropertyGet<aiVector3D>(props, "PostRotation", ok);
             if (ok && PostRotation.SquareLength() > zero_epsilon) {
             if (ok && PostRotation.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_PostRotation);
 
 
                 GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PostRotation, chain[TransformationComp_PostRotation]);
                 GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PostRotation, chain[TransformationComp_PostRotation]);
             }
             }
 
 
             const aiVector3D& RotationPivot = PropertyGet<aiVector3D>(props, "RotationPivot", ok);
             const aiVector3D& RotationPivot = PropertyGet<aiVector3D>(props, "RotationPivot", ok);
             if (ok && RotationPivot.SquareLength() > zero_epsilon) {
             if (ok && RotationPivot.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_RotationPivot) | (1 << TransformationComp_RotationPivotInverse);
 
 
                 aiMatrix4x4::Translation(RotationPivot, chain[TransformationComp_RotationPivot]);
                 aiMatrix4x4::Translation(RotationPivot, chain[TransformationComp_RotationPivot]);
                 aiMatrix4x4::Translation(-RotationPivot, chain[TransformationComp_RotationPivotInverse]);
                 aiMatrix4x4::Translation(-RotationPivot, chain[TransformationComp_RotationPivotInverse]);
@@ -716,21 +747,21 @@ namespace Assimp {
 
 
             const aiVector3D& RotationOffset = PropertyGet<aiVector3D>(props, "RotationOffset", ok);
             const aiVector3D& RotationOffset = PropertyGet<aiVector3D>(props, "RotationOffset", ok);
             if (ok && RotationOffset.SquareLength() > zero_epsilon) {
             if (ok && RotationOffset.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_RotationOffset);
 
 
                 aiMatrix4x4::Translation(RotationOffset, chain[TransformationComp_RotationOffset]);
                 aiMatrix4x4::Translation(RotationOffset, chain[TransformationComp_RotationOffset]);
             }
             }
 
 
             const aiVector3D& ScalingOffset = PropertyGet<aiVector3D>(props, "ScalingOffset", ok);
             const aiVector3D& ScalingOffset = PropertyGet<aiVector3D>(props, "ScalingOffset", ok);
             if (ok && ScalingOffset.SquareLength() > zero_epsilon) {
             if (ok && ScalingOffset.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_ScalingOffset);
 
 
                 aiMatrix4x4::Translation(ScalingOffset, chain[TransformationComp_ScalingOffset]);
                 aiMatrix4x4::Translation(ScalingOffset, chain[TransformationComp_ScalingOffset]);
             }
             }
 
 
             const aiVector3D& ScalingPivot = PropertyGet<aiVector3D>(props, "ScalingPivot", ok);
             const aiVector3D& ScalingPivot = PropertyGet<aiVector3D>(props, "ScalingPivot", ok);
             if (ok && ScalingPivot.SquareLength() > zero_epsilon) {
             if (ok && ScalingPivot.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_ScalingPivot) | (1 << TransformationComp_ScalingPivotInverse);
 
 
                 aiMatrix4x4::Translation(ScalingPivot, chain[TransformationComp_ScalingPivot]);
                 aiMatrix4x4::Translation(ScalingPivot, chain[TransformationComp_ScalingPivot]);
                 aiMatrix4x4::Translation(-ScalingPivot, chain[TransformationComp_ScalingPivotInverse]);
                 aiMatrix4x4::Translation(-ScalingPivot, chain[TransformationComp_ScalingPivotInverse]);
@@ -738,22 +769,28 @@ namespace Assimp {
 
 
             const aiVector3D& Translation = PropertyGet<aiVector3D>(props, "Lcl Translation", ok);
             const aiVector3D& Translation = PropertyGet<aiVector3D>(props, "Lcl Translation", ok);
             if (ok && Translation.SquareLength() > zero_epsilon) {
             if (ok && Translation.SquareLength() > zero_epsilon) {
+                chainBits = chainBits | (1 << TransformationComp_Translation);
+
                 aiMatrix4x4::Translation(Translation, chain[TransformationComp_Translation]);
                 aiMatrix4x4::Translation(Translation, chain[TransformationComp_Translation]);
             }
             }
 
 
             const aiVector3D& Scaling = PropertyGet<aiVector3D>(props, "Lcl Scaling", ok);
             const aiVector3D& Scaling = PropertyGet<aiVector3D>(props, "Lcl Scaling", ok);
             if (ok && (Scaling - all_ones).SquareLength() > zero_epsilon) {
             if (ok && (Scaling - all_ones).SquareLength() > zero_epsilon) {
+                chainBits = chainBits | (1 << TransformationComp_Scaling);
+
                 aiMatrix4x4::Scaling(Scaling, chain[TransformationComp_Scaling]);
                 aiMatrix4x4::Scaling(Scaling, chain[TransformationComp_Scaling]);
             }
             }
 
 
             const aiVector3D& Rotation = PropertyGet<aiVector3D>(props, "Lcl Rotation", ok);
             const aiVector3D& Rotation = PropertyGet<aiVector3D>(props, "Lcl Rotation", ok);
             if (ok && Rotation.SquareLength() > zero_epsilon) {
             if (ok && Rotation.SquareLength() > zero_epsilon) {
+                chainBits = chainBits | (1 << TransformationComp_Rotation);
+
                 GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]);
                 GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]);
             }
             }
 
 
             const aiVector3D& GeometricScaling = PropertyGet<aiVector3D>(props, "GeometricScaling", ok);
             const aiVector3D& GeometricScaling = PropertyGet<aiVector3D>(props, "GeometricScaling", ok);
             if (ok && (GeometricScaling - all_ones).SquareLength() > zero_epsilon) {
             if (ok && (GeometricScaling - all_ones).SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_GeometricScaling);
                 aiMatrix4x4::Scaling(GeometricScaling, chain[TransformationComp_GeometricScaling]);
                 aiMatrix4x4::Scaling(GeometricScaling, chain[TransformationComp_GeometricScaling]);
                 aiVector3D GeometricScalingInverse = GeometricScaling;
                 aiVector3D GeometricScalingInverse = GeometricScaling;
                 bool canscale = true;
                 bool canscale = true;
@@ -768,13 +805,14 @@ namespace Assimp {
                     }
                     }
                 }
                 }
                 if (canscale) {
                 if (canscale) {
+                    chainBits = chainBits | (1 << TransformationComp_GeometricScalingInverse);
                     aiMatrix4x4::Scaling(GeometricScalingInverse, chain[TransformationComp_GeometricScalingInverse]);
                     aiMatrix4x4::Scaling(GeometricScalingInverse, chain[TransformationComp_GeometricScalingInverse]);
                 }
                 }
             }
             }
 
 
             const aiVector3D& GeometricRotation = PropertyGet<aiVector3D>(props, "GeometricRotation", ok);
             const aiVector3D& GeometricRotation = PropertyGet<aiVector3D>(props, "GeometricRotation", ok);
             if (ok && GeometricRotation.SquareLength() > zero_epsilon) {
             if (ok && GeometricRotation.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_GeometricRotation) | (1 << TransformationComp_GeometricRotationInverse);
                 GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotation]);
                 GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotation]);
                 GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotationInverse]);
                 GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotationInverse]);
                 chain[TransformationComp_GeometricRotationInverse].Inverse();
                 chain[TransformationComp_GeometricRotationInverse].Inverse();
@@ -782,7 +820,7 @@ namespace Assimp {
 
 
             const aiVector3D& GeometricTranslation = PropertyGet<aiVector3D>(props, "GeometricTranslation", ok);
             const aiVector3D& GeometricTranslation = PropertyGet<aiVector3D>(props, "GeometricTranslation", ok);
             if (ok && GeometricTranslation.SquareLength() > zero_epsilon) {
             if (ok && GeometricTranslation.SquareLength() > zero_epsilon) {
-                is_complex = true;
+                chainBits = chainBits | (1 << TransformationComp_GeometricTranslation) | (1 << TransformationComp_GeometricTranslationInverse);
                 aiMatrix4x4::Translation(GeometricTranslation, chain[TransformationComp_GeometricTranslation]);
                 aiMatrix4x4::Translation(GeometricTranslation, chain[TransformationComp_GeometricTranslation]);
                 aiMatrix4x4::Translation(-GeometricTranslation, chain[TransformationComp_GeometricTranslationInverse]);
                 aiMatrix4x4::Translation(-GeometricTranslation, chain[TransformationComp_GeometricTranslationInverse]);
             }
             }
@@ -790,12 +828,12 @@ namespace Assimp {
             // is_complex needs to be consistent with NeedsComplexTransformationChain()
             // is_complex needs to be consistent with NeedsComplexTransformationChain()
             // or the interplay between this code and the animation converter would
             // or the interplay between this code and the animation converter would
             // not be guaranteed.
             // not be guaranteed.
-            ai_assert(NeedsComplexTransformationChain(model) == is_complex);
+            //ai_assert(NeedsComplexTransformationChain(model) == ((chainBits & chainMaskComplex) != 0));
 
 
             // now, if we have more than just Translation, Scaling and Rotation,
             // now, if we have more than just Translation, Scaling and Rotation,
             // we need to generate a full node chain to accommodate for assimp's
             // we need to generate a full node chain to accommodate for assimp's
             // lack to express pivots and offsets.
             // lack to express pivots and offsets.
-            if (is_complex && doc.Settings().preservePivots) {
+            if ((chainBits & chainMaskComplex) && doc.Settings().preservePivots) {
                 FBXImporter::LogInfo("generating full transformation chain for node: " + name);
                 FBXImporter::LogInfo("generating full transformation chain for node: " + name);
 
 
                 // query the anim_chain_bits dictionary to find out which chain elements
                 // query the anim_chain_bits dictionary to find out which chain elements
@@ -808,7 +846,7 @@ namespace Assimp {
                 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) {
                 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) {
                     const TransformationComp comp = static_cast<TransformationComp>(i);
                     const TransformationComp comp = static_cast<TransformationComp>(i);
 
 
-                    if (chain[i].IsIdentity() && (anim_chain_bitmask & bit) == 0) {
+                    if ((chainBits & bit) == 0 && (anim_chain_bitmask & bit) == 0) {
                         continue;
                         continue;
                     }
                     }
 
 
@@ -892,7 +930,8 @@ namespace Assimp {
             }
             }
         }
         }
 
 
-        void FBXConverter::ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform)
+        void FBXConverter::ConvertModel(const Model &model, aiNode *parent, aiNode *root_node,
+                                        const aiMatrix4x4 &absolute_transform)
         {
         {
             const std::vector<const Geometry*>& geos = model.GetGeometry();
             const std::vector<const Geometry*>& geos = model.GetGeometry();
 
 
@@ -904,11 +943,12 @@ namespace Assimp {
                 const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*>(geo);
                 const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*>(geo);
                 const LineGeometry* const line = dynamic_cast<const LineGeometry*>(geo);
                 const LineGeometry* const line = dynamic_cast<const LineGeometry*>(geo);
                 if (mesh) {
                 if (mesh) {
-                    const std::vector<unsigned int>& indices = ConvertMesh(*mesh, model, node_global_transform, nd);
+                    const std::vector<unsigned int>& indices = ConvertMesh(*mesh, model, parent, root_node,
+                                                                           absolute_transform);
                     std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
                     std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
                 }
                 }
                 else if (line) {
                 else if (line) {
-                    const std::vector<unsigned int>& indices = ConvertLine(*line, model, node_global_transform, nd);
+                    const std::vector<unsigned int>& indices = ConvertLine(*line, model, parent, root_node);
                     std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
                     std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
                 }
                 }
                 else {
                 else {
@@ -917,15 +957,16 @@ namespace Assimp {
             }
             }
 
 
             if (meshes.size()) {
             if (meshes.size()) {
-                nd.mMeshes = new unsigned int[meshes.size()]();
-                nd.mNumMeshes = static_cast<unsigned int>(meshes.size());
+                parent->mMeshes = new unsigned int[meshes.size()]();
+                parent->mNumMeshes = static_cast<unsigned int>(meshes.size());
 
 
-                std::swap_ranges(meshes.begin(), meshes.end(), nd.mMeshes);
+                std::swap_ranges(meshes.begin(), meshes.end(), parent->mMeshes);
             }
             }
         }
         }
 
 
-        std::vector<unsigned int> FBXConverter::ConvertMesh(const MeshGeometry& mesh, const Model& model,
-            const aiMatrix4x4& node_global_transform, aiNode& nd)
+        std::vector<unsigned int>
+        FBXConverter::ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node,
+                                  const aiMatrix4x4 &absolute_transform)
         {
         {
             std::vector<unsigned int> temp;
             std::vector<unsigned int> temp;
 
 
@@ -949,18 +990,18 @@ namespace Assimp {
                 const MatIndexArray::value_type base = mindices[0];
                 const MatIndexArray::value_type base = mindices[0];
                 for (MatIndexArray::value_type index : mindices) {
                 for (MatIndexArray::value_type index : mindices) {
                     if (index != base) {
                     if (index != base) {
-                        return ConvertMeshMultiMaterial(mesh, model, node_global_transform, nd);
+                        return ConvertMeshMultiMaterial(mesh, model, parent, root_node, absolute_transform);
                     }
                     }
                 }
                 }
             }
             }
 
 
             // faster code-path, just copy the data
             // faster code-path, just copy the data
-            temp.push_back(ConvertMeshSingleMaterial(mesh, model, node_global_transform, nd));
+            temp.push_back(ConvertMeshSingleMaterial(mesh, model, absolute_transform, parent, root_node));
             return temp;
             return temp;
         }
         }
 
 
         std::vector<unsigned int> FBXConverter::ConvertLine(const LineGeometry& line, const Model& model,
         std::vector<unsigned int> FBXConverter::ConvertLine(const LineGeometry& line, const Model& model,
-            const aiMatrix4x4& node_global_transform, aiNode& nd)
+                                                            aiNode *parent, aiNode *root_node)
         {
         {
             std::vector<unsigned int> temp;
             std::vector<unsigned int> temp;
 
 
@@ -971,7 +1012,7 @@ namespace Assimp {
                 return temp;
                 return temp;
             }
             }
 
 
-            aiMesh* const out_mesh = SetupEmptyMesh(line, nd);
+            aiMesh* const out_mesh = SetupEmptyMesh(line, root_node);
             out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
             out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
 
 
             // copy vertices
             // copy vertices
@@ -1006,7 +1047,7 @@ namespace Assimp {
             return temp;
             return temp;
         }
         }
 
 
-        aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode& nd)
+        aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode *parent)
         {
         {
             aiMesh* const out_mesh = new aiMesh();
             aiMesh* const out_mesh = new aiMesh();
             meshes.push_back(out_mesh);
             meshes.push_back(out_mesh);
@@ -1023,17 +1064,18 @@ namespace Assimp {
             }
             }
             else
             else
             {
             {
-                out_mesh->mName = nd.mName;
+                out_mesh->mName = parent->mName;
             }
             }
 
 
             return out_mesh;
             return out_mesh;
         }
         }
 
 
-        unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model,
-            const aiMatrix4x4& node_global_transform, aiNode& nd)
+        unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model,
+                                                             const aiMatrix4x4 &absolute_transform, aiNode *parent,
+                                                             aiNode *root_node)
         {
         {
             const MatIndexArray& mindices = mesh.GetMaterialIndices();
             const MatIndexArray& mindices = mesh.GetMaterialIndices();
-            aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd);
+            aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent);
 
 
             const std::vector<aiVector3D>& vertices = mesh.GetVertices();
             const std::vector<aiVector3D>& vertices = mesh.GetVertices();
             const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
             const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
@@ -1100,7 +1142,7 @@ namespace Assimp {
                         binormals = &tempBinormals;
                         binormals = &tempBinormals;
                     }
                     }
                     else {
                     else {
-                        binormals = NULL;
+                        binormals = nullptr;
                     }
                     }
                 }
                 }
 
 
@@ -1150,8 +1192,9 @@ namespace Assimp {
                 ConvertMaterialForMesh(out_mesh, model, mesh, mindices[0]);
                 ConvertMaterialForMesh(out_mesh, model, mesh, mindices[0]);
             }
             }
 
 
-            if (doc.Settings().readWeights && mesh.DeformerSkin() != NULL) {
-                ConvertWeights(out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION);
+            if (doc.Settings().readWeights && mesh.DeformerSkin() != nullptr) {
+                ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, NO_MATERIAL_SEPARATION,
+                               nullptr);
             }
             }
 
 
             std::vector<aiAnimMesh*> animMeshes;
             std::vector<aiAnimMesh*> animMeshes;
@@ -1196,8 +1239,10 @@ namespace Assimp {
             return static_cast<unsigned int>(meshes.size() - 1);
             return static_cast<unsigned int>(meshes.size() - 1);
         }
         }
 
 
-        std::vector<unsigned int> FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
-            const aiMatrix4x4& node_global_transform, aiNode& nd)
+        std::vector<unsigned int>
+        FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent,
+                                               aiNode *root_node,
+                                               const aiMatrix4x4 &absolute_transform)
         {
         {
             const MatIndexArray& mindices = mesh.GetMaterialIndices();
             const MatIndexArray& mindices = mesh.GetMaterialIndices();
             ai_assert(mindices.size());
             ai_assert(mindices.size());
@@ -1208,7 +1253,7 @@ namespace Assimp {
             for (MatIndexArray::value_type index : mindices) {
             for (MatIndexArray::value_type index : mindices) {
                 if (had.find(index) == had.end()) {
                 if (had.find(index) == had.end()) {
 
 
-                    indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, node_global_transform, nd));
+                    indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, parent, root_node, absolute_transform));
                     had.insert(index);
                     had.insert(index);
                 }
                 }
             }
             }
@@ -1216,18 +1261,18 @@ namespace Assimp {
             return indices;
             return indices;
         }
         }
 
 
-        unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
-            MatIndexArray::value_type index,
-            const aiMatrix4x4& node_global_transform,
-            aiNode& nd)
+        unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model,
+                                                            MatIndexArray::value_type index,
+                                                            aiNode *parent, aiNode *root_node,
+                                                            const aiMatrix4x4 &absolute_transform)
         {
         {
-            aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd);
+            aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent);
 
 
             const MatIndexArray& mindices = mesh.GetMaterialIndices();
             const MatIndexArray& mindices = mesh.GetMaterialIndices();
             const std::vector<aiVector3D>& vertices = mesh.GetVertices();
             const std::vector<aiVector3D>& vertices = mesh.GetVertices();
             const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
             const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
 
 
-            const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != NULL;
+            const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != nullptr;
 
 
             unsigned int count_faces = 0;
             unsigned int count_faces = 0;
             unsigned int count_vertices = 0;
             unsigned int count_vertices = 0;
@@ -1247,10 +1292,10 @@ namespace Assimp {
             ai_assert(count_faces);
             ai_assert(count_faces);
             ai_assert(count_vertices);
             ai_assert(count_vertices);
 
 
-            // mapping from output indices to DOM indexing, needed to resolve weights
+            // mapping from output indices to DOM indexing, needed to resolve weights or blendshapes
             std::vector<unsigned int> reverseMapping;
             std::vector<unsigned int> reverseMapping;
             std::map<unsigned int, unsigned int> translateIndexMap;
             std::map<unsigned int, unsigned int> translateIndexMap;
-            if (process_weights) {
+            if (process_weights || mesh.GetBlendShapes().size() > 0) {
                 reverseMapping.resize(count_vertices);
                 reverseMapping.resize(count_vertices);
             }
             }
 
 
@@ -1287,7 +1332,7 @@ namespace Assimp {
                         binormals = &tempBinormals;
                         binormals = &tempBinormals;
                     }
                     }
                     else {
                     else {
-                        binormals = NULL;
+                        binormals = nullptr;
                     }
                     }
                 }
                 }
 
 
@@ -1386,7 +1431,7 @@ namespace Assimp {
             ConvertMaterialForMesh(out_mesh, model, mesh, index);
             ConvertMaterialForMesh(out_mesh, model, mesh, index);
 
 
             if (process_weights) {
             if (process_weights) {
-                ConvertWeights(out_mesh, model, mesh, node_global_transform, index, &reverseMapping);
+                ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, index, &reverseMapping);
             }
             }
 
 
             std::vector<aiAnimMesh*> animMeshes;
             std::vector<aiAnimMesh*> animMeshes;
@@ -1407,7 +1452,10 @@ namespace Assimp {
                             unsigned int count = 0;
                             unsigned int count = 0;
                             const unsigned int* outIndices = mesh.ToOutputVertexIndex(index, count);
                             const unsigned int* outIndices = mesh.ToOutputVertexIndex(index, count);
                             for (unsigned int k = 0; k < count; k++) {
                             for (unsigned int k = 0; k < count; k++) {
-                                unsigned int index = translateIndexMap[outIndices[k]];
+                                unsigned int outIndex = outIndices[k];
+                                if (translateIndexMap.find(outIndex) == translateIndexMap.end())
+                                    continue;
+                                unsigned int index = translateIndexMap[outIndex];
                                 animMesh->mVertices[index] += vertex;
                                 animMesh->mVertices[index] += vertex;
                                 if (animMesh->mNormals != nullptr) {
                                 if (animMesh->mNormals != nullptr) {
                                     animMesh->mNormals[index] += normal;
                                     animMesh->mNormals[index] += normal;
@@ -1421,13 +1469,22 @@ namespace Assimp {
                 }
                 }
             }
             }
 
 
+            const size_t numAnimMeshes = animMeshes.size();
+            if (numAnimMeshes > 0) {
+                out_mesh->mNumAnimMeshes = static_cast<unsigned int>(numAnimMeshes);
+                out_mesh->mAnimMeshes = new aiAnimMesh*[numAnimMeshes];
+                for (size_t i = 0; i < numAnimMeshes; i++) {
+                    out_mesh->mAnimMeshes[i] = animMeshes.at(i);
+                }
+            }
+
             return static_cast<unsigned int>(meshes.size() - 1);
             return static_cast<unsigned int>(meshes.size() - 1);
         }
         }
 
 
-        void FBXConverter::ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo,
-            const aiMatrix4x4& node_global_transform,
-            unsigned int materialIndex,
-            std::vector<unsigned int>* outputVertStartIndices)
+        void FBXConverter::ConvertWeights(aiMesh *out, const Model &model, const MeshGeometry &geo,
+                                          const aiMatrix4x4 &absolute_transform,
+                                          aiNode *parent, aiNode *root_node, unsigned int materialIndex,
+                                          std::vector<unsigned int> *outputVertStartIndices)
         {
         {
             ai_assert(geo.DeformerSkin());
             ai_assert(geo.DeformerSkin());
 
 
@@ -1438,41 +1495,35 @@ namespace Assimp {
             const Skin& sk = *geo.DeformerSkin();
             const Skin& sk = *geo.DeformerSkin();
 
 
             std::vector<aiBone*> bones;
             std::vector<aiBone*> bones;
-            bones.reserve(sk.Clusters().size());
 
 
             const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION;
             const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION;
             ai_assert(no_mat_check || outputVertStartIndices);
             ai_assert(no_mat_check || outputVertStartIndices);
 
 
             try {
             try {
-
+                // iterate over the sub deformers
                 for (const Cluster* cluster : sk.Clusters()) {
                 for (const Cluster* cluster : sk.Clusters()) {
                     ai_assert(cluster);
                     ai_assert(cluster);
 
 
                     const WeightIndexArray& indices = cluster->GetIndices();
                     const WeightIndexArray& indices = cluster->GetIndices();
 
 
-                    if (indices.empty() && mRemoveEmptyBones ) {
-                        continue;
-                    }
-
                     const MatIndexArray& mats = geo.GetMaterialIndices();
                     const MatIndexArray& mats = geo.GetMaterialIndices();
 
 
-                    bool ok = false;
-
                     const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
                     const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
 
 
                     count_out_indices.clear();
                     count_out_indices.clear();
                     index_out_indices.clear();
                     index_out_indices.clear();
                     out_indices.clear();
                     out_indices.clear();
 
 
+
                     // now check if *any* of these weights is contained in the output mesh,
                     // now check if *any* of these weights is contained in the output mesh,
                     // taking notes so we don't need to do it twice.
                     // taking notes so we don't need to do it twice.
                     for (WeightIndexArray::value_type index : indices) {
                     for (WeightIndexArray::value_type index : indices) {
 
 
                         unsigned int count = 0;
                         unsigned int count = 0;
                         const unsigned int* const out_idx = geo.ToOutputVertexIndex(index, count);
                         const unsigned int* const out_idx = geo.ToOutputVertexIndex(index, count);
-                        // ToOutputVertexIndex only returns NULL if index is out of bounds
+                        // ToOutputVertexIndex only returns nullptr if index is out of bounds
                         // which should never happen
                         // which should never happen
-                        ai_assert(out_idx != NULL);
+                        ai_assert(out_idx != nullptr);
 
 
                         index_out_indices.push_back(no_index_sentinel);
                         index_out_indices.push_back(no_index_sentinel);
                         count_out_indices.push_back(0);
                         count_out_indices.push_back(0);
@@ -1497,75 +1548,111 @@ namespace Assimp {
                                     out_indices.push_back(std::distance(outputVertStartIndices->begin(), it));
                                     out_indices.push_back(std::distance(outputVertStartIndices->begin(), it));
                                 }
                                 }
 
 
-                                ++count_out_indices.back();
-                                ok = true;
+                                ++count_out_indices.back();                               
                             }
                             }
                         }
                         }
                     }
                     }
-                    
+
                     // if we found at least one, generate the output bones
                     // if we found at least one, generate the output bones
                     // XXX this could be heavily simplified by collecting the bone
                     // XXX this could be heavily simplified by collecting the bone
                     // data in a single step.
                     // data in a single step.
-                    if (ok && mRemoveEmptyBones) {
-                        ConvertCluster(bones, model, *cluster, out_indices, index_out_indices,
-                            count_out_indices, node_global_transform);
-                    }
+                    ConvertCluster(bones, cluster, out_indices, index_out_indices,
+                                   count_out_indices, absolute_transform, parent, root_node);
                 }
                 }
+
+                bone_map.clear();
             }
             }
-            catch (std::exception&) {
+            catch (std::exception&e) {
                 std::for_each(bones.begin(), bones.end(), Util::delete_fun<aiBone>());
                 std::for_each(bones.begin(), bones.end(), Util::delete_fun<aiBone>());
                 throw;
                 throw;
             }
             }
 
 
             if (bones.empty()) {
             if (bones.empty()) {
+                out->mBones = nullptr;
+                out->mNumBones = 0;
                 return;
                 return;
-            }
+            } else {
+                out->mBones = new aiBone *[bones.size()]();
+                out->mNumBones = static_cast<unsigned int>(bones.size());
 
 
-            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)) {
+                std::cout << "retrieved bone from lookup " << bone_name.C_Str() << ". Deformer: " << deformer_name
+                          << std::endl;
+                bone = bone_map[deformer_name];
+            } else {
+                std::cout << "created new bone " << bone_name.C_Str() << ". Deformer: " << deformer_name << std::endl;
+                bone = new aiBone();
+                bone->mName = bone_name;
 
 
-            bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform;
+                // store local transform link for post processing
+                bone->mOffsetMatrix = cl->TransformLink();
+                bone->mOffsetMatrix.Inverse();
 
 
-            bone->mNumWeights = static_cast<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
+                //
 
 
-                const size_t cc = count_out_indices[i];
-                for (size_t j = 0; j < cc; ++j) {
-                    aiVertexWeight& out_weight = *cursor++;
+                aiVertexWeight *cursor = nullptr;
 
 
-                    out_weight.mVertexId = static_cast<unsigned int>(out_indices[index_index + j]);
-                    out_weight.mWeight = weights[i];
+                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) {
+                        // 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];
+                    }
                 }
                 }
+
+                bone_map.insert(std::pair<const std::string, aiBone *>(deformer_name, bone));
             }
             }
+
+            std::cout << "bone research: Indicies size: " << out_indices.size() << std::endl;
+
+            // lookup must be populated in case something goes wrong
+            // this also allocates bones to mesh instance outside
+            local_mesh_bones.push_back(bone);
         }
         }
 
 
         void FBXConverter::ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
         void FBXConverter::ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
@@ -1695,7 +1782,7 @@ namespace Assimp {
                 bool textureReady = false; //tells if our texture is ready (if it was loaded or if it was found)
                 bool textureReady = false; //tells if our texture is ready (if it was loaded or if it was found)
                 unsigned int index;
                 unsigned int index;
 
 
-                VideoMap::const_iterator it = textures_converted.find(media);
+                VideoMap::const_iterator it = textures_converted.find(*media);
                 if (it != textures_converted.end()) {
                 if (it != textures_converted.end()) {
                     index = (*it).second;
                     index = (*it).second;
                     textureReady = true;
                     textureReady = true;
@@ -1703,7 +1790,7 @@ namespace Assimp {
                 else {
                 else {
                     if (media->ContentLength() > 0) {
                     if (media->ContentLength() > 0) {
                         index = ConvertVideo(*media);
                         index = ConvertVideo(*media);
-                        textures_converted[media] = index;
+                        textures_converted[*media] = index;
                         textureReady = true;
                         textureReady = true;
                     }
                     }
                 }
                 }
@@ -1986,6 +2073,21 @@ namespace Assimp {
             TrySetTextureProperties(out_mat, textures, "Maya|SpecularTexture", aiTextureType_SPECULAR, mesh);
             TrySetTextureProperties(out_mat, textures, "Maya|SpecularTexture", aiTextureType_SPECULAR, mesh);
             TrySetTextureProperties(out_mat, textures, "Maya|FalloffTexture", aiTextureType_OPACITY, mesh);
             TrySetTextureProperties(out_mat, textures, "Maya|FalloffTexture", aiTextureType_OPACITY, mesh);
             TrySetTextureProperties(out_mat, textures, "Maya|ReflectionMapTexture", aiTextureType_REFLECTION, mesh);
             TrySetTextureProperties(out_mat, textures, "Maya|ReflectionMapTexture", aiTextureType_REFLECTION, mesh);
+            
+            // Maya PBR
+            TrySetTextureProperties(out_mat, textures, "Maya|baseColor|file", aiTextureType_BASE_COLOR, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|normalCamera|file", aiTextureType_NORMAL_CAMERA, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|emissionColor|file", aiTextureType_EMISSION_COLOR, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|metalness|file", aiTextureType_METALNESS, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|diffuseRoughness|file", aiTextureType_DIFFUSE_ROUGHNESS, mesh);
+            
+            // Maya stingray
+            TrySetTextureProperties(out_mat, textures, "Maya|TEX_color_map|file", aiTextureType_BASE_COLOR, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|TEX_normal_map|file", aiTextureType_NORMAL_CAMERA, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|TEX_emissive_map|file", aiTextureType_EMISSION_COLOR, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|TEX_metallic_map|file", aiTextureType_METALNESS, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|TEX_roughness_map|file", aiTextureType_DIFFUSE_ROUGHNESS, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|TEX_ao_map|file", aiTextureType_AMBIENT_OCCLUSION, mesh);            
         }
         }
 
 
         void FBXConverter::SetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh)
         void FBXConverter::SetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh)
@@ -2212,13 +2314,13 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
             if (media != nullptr && media->ContentLength() > 0) {
             if (media != nullptr && media->ContentLength() > 0) {
                 unsigned int index;
                 unsigned int index;
 
 
-                VideoMap::const_iterator it = textures_converted.find(media);
+                VideoMap::const_iterator it = textures_converted.find(*media);
                 if (it != textures_converted.end()) {
                 if (it != textures_converted.end()) {
                     index = (*it).second;
                     index = (*it).second;
                 }
                 }
                 else {
                 else {
                     index = ConvertVideo(*media);
                     index = ConvertVideo(*media);
-                    textures_converted[media] = index;
+                    textures_converted[*media] = index;
                 }
                 }
 
 
                 // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture)
                 // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture)
@@ -2646,7 +2748,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
         // sanity check whether the input is ok
         // sanity check whether the input is ok
         static void validateAnimCurveNodes(const std::vector<const AnimationCurveNode*>& curves,
         static void validateAnimCurveNodes(const std::vector<const AnimationCurveNode*>& curves,
             bool strictMode) {
             bool strictMode) {
-            const Object* target(NULL);
+            const Object* target(nullptr);
             for (const AnimationCurveNode* node : curves) {
             for (const AnimationCurveNode* node : curves) {
                 if (!target) {
                 if (!target) {
                     target = node->Target();
                     target = node->Target();
@@ -2677,7 +2779,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
 #ifdef ASSIMP_BUILD_DEBUG
 #ifdef ASSIMP_BUILD_DEBUG
             validateAnimCurveNodes(curves, doc.Settings().strictMode);
             validateAnimCurveNodes(curves, doc.Settings().strictMode);
 #endif
 #endif
-            const AnimationCurveNode* curve_node = NULL;
+            const AnimationCurveNode* curve_node = nullptr;
             for (const AnimationCurveNode* node : curves) {
             for (const AnimationCurveNode* node : curves) {
                 ai_assert(node);
                 ai_assert(node);
 
 
@@ -2937,7 +3039,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
                 TransformationCompDefaultValue(comp)
                 TransformationCompDefaultValue(comp)
                 );
                 );
 
 
-            const float epsilon = 1e-6f;
+            const float epsilon = Math::getEpsilon<float>();
             return (dyn_val - static_val).SquareLength() < epsilon;
             return (dyn_val - static_val).SquareLength() < epsilon;
         }
         }
 
 
@@ -3520,52 +3622,12 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
             out->mMetaData->Set(14, "CustomFrameRate", doc.GlobalSettings().CustomFrameRate());
             out->mMetaData->Set(14, "CustomFrameRate", doc.GlobalSettings().CustomFrameRate());
         }
         }
 
 
-        void FBXConverter::ConvertToUnitScale( FbxUnit unit ) {
-            if (mCurrentUnit == unit) {
-                return;
-            }
-
-            ai_real scale = 1.0;
-            if (mCurrentUnit == FbxUnit::cm) {
-                if (unit == FbxUnit::m) {
-                    scale = (ai_real)0.01;
-                } else if (unit == FbxUnit::km) {
-                    scale = (ai_real)0.00001;
-                }
-            } else if (mCurrentUnit == FbxUnit::m) {
-                if (unit == FbxUnit::cm) {
-                    scale = (ai_real)100.0;
-                } else if (unit == FbxUnit::km) {
-                    scale = (ai_real)0.001;
-                }
-            } else if (mCurrentUnit == FbxUnit::km) {
-                if (unit == FbxUnit::cm) {
-                    scale = (ai_real)100000.0;
-                } else if (unit == FbxUnit::m) {
-                    scale = (ai_real)1000.0;
-                }
-            }
-            
-            for (auto mesh : meshes) {
-                if (nullptr == mesh) {
-                    continue;
-                }
-
-                if (mesh->HasPositions()) {
-                    for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
-                        aiVector3D &pos = mesh->mVertices[i];
-                        pos *= scale;
-                    }
-                }
-            }
-        }
-
         void FBXConverter::TransferDataToScene()
         void FBXConverter::TransferDataToScene()
         {
         {
             ai_assert(!out->mMeshes);
             ai_assert(!out->mMeshes);
             ai_assert(!out->mNumMeshes);
             ai_assert(!out->mNumMeshes);
 
 
-            // note: the trailing () ensures initialization with NULL - not
+            // note: the trailing () ensures initialization with nullptr - not
             // many C++ users seem to know this, so pointing it out to avoid
             // many C++ users seem to know this, so pointing it out to avoid
             // confusion why this code works.
             // confusion why this code works.
 
 
@@ -3612,10 +3674,51 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
             }
             }
         }
         }
 
 
+        void FBXConverter::ConvertOrphantEmbeddedTextures()
+        {
+            // in C++14 it could be:
+            // for (auto&& [id, object] : objects)
+            for (auto&& id_and_object : doc.Objects())
+            {
+                auto&& id = std::get<0>(id_and_object);
+                auto&& object = std::get<1>(id_and_object);
+                // If an object doesn't have parent
+                if (doc.ConnectionsBySource().count(id) == 0)
+                {
+                    const Texture* realTexture = nullptr;
+                    try
+                    {
+                        const auto& element = object->GetElement();
+                        const Token& key = element.KeyToken();
+                        const char* obtype = key.begin();
+                        const size_t length = static_cast<size_t>(key.end() - key.begin());
+                        if (strncmp(obtype, "Texture", length) == 0)
+                        {
+                            const Texture* texture = static_cast<const Texture*>(object->Get());
+                            if (texture->Media() && texture->Media()->ContentLength() > 0)
+                            {
+                                realTexture = texture;
+                            }
+                        }
+                    }
+                    catch (...)
+                    {
+                        // do nothing
+                    }
+                    if (realTexture)
+                    {
+                        const Video* media = realTexture->Media();
+                        unsigned int index = ConvertVideo(*media);
+                        textures_converted[*media] = index;
+                    }
+                }
+            }
+        }
+
         // ------------------------------------------------------------------------------------------------
         // ------------------------------------------------------------------------------------------------
-        void ConvertToAssimpScene(aiScene* out, const Document& doc, bool removeEmptyBones, FbxUnit unit)
+        void ConvertToAssimpScene(aiScene* out, const Document& doc, bool removeEmptyBones)
         {
         {
-            FBXConverter converter(out, doc, removeEmptyBones, unit);
+            FBXConverter converter(out, doc, removeEmptyBones);
         }
         }
 
 
     } // !FBX
     } // !FBX

+ 55 - 46
code/FBX/FBXConverter.h

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

+ 0 - 8
code/FBX/FBXDocument.cpp

@@ -90,14 +90,6 @@ const Object* LazyObject::Get(bool dieOnError)
         return object.get();
         return object.get();
     }
     }
 
 
-    // if this is the root object, we return a dummy since there
-    // is no root object int he fbx file - it is just referenced
-    // with id 0.
-    if(id == 0L) {
-        object.reset(new Object(id, element, "Model::RootNode"));
-        return object.get();
-    }
-
     const Token& key = element.KeyToken();
     const Token& key = element.KeyToken();
     const TokenList& tokens = element.Tokens();
     const TokenList& tokens = element.Tokens();
 
 

+ 37 - 2
code/FBX/FBXDocument.h

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

+ 7 - 7
code/FBX/FBXExportNode.cpp

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

+ 1 - 5
code/FBX/FBXExportProperty.cpp

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

+ 116 - 74
code/FBX/FBXExporter.cpp

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

+ 1 - 1
code/FBX/FBXExporter.h

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

+ 102 - 107
code/FBX/FBXImporter.cpp

@@ -48,26 +48,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 #include "FBXImporter.h"
 #include "FBXImporter.h"
 
 
-#include "FBXTokenizer.h"
+#include "FBXConverter.h"
+#include "FBXDocument.h"
 #include "FBXParser.h"
 #include "FBXParser.h"
+#include "FBXTokenizer.h"
 #include "FBXUtil.h"
 #include "FBXUtil.h"
-#include "FBXDocument.h"
-#include "FBXConverter.h"
 
 
-#include <assimp/StreamReader.h>
 #include <assimp/MemoryIOWrapper.h>
 #include <assimp/MemoryIOWrapper.h>
-#include <assimp/Importer.hpp>
+#include <assimp/StreamReader.h>
 #include <assimp/importerdesc.h>
 #include <assimp/importerdesc.h>
+#include <assimp/Importer.hpp>
 
 
 namespace Assimp {
 namespace Assimp {
 
 
-template<>
-const char* LogFunctions<FBXImporter>::Prefix() {
-    static auto prefix = "FBX: ";
-    return prefix;
+template <>
+const char *LogFunctions<FBXImporter>::Prefix() {
+	static auto prefix = "FBX: ";
+	return prefix;
 }
 }
 
 
-}
+} // namespace Assimp
 
 
 using namespace Assimp;
 using namespace Assimp;
 using namespace Assimp::Formatter;
 using namespace Assimp::Formatter;
@@ -76,128 +76,123 @@ using namespace Assimp::FBX;
 namespace {
 namespace {
 
 
 static const aiImporterDesc desc = {
 static const aiImporterDesc desc = {
-    "Autodesk FBX Importer",
-    "",
-    "",
-    "",
-    aiImporterFlags_SupportTextFlavour,
-    0,
-    0,
-    0,
-    0,
-    "fbx"
+	"Autodesk FBX Importer",
+	"",
+	"",
+	"",
+	aiImporterFlags_SupportTextFlavour,
+	0,
+	0,
+	0,
+	0,
+	"fbx"
 };
 };
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by #Importer
 // Constructor to be privately used by #Importer
-FBXImporter::FBXImporter()
-{
+FBXImporter::FBXImporter() {
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
 // Destructor, private as well
-FBXImporter::~FBXImporter()
-{
+FBXImporter::~FBXImporter() {
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 // Returns whether the class can handle the format of the given file.
-bool FBXImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
-{
-    const std::string& extension = GetExtension(pFile);
-    if (extension == std::string( desc.mFileExtensions ) ) {
-        return true;
-    }
-
-    else if ((!extension.length() || checkSig) && pIOHandler)   {
-        // at least ASCII-FBX files usually have a 'FBX' somewhere in their head
-        const char* tokens[] = {"fbx"};
-        return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
-    }
-    return false;
+bool FBXImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
+	const std::string &extension = GetExtension(pFile);
+	if (extension == std::string(desc.mFileExtensions)) {
+		return true;
+	}
+
+	else if ((!extension.length() || checkSig) && pIOHandler) {
+		// at least ASCII-FBX files usually have a 'FBX' somewhere in their head
+		const char *tokens[] = { "fbx" };
+		return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
+	}
+	return false;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // List all extensions handled by this loader
 // List all extensions handled by this loader
-const aiImporterDesc* FBXImporter::GetInfo () const
-{
-    return &desc;
+const aiImporterDesc *FBXImporter::GetInfo() const {
+	return &desc;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Setup configuration properties for the loader
 // Setup configuration properties for the loader
-void FBXImporter::SetupProperties(const Importer* pImp)
-{
-    settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true);
-    settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false);
-    settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true);
-    settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true);
-    settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true);
-    settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true);
-    settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true);
-    settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false);
-    settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true);
-    settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
-    settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false);
-    settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true);
-    settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false);
+void FBXImporter::SetupProperties(const Importer *pImp) {
+	settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true);
+	settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false);
+	settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true);
+	settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true);
+	settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true);
+	settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true);
+	settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true);
+	settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false);
+	settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true);
+	settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
+	settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false);
+	settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true);
+	settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure.
 // Imports the given file into the given scene structure.
-void FBXImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
-{
-    std::unique_ptr<IOStream> stream(pIOHandler->Open(pFile,"rb"));
-    if (!stream) {
-        ThrowException("Could not open file for reading");
-    }
-
-    // read entire file into memory - no streaming for this, fbx
-    // files can grow large, but the assimp output data structure
-    // then becomes very large, too. Assimp doesn't support
-    // streaming for its output data structures so the net win with
-    // streaming input data would be very low.
-    std::vector<char> contents;
-    contents.resize(stream->FileSize()+1);
-    stream->Read( &*contents.begin(), 1, contents.size()-1 );
-    contents[ contents.size() - 1 ] = 0;
-    const char* const begin = &*contents.begin();
-
-    // broadphase tokenizing pass in which we identify the core
-    // syntax elements of FBX (brackets, commas, key:value mappings)
-    TokenList tokens;
-    try {
-
-        bool is_binary = false;
-        if (!strncmp(begin,"Kaydara FBX Binary",18)) {
-            is_binary = true;
-            TokenizeBinary(tokens,begin,contents.size());
-        }
-        else {
-            Tokenize(tokens,begin);
-        }
-
-        // use this information to construct a very rudimentary
-        // parse-tree representing the FBX scope structure
-        Parser parser(tokens, is_binary);
-
-        // take the raw parse-tree and convert it to a FBX DOM
-        Document doc(parser,settings);
-
-        FbxUnit unit(FbxUnit::cm);
-        if (settings.convertToMeters) {
-            unit = FbxUnit::m;
-        }
-        // convert the FBX DOM to aiScene
-        ConvertToAssimpScene(pScene,doc, settings.removeEmptyBones, unit);
-
-        std::for_each(tokens.begin(),tokens.end(),Util::delete_fun<Token>());
-    }
-    catch(std::exception&) {
-        std::for_each(tokens.begin(),tokens.end(),Util::delete_fun<Token>());
-        throw;
-    }
+void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
+	std::unique_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
+	if (!stream) {
+		ThrowException("Could not open file for reading");
+	}
+
+	// read entire file into memory - no streaming for this, fbx
+	// files can grow large, but the assimp output data structure
+	// then becomes very large, too. Assimp doesn't support
+	// streaming for its output data structures so the net win with
+	// streaming input data would be very low.
+	std::vector<char> contents;
+	contents.resize(stream->FileSize() + 1);
+	stream->Read(&*contents.begin(), 1, contents.size() - 1);
+	contents[contents.size() - 1] = 0;
+	const char *const begin = &*contents.begin();
+
+	// broadphase tokenizing pass in which we identify the core
+	// syntax elements of FBX (brackets, commas, key:value mappings)
+	TokenList tokens;
+	try {
+
+		bool is_binary = false;
+		if (!strncmp(begin, "Kaydara FBX Binary", 18)) {
+			is_binary = true;
+			TokenizeBinary(tokens, begin, contents.size());
+		} else {
+			Tokenize(tokens, begin);
+		}
+
+		// use this information to construct a very rudimentary
+		// parse-tree representing the FBX scope structure
+		Parser parser(tokens, is_binary);
+
+		// take the raw parse-tree and convert it to a FBX DOM
+		Document doc(parser, settings);
+
+		// convert the FBX DOM to aiScene
+		ConvertToAssimpScene(pScene, doc, settings.removeEmptyBones);
+
+		// size relative to cm
+		float size_relative_to_cm = doc.GlobalSettings().UnitScaleFactor();
+
+		// Set FBX file scale is relative to CM must be converted to M for
+		// assimp universal format (M)
+		SetFileScale(size_relative_to_cm * 0.01f);
+
+		std::for_each(tokens.begin(), tokens.end(), Util::delete_fun<Token>());
+	} catch (std::exception &) {
+		std::for_each(tokens.begin(), tokens.end(), Util::delete_fun<Token>());
+		throw;
+	}
 }
 }
 
 
 #endif // !ASSIMP_BUILD_NO_FBX_IMPORTER
 #endif // !ASSIMP_BUILD_NO_FBX_IMPORTER

+ 11 - 14
code/FBX/FBXMeshGeometry.cpp

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

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

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

+ 1 - 1
code/Importer/IFC/IFCGeometry.cpp

@@ -128,7 +128,7 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m
         outer_polygon_it = begin + master_bounds;
         outer_polygon_it = begin + master_bounds;
     }
     }
     else {
     else {
-        for(iit = begin; iit != end; iit++) {
+        for(iit = begin; iit != end; ++iit) {
             // find the polygon with the largest area and take it as the outer bound.
             // find the polygon with the largest area and take it as the outer bound.
             IfcVector3& n = normals[std::distance(begin,iit)];
             IfcVector3& n = normals[std::distance(begin,iit)];
             const IfcFloat area = n.SquareLength();
             const IfcFloat area = n.SquareLength();

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

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

+ 0 - 1
code/Irr/IRRMeshLoader.cpp

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

+ 420 - 0
code/M3D/M3DExporter.cpp

@@ -0,0 +1,420 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+Copyright (c) 2019 bzt
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+#ifndef ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER
+
+#define M3D_IMPLEMENTATION
+#define M3D_NOIMPORTER
+#define M3D_EXPORTER
+#define M3D_ASCII
+#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
+#define M3D_NODUP
+#endif
+
+// Header files, standard library.
+#include <memory> // shared_ptr
+#include <string>
+#include <vector>
+
+#include <assimp/version.h> // aiGetVersion
+#include <assimp/IOSystem.hpp>
+#include <assimp/Exporter.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/Exceptional.h> // DeadlyExportError
+#include <assimp/material.h> // aiTextureType
+#include <assimp/scene.h>
+#include <assimp/mesh.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)
+ */
+using namespace Assimp;
+
+namespace Assimp {
+
+    // ---------------------------------------------------------------------
+    // Worker function for exporting a scene to binary M3D.
+    // Prototyped and registered in Exporter.cpp
+    void ExportSceneM3D (
+        const char* pFile,
+        IOSystem* pIOSystem,
+        const aiScene* pScene,
+        const ExportProperties* pProperties
+    ){
+        // initialize the exporter
+        M3DExporter exporter(pScene, pProperties);
+
+        // perform binary export
+        exporter.doExport(pFile, pIOSystem, false);
+    }
+
+    // ---------------------------------------------------------------------
+    // Worker function for exporting a scene to ASCII A3D.
+    // Prototyped and registered in Exporter.cpp
+    void ExportSceneA3D (
+        const char* pFile,
+        IOSystem* pIOSystem,
+        const aiScene* pScene,
+        const ExportProperties* pProperties
+
+    ){
+        // initialize the exporter
+        M3DExporter exporter(pScene, pProperties);
+
+        // perform ascii export
+        exporter.doExport(pFile, pIOSystem, true);
+    }
+
+} // end of namespace Assimp
+
+// ------------------------------------------------------------------------------------------------
+M3DExporter::M3DExporter ( const aiScene* pScene, const ExportProperties* pProperties )
+: mScene(pScene)
+, mProperties(pProperties)
+, outfile()
+, m3d(nullptr) { }
+
+// ------------------------------------------------------------------------------------------------
+void M3DExporter::doExport (
+    const char* pFile,
+    IOSystem* pIOSystem,
+    bool toAscii
+){
+    // TODO: convert mProperties into M3D_EXP_* flags
+    (void)mProperties;
+
+    // open the indicated file for writing (in binary / ASCII mode)
+    outfile.reset(pIOSystem->Open(pFile, toAscii ? "wt" : "wb"));
+    if (!outfile) {
+        throw DeadlyExportError( "could not open output .m3d file: " + std::string(pFile) );
+    }
+
+    // use malloc() here because m3d_free() will call free()
+    m3d = (m3d_t*)calloc(1, sizeof(m3d_t));
+    if(!m3d) {
+        throw DeadlyExportError( "memory allocation error" );
+    }
+    m3d->name = _m3d_safestr((char*)&mScene->mRootNode->mName.data, 2);
+
+    // Create a model from assimp structures
+    aiMatrix4x4 m;
+    NodeWalk(mScene->mRootNode, m);
+
+    // serialize the structures
+    unsigned int size;
+    unsigned char *output = m3d_save(m3d, M3D_EXP_FLOAT,
+        M3D_EXP_EXTRA | (toAscii ? M3D_EXP_ASCII : 0), &size);
+    m3d_free(m3d);
+    if(!output || size < 8) {
+        throw DeadlyExportError( "unable to serialize into Model 3D" );
+    }
+
+    // Write out serialized model
+    outfile->Write(output, size, 1);
+
+    // explicitly release file pointer,
+    // so we don't have to rely on class destruction.
+    outfile.reset();
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// helper to add a vertex (private to NodeWalk)
+m3dv_t *M3DExporter::AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx)
+{
+    if(v->x == (M3D_FLOAT)-0.0) v->x = (M3D_FLOAT)0.0;
+    if(v->y == (M3D_FLOAT)-0.0) v->y = (M3D_FLOAT)0.0;
+    if(v->z == (M3D_FLOAT)-0.0) v->z = (M3D_FLOAT)0.0;
+    if(v->w == (M3D_FLOAT)-0.0) v->w = (M3D_FLOAT)0.0;
+    vrtx = (m3dv_t*)M3D_REALLOC(vrtx, ((*numvrtx) + 1) * sizeof(m3dv_t));
+    memcpy(&vrtx[*numvrtx], v, sizeof(m3dv_t));
+    *idx = *numvrtx;
+    (*numvrtx)++;
+    return vrtx;
+}
+
+// ------------------------------------------------------------------------------------------------
+// helper to add a tmap (private to NodeWalk)
+m3dti_t *M3DExporter::AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx)
+{
+    tmap = (m3dti_t*)M3D_REALLOC(tmap, ((*numtmap) + 1) * sizeof(m3dti_t));
+    memcpy(&tmap[*numtmap], ti, sizeof(m3dti_t));
+    *idx = *numtmap;
+    (*numtmap)++;
+    return tmap;
+}
+
+// ------------------------------------------------------------------------------------------------
+// recursive node walker
+void M3DExporter::NodeWalk(const aiNode* pNode, aiMatrix4x4 m)
+{
+    aiMatrix4x4 nm = m * pNode->mTransformation;
+
+    for(unsigned int i = 0; i < pNode->mNumMeshes; i++) {
+        const aiMesh *mesh = mScene->mMeshes[pNode->mMeshes[i]];
+        unsigned int mi = (M3D_INDEX)-1U;
+        if(mScene->mMaterials) {
+            // get the material for this mesh
+            mi = addMaterial(mScene->mMaterials[mesh->mMaterialIndex]);
+        }
+        // iterate through the mesh faces
+        for(unsigned int j = 0; j < mesh->mNumFaces; j++) {
+            unsigned int n;
+            const aiFace* face = &(mesh->mFaces[j]);
+            // only triangle meshes supported for now
+            if(face->mNumIndices != 3) {
+                throw DeadlyExportError( "use aiProcess_Triangulate before export" );
+            }
+            // add triangle to the output
+            n = m3d->numface++;
+            m3d->face = (m3df_t*)M3D_REALLOC(m3d->face,
+                m3d->numface * sizeof(m3df_t));
+            if(!m3d->face) {
+                throw DeadlyExportError( "memory allocation error" );
+            }
+            /* set all index to -1 by default */
+            m3d->face[n].vertex[0] = m3d->face[n].vertex[1] = m3d->face[n].vertex[2] =
+            m3d->face[n].normal[0] = m3d->face[n].normal[1] = m3d->face[n].normal[2] =
+            m3d->face[n].texcoord[0] = m3d->face[n].texcoord[1] = m3d->face[n].texcoord[2] = -1U;
+            m3d->face[n].materialid = mi;
+            for(unsigned int k = 0; k < face->mNumIndices; k++) {
+                // get the vertex's index
+                unsigned int l = face->mIndices[k];
+                unsigned int idx;
+                m3dv_t vertex;
+                m3dti_t ti;
+                // multiply the position vector by the transformation matrix
+                aiVector3D v = mesh->mVertices[l];
+                v *= nm;
+                vertex.x = v.x;
+                vertex.y = v.y;
+                vertex.z = v.z;
+                vertex.w = 1.0;
+                vertex.color = 0;
+                vertex.skinid = -1U;
+                // add color if defined
+                if(mesh->HasVertexColors(0))
+                    vertex.color = mkColor(&mesh->mColors[0][l]);
+                // save the vertex to the output
+                m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex,
+                    &vertex, &idx);
+                m3d->face[n].vertex[k] = (M3D_INDEX)idx;
+                // do we have texture coordinates?
+                if(mesh->HasTextureCoords(0)) {
+                    ti.u = mesh->mTextureCoords[0][l].x;
+                    ti.v = mesh->mTextureCoords[0][l].y;
+                    m3d->tmap = AddTmap(m3d->tmap, &m3d->numtmap, &ti, &idx);
+                    m3d->face[n].texcoord[k] = (M3D_INDEX)idx;
+                }
+                // do we have normal vectors?
+                if(mesh->HasNormals()) {
+                    vertex.x = mesh->mNormals[l].x;
+                    vertex.y = mesh->mNormals[l].y;
+                    vertex.z = mesh->mNormals[l].z;
+                    vertex.color = 0;
+                    m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, &vertex, &idx);
+                    m3d->face[n].normal[k] = (M3D_INDEX)idx;
+                }
+            }
+        }
+    }
+    // repeat for the children nodes
+    for (unsigned int i = 0; i < pNode->mNumChildren; i++) {
+        NodeWalk(pNode->mChildren[i], nm);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// convert aiColor4D into uint32_t
+uint32_t M3DExporter::mkColor(aiColor4D* c)
+{
+    return  ((uint8_t)(c->a*255) << 24L) |
+            ((uint8_t)(c->b*255) << 16L) |
+            ((uint8_t)(c->g*255) <<  8L) |
+            ((uint8_t)(c->r*255) <<  0L);
+}
+
+// ------------------------------------------------------------------------------------------------
+// add a material to the output
+M3D_INDEX M3DExporter::addMaterial(const aiMaterial *mat)
+{
+    unsigned int mi = -1U;
+    aiColor4D c;
+    aiString name;
+    ai_real f;
+    char *fn;
+
+    if(mat && mat->Get(AI_MATKEY_NAME, name) == AI_SUCCESS && name.length &&
+        strcmp((char*)&name.data, AI_DEFAULT_MATERIAL_NAME)) {
+        // check if we have saved a material by this name. This has to be done
+        // because only the referenced materials should be added to the output
+        for(unsigned int i = 0; i < m3d->nummaterial; i++)
+            if(!strcmp((char*)&name.data, m3d->material[i].name)) {
+                mi = i;
+                break;
+            }
+        // if not found, add the material to the output
+        if(mi == -1U) {
+            unsigned int k;
+            mi = m3d->nummaterial++;
+            m3d->material = (m3dm_t*)M3D_REALLOC(m3d->material, m3d->nummaterial
+                * sizeof(m3dm_t));
+            if(!m3d->material) {
+                throw DeadlyExportError( "memory allocation error" );
+            }
+            m3d->material[mi].name = _m3d_safestr((char*)&name.data, 0);
+            m3d->material[mi].numprop = 0;
+            m3d->material[mi].prop = NULL;
+            // iterate through the material property table and see what we got
+            for(k = 0; k < 15; k++) {
+                unsigned int j;
+                if(m3d_propertytypes[k].format == m3dpf_map)
+                    continue;
+                if(aiProps[k].pKey) {
+                    switch(m3d_propertytypes[k].format) {
+                        case m3dpf_color:
+                            if(mat->Get(aiProps[k].pKey, aiProps[k].type,
+                                aiProps[k].index, c) == AI_SUCCESS)
+                                addProp(&m3d->material[mi],
+                                    m3d_propertytypes[k].id, mkColor(&c));
+                        break;
+                        case m3dpf_float:
+                            if(mat->Get(aiProps[k].pKey, aiProps[k].type,
+                                aiProps[k].index, f) == AI_SUCCESS)
+                                    addProp(&m3d->material[mi],
+                                        m3d_propertytypes[k].id,
+                                        /* not (uint32_t)f, because we don't want to convert
+                                         * it, we want to see it as 32 bits of memory */
+                                        *((uint32_t*)&f));
+                        break;
+                        case m3dpf_uint8:
+                            if(mat->Get(aiProps[k].pKey, aiProps[k].type,
+                                aiProps[k].index, j) == AI_SUCCESS) {
+                                // special conversion for illumination model property
+                                if(m3d_propertytypes[k].id == m3dp_il) {
+                                    switch(j) {
+                                        case aiShadingMode_NoShading: j = 0; break;
+                                        case aiShadingMode_Phong: j = 2; break;
+                                        default: j = 1; break;
+                                    }
+                                }
+                                addProp(&m3d->material[mi],
+                                    m3d_propertytypes[k].id, j);
+                            }
+                        break;
+                        default:
+                            if(mat->Get(aiProps[k].pKey, aiProps[k].type,
+                                aiProps[k].index, j) == AI_SUCCESS)
+                                addProp(&m3d->material[mi],
+                                    m3d_propertytypes[k].id, j);
+                        break;
+                    }
+                }
+                if(aiTxProps[k].pKey &&
+                    mat->GetTexture((aiTextureType)aiTxProps[k].type,
+                        aiTxProps[k].index, &name, NULL, NULL, NULL,
+                        NULL, NULL) == AI_SUCCESS) {
+                        unsigned int i;
+                        for(j = name.length-1; j > 0 && name.data[j]!='.'; j++);
+                        if(j && name.data[j]=='.' &&
+                            (name.data[j+1]=='p' || name.data[j+1]=='P') &&
+                            (name.data[j+1]=='n' || name.data[j+1]=='N') &&
+                            (name.data[j+1]=='g' || name.data[j+1]=='G'))
+                                name.data[j]=0;
+                        // do we have this texture saved already?
+                        fn = _m3d_safestr((char*)&name.data, 0);
+                        for(j = 0, i = -1U; j < m3d->numtexture; j++)
+                            if(!strcmp(fn, m3d->texture[j].name)) {
+                                i = j;
+                                free(fn);
+                                break;
+                        }
+                        if(i == -1U) {
+                            i = m3d->numtexture++;
+                            m3d->texture = (m3dtx_t*)M3D_REALLOC(
+                                m3d->texture,
+                                m3d->numtexture * sizeof(m3dtx_t));
+                            if(!m3d->texture) {
+                                throw DeadlyExportError( "memory allocation error" );
+                            }
+                            // we don't need the texture itself, only its name
+                            m3d->texture[i].name = fn;
+                            m3d->texture[i].w = 0;
+                            m3d->texture[i].h = 0;
+                            m3d->texture[i].d = NULL;
+                        }
+                        addProp(&m3d->material[mi],
+                            m3d_propertytypes[k].id + 128, i);
+                }
+            }
+        }
+    }
+    return mi;
+}
+
+// ------------------------------------------------------------------------------------------------
+// add a material property to the output
+void M3DExporter::addProp(m3dm_t *m, uint8_t type, uint32_t value)
+{
+    unsigned int i;
+    i = m->numprop++;
+    m->prop = (m3dp_t*)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t));
+    if(!m->prop) { throw DeadlyExportError( "memory allocation error" ); }
+    m->prop[i].type = type;
+    m->prop[i].value.num = value;
+}
+
+#endif // ASSIMP_BUILD_NO_M3D_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 100 - 0
code/M3D/M3DExporter.h

@@ -0,0 +1,100 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+Copyright (c) 2019 bzt
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file M3DExporter.h
+*   @brief Declares the exporter class to write a scene to a Model 3D file
+*/
+#ifndef AI_M3DEXPORTER_H_INC
+#define AI_M3DEXPORTER_H_INC
+
+#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER
+
+#include "m3d.h"
+
+#include <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;
+
+    // ---------------------------------------------------------------------
+    /** 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
+        m3d_t *m3d; // model for the C library to convert to
+
+        // helper to do the recursive walking
+        void NodeWalk(const aiNode* pNode, aiMatrix4x4 m);
+        m3dv_t *AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx);
+        m3dti_t *AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx);
+        uint32_t mkColor(aiColor4D* c);
+        M3D_INDEX addMaterial(const aiMaterial *mat);
+        void addProp(m3dm_t *m, uint8_t type, uint32_t value);
+    };
+}
+
+#endif // ASSIMP_BUILD_NO_M3D_EXPORTER
+
+#endif // AI_M3DEXPORTER_H_INC

+ 766 - 0
code/M3D/M3DImporter.cpp

@@ -0,0 +1,766 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+Copyright (c) 2019 bzt
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
+
+#define M3D_IMPLEMENTATION
+#define M3D_ASCII
+#define M3D_NONORMALS       /* leave the post-processing to Assimp */
+#define M3D_NOWEIGHTS
+#define M3D_NOANIMATION
+
+#include <assimp/IOStreamBuffer.h>
+#include <memory>
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/Importer.hpp>
+#include <assimp/scene.h>
+#include <assimp/ai_assert.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/importerdesc.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_SupportBinaryFlavour,
+    0,
+    0,
+    0,
+    0,
+    "m3d a3d"
+};
+
+// workaround: the SDK expects a C callback, but we want to use Assimp::IOSystem to implement that
+extern "C" {
+    void* m3dimporter_pIOHandler;
+
+    unsigned char *m3dimporter_readfile(char *fn, unsigned int *size) {
+        ai_assert( nullptr != fn );
+        ai_assert( nullptr != size );
+        std::string file(fn);
+        std::unique_ptr<Assimp::IOStream> pStream(
+            (reinterpret_cast<Assimp::IOSystem*>(m3dimporter_pIOHandler))->Open( file, "rb"));
+        size_t fileSize = 0;
+        unsigned char *data = NULL;
+        // sometimes pStream is nullptr for some reason (should be an empty object returning nothing I guess)
+        if(pStream) {
+            fileSize = pStream->FileSize();
+            // should be allocated with malloc(), because the library will call free() to deallocate
+            data = (unsigned char*)malloc(fileSize);
+            if( !data || !pStream.get() || !fileSize || fileSize != pStream->Read(data,1,fileSize)) {
+                pStream.reset();
+                *size = 0;
+                // don't throw a deadly exception, it's not fatal if we can't read an external asset
+                return nullptr;
+            }
+            pStream.reset();
+        }
+        *size = (int)fileSize;
+        return data;
+    }
+}
+
+namespace Assimp {
+
+using namespace std;
+
+// ------------------------------------------------------------------------------------------------
+//  Default constructor
+M3DImporter::M3DImporter()
+: mScene(nullptr)
+, m3d(nullptr) { }
+
+// ------------------------------------------------------------------------------------------------
+//  Destructor.
+M3DImporter::~M3DImporter() {}
+
+// ------------------------------------------------------------------------------------------------
+//  Returns true, if file is a binary or ASCII Model 3D file.
+bool M3DImporter::CanRead(const std::string& pFile, IOSystem*  pIOHandler , bool checkSig) const {
+    const std::string extension = GetExtension(pFile);
+
+    if (extension == "m3d" || extension == "a3d")
+        return true;
+    else if (!extension.length() || checkSig)   {
+        if (!pIOHandler) {
+            return true;
+        }
+        /*
+         * don't use CheckMagicToken because that checks with swapped bytes too, leading to false
+         * positives. This magic is not uint32_t, but char[4], so memcmp is the best way
+
+        const char* tokens[] = {"3DMO", "3dmo"};
+        return CheckMagicToken(pIOHandler,pFile,tokens,2,0,4);
+        */
+        std::unique_ptr<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 */ || !memcmp(data, "3dmo", 4) /* ASCII */;
+    }
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiImporterDesc* M3DImporter::GetInfo() const {
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Model 3D import implementation
+void M3DImporter::InternReadFile( const std::string &file, aiScene* pScene, IOSystem* pIOHandler) {
+    // Read file into memory
+    std::unique_ptr<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::unique_ptr<unsigned char[]> _buffer (new unsigned char[fileSize]);
+    unsigned char *data( _buffer.get() );
+    if(fileSize != pStream->Read(data,1,fileSize)) {
+        throw DeadlyImportError( "Failed to read the file " + file + "." );
+    }
+
+    // Get the path for external assets
+    std::string  folderName( "./" );
+    std::string::size_type pos = file.find_last_of( "\\/" );
+    if ( pos != std::string::npos ) {
+        folderName = file.substr( 0, pos );
+        if ( !folderName.empty() ) {
+            pIOHandler->PushDirectory( folderName );
+        }
+    }
+    // pass this IOHandler to the C callback
+    m3dimporter_pIOHandler = pIOHandler;
+
+    //DefaultLogger::create("/dev/stderr", Logger::VERBOSE);
+    ASSIMP_LOG_DEBUG_F("M3D: loading ", file);
+
+    // let the C SDK do the hard work for us
+    m3d = m3d_load(&data[0], m3dimporter_readfile, free, nullptr);
+    m3dimporter_pIOHandler = nullptr;
+    if( !m3d ) {
+        throw DeadlyImportError( "Unable to parse " + file + " as M3D." );
+    }
+
+    // create the root node
+    pScene->mRootNode = new aiNode;
+    pScene->mRootNode->mName = aiString(std::string(std::string(m3d->name)));
+    pScene->mRootNode->mTransformation = aiMatrix4x4();
+    pScene->mRootNode->mNumChildren = 0;
+    mScene = pScene;
+
+    ASSIMP_LOG_DEBUG("M3D: root node " + std::string(m3d->name));
+
+    // now we just have to fill up the Assimp structures in pScene
+    importMaterials();
+    importTextures();
+    importBones(-1U, pScene->mRootNode);
+    importMeshes();
+    importAnimations();
+
+    // we don't need the SDK's version any more
+    m3d_free(m3d);
+
+    // Pop directory stack
+    if ( pIOHandler->StackSize() > 0 ) {
+        pIOHandler->PopDirectory();
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// convert materials. properties are converted using a static table in M3DMaterials.h
+void M3DImporter::importMaterials()
+{
+    unsigned int i, j, k, l, n;
+    m3dm_t *m;
+    aiString name = aiString(AI_DEFAULT_MATERIAL_NAME);
+    aiColor4D c;
+    ai_real f;
+
+    ai_assert(mScene != nullptr);
+    ai_assert(m3d != nullptr);
+
+    mScene->mNumMaterials = m3d->nummaterial + 1;
+    mScene->mMaterials = new aiMaterial*[ m3d->nummaterial + 1 ];
+
+    ASSIMP_LOG_DEBUG_F("M3D: importMaterials ", mScene->mNumMaterials);
+
+    // add a default material as first
+    aiMaterial* mat = new aiMaterial;
+    mat->AddProperty( &name, AI_MATKEY_NAME );
+    c.a = 1.0; c.b = c.g = c.r = 0.6;
+    mat->AddProperty( &c, 1, AI_MATKEY_COLOR_DIFFUSE);
+    mScene->mMaterials[0] = mat;
+
+    for(i = 0; i < m3d->nummaterial; i++) {
+        m = &m3d->material[i];
+        aiMaterial* mat = new aiMaterial;
+        name.Set(std::string(m->name));
+        mat->AddProperty( &name, AI_MATKEY_NAME );
+        for(j = 0; j < m->numprop; j++) {
+            // look up property type
+            // 0 - 127 scalar values,
+            // 128 - 255 the same properties but for texture maps
+            k = 256;
+            for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++)
+                if(m->prop[j].type == m3d_propertytypes[l].id ||
+                    m->prop[j].type == m3d_propertytypes[l].id + 128) {
+                        k = l;
+                        break;
+                    }
+            // should never happen, but be safe than sorry
+            if(k == 256) continue;
+
+            // scalar properties
+            if(m->prop[j].type < 128 && aiProps[k].pKey) {
+                switch(m3d_propertytypes[k].format) {
+                    case m3dpf_color:
+                        c = mkColor(m->prop[j].value.color);
+                        mat->AddProperty(&c, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index);
+                    break;
+                    case m3dpf_float:
+                        f = m->prop[j].value.fnum;
+                        mat->AddProperty(&f, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index);
+                    break;
+                    default:
+                        n = m->prop[j].value.num;
+                        if(m->prop[j].type == m3dp_il) {
+                            switch(n) {
+                                case 0:  n = aiShadingMode_NoShading; break;
+                                case 2:  n = aiShadingMode_Phong; break;
+                                default: n = aiShadingMode_Gouraud; break;
+                            }
+                        }
+                        mat->AddProperty(&n, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index);
+                    break;
+                }
+            }
+            // texture map properties
+            if(m->prop[j].type >= 128 && aiTxProps[k].pKey &&
+                // extra check, should never happen, do we have the refered texture?
+                m->prop[j].value.textureid < m3d->numtexture &&
+                m3d->texture[m->prop[j].value.textureid].name) {
+                    name.Set(std::string(std::string(m3d->texture[m->prop[j].value.textureid].name) + ".png"));
+                    mat->AddProperty(&name, aiTxProps[k].pKey, aiTxProps[k].type, aiTxProps[k].index);
+                    n = 0;
+                    mat->AddProperty(&n, 1, _AI_MATKEY_UVWSRC_BASE, aiProps[k].type, aiProps[k].index);
+            }
+        }
+        mScene->mMaterials[i + 1] = mat;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// import textures, this is the simplest of all
+void M3DImporter::importTextures()
+{
+    unsigned int i;
+    const char *formatHint[] = { "rgba0800", "rgba0808", "rgba8880", "rgba8888" };
+    m3dtx_t *t;
+
+    ai_assert(mScene != nullptr);
+    ai_assert(m3d != nullptr);
+
+    mScene->mNumTextures = m3d->numtexture;
+    ASSIMP_LOG_DEBUG_F("M3D: importTextures ", mScene->mNumTextures);
+
+    if(!m3d->numtexture)
+        return;
+
+    mScene->mTextures = new aiTexture*[m3d->numtexture];
+    for(i = 0; i < m3d->numtexture; i++) {
+        unsigned int j, k;
+        t = &m3d->texture[i];
+        if(!t->w || !t->h || !t->f || !t->d) continue;
+        aiTexture *tx = new aiTexture;
+        strcpy(tx->achFormatHint, formatHint[t->f - 1]);
+        tx->mFilename = aiString(std::string(t->name) + ".png");
+        tx->mWidth = t->w;
+        tx->mHeight = t->h;
+        tx->pcData = new aiTexel[ tx->mWidth*tx->mHeight ];
+        for(j = k = 0; j < tx->mWidth*tx->mHeight; j++) {
+            switch(t->f) {
+                case 1: tx->pcData[j].g = t->d[k++]; break;
+                case 2: tx->pcData[j].g = t->d[k++]; tx->pcData[j].a = t->d[k++]; break;
+                case 3:
+                    tx->pcData[j].r = t->d[k++]; tx->pcData[j].g = t->d[k++];
+                    tx->pcData[j].b = t->d[k++]; tx->pcData[j].a = 255;
+                break;
+                case 4:
+                    tx->pcData[j].r = t->d[k++]; tx->pcData[j].g = t->d[k++];
+                    tx->pcData[j].b = t->d[k++]; tx->pcData[j].a = t->d[k++];
+                break;
+            }
+        }
+        mScene->mTextures[i] = tx;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// this is tricky. M3D has a global vertex and UV list, and faces are indexing them
+// individually. In assimp there're per mesh vertex and UV lists, and they must be
+// indexed simultaneously.
+void M3DImporter::importMeshes()
+{
+    unsigned int i, j, k, l, numpoly = 3, lastMat = -2U;
+    std::vector<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 != nullptr);
+    ai_assert(mScene->mRootNode != nullptr);
+
+    ASSIMP_LOG_DEBUG_F("M3D: importMeshes ", m3d->numface);
+
+    for(i = 0; i < m3d->numface; i++) {
+        // we must switch mesh if material changes
+        if(lastMat != m3d->face[i].materialid) {
+            lastMat = m3d->face[i].materialid;
+            if(pMesh && vertices && vertices->size() && faces && faces->size()) {
+                populateMesh(pMesh, faces, vertices, normals, texcoords, colors, vertexids);
+                meshes->push_back(pMesh);
+                delete faces;
+                delete vertices;
+                delete normals;
+                delete texcoords;
+                delete colors;
+                delete vertexids;   // this is not stored in pMesh, just to collect bone vertices
+            }
+            pMesh = new aiMesh;
+            pMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+            pMesh->mMaterialIndex = lastMat + 1;
+            faces = new std::vector<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 = vertices->size();
+            pFace->mIndices[j] = k;
+            l = m3d->face[i].vertex[j];
+            pos.x = m3d->vertex[l].x;
+            pos.y = m3d->vertex[l].y;
+            pos.z = m3d->vertex[l].z;
+            vertices->push_back(pos);
+            colors->push_back(mkColor(m3d->vertex[l].color));
+            // add a bone to temporary vector
+            if(m3d->vertex[l].skinid != -1U &&m3d->vertex[l].skinid != -2U && m3d->skin && m3d->bone) {
+                // this is complicated, because M3D stores a list of bone id / weight pairs per
+                // vertex but assimp uses lists of local vertex id/weight pairs per local bone list
+                vertexids->push_back(l);
+            }
+            l = m3d->face[i].texcoord[j];
+            if(l != -1U) {
+                uv.x = m3d->tmap[l].u;
+                uv.y = m3d->tmap[l].v;
+                uv.z = 0.0;
+                texcoords->push_back(uv);
+            }
+            l = m3d->face[i].normal[j];
+            if(l != -1U) {
+                norm.x = m3d->vertex[l].x;
+                norm.y = m3d->vertex[l].y;
+                norm.z = m3d->vertex[l].z;
+                normals->push_back(norm);
+            }
+        }
+        faces->push_back(*pFace);
+        delete pFace;
+    }
+    // if there's data left in the temporary vectors, flush them
+    if(pMesh && vertices->size() && faces->size()) {
+        populateMesh(pMesh, faces, vertices, normals, texcoords, colors, vertexids);
+        meshes->push_back(pMesh);
+    }
+
+    // create global mesh list in scene
+    mScene->mNumMeshes = meshes->size();
+    mScene->mMeshes = new aiMesh*[mScene->mNumMeshes];
+    std::copy(meshes->begin(), meshes->end(), mScene->mMeshes);
+
+    // create mesh indeces in root node
+    mScene->mRootNode->mNumMeshes = meshes->size();
+    mScene->mRootNode->mMeshes = new unsigned int[meshes->size()];
+    for(i = 0; i < meshes->size(); i++) {
+        mScene->mRootNode->mMeshes[i] = i;
+    }
+
+    delete meshes;
+    if(faces)       delete faces;
+    if(vertices)    delete vertices;
+    if(normals)     delete normals;
+    if(texcoords)   delete texcoords;
+    if(colors)      delete colors;
+    if(vertexids)   delete vertexids;
+}
+
+// ------------------------------------------------------------------------------------------------
+// a reentrant node parser. Otherwise this is simple
+void M3DImporter::importBones(unsigned int parentid, aiNode *pParent)
+{
+    unsigned int i, n;
+
+    ai_assert(pParent != nullptr);
+    ai_assert(mScene != nullptr);
+    ai_assert(m3d != nullptr);
+
+    ASSIMP_LOG_DEBUG_F("M3D: importBones ", m3d->numbone, " parentid ", (int)parentid);
+
+    for(n = 0, i = parentid + 1; i < m3d->numbone; i++)
+        if(m3d->bone[i].parent == parentid) n++;
+    pParent->mChildren = new aiNode*[n];
+
+    for(i = parentid + 1; i < m3d->numbone; i++) {
+        if(m3d->bone[i].parent == parentid) {
+            aiNode *pChild = new aiNode;
+            pChild->mParent = pParent;
+            pChild->mName = aiString(std::string(m3d->bone[i].name));
+            convertPose(&pChild->mTransformation, m3d->bone[i].pos, m3d->bone[i].ori);
+            pChild->mNumChildren = 0;
+            pParent->mChildren[pParent->mNumChildren] = pChild;
+            pParent->mNumChildren++;
+            importBones(i, pChild);
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// this is another headache. M3D stores list of changed bone id/position/orientation triplets and
+// a timestamp per frame, but assimp needs timestamp and lists of position, orientation lists per
+// bone, so we have to convert between the two conceptually different representation forms
+void M3DImporter::importAnimations()
+{
+    unsigned int i, j, k, l, pos, ori;
+    double t;
+    m3da_t *a;
+
+    ai_assert(mScene != nullptr);
+    ai_assert(m3d != nullptr);
+
+    mScene->mNumAnimations = m3d->numaction;
+
+    ASSIMP_LOG_DEBUG_F("M3D: importAnimations ", mScene->mNumAnimations);
+
+    if(!m3d->numaction || !m3d->numbone)
+        return;
+
+    mScene->mAnimations = new aiAnimation*[m3d->numaction];
+    for(i = 0; i < m3d->numaction; i++) {
+        a = &m3d->action[i];
+        aiAnimation *pAnim = new aiAnimation;
+        pAnim->mName = aiString(std::string(a->name));
+        pAnim->mDuration = ((double)a->durationmsec) / 10;
+        pAnim->mTicksPerSecond = 100;
+        // now we know how many bones are referenced in this animation
+        pAnim->mNumChannels = m3d->numbone;
+        pAnim->mChannels = new aiNodeAnim*[pAnim->mNumChannels];
+        for(l = 0; l < m3d->numbone; l++) {
+            unsigned int n;
+            pAnim->mChannels[l] = new aiNodeAnim;
+            pAnim->mChannels[l]->mNodeName = aiString(std::string(m3d->bone[l].name));
+            // now n is the size of positions / orientations arrays
+            pAnim->mChannels[l]->mNumPositionKeys = pAnim->mChannels[l]->mNumRotationKeys = a->numframe;
+            pAnim->mChannels[l]->mPositionKeys = new aiVectorKey[a->numframe];
+            pAnim->mChannels[l]->mRotationKeys = new aiQuatKey[a->numframe];
+            pos = m3d->bone[l].pos;
+            ori = m3d->bone[l].ori;
+            for(j = n = 0; j < a->numframe; j++) {
+                t = ((double)a->frame[j].msec) / 10;
+                for(k = 0; k < a->frame[j].numtransform; k++) {
+                    if(a->frame[j].transform[k].boneid == l) {
+                        pos = a->frame[j].transform[k].pos;
+                        ori = a->frame[j].transform[k].ori;
+                    }
+                }
+                m3dv_t *v = &m3d->vertex[pos];
+                m3dv_t *q = &m3d->vertex[ori];
+                pAnim->mChannels[l]->mPositionKeys[j].mTime = t;
+                pAnim->mChannels[l]->mPositionKeys[j].mValue.x = v->x;
+                pAnim->mChannels[l]->mPositionKeys[j].mValue.y = v->y;
+                pAnim->mChannels[l]->mPositionKeys[j].mValue.z = v->z;
+                pAnim->mChannels[l]->mRotationKeys[j].mTime = t;
+                pAnim->mChannels[l]->mRotationKeys[j].mValue.w = q->w;
+                pAnim->mChannels[l]->mRotationKeys[j].mValue.x = q->x;
+                pAnim->mChannels[l]->mRotationKeys[j].mValue.y = q->y;
+                pAnim->mChannels[l]->mRotationKeys[j].mValue.z = q->z;
+            }// foreach frame
+        }// foreach bones
+        mScene->mAnimations[i] = pAnim;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// convert uint32_t into aiColor4D
+aiColor4D M3DImporter::mkColor(uint32_t c) {
+    aiColor4D color;
+    color.a = ((float)((c >> 24)&0xff)) / 255;
+    color.b = ((float)((c >> 16)&0xff)) / 255;
+    color.g = ((float)((c >>  8)&0xff)) / 255;
+    color.r = ((float)((c >>  0)&0xff)) / 255;
+    return color;
+}
+
+// ------------------------------------------------------------------------------------------------
+// convert a position id and orientation id into a 4 x 4 transformation matrix
+void M3DImporter::convertPose(aiMatrix4x4 *m, unsigned int posid, unsigned int orientid)
+{
+    ai_assert(m != nullptr);
+    ai_assert(m3d != nullptr);
+    ai_assert(posid != -1U && posid < m3d->numvertex);
+    ai_assert(orientid != -1U && orientid < m3d->numvertex);
+    m3dv_t *p = &m3d->vertex[posid];
+    m3dv_t *q = &m3d->vertex[orientid];
+
+    /* quaternion to matrix. Do NOT use aiQuaternion to aiMatrix3x3, gives bad results */
+    if(q->x == 0.0 && q->y == 0.0 && q->z >= 0.7071065 && q->z <= 0.7071075 && q->w == 0.0) {
+        m->a2 = m->a3 = m->b1 = m->b3 = m->c1 = m->c2 = 0.0;
+        m->a1 = m->b2 = m->c3 = -1.0;
+    } else {
+        m->a1 = 1 - 2 * (q->y * q->y + q->z * q->z); if(m->a1 > -M3D_EPSILON && m->a1 < M3D_EPSILON) m->a1 = 0.0;
+        m->a2 = 2 * (q->x * q->y - q->z * q->w);     if(m->a2 > -M3D_EPSILON && m->a2 < M3D_EPSILON) m->a2 = 0.0;
+        m->a3 = 2 * (q->x * q->z + q->y * q->w);     if(m->a3 > -M3D_EPSILON && m->a3 < M3D_EPSILON) m->a3 = 0.0;
+        m->b1 = 2 * (q->x * q->y + q->z * q->w);     if(m->b1 > -M3D_EPSILON && m->b1 < M3D_EPSILON) m->b1 = 0.0;
+        m->b2 = 1 - 2 * (q->x * q->x + q->z * q->z); if(m->b2 > -M3D_EPSILON && m->b2 < M3D_EPSILON) m->b2 = 0.0;
+        m->b3 = 2 * (q->y * q->z - q->x * q->w);     if(m->b3 > -M3D_EPSILON && m->b3 < M3D_EPSILON) m->b3 = 0.0;
+        m->c1 = 2 * (q->x * q->z - q->y * q->w);     if(m->c1 > -M3D_EPSILON && m->c1 < M3D_EPSILON) m->c1 = 0.0;
+        m->c2 = 2 * (q->y * q->z + q->x * q->w);     if(m->c2 > -M3D_EPSILON && m->c2 < M3D_EPSILON) m->c2 = 0.0;
+        m->c3 = 1 - 2 * (q->x * q->x + q->y * q->y); if(m->c3 > -M3D_EPSILON && m->c3 < M3D_EPSILON) m->c3 = 0.0;
+    }
+
+    /* set translation */
+    m->a4 = p->x; m->b4 = p->y; m->c4 = p->z;
+
+    m->d1 = 0; m->d2 = 0; m->d3 = 0; m->d4 = 1;
+}
+
+// ------------------------------------------------------------------------------------------------
+// find a node by name
+aiNode *M3DImporter::findNode(aiNode *pNode, aiString name)
+{
+    unsigned int i;
+
+    ai_assert(pNode != nullptr);
+    ai_assert(mScene != nullptr);
+
+    if(pNode->mName == name)
+        return pNode;
+    for(i = 0; i < pNode->mNumChildren; i++) {
+        aiNode *pChild = findNode(pNode->mChildren[i], name);
+        if(pChild) return pChild;
+    }
+    return nullptr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// fills up offsetmatrix in mBones
+void M3DImporter::calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m)
+{
+    ai_assert(pNode != nullptr);
+    ai_assert(mScene != nullptr);
+
+    if(pNode->mParent) {
+        calculateOffsetMatrix(pNode->mParent, m);
+        *m *= pNode->mTransformation;
+    } else {
+        *m = pNode->mTransformation;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// because M3D has a global mesh, global vertex ids and stores materialid on the face, we need
+// temporary lists to collect data for an aiMesh, which requires local arrays and local indeces
+// this function fills up an aiMesh with those temporary lists
+void M3DImporter::populateMesh(aiMesh *pMesh, std::vector<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 != nullptr);
+
+    ASSIMP_LOG_DEBUG_F("M3D: populateMesh numvertices ", vertices->size(), " numfaces ", faces->size(),
+        " numnormals ", normals->size(), " numtexcoord ", texcoords->size(), " numbones ", m3d->numbone);
+
+    if(vertices->size() && faces->size()) {
+        pMesh->mNumFaces = faces->size();
+        pMesh->mFaces = new aiFace[pMesh->mNumFaces];
+        std::copy(faces->begin(), faces->end(), pMesh->mFaces);
+        pMesh->mNumVertices = vertices->size();
+        pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
+        std::copy(vertices->begin(), vertices->end(), pMesh->mVertices);
+        if(normals->size() == vertices->size()) {
+            pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
+            std::copy(normals->begin(), normals->end(), pMesh->mNormals);
+        }
+        if(texcoords->size() == vertices->size()) {
+            pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices];
+            std::copy(texcoords->begin(), texcoords->end(), pMesh->mTextureCoords[0]);
+            pMesh->mNumUVComponents[0] = 2;
+        }
+        if(colors->size() == vertices->size()) {
+            pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices];
+            std::copy(colors->begin(), colors->end(), pMesh->mColors[0]);
+        }
+        // this is complicated, because M3D stores a list of bone id / weight pairs per
+        // vertex but assimp uses lists of local vertex id/weight pairs per local bone list
+        pMesh->mNumBones = m3d->numbone;
+        /* we need aiBone with mOffsetMatrix for bones without weights as well */
+        if(pMesh->mNumBones) {
+            pMesh->mBones = new aiBone*[pMesh->mNumBones];
+            for(unsigned int i = 0; i < m3d->numbone; i++) {
+                aiNode *pNode;
+                pMesh->mBones[i] = new aiBone;
+                pMesh->mBones[i]->mName = aiString(std::string(m3d->bone[i].name));
+                pMesh->mBones[i]->mNumWeights = 0;
+                pNode = findNode(mScene->mRootNode, pMesh->mBones[i]->mName);
+                if(pNode) {
+                    calculateOffsetMatrix(pNode, &pMesh->mBones[i]->mOffsetMatrix);
+                    pMesh->mBones[i]->mOffsetMatrix.Inverse();
+                } else
+                    pMesh->mBones[i]->mOffsetMatrix = aiMatrix4x4();
+            }
+            if(vertexids->size()) {
+                unsigned int i, j;
+                // first count how many vertices we have per bone
+                for(i = 0; i < vertexids->size(); i++) {
+                    unsigned int s = m3d->vertex[vertexids->at(i)].skinid;
+                    if(s != -1U && s!= -2U) {
+                        for(unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) {
+                                aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name));
+                                for(j = 0; j < pMesh->mNumBones; j++) {
+                                    if(pMesh->mBones[j]->mName == name) {
+                                        pMesh->mBones[j]->mNumWeights++;
+                                        break;
+                                    }
+                                }
+                        }
+                    }
+                }
+                // allocate mWeights
+                for(j = 0; j < pMesh->mNumBones; j++) {
+                    aiBone *pBone = pMesh->mBones[j];
+                    if(pBone->mNumWeights) {
+                        pBone->mWeights = new aiVertexWeight[pBone->mNumWeights];
+                        pBone->mNumWeights = 0;
+                    }
+                }
+                // fill up with data
+                for(i = 0; i < vertexids->size(); i++) {
+                    unsigned int s = m3d->vertex[vertexids->at(i)].skinid;
+                    if(s != -1U && s!= -2U) {
+                        for(unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) {
+                                aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name));
+                                for(j = 0; j < pMesh->mNumBones; j++) {
+                                    if(pMesh->mBones[j]->mName == name) {
+                                        aiBone *pBone = pMesh->mBones[j];
+                                        pBone->mWeights[pBone->mNumWeights].mVertexId = i;
+                                        pBone->mWeights[pBone->mNumWeights].mWeight = m3d->skin[s].weight[k];
+                                        pBone->mNumWeights++;
+                                        break;
+                                    }
+                                }
+                        } // foreach skin
+                    }
+                } // foreach vertexids
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+
+}   // Namespace Assimp
+
+#endif // !! ASSIMP_BUILD_NO_M3D_IMPORTER

+ 106 - 0
code/M3D/M3DImporter.h

@@ -0,0 +1,106 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+Copyright (c) 2019 bzt
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file M3DImporter.h
+*   @brief Declares the importer class to read a scene from a Model 3D file
+*/
+#ifndef AI_M3DIMPORTER_H_INC
+#define AI_M3DIMPORTER_H_INC
+
+#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
+
+#include "m3d.h"
+#include <assimp/BaseImporter.h>
+#include <assimp/material.h>
+#include <vector>
+
+struct aiMesh;
+struct aiNode;
+struct aiMaterial;
+struct aiFace;
+
+namespace Assimp {
+
+class M3DImporter : public BaseImporter {
+public:
+    /// \brief  Default constructor
+    M3DImporter();
+
+    /// \brief  Destructor
+    ~M3DImporter();
+
+public:
+    /// \brief  Returns whether the class can handle the format of the given file.
+    /// \remark See BaseImporter::CanRead() for details.
+    bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const;
+
+private:
+    aiScene* mScene; // the scene to import to
+    m3d_t *m3d; // model for the C library to convert from
+
+    //! \brief  Appends the supported extension.
+    const aiImporterDesc* GetInfo () const;
+
+    //! \brief  File import implementation.
+    void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler);
+
+    void importMaterials();
+    void importTextures();
+    void importMeshes();
+    void importBones(unsigned int parentid, aiNode *pParent);
+    void importAnimations();
+
+    // helper functions
+    aiColor4D mkColor(uint32_t c);
+    void convertPose(aiMatrix4x4 *m, unsigned int posid, unsigned int orientid);
+    aiNode *findNode(aiNode *pNode, aiString name);
+    void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m);
+    void populateMesh(aiMesh *pMesh, std::vector<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_AMBIENT(0) },                        /* m3dp_map_Ka */
+    { AI_MATKEY_TEXTURE_SPECULAR(0) },                       /* m3dp_map_Ks */
+    { AI_MATKEY_TEXTURE_SHININESS(0) },                      /* m3dp_map_Ns */
+    { AI_MATKEY_TEXTURE_EMISSIVE(0) },                       /* m3dp_map_Ke */
+    { NULL, 0, 0 },                                          /* m3dp_map_Tf */
+    { AI_MATKEY_TEXTURE_HEIGHT(0) },                         /* m3dp_bump */
+    { AI_MATKEY_TEXTURE_OPACITY(0) },                        /* m3dp_map_d */
+    { AI_MATKEY_TEXTURE_REFLECTION(0) },                     /* m3dp_refl */
+
+    { AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE_ROUGHNESS,0) },/* m3dp_map_Pr */
+    { AI_MATKEY_TEXTURE(aiTextureType_METALNESS,0) },        /* m3dp_map_Pm */
+    { NULL, 0, 0 },                                          /* m3dp_map_Ps */
+    { AI_MATKEY_TEXTURE(aiTextureType_AMBIENT_OCCLUSION,0) },/* m3dp_map_Ni */
+    { NULL, 0, 0 },                                          /* m3dp_map_Nt */
+    { NULL, 0, 0 },
+    { NULL, 0, 0 },
+    { NULL, 0, 0 }
+};
+
+#endif // AI_M3DMATERIALS_H_INC

+ 5568 - 0
code/M3D/m3d.h

@@ -0,0 +1,5568 @@
+/*
+ * m3d.h
+ *
+ * Copyright (C) 2019 bzt (bztsrc@gitlab)
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * @brief ANSI C89 / C++11 single header importer / exporter SDK for the Model 3D (.M3D) format
+ * https://gitlab.com/bztsrc/model3d
+ *
+ * PNG decompressor included from (with minor modifications to make it C89 valid):
+ *  stb_image - v2.13 - public domain image loader - http://nothings.org/stb_image.h
+ *
+ * @version: 1.0.0
+ */
+
+#ifndef _M3D_H_
+#define _M3D_H_
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+#include <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_INDEXMAX 0xfffffffe
+#else
+typedef uint16_t M3D_INDEX;
+#define M3D_INDEXMAX 0xfffe
+#endif
+#ifndef M3D_NUMBONE
+#define M3D_NUMBONE 4
+#endif
+#ifndef M3D_BONEMAXLEVEL
+#define M3D_BONEMAXLEVEL 8
+#endif
+#ifndef _MSC_VER
+#define _inline __inline__
+#define _pack __attribute__((packed))
+#define _unused __attribute__((unused))
+#else
+#define _inline
+#define _pack
+#define _unused
+#endif
+#ifndef  __cplusplus
+#define _register register
+#else
+#define _register
+#endif
+
+/*** File format structures ***/
+
+/**
+ * M3D file format structure
+ *  3DMO m3dchunk_t file header chunk, may followed by compressed data
+ *  HEAD m3dhdr_t model header chunk
+ *  n x m3dchunk_t more chunks follow
+ *      PRVW preview chunk (optional)
+ *      CMAP color map chunk (optional)
+ *      TMAP texture map chunk (optional)
+ *      VRTS vertex data chunk (optional if it's a material library)
+ *      BONE bind-pose skeleton, bone hierarchy chunk (optional)
+ *          n x m3db_t contains propably more, but at least one bone
+ *          n x m3ds_t skin group records
+ *      MTRL* material chunk(s), can be more (optional)
+ *          n x m3dp_t each material contains propapbly more, but at least one property
+ *                     the properties are configurable with a static array, see m3d_propertytypes
+ *      n x m3dchunk_t at least one, but maybe more face chunks
+ *          PROC* procedural face, or
+ *          MESH* triangle mesh (vertex index list) or
+ *          SHPE* mathematical shapes like parameterized surfaces
+ *      LBLS* annotation label chunks, can be more (optional)
+ *      ACTN* action chunk(s), animation-pose skeletons, can be more (optional)
+ *          n x m3dfr_t each action contains probably more, but at least one frame
+ *              n x m3dtr_t each frame contains probably more, but at least one transformation
+ *      ASET* inlined asset chunk(s), can be more (optional)
+ *  OMD3 end chunk
+ *
+ * Typical chunks for a game engine: 3DMO, HEAD, CMAP, TMAP, VRTS, BONE, MTRL, MESH, ACTN, OMD3
+ * Typical chunks for CAD software:  3DMO, HEAD, PRVW, CMAP, TMAP, VRTS, MTRL, SHPE, LBLS, OMD3
+ */
+#ifdef _MSC_VER
+#pragma pack(push)
+#pragma pack(1)
+#endif
+
+typedef struct {
+    char magic[4];
+    uint32_t length;
+    float scale; /* deliberately not M3D_FLOAT */
+    uint32_t types;
+} _pack m3dhdr_t;
+
+typedef struct {
+    char magic[4];
+    uint32_t length;
+} _pack m3dchunk_t;
+
+#ifdef _MSC_VER
+#pragma pack(pop)
+#endif
+
+/*** in-memory model structure ***/
+
+/* textmap entry */
+typedef struct {
+    M3D_FLOAT u;
+    M3D_FLOAT v;
+} m3dti_t;
+#define m3d_textureindex_t m3dti_t
+
+/* texture */
+typedef struct {
+    char *name;                 /* texture name */
+    uint8_t *d;                 /* pixels data */
+    uint16_t w;                 /* width */
+    uint16_t h;                 /* height */
+    uint8_t f;                  /* format, 1 = grayscale, 2 = grayscale+alpha, 3 = rgb, 4 = rgba */
+} m3dtx_t;
+#define m3d_texturedata_t m3dtx_t
+
+typedef struct {
+    M3D_INDEX vertexid;
+    M3D_FLOAT weight;
+} m3dw_t;
+#define m3d_weight_t m3dw_t
+
+/* bone entry */
+typedef struct {
+    M3D_INDEX parent;           /* parent bone index */
+    char *name;                 /* name for this bone */
+    M3D_INDEX pos;              /* vertex index position */
+    M3D_INDEX ori;              /* vertex index orientation (quaternion) */
+    M3D_INDEX numweight;        /* number of controlled vertices */
+    m3dw_t *weight;             /* weights for those vertices */
+    M3D_FLOAT mat4[16];         /* transformation matrix */
+} m3db_t;
+#define m3d_bone_t m3db_t
+
+/* skin: bone per vertex entry */
+typedef struct {
+    M3D_INDEX boneid[M3D_NUMBONE];
+    M3D_FLOAT weight[M3D_NUMBONE];
+} m3ds_t;
+#define m3d_skin_t m3ds_t
+
+/* vertex entry */
+typedef struct {
+    M3D_FLOAT x;                /* 3D coordinates and weight */
+    M3D_FLOAT y;
+    M3D_FLOAT z;
+    M3D_FLOAT w;
+    uint32_t color;             /* default vertex color */
+    M3D_INDEX skinid;           /* skin index */
+#ifdef M3D_VERTEXTYPE
+    uint8_t type;
+#endif
+} m3dv_t;
+#define m3d_vertex_t m3dv_t
+
+/* material property formats */
+enum {
+    m3dpf_color,
+    m3dpf_uint8,
+    m3dpf_uint16,
+    m3dpf_uint32,
+    m3dpf_float,
+    m3dpf_map
+};
+typedef struct {
+    uint8_t format;
+    uint8_t id;
+#ifdef M3D_ASCII
+#define M3D_PROPERTYDEF(f,i,n) { (f), (i), (char*)(n) }
+    char *key;
+#else
+#define M3D_PROPERTYDEF(f,i,n) { (f), (i) }
+#endif
+} m3dpd_t;
+
+/* material property types */
+/* You shouldn't change the first 8 display and first 4 physical property. Assign the rest as you like. */
+enum {
+    m3dp_Kd = 0,                /* scalar display properties */
+    m3dp_Ka,
+    m3dp_Ks,
+    m3dp_Ns,
+    m3dp_Ke,
+    m3dp_Tf,
+    m3dp_Km,
+    m3dp_d,
+    m3dp_il,
+
+    m3dp_Pr = 64,               /* scalar physical properties */
+    m3dp_Pm,
+    m3dp_Ps,
+    m3dp_Ni,
+    m3dp_Nt,
+
+    m3dp_map_Kd = 128,          /* textured display map properties */
+    m3dp_map_Ka,
+    m3dp_map_Ks,
+    m3dp_map_Ns,
+    m3dp_map_Ke,
+    m3dp_map_Tf,
+    m3dp_map_Km, /* bump map */
+    m3dp_map_D,
+    m3dp_map_il, /* reflection map */
+
+    m3dp_map_Pr = 192,          /* textured physical map properties */
+    m3dp_map_Pm,
+    m3dp_map_Ps,
+    m3dp_map_Ni,
+    m3dp_map_Nt
+};
+enum {                          /* aliases */
+    m3dp_bump = m3dp_map_Km,
+    m3dp_refl = m3dp_map_Pm
+};
+
+/* material property */
+typedef struct {
+    uint8_t type;               /* property type, see "m3dp_*" enumeration */
+    union {
+        uint32_t color;         /* if value is a color, m3dpf_color */
+        uint32_t num;           /* if value is a number, m3dpf_uint8, m3pf_uint16, m3dpf_uint32 */
+        float    fnum;          /* if value is a floating point number, m3dpf_float */
+        M3D_INDEX textureid;    /* if value is a texture, m3dpf_map */
+    } value;
+} m3dp_t;
+#define m3d_property_t m3dp_t
+
+/* material entry */
+typedef struct {
+    char *name;                 /* name of the material */
+    uint8_t numprop;            /* number of properties */
+    m3dp_t *prop;               /* properties array */
+} m3dm_t;
+#define m3d_material_t m3dm_t
+
+/* face entry */
+typedef struct {
+    M3D_INDEX materialid;       /* material index */
+    M3D_INDEX vertex[3];        /* 3D points of the triangle in CCW order */
+    M3D_INDEX normal[3];        /* normal vectors */
+    M3D_INDEX texcoord[3];      /* UV coordinates */
+} m3df_t;
+#define m3d_face_t m3df_t
+
+/* shape command types. must match the row in m3d_commandtypes */
+enum {
+    /* special commands */
+    m3dc_use = 0,               /* use material */
+    m3dc_inc,                   /* include another shape */
+    m3dc_mesh,                  /* include part of polygon mesh */
+    /* approximations */
+    m3dc_div,                   /* subdivision by constant resolution for both u, v */
+    m3dc_sub,                   /* subdivision by constant, different for u and v */
+    m3dc_len,                   /* spacial subdivision by maxlength */
+    m3dc_dist,                  /* subdivision by maxdistance and maxangle */
+    /* modifiers */
+    m3dc_degu,                  /* degree for both u, v */
+    m3dc_deg,                   /* separate degree for u and v */
+    m3dc_rangeu,                /* range for u */
+    m3dc_range,                 /* range for u and v */
+    m3dc_paru,                  /* u parameters (knots) */
+    m3dc_parv,                  /* v parameters */
+    m3dc_trim,                  /* outer trimming curve */
+    m3dc_hole,                  /* inner trimming curve */
+    m3dc_scrv,                  /* spacial curve */
+    m3dc_sp,                    /* special points */
+    /* helper curves */
+    m3dc_bez1,                  /* Bezier 1D */
+    m3dc_bsp1,                  /* B-spline 1D */
+    m3dc_bez2,                  /* bezier 2D */
+    m3dc_bsp2,                  /* B-spline 2D */
+    /* surfaces */
+    m3dc_bezun,                 /* Bezier 3D with control, UV, normal */
+    m3dc_bezu,                  /* with control and UV */
+    m3dc_bezn,                  /* with control and normal */
+    m3dc_bez,                   /* control points only */
+    m3dc_nurbsun,               /* B-spline 3D */
+    m3dc_nurbsu,
+    m3dc_nurbsn,
+    m3dc_nurbs,
+    m3dc_conn,                 /* connect surfaces */
+    /* geometrical */
+    m3dc_line,
+    m3dc_polygon,
+    m3dc_circle,
+    m3dc_cylinder,
+    m3dc_shpere,
+    m3dc_torus,
+    m3dc_cone,
+    m3dc_cube
+};
+
+/* shape command argument types */
+enum {
+    m3dcp_mi_t = 1,             /* material index */
+    m3dcp_hi_t,                 /* shape index */
+    m3dcp_fi_t,                 /* face index */
+    m3dcp_ti_t,                 /* texture map index */
+    m3dcp_vi_t,                 /* vertex index */
+    m3dcp_qi_t,                 /* vertex index for quaternions */
+    m3dcp_vc_t,                 /* coordinate or radius, float scalar */
+    m3dcp_i1_t,                 /* int8 scalar */
+    m3dcp_i2_t,                 /* int16 scalar */
+    m3dcp_i4_t,                 /* int32 scalar */
+    m3dcp_va_t                  /* variadic arguments */
+};
+
+#define M3D_CMDMAXARG 8         /* if you increase this, add more arguments to the macro below */
+typedef struct {
+#ifdef M3D_ASCII
+#define M3D_CMDDEF(t,n,p,a,b,c,d,e,f,g,h) { (char*)(n), (p), { (a), (b), (c), (d), (e), (f), (g), (h) } }
+    char *key;
+#else
+#define M3D_CMDDEF(t,n,p,a,b,c,d,e,f,g,h) { (p), { (a), (b), (c), (d), (e), (f), (g), (h) } }
+#endif
+    uint8_t p;
+    uint8_t a[M3D_CMDMAXARG];
+} m3dcd_t;
+
+/* shape command */
+typedef struct {
+    uint16_t type;              /* shape type */
+    uint32_t *arg;              /* arguments array */
+} m3dc_t;
+#define m3d_shapecommand_t m3dc_t
+
+/* shape entry */
+typedef struct {
+    char *name;                 /* name of the mathematical shape */
+    M3D_INDEX group;            /* group this shape belongs to or -1 */
+    uint32_t numcmd;            /* number of commands */
+    m3dc_t *cmd;                /* commands array */
+} m3dh_t;
+#define m3d_shape_t m3dh_t
+
+/* label entry */
+typedef struct {
+    char *name;                 /* name of the annotation group or NULL */
+    char *lang;                 /* language code or NULL */
+    char *text;                 /* the label text */
+    uint32_t color;             /* color */
+    M3D_INDEX vertexid;         /* the vertex the label refers to */
+} m3dl_t;
+#define m3d_label_t m3dl_t
+
+/* frame transformations / working copy skeleton entry */
+typedef struct {
+    M3D_INDEX boneid;           /* selects a node in bone hierarchy */
+    M3D_INDEX pos;              /* vertex index new position */
+    M3D_INDEX ori;              /* vertex index new orientation (quaternion) */
+} m3dtr_t;
+#define m3d_transform_t m3dtr_t
+
+/* animation frame entry */
+typedef struct {
+    uint32_t msec;              /* frame's position on the timeline, timestamp */
+    M3D_INDEX numtransform;     /* number of transformations in this frame */
+    m3dtr_t *transform;         /* transformations */
+} m3dfr_t;
+#define m3d_frame_t m3dfr_t
+
+/* model action entry */
+typedef struct {
+    char *name;                 /* name of the action */
+    uint32_t durationmsec;      /* duration in millisec (1/1000 sec) */
+    M3D_INDEX numframe;         /* number of frames in this animation */
+    m3dfr_t *frame;             /* frames array */
+} m3da_t;
+#define m3d_action_t m3da_t
+
+/* inlined asset */
+typedef struct {
+    char *name;                 /* asset name (same pointer as in texture[].name) */
+    uint8_t *data;              /* compressed asset data */
+    uint32_t length;            /* compressed data length */
+} m3di_t;
+#define m3d_inlinedasset_t m3di_t
+
+/*** in-memory model structure ***/
+#define M3D_FLG_FREERAW     (1<<0)
+#define M3D_FLG_FREESTR     (1<<1)
+#define M3D_FLG_MTLLIB      (1<<2)
+#define M3D_FLG_GENNORM     (1<<3)
+
+typedef struct {
+    m3dhdr_t *raw;              /* pointer to raw data */
+    char flags;                 /* internal flags */
+    char errcode;               /* returned error code */
+    char vc_s, vi_s, si_s, ci_s, ti_s, bi_s, nb_s, sk_s, fc_s, hi_s,fi_s;  /* decoded sizes for types */
+    char *name;                 /* name of the model, like "Utah teapot" */
+    char *license;              /* usage condition or license, like "MIT", "LGPL" or "BSD-3clause" */
+    char *author;               /* nickname, email, homepage or github URL etc. */
+    char *desc;                 /* comments, descriptions. May contain '\n' newline character */
+    M3D_FLOAT scale;            /* the model's bounding cube's size in SI meters */
+    M3D_INDEX numcmap;
+    uint32_t *cmap;             /* color map */
+    M3D_INDEX numtmap;
+    m3dti_t *tmap;              /* texture map indices */
+    M3D_INDEX numtexture;
+    m3dtx_t *texture;           /* uncompressed textures */
+    M3D_INDEX numbone;
+    m3db_t *bone;               /* bone hierarchy */
+    M3D_INDEX numvertex;
+    m3dv_t *vertex;             /* vertex data */
+    M3D_INDEX numskin;
+    m3ds_t *skin;               /* skin data */
+    M3D_INDEX nummaterial;
+    m3dm_t *material;           /* material list */
+    M3D_INDEX numface;
+    m3df_t *face;               /* model face, polygon (triangle) mesh */
+    M3D_INDEX numshape;
+    m3dh_t *shape;              /* model face, shape commands */
+    M3D_INDEX numlabel;
+    m3dl_t *label;              /* annotation labels */
+    M3D_INDEX numaction;
+    m3da_t *action;             /* action animations */
+    M3D_INDEX numinlined;
+    m3di_t *inlined;            /* inlined assets */
+    M3D_INDEX numextra;
+    m3dchunk_t **extra;         /* unknown chunks, application / engine specific data probably */
+    m3di_t preview;             /* preview chunk */
+} m3d_t;
+
+/*** export parameters ***/
+#define M3D_EXP_INT8        0
+#define M3D_EXP_INT16       1
+#define M3D_EXP_FLOAT       2
+#define M3D_EXP_DOUBLE      3
+
+#define M3D_EXP_NOCMAP      (1<<0)
+#define M3D_EXP_NOMATERIAL  (1<<1)
+#define M3D_EXP_NOFACE      (1<<2)
+#define M3D_EXP_NONORMAL    (1<<3)
+#define M3D_EXP_NOTXTCRD    (1<<4)
+#define M3D_EXP_FLIPTXTCRD  (1<<5)
+#define M3D_EXP_NORECALC    (1<<6)
+#define M3D_EXP_IDOSUCK     (1<<7)
+#define M3D_EXP_NOBONE      (1<<8)
+#define M3D_EXP_NOACTION    (1<<9)
+#define M3D_EXP_INLINE      (1<<10)
+#define M3D_EXP_EXTRA       (1<<11)
+#define M3D_EXP_NOZLIB      (1<<14)
+#define M3D_EXP_ASCII       (1<<15)
+
+/*** error codes ***/
+#define M3D_SUCCESS         0
+#define M3D_ERR_ALLOC       -1
+#define M3D_ERR_BADFILE     -2
+#define M3D_ERR_UNIMPL      -65
+#define M3D_ERR_UNKPROP     -66
+#define M3D_ERR_UNKMESH     -67
+#define M3D_ERR_UNKIMG      -68
+#define M3D_ERR_UNKFRAME    -69
+#define M3D_ERR_UNKCMD      -70
+#define M3D_ERR_TRUNC       -71
+#define M3D_ERR_CMAP        -72
+#define M3D_ERR_TMAP        -73
+#define M3D_ERR_VRTS        -74
+#define M3D_ERR_BONE        -75
+#define M3D_ERR_MTRL        -76
+#define M3D_ERR_SHPE        -77
+
+#define M3D_ERR_ISFATAL(x)  ((x) < 0 && (x) > -65)
+
+/* callbacks */
+typedef unsigned char *(*m3dread_t)(char *filename, unsigned int *size);                        /* read file contents into buffer */
+typedef void (*m3dfree_t)(void *buffer);                                                        /* free file contents buffer */
+typedef int (*m3dtxsc_t)(const char *name, const void *script, uint32_t len, m3dtx_t *output);  /* interpret texture script */
+typedef int (*m3dprsc_t)(const char *name, const void *script, uint32_t len, m3d_t *model);     /* interpret surface script */
+#endif /* ifndef M3D_APIVERSION */
+
+/*** C prototypes ***/
+/* import / export */
+m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d_t *mtllib);
+unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size);
+void m3d_free(m3d_t *model);
+/* generate animation pose skeleton */
+m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t *skeleton);
+m3db_t *m3d_pose(m3d_t *model, M3D_INDEX actionid, uint32_t msec);
+
+/* private prototypes used by both importer and exporter */
+char *_m3d_safestr(char *in, int morelines);
+
+/*** C implementation ***/
+#ifdef M3D_IMPLEMENTATION
+#if !defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER)
+/* material property definitions */
+static m3dpd_t m3d_propertytypes[] = {
+    M3D_PROPERTYDEF(m3dpf_color, m3dp_Kd, "Kd"),    /* diffuse color */
+    M3D_PROPERTYDEF(m3dpf_color, m3dp_Ka, "Ka"),    /* ambient color */
+    M3D_PROPERTYDEF(m3dpf_color, m3dp_Ks, "Ks"),    /* specular color */
+    M3D_PROPERTYDEF(m3dpf_float, m3dp_Ns, "Ns"),    /* specular exponent */
+    M3D_PROPERTYDEF(m3dpf_color, m3dp_Ke, "Ke"),    /* emissive (emitting light of this color) */
+    M3D_PROPERTYDEF(m3dpf_color, m3dp_Tf, "Tf"),    /* transmission color */
+    M3D_PROPERTYDEF(m3dpf_float, m3dp_Km, "Km"),    /* bump strength */
+    M3D_PROPERTYDEF(m3dpf_float, m3dp_d,  "d"),     /* dissolve (transparency) */
+    M3D_PROPERTYDEF(m3dpf_uint8, m3dp_il, "il"),    /* illumination model (informational, ignored by PBR-shaders) */
+
+    M3D_PROPERTYDEF(m3dpf_float, m3dp_Pr, "Pr"),    /* roughness */
+    M3D_PROPERTYDEF(m3dpf_float, m3dp_Pm, "Pm"),    /* metallic, also reflection */
+    M3D_PROPERTYDEF(m3dpf_float, m3dp_Ps, "Ps"),    /* sheen */
+    M3D_PROPERTYDEF(m3dpf_float, m3dp_Ni, "Ni"),    /* index of refraction (optical density) */
+    M3D_PROPERTYDEF(m3dpf_float, m3dp_Nt, "Nt"),    /* thickness of face in millimeter, for printing */
+
+    /* aliases, note that "map_*" aliases are handled automatically */
+    M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Km, "bump"),
+    M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Pm, "refl")
+};
+/* shape command definitions. if more commands start with the same string, the longer must come first */
+static m3dcd_t m3d_commandtypes[] = {
+    /* technical */
+    M3D_CMDDEF(m3dc_use,     "use",     1, m3dcp_mi_t, 0, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_inc,     "inc",     3, m3dcp_hi_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vi_t, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_mesh,    "mesh",    1, m3dcp_fi_t, m3dcp_fi_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vi_t, 0, 0, 0),
+    /* approximations */
+    M3D_CMDDEF(m3dc_div,     "div",     1, m3dcp_vc_t, 0, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_sub,     "sub",     2, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_len,     "len",     1, m3dcp_vc_t, 0, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_dist,    "dist",    2, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0),
+    /* modifiers */
+    M3D_CMDDEF(m3dc_degu,    "degu",    1, m3dcp_i1_t, 0, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_deg,     "deg",     2, m3dcp_i1_t, m3dcp_i1_t, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_rangeu,  "rangeu",  1, m3dcp_ti_t, 0, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_range,   "range",   2, m3dcp_ti_t, m3dcp_ti_t, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_paru,    "paru",    2, m3dcp_va_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_parv,    "parv",    2, m3dcp_va_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_trim,    "trim",    3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_hole,    "hole",    3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_scrv,    "scrv",    3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_sp,      "sp",      2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+    /* helper curves */
+    M3D_CMDDEF(m3dc_bez1,    "bez1",    2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_bsp1,    "bsp1",    2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_bez2,    "bez2",    2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_bsp2,    "bsp2",    2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+    /* surfaces */
+    M3D_CMDDEF(m3dc_bezun,   "bezun",   4, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, m3dcp_vi_t, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_bezu,    "bezu",    3, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_bezn,    "bezn",    3, m3dcp_va_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_bez,     "bez",     2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_nurbsun, "nurbsun", 4, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, m3dcp_vi_t, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_nurbsu,  "nurbsu",  3, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_nurbsn,  "nurbsn",  3, m3dcp_va_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_nurbs,   "nurbs",   2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_conn,    "conn",    6, m3dcp_i2_t, m3dcp_ti_t, m3dcp_i2_t, m3dcp_i2_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0),
+    /* geometrical */
+    M3D_CMDDEF(m3dc_line,    "line",    2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_polygon, "polygon", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_circle,  "circle",  3, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_cylinder,"cylinder",6, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, 0, 0),
+    M3D_CMDDEF(m3dc_shpere,  "shpere",  2, m3dcp_vi_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_torus,   "torus",   4, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_cone,    "cone",    3, m3dcp_vi_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0),
+    M3D_CMDDEF(m3dc_cube,    "cube",    3, m3dcp_vi_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0)
+};
+#endif
+
+#include <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) {
+            _m3dstbi__init_zdefaults();
+            if (!_m3dstbi__zbuild_huffman(&a->z_length  , _m3dstbi__zdefault_length  , 288)) return 0;
+            if (!_m3dstbi__zbuild_huffman(&a->z_distance, _m3dstbi__zdefault_distance,  32)) return 0;
+         } else {
+            if (!_m3dstbi__compute_huffman_codes(a)) return 0;
+         }
+         if (!_m3dstbi__parse_huffman_block(a)) return 0;
+      }
+   } while (!final);
+   return 1;
+}
+
+static int _m3dstbi__do_zlib(_m3dstbi__zbuf *a, char *obuf, int olen, int exp, int parse_header)
+{
+   a->zout_start = obuf;
+   a->zout       = obuf;
+   a->zout_end   = obuf + olen;
+   a->z_expandable = exp;
+
+   return _m3dstbi__parse_zlib(a, parse_header);
+}
+
+char *_m3dstbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header)
+{
+   _m3dstbi__zbuf a;
+   char *p = (char *) _m3dstbi__malloc(initial_size);
+   if (p == NULL) return NULL;
+   a.zbuffer = (unsigned char *) buffer;
+   a.zbuffer_end = (unsigned char *) buffer + len;
+   if (_m3dstbi__do_zlib(&a, p, initial_size, 1, parse_header)) {
+      if (outlen) *outlen = (int) (a.zout - a.zout_start);
+      return a.zout_start;
+   } else {
+      STBI_FREE(a.zout_start);
+      return NULL;
+   }
+}
+
+typedef struct
+{
+   _m3dstbi__uint32 length;
+   _m3dstbi__uint32 type;
+} _m3dstbi__pngchunk;
+
+static _m3dstbi__pngchunk _m3dstbi__get_chunk_header(_m3dstbi__context *s)
+{
+   _m3dstbi__pngchunk c;
+   c.length = _m3dstbi__get32be(s);
+   c.type   = _m3dstbi__get32be(s);
+   return c;
+}
+
+_inline static int _m3dstbi__check_png_header(_m3dstbi__context *s)
+{
+   static unsigned char png_sig[8] = { 137,80,78,71,13,10,26,10 };
+   int i;
+   for (i=0; i < 8; ++i)
+      if (_m3dstbi__get8(s) != png_sig[i]) return _m3dstbi__err("bad png sig","Not a PNG");
+   return 1;
+}
+
+typedef struct
+{
+   _m3dstbi__context *s;
+   unsigned char *idata, *expanded, *out;
+   int depth;
+} _m3dstbi__png;
+
+
+enum {
+   STBI__F_none=0,
+   STBI__F_sub=1,
+   STBI__F_up=2,
+   STBI__F_avg=3,
+   STBI__F_paeth=4,
+   STBI__F_avg_first,
+   STBI__F_paeth_first
+};
+
+static unsigned char first_row_filter[5] =
+{
+   STBI__F_none,
+   STBI__F_sub,
+   STBI__F_none,
+   STBI__F_avg_first,
+   STBI__F_paeth_first
+};
+
+static int _m3dstbi__paeth(int a, int b, int c)
+{
+   int p = a + b - c;
+   int pa = abs(p-a);
+   int pb = abs(p-b);
+   int pc = abs(p-c);
+   if (pa <= pb && pa <= pc) return a;
+   if (pb <= pc) return b;
+   return c;
+}
+
+static unsigned char _m3dstbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };
+
+static int _m3dstbi__create_png_image_raw(_m3dstbi__png *a, unsigned char *raw, _m3dstbi__uint32 raw_len, int out_n, _m3dstbi__uint32 x, _m3dstbi__uint32 y, int depth, int color)
+{
+   int bytes = (depth == 16? 2 : 1);
+   _m3dstbi__context *s = a->s;
+   _m3dstbi__uint32 i,j,stride = x*out_n*bytes;
+   _m3dstbi__uint32 img_len, img_width_bytes;
+   int k;
+   int img_n = s->img_n;
+
+   int output_bytes = out_n*bytes;
+   int filter_bytes = img_n*bytes;
+   int width = x;
+
+   STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1);
+   a->out = (unsigned char *) _m3dstbi__malloc_mad3(x, y, output_bytes, 0);
+   if (!a->out) return _m3dstbi__err("outofmem", "Out of memory");
+
+   if (!_m3dstbi__mad3sizes_valid(img_n, x, depth, 7)) return _m3dstbi__err("too large", "Corrupt PNG");
+   img_width_bytes = (((img_n * x * depth) + 7) >> 3);
+   img_len = (img_width_bytes + 1) * y;
+   if (s->img_x == x && s->img_y == y) {
+      if (raw_len != img_len) return _m3dstbi__err("not enough pixels","Corrupt PNG");
+   } else {
+      if (raw_len < img_len) return _m3dstbi__err("not enough pixels","Corrupt PNG");
+   }
+
+   for (j=0; j < y; ++j) {
+      unsigned char *cur = a->out + stride*j;
+      unsigned char *prior = cur - stride;
+      int filter = *raw++;
+
+      if (filter > 4)
+         return _m3dstbi__err("invalid filter","Corrupt PNG");
+
+      if (depth < 8) {
+         STBI_ASSERT(img_width_bytes <= x);
+         cur += x*out_n - img_width_bytes;
+         filter_bytes = 1;
+         width = img_width_bytes;
+      }
+      prior = cur - stride;
+
+      if (j == 0) filter = first_row_filter[filter];
+
+      for (k=0; k < filter_bytes; ++k) {
+         switch (filter) {
+            case STBI__F_none       : cur[k] = raw[k]; break;
+            case STBI__F_sub        : cur[k] = raw[k]; break;
+            case STBI__F_up         : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
+            case STBI__F_avg        : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break;
+            case STBI__F_paeth      : cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(0,prior[k],0)); break;
+            case STBI__F_avg_first  : cur[k] = raw[k]; break;
+            case STBI__F_paeth_first: cur[k] = raw[k]; break;
+         }
+      }
+
+      if (depth == 8) {
+         if (img_n != out_n)
+            cur[img_n] = 255;
+         raw += img_n;
+         cur += out_n;
+         prior += out_n;
+      } else if (depth == 16) {
+         if (img_n != out_n) {
+            cur[filter_bytes]   = 255;
+            cur[filter_bytes+1] = 255;
+         }
+         raw += filter_bytes;
+         cur += output_bytes;
+         prior += output_bytes;
+      } else {
+         raw += 1;
+         cur += 1;
+         prior += 1;
+      }
+
+      if (depth < 8 || img_n == out_n) {
+         int nk = (width - 1)*filter_bytes;
+         #define STBI__CASE(f) \
+             case f:     \
+                for (k=0; k < nk; ++k)
+         switch (filter) {
+            case STBI__F_none:         memcpy(cur, raw, nk); break;
+            STBI__CASE(STBI__F_sub)          { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break;
+            STBI__CASE(STBI__F_up)           { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
+            STBI__CASE(STBI__F_avg)          { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break;
+            STBI__CASE(STBI__F_paeth)        { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break;
+            STBI__CASE(STBI__F_avg_first)    { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break;
+            STBI__CASE(STBI__F_paeth_first)  { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k-filter_bytes],0,0)); } break;
+         }
+         #undef STBI__CASE
+         raw += nk;
+      } else {
+         STBI_ASSERT(img_n+1 == out_n);
+         #define STBI__CASE(f) \
+             case f:     \
+                for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \
+                   for (k=0; k < filter_bytes; ++k)
+         switch (filter) {
+            STBI__CASE(STBI__F_none)         { cur[k] = raw[k]; } break;
+            STBI__CASE(STBI__F_sub)          { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break;
+            STBI__CASE(STBI__F_up)           { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
+            STBI__CASE(STBI__F_avg)          { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break;
+            STBI__CASE(STBI__F_paeth)        { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break;
+            STBI__CASE(STBI__F_avg_first)    { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break;
+            STBI__CASE(STBI__F_paeth_first)  { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k- output_bytes],0,0)); } break;
+         }
+         #undef STBI__CASE
+
+         if (depth == 16) {
+            cur = a->out + stride*j;
+            for (i=0; i < x; ++i,cur+=output_bytes) {
+               cur[filter_bytes+1] = 255;
+            }
+         }
+      }
+   }
+
+   if (depth < 8) {
+      for (j=0; j < y; ++j) {
+         unsigned char *cur = a->out + stride*j;
+         unsigned char *in  = a->out + stride*j + x*out_n - img_width_bytes;
+         unsigned char scale = (color == 0) ? _m3dstbi__depth_scale_table[depth] : 1;
+
+         if (depth == 4) {
+            for (k=x*img_n; k >= 2; k-=2, ++in) {
+               *cur++ = scale * ((*in >> 4)       );
+               *cur++ = scale * ((*in     ) & 0x0f);
+            }
+            if (k > 0) *cur++ = scale * ((*in >> 4)       );
+         } else if (depth == 2) {
+            for (k=x*img_n; k >= 4; k-=4, ++in) {
+               *cur++ = scale * ((*in >> 6)       );
+               *cur++ = scale * ((*in >> 4) & 0x03);
+               *cur++ = scale * ((*in >> 2) & 0x03);
+               *cur++ = scale * ((*in     ) & 0x03);
+            }
+            if (k > 0) *cur++ = scale * ((*in >> 6)       );
+            if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03);
+            if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03);
+         } else if (depth == 1) {
+            for (k=x*img_n; k >= 8; k-=8, ++in) {
+               *cur++ = scale * ((*in >> 7)       );
+               *cur++ = scale * ((*in >> 6) & 0x01);
+               *cur++ = scale * ((*in >> 5) & 0x01);
+               *cur++ = scale * ((*in >> 4) & 0x01);
+               *cur++ = scale * ((*in >> 3) & 0x01);
+               *cur++ = scale * ((*in >> 2) & 0x01);
+               *cur++ = scale * ((*in >> 1) & 0x01);
+               *cur++ = scale * ((*in     ) & 0x01);
+            }
+            if (k > 0) *cur++ = scale * ((*in >> 7)       );
+            if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01);
+            if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01);
+            if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01);
+            if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01);
+            if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01);
+            if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);
+         }
+         if (img_n != out_n) {
+            int q;
+            cur = a->out + stride*j;
+            if (img_n == 1) {
+               for (q=x-1; q >= 0; --q) {
+                  cur[q*2+1] = 255;
+                  cur[q*2+0] = cur[q];
+               }
+            } else {
+               STBI_ASSERT(img_n == 3);
+               for (q=x-1; q >= 0; --q) {
+                  cur[q*4+3] = 255;
+                  cur[q*4+2] = cur[q*3+2];
+                  cur[q*4+1] = cur[q*3+1];
+                  cur[q*4+0] = cur[q*3+0];
+               }
+            }
+         }
+      }
+   } else if (depth == 16) {
+      unsigned char *cur = a->out;
+      _m3dstbi__uint16 *cur16 = (_m3dstbi__uint16*)cur;
+
+      for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) {
+         *cur16 = (cur[0] << 8) | cur[1];
+      }
+   }
+
+   return 1;
+}
+
+static int _m3dstbi__create_png_image(_m3dstbi__png *a, unsigned char *image_data, _m3dstbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced)
+{
+   int bytes = (depth == 16 ? 2 : 1);
+   int out_bytes = out_n * bytes;
+   unsigned char *final;
+   int p;
+   if (!interlaced)
+      return _m3dstbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color);
+
+   final = (unsigned char *) _m3dstbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0);
+   for (p=0; p < 7; ++p) {
+      int xorig[] = { 0,4,0,2,0,1,0 };
+      int yorig[] = { 0,0,4,0,2,0,1 };
+      int xspc[]  = { 8,8,4,4,2,2,1 };
+      int yspc[]  = { 8,8,8,4,4,2,2 };
+      int i,j,x,y;
+      x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p];
+      y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p];
+      if (x && y) {
+         _m3dstbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y;
+         if (!_m3dstbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) {
+            STBI_FREE(final);
+            return 0;
+         }
+         for (j=0; j < y; ++j) {
+            for (i=0; i < x; ++i) {
+               int out_y = j*yspc[p]+yorig[p];
+               int out_x = i*xspc[p]+xorig[p];
+               memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes,
+                      a->out + (j*x+i)*out_bytes, out_bytes);
+            }
+         }
+         STBI_FREE(a->out);
+         image_data += img_len;
+         image_data_len -= img_len;
+      }
+   }
+   a->out = final;
+
+   return 1;
+}
+
+static int _m3dstbi__compute_transparency(_m3dstbi__png *z, unsigned char tc[3], int out_n)
+{
+   _m3dstbi__context *s = z->s;
+   _m3dstbi__uint32 i, pixel_count = s->img_x * s->img_y;
+   unsigned char *p = z->out;
+
+   STBI_ASSERT(out_n == 2 || out_n == 4);
+
+   if (out_n == 2) {
+      for (i=0; i < pixel_count; ++i) {
+         p[1] = (p[0] == tc[0] ? 0 : 255);
+         p += 2;
+      }
+   } else {
+      for (i=0; i < pixel_count; ++i) {
+         if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
+            p[3] = 0;
+         p += 4;
+      }
+   }
+   return 1;
+}
+
+static int _m3dstbi__compute_transparency16(_m3dstbi__png *z, _m3dstbi__uint16 tc[3], int out_n)
+{
+   _m3dstbi__context *s = z->s;
+   _m3dstbi__uint32 i, pixel_count = s->img_x * s->img_y;
+   _m3dstbi__uint16 *p = (_m3dstbi__uint16*) z->out;
+
+   STBI_ASSERT(out_n == 2 || out_n == 4);
+
+   if (out_n == 2) {
+      for (i = 0; i < pixel_count; ++i) {
+         p[1] = (p[0] == tc[0] ? 0 : 65535);
+         p += 2;
+      }
+   } else {
+      for (i = 0; i < pixel_count; ++i) {
+         if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
+            p[3] = 0;
+         p += 4;
+      }
+   }
+   return 1;
+}
+
+static int _m3dstbi__expand_png_palette(_m3dstbi__png *a, unsigned char *palette, int len, int pal_img_n)
+{
+   _m3dstbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y;
+   unsigned char *p, *temp_out, *orig = a->out;
+
+   p = (unsigned char *) _m3dstbi__malloc_mad2(pixel_count, pal_img_n, 0);
+   if (p == NULL) return _m3dstbi__err("outofmem", "Out of memory");
+
+   temp_out = p;
+
+   if (pal_img_n == 3) {
+      for (i=0; i < pixel_count; ++i) {
+         int n = orig[i]*4;
+         p[0] = palette[n  ];
+         p[1] = palette[n+1];
+         p[2] = palette[n+2];
+         p += 3;
+      }
+   } else {
+      for (i=0; i < pixel_count; ++i) {
+         int n = orig[i]*4;
+         p[0] = palette[n  ];
+         p[1] = palette[n+1];
+         p[2] = palette[n+2];
+         p[3] = palette[n+3];
+         p += 4;
+      }
+   }
+   STBI_FREE(a->out);
+   a->out = temp_out;
+
+   STBI_NOTUSED(len);
+
+   return 1;
+}
+
+#define STBI__PNG_TYPE(a,b,c,d)  (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d))
+
+static int _m3dstbi__parse_png_file(_m3dstbi__png *z, int scan, int req_comp)
+{
+   unsigned char palette[1024], pal_img_n=0;
+   unsigned char has_trans=0, tc[3];
+   _m3dstbi__uint16 tc16[3];
+   _m3dstbi__uint32 ioff=0, idata_limit=0, i, pal_len=0;
+   int first=1,k,interlace=0, color=0;
+   _m3dstbi__context *s = z->s;
+
+   z->expanded = NULL;
+   z->idata = NULL;
+   z->out = NULL;
+
+   if (!_m3dstbi__check_png_header(s)) return 0;
+
+   if (scan == STBI__SCAN_type) return 1;
+
+   for (;;) {
+      _m3dstbi__pngchunk c = _m3dstbi__get_chunk_header(s);
+      switch (c.type) {
+         case STBI__PNG_TYPE('C','g','B','I'):
+            _m3dstbi__skip(s, c.length);
+            break;
+         case STBI__PNG_TYPE('I','H','D','R'): {
+            int comp,filter;
+            if (!first) return _m3dstbi__err("multiple IHDR","Corrupt PNG");
+            first = 0;
+            if (c.length != 13) return _m3dstbi__err("bad IHDR len","Corrupt PNG");
+            s->img_x = _m3dstbi__get32be(s); if (s->img_x > (1 << 24)) return _m3dstbi__err("too large","Very large image (corrupt?)");
+            s->img_y = _m3dstbi__get32be(s); if (s->img_y > (1 << 24)) return _m3dstbi__err("too large","Very large image (corrupt?)");
+            z->depth = _m3dstbi__get8(s);  if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16)  return _m3dstbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only");
+            color = _m3dstbi__get8(s);  if (color > 6)         return _m3dstbi__err("bad ctype","Corrupt PNG");
+            if (color == 3 && z->depth == 16)                  return _m3dstbi__err("bad ctype","Corrupt PNG");
+            if (color == 3) pal_img_n = 3; else if (color & 1) return _m3dstbi__err("bad ctype","Corrupt PNG");
+            comp  = _m3dstbi__get8(s);  if (comp) return _m3dstbi__err("bad comp method","Corrupt PNG");
+            filter= _m3dstbi__get8(s);  if (filter) return _m3dstbi__err("bad filter method","Corrupt PNG");
+            interlace = _m3dstbi__get8(s); if (interlace>1) return _m3dstbi__err("bad interlace method","Corrupt PNG");
+            if (!s->img_x || !s->img_y) return _m3dstbi__err("0-pixel image","Corrupt PNG");
+            if (!pal_img_n) {
+               s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0);
+               if ((1 << 30) / s->img_x / s->img_n < s->img_y) return _m3dstbi__err("too large", "Image too large to decode");
+               if (scan == STBI__SCAN_header) return 1;
+            } else {
+               s->img_n = 1;
+               if ((1 << 30) / s->img_x / 4 < s->img_y) return _m3dstbi__err("too large","Corrupt PNG");
+            }
+            break;
+         }
+
+         case STBI__PNG_TYPE('P','L','T','E'):  {
+            if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG");
+            if (c.length > 256*3) return _m3dstbi__err("invalid PLTE","Corrupt PNG");
+            pal_len = c.length / 3;
+            if (pal_len * 3 != c.length) return _m3dstbi__err("invalid PLTE","Corrupt PNG");
+            for (i=0; i < pal_len; ++i) {
+               palette[i*4+0] = _m3dstbi__get8(s);
+               palette[i*4+1] = _m3dstbi__get8(s);
+               palette[i*4+2] = _m3dstbi__get8(s);
+               palette[i*4+3] = 255;
+            }
+            break;
+         }
+
+         case STBI__PNG_TYPE('t','R','N','S'): {
+            if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG");
+            if (z->idata) return _m3dstbi__err("tRNS after IDAT","Corrupt PNG");
+            if (pal_img_n) {
+               if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; }
+               if (pal_len == 0) return _m3dstbi__err("tRNS before PLTE","Corrupt PNG");
+               if (c.length > pal_len) return _m3dstbi__err("bad tRNS len","Corrupt PNG");
+               pal_img_n = 4;
+               for (i=0; i < c.length; ++i)
+                  palette[i*4+3] = _m3dstbi__get8(s);
+            } else {
+               if (!(s->img_n & 1)) return _m3dstbi__err("tRNS with alpha","Corrupt PNG");
+               if (c.length != (_m3dstbi__uint32) s->img_n*2) return _m3dstbi__err("bad tRNS len","Corrupt PNG");
+               has_trans = 1;
+               if (z->depth == 16) {
+                  for (k = 0; k < s->img_n; ++k) tc16[k] = (_m3dstbi__uint16)_m3dstbi__get16be(s);
+               } else {
+                  for (k = 0; k < s->img_n; ++k) tc[k] = (unsigned char)(_m3dstbi__get16be(s) & 255) * _m3dstbi__depth_scale_table[z->depth];
+               }
+            }
+            break;
+         }
+
+         case STBI__PNG_TYPE('I','D','A','T'): {
+            if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG");
+            if (pal_img_n && !pal_len) return _m3dstbi__err("no PLTE","Corrupt PNG");
+            if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; }
+            if ((int)(ioff + c.length) < (int)ioff) return 0;
+            if (ioff + c.length > idata_limit) {
+               _m3dstbi__uint32 idata_limit_old = idata_limit;
+               unsigned char *p;
+               if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096;
+               while (ioff + c.length > idata_limit)
+                  idata_limit *= 2;
+               STBI_NOTUSED(idata_limit_old);
+               p = (unsigned char *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return _m3dstbi__err("outofmem", "Out of memory");
+               z->idata = p;
+            }
+            if (!_m3dstbi__getn(s, z->idata+ioff,c.length)) return _m3dstbi__err("outofdata","Corrupt PNG");
+            ioff += c.length;
+            break;
+         }
+
+         case STBI__PNG_TYPE('I','E','N','D'): {
+            _m3dstbi__uint32 raw_len, bpl;
+            if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG");
+            if (scan != STBI__SCAN_load) return 1;
+            if (z->idata == NULL) return _m3dstbi__err("no IDAT","Corrupt PNG");
+            bpl = (s->img_x * z->depth + 7) / 8;
+            raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */;
+            z->expanded = (unsigned char *) _m3dstbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, 1);
+            if (z->expanded == NULL) return 0;
+            STBI_FREE(z->idata); z->idata = NULL;
+            if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans)
+               s->img_out_n = s->img_n+1;
+            else
+               s->img_out_n = s->img_n;
+            if (!_m3dstbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0;
+            if (has_trans) {
+               if (z->depth == 16) {
+                  if (!_m3dstbi__compute_transparency16(z, tc16, s->img_out_n)) return 0;
+               } else {
+                  if (!_m3dstbi__compute_transparency(z, tc, s->img_out_n)) return 0;
+               }
+            }
+            if (pal_img_n) {
+               s->img_n = pal_img_n;
+               s->img_out_n = pal_img_n;
+               if (req_comp >= 3) s->img_out_n = req_comp;
+               if (!_m3dstbi__expand_png_palette(z, palette, pal_len, s->img_out_n))
+                  return 0;
+            } else if (has_trans) {
+               ++s->img_n;
+            }
+            STBI_FREE(z->expanded); z->expanded = NULL;
+            return 1;
+         }
+
+         default:
+            if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG");
+            if ((c.type & (1 << 29)) == 0) {
+               return _m3dstbi__err("invalid_chunk", "PNG not supported: unknown PNG chunk type");
+            }
+            _m3dstbi__skip(s, c.length);
+            break;
+      }
+      _m3dstbi__get32be(s);
+   }
+}
+
+static void *_m3dstbi__do_png(_m3dstbi__png *p, int *x, int *y, int *n, int req_comp, _m3dstbi__result_info *ri)
+{
+   void *result=NULL;
+   if (req_comp < 0 || req_comp > 4) { _m3dstbi__err("bad req_comp", "Internal error"); return NULL; }
+   if (_m3dstbi__parse_png_file(p, STBI__SCAN_load, req_comp)) {
+      if (p->depth < 8)
+         ri->bits_per_channel = 8;
+      else
+         ri->bits_per_channel = p->depth;
+      result = p->out;
+      p->out = NULL;
+      if (req_comp && req_comp != p->s->img_out_n) {
+         if (ri->bits_per_channel == 8)
+            result = _m3dstbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
+         else
+            result = _m3dstbi__convert_format16((_m3dstbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
+         p->s->img_out_n = req_comp;
+         if (result == NULL) return result;
+      }
+      *x = p->s->img_x;
+      *y = p->s->img_y;
+      if (n) *n = p->s->img_n;
+   }
+   STBI_FREE(p->out);      p->out      = NULL;
+   STBI_FREE(p->expanded); p->expanded = NULL;
+   STBI_FREE(p->idata);    p->idata    = NULL;
+
+   return result;
+}
+
+static void *_m3dstbi__png_load(_m3dstbi__context *s, int *x, int *y, int *comp, int req_comp, _m3dstbi__result_info *ri)
+{
+   _m3dstbi__png p;
+   p.s = s;
+   return _m3dstbi__do_png(&p, x,y,comp,req_comp, ri);
+}
+#define stbi__context _m3dstbi__context
+#define stbi__result_info _m3dstbi__result_info
+#define stbi__png_load _m3dstbi__png_load
+#define stbi_zlib_decode_malloc_guesssize_headerflag _m3dstbi_zlib_decode_malloc_guesssize_headerflag
+#endif
+
+#if defined(M3D_EXPORTER) && !defined(INCLUDE_STB_IMAGE_WRITE_H)
+/* zlib_compressor from
+
+   stb_image_write - v1.13 - public domain - http://nothings.org/stb/stb_image_write.h
+*/
+typedef unsigned char _m3dstbiw__uc;
+typedef unsigned short _m3dstbiw__us;
+
+typedef uint16_t _m3dstbiw__uint16;
+typedef int16_t  _m3dstbiw__int16;
+typedef uint32_t _m3dstbiw__uint32;
+typedef int32_t  _m3dstbiw__int32;
+
+#define STBIW_MALLOC(s)     M3D_MALLOC(s)
+#define STBIW_REALLOC(p,ns) M3D_REALLOC(p,ns)
+#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz)
+#define STBIW_FREE          M3D_FREE
+#define STBIW_MEMMOVE       memmove
+#define STBIW_UCHAR         (uint8_t)
+#define STBIW_ASSERT(x)
+#define _m3dstbiw___sbraw(a)     ((int *) (a) - 2)
+#define _m3dstbiw___sbm(a)       _m3dstbiw___sbraw(a)[0]
+#define _m3dstbiw___sbn(a)       _m3dstbiw___sbraw(a)[1]
+
+#define _m3dstbiw___sbneedgrow(a,n)  ((a)==0 || _m3dstbiw___sbn(a)+n >= _m3dstbiw___sbm(a))
+#define _m3dstbiw___sbmaybegrow(a,n) (_m3dstbiw___sbneedgrow(a,(n)) ? _m3dstbiw___sbgrow(a,n) : 0)
+#define _m3dstbiw___sbgrow(a,n)  _m3dstbiw___sbgrowf((void **) &(a), (n), sizeof(*(a)))
+
+#define _m3dstbiw___sbpush(a, v)      (_m3dstbiw___sbmaybegrow(a,1), (a)[_m3dstbiw___sbn(a)++] = (v))
+#define _m3dstbiw___sbcount(a)        ((a) ? _m3dstbiw___sbn(a) : 0)
+#define _m3dstbiw___sbfree(a)         ((a) ? STBIW_FREE(_m3dstbiw___sbraw(a)),0 : 0)
+
+static void *_m3dstbiw___sbgrowf(void **arr, int increment, int itemsize)
+{
+   int m = *arr ? 2*_m3dstbiw___sbm(*arr)+increment : increment+1;
+   void *p = STBIW_REALLOC_SIZED(*arr ? _m3dstbiw___sbraw(*arr) : 0, *arr ? (_m3dstbiw___sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2);
+   STBIW_ASSERT(p);
+   if (p) {
+      if (!*arr) ((int *) p)[1] = 0;
+      *arr = (void *) ((int *) p + 2);
+      _m3dstbiw___sbm(*arr) = m;
+   }
+   return *arr;
+}
+
+static unsigned char *_m3dstbiw___zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount)
+{
+   while (*bitcount >= 8) {
+      _m3dstbiw___sbpush(data, STBIW_UCHAR(*bitbuffer));
+      *bitbuffer >>= 8;
+      *bitcount -= 8;
+   }
+   return data;
+}
+
+static int _m3dstbiw___zlib_bitrev(int code, int codebits)
+{
+   int res=0;
+   while (codebits--) {
+      res = (res << 1) | (code & 1);
+      code >>= 1;
+   }
+   return res;
+}
+
+static unsigned int _m3dstbiw___zlib_countm(unsigned char *a, unsigned char *b, int limit)
+{
+   int i;
+   for (i=0; i < limit && i < 258; ++i)
+      if (a[i] != b[i]) break;
+   return i;
+}
+
+static unsigned int _m3dstbiw___zhash(unsigned char *data)
+{
+   _m3dstbiw__uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16);
+   hash ^= hash << 3;
+   hash += hash >> 5;
+   hash ^= hash << 4;
+   hash += hash >> 17;
+   hash ^= hash << 25;
+   hash += hash >> 6;
+   return hash;
+}
+
+#define _m3dstbiw___zlib_flush() (out = _m3dstbiw___zlib_flushf(out, &bitbuf, &bitcount))
+#define _m3dstbiw___zlib_add(code,codebits) \
+      (bitbuf |= (code) << bitcount, bitcount += (codebits), _m3dstbiw___zlib_flush())
+#define _m3dstbiw___zlib_huffa(b,c)  _m3dstbiw___zlib_add(_m3dstbiw___zlib_bitrev(b,c),c)
+#define _m3dstbiw___zlib_huff1(n)  _m3dstbiw___zlib_huffa(0x30 + (n), 8)
+#define _m3dstbiw___zlib_huff2(n)  _m3dstbiw___zlib_huffa(0x190 + (n)-144, 9)
+#define _m3dstbiw___zlib_huff3(n)  _m3dstbiw___zlib_huffa(0 + (n)-256,7)
+#define _m3dstbiw___zlib_huff4(n)  _m3dstbiw___zlib_huffa(0xc0 + (n)-280,8)
+#define _m3dstbiw___zlib_huff(n)  ((n) <= 143 ? _m3dstbiw___zlib_huff1(n) : (n) <= 255 ? _m3dstbiw___zlib_huff2(n) : (n) <= 279 ? _m3dstbiw___zlib_huff3(n) : _m3dstbiw___zlib_huff4(n))
+#define _m3dstbiw___zlib_huffb(n) ((n) <= 143 ? _m3dstbiw___zlib_huff1(n) : _m3dstbiw___zlib_huff2(n))
+
+#define _m3dstbiw___ZHASH   16384
+
+unsigned char * _m3dstbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality)
+{
+   static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 };
+   static unsigned char  lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4,  4,  5,  5,  5,  5,  0 };
+   static unsigned short distc[]   = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 };
+   static unsigned char  disteb[]  = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 };
+   unsigned int bitbuf=0;
+   int i,j, bitcount=0;
+   unsigned char *out = NULL;
+   unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(_m3dstbiw___ZHASH * sizeof(char**));
+   if (hash_table == NULL)
+      return NULL;
+   if (quality < 5) quality = 5;
+
+   _m3dstbiw___sbpush(out, 0x78);
+   _m3dstbiw___sbpush(out, 0x5e);
+   _m3dstbiw___zlib_add(1,1);
+   _m3dstbiw___zlib_add(1,2);
+
+   for (i=0; i < _m3dstbiw___ZHASH; ++i)
+      hash_table[i] = NULL;
+
+   i=0;
+   while (i < data_len-3) {
+      int h = _m3dstbiw___zhash(data+i)&(_m3dstbiw___ZHASH-1), best=3;
+      unsigned char *bestloc = 0;
+      unsigned char **hlist = hash_table[h];
+      int n = _m3dstbiw___sbcount(hlist);
+      for (j=0; j < n; ++j) {
+         if (hlist[j]-data > i-32768) {
+            int d = _m3dstbiw___zlib_countm(hlist[j], data+i, data_len-i);
+            if (d >= best) best=d,bestloc=hlist[j];
+         }
+      }
+      if (hash_table[h] && _m3dstbiw___sbn(hash_table[h]) == 2*quality) {
+         STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality);
+         _m3dstbiw___sbn(hash_table[h]) = quality;
+      }
+      _m3dstbiw___sbpush(hash_table[h],data+i);
+
+      if (bestloc) {
+         h = _m3dstbiw___zhash(data+i+1)&(_m3dstbiw___ZHASH-1);
+         hlist = hash_table[h];
+         n = _m3dstbiw___sbcount(hlist);
+         for (j=0; j < n; ++j) {
+            if (hlist[j]-data > i-32767) {
+               int e = _m3dstbiw___zlib_countm(hlist[j], data+i+1, data_len-i-1);
+               if (e > best) {
+                  bestloc = NULL;
+                  break;
+               }
+            }
+         }
+      }
+
+      if (bestloc) {
+         int d = (int) (data+i - bestloc);
+         STBIW_ASSERT(d <= 32767 && best <= 258);
+         for (j=0; best > lengthc[j+1]-1; ++j);
+         _m3dstbiw___zlib_huff(j+257);
+         if (lengtheb[j]) _m3dstbiw___zlib_add(best - lengthc[j], lengtheb[j]);
+         for (j=0; d > distc[j+1]-1; ++j);
+         _m3dstbiw___zlib_add(_m3dstbiw___zlib_bitrev(j,5),5);
+         if (disteb[j]) _m3dstbiw___zlib_add(d - distc[j], disteb[j]);
+         i += best;
+      } else {
+         _m3dstbiw___zlib_huffb(data[i]);
+         ++i;
+      }
+   }
+   for (;i < data_len; ++i)
+      _m3dstbiw___zlib_huffb(data[i]);
+   _m3dstbiw___zlib_huff(256);
+   while (bitcount)
+      _m3dstbiw___zlib_add(0,1);
+
+   for (i=0; i < _m3dstbiw___ZHASH; ++i)
+      (void) _m3dstbiw___sbfree(hash_table[i]);
+   STBIW_FREE(hash_table);
+
+   {
+      unsigned int s1=1, s2=0;
+      int blocklen = (int) (data_len % 5552);
+      j=0;
+      while (j < data_len) {
+         for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1;
+         s1 %= 65521, s2 %= 65521;
+         j += blocklen;
+         blocklen = 5552;
+      }
+      _m3dstbiw___sbpush(out, STBIW_UCHAR(s2 >> 8));
+      _m3dstbiw___sbpush(out, STBIW_UCHAR(s2));
+      _m3dstbiw___sbpush(out, STBIW_UCHAR(s1 >> 8));
+      _m3dstbiw___sbpush(out, STBIW_UCHAR(s1));
+   }
+   *out_len = _m3dstbiw___sbn(out);
+   STBIW_MEMMOVE(_m3dstbiw___sbraw(out), out, *out_len);
+   return (unsigned char *) _m3dstbiw___sbraw(out);
+}
+#define stbi_zlib_compress _m3dstbi_zlib_compress
+#else
+unsigned char * _m3dstbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality);
+#endif
+
+#define M3D_CHUNKMAGIC(m, a,b,c,d) ((m)[0]==(a) && (m)[1]==(b) && (m)[2]==(c) && (m)[3]==(d))
+
+#ifdef M3D_ASCII
+#include <stdio.h>          /* get sprintf */
+#include <locale.h>         /* sprintf and strtod cares about number locale */
+#endif
+
+#if !defined(M3D_NOIMPORTER) && defined(M3D_ASCII)
+/* helper functions for the ASCII parser */
+static char *_m3d_findarg(char *s) {
+    while(s && *s && *s != ' ' && *s != '\t' && *s != '\r' && *s != '\n') s++;
+    while(s && *s && (*s == ' ' || *s == '\t')) s++;
+    return s;
+}
+static char *_m3d_findnl(char *s) {
+    while(s && *s && *s != '\r' && *s != '\n') s++;
+    if(*s == '\r') s++;
+    if(*s == '\n') s++;
+    return s;
+}
+static char *_m3d_gethex(char *s, uint32_t *ret)
+{
+    if(*s == '#') s++;
+    *ret = 0;
+    for(; *s; s++) {
+        if(*s >= '0' && *s <= '9') {      *ret <<= 4; *ret += (uint32_t)(*s-'0'); }
+        else if(*s >= 'a' && *s <= 'f') { *ret <<= 4; *ret += (uint32_t)(*s-'a'+10); }
+        else if(*s >= 'A' && *s <= 'F') { *ret <<= 4; *ret += (uint32_t)(*s-'A'+10); }
+        else break;
+    }
+    return _m3d_findarg(s);
+}
+static char *_m3d_getint(char *s, uint32_t *ret)
+{
+    char *e = s;
+    if(!s || !*s || *s == '\r' || *s == '\n') return s;
+    for(; *e >= '0' && *e <= '9'; e++);
+    *ret = atoi(s);
+    return e;
+}
+static char *_m3d_getfloat(char *s, M3D_FLOAT *ret)
+{
+    char *e = s;
+    if(!s || !*s || *s == '\r' || *s == '\n') return s;
+    for(; *e == '-' || *e == '+' || *e == '.' || (*e >= '0' && *e <= '9') || *e == 'e' || *e == 'E'; e++);
+    *ret = (M3D_FLOAT)strtod(s, NULL);
+    return _m3d_findarg(e);
+}
+#endif
+#if !defined(M3D_NODUP) && (defined(M3D_ASCII) || defined(M3D_EXPORTER))
+/* helper function to create safe strings */
+char *_m3d_safestr(char *in, int morelines)
+{
+    char *out, *o, *i = in;
+    int l;
+    if(!in || !*in) {
+        out = (char*)M3D_MALLOC(1);
+        if(!out) return NULL;
+        out[0] =0;
+    } else {
+        for(o = in, l = 0; *o && ((morelines & 1) || (*o != '\r' && *o != '\n')) && l < 256; o++, l++);
+        out = o = (char*)M3D_MALLOC(l+1);
+        if(!out) return NULL;
+        while(*i == ' ' || *i == '\t' || *i == '\r' || (morelines && *i == '\n')) i++;
+        for(; *i && (morelines || (*i != '\r' && *i != '\n')); i++) {
+            if(*i == '\r') continue;
+            if(*i == '\n') {
+                if(morelines >= 3 && o > out && *(o-1) == '\n') break;
+                if(i > in && *(i-1) == '\n') continue;
+                if(morelines & 1) {
+                    if(morelines == 1) *o++ = '\r';
+                    *o++ = '\n';
+                } else
+                    break;
+            } else
+            if(*i == ' ' || *i == '\t') {
+                *o++ = morelines? ' ' : '_';
+            } else
+                *o++ = !morelines && (*i == '/' || *i == '\\') ? '_' : *i;
+        }
+        for(; o > out && (*(o-1) == ' ' || *(o-1) == '\t' || *(o-1) == '\r' || *(o-1) == '\n'); o--);
+        *o = 0;
+        out = (char*)M3D_REALLOC(out, (uint64_t)o - (uint64_t)out + 1);
+    }
+    return out;
+}
+#endif
+#ifndef M3D_NOIMPORTER
+/* helper function to load and decode/generate a texture */
+M3D_INDEX _m3d_gettx(m3d_t *model, m3dread_t readfilecb, m3dfree_t freecb, char *fn)
+{
+    unsigned int i, len = 0;
+    unsigned char *buff = NULL;
+    char *fn2;
+#ifdef STBI__PNG_TYPE
+    unsigned int w, h;
+    stbi__context s;
+    stbi__result_info ri;
+#endif
+
+    /* do we have loaded this texture already? */
+    for(i = 0; i < model->numtexture; i++)
+        if(!strcmp(fn, model->texture[i].name)) return i;
+    /* see if it's inlined in the model */
+    if(model->inlined) {
+        for(i = 0; i < model->numinlined; i++)
+            if(!strcmp(fn, model->inlined[i].name)) {
+                buff = model->inlined[i].data;
+                len = model->inlined[i].length;
+                freecb = NULL;
+                break;
+            }
+    }
+    /* try to load from external source */
+    if(!buff && readfilecb) {
+        i = strlen(fn);
+        if(i < 5 || fn[i - 4] != '.') {
+            fn2 = (char*)M3D_MALLOC(i + 5);
+            if(!fn2) { model->errcode = M3D_ERR_ALLOC; return (M3D_INDEX)-1U; }
+            memcpy(fn2, fn, i);
+            memcpy(fn2+i, ".png", 5);
+            buff = (*readfilecb)(fn2, &len);
+            M3D_FREE(fn2);
+        }
+        if(!buff)
+            buff = (*readfilecb)(fn, &len);
+    }
+    if(!buff) return (M3D_INDEX)-1U;
+    /* add to textures array */
+    i = model->numtexture++;
+    model->texture = (m3dtx_t*)M3D_REALLOC(model->texture, model->numtexture * sizeof(m3dtx_t));
+    if(!model->texture) {
+        if(freecb) (*freecb)(buff);
+        model->errcode = M3D_ERR_ALLOC;
+        return (M3D_INDEX)-1U;
+    }
+    model->texture[i].name = fn;
+    model->texture[i].w = model->texture[i].h = 0; model->texture[i].d = NULL;
+    if(buff[0] == 0x89 && buff[1] == 'P' && buff[2] == 'N' && buff[3] == 'G') {
+#ifdef STBI__PNG_TYPE
+        s.read_from_callbacks = 0;
+        s.img_buffer = s.img_buffer_original = (unsigned char *) buff;
+        s.img_buffer_end = s.img_buffer_original_end = (unsigned char *) buff+len;
+        /* don't use model->texture[i].w directly, it's a uint16_t */
+        w = h = len = 0;
+        ri.bits_per_channel = 8;
+        model->texture[i].d = (uint8_t*)stbi__png_load(&s, (int*)&w, (int*)&h, (int*)&len, 0, &ri);
+        model->texture[i].w = w;
+        model->texture[i].h = h;
+        model->texture[i].f = (uint8_t)len;
+#endif
+    } else {
+#ifdef M3D_TX_INTERP
+        if((model->errcode = M3D_TX_INTERP(fn, buff, len, &model->texture[i])) != M3D_SUCCESS) {
+            M3D_LOG("Unable to generate texture");
+            M3D_LOG(fn);
+        }
+#else
+        M3D_LOG("Unimplemented interpreter");
+        M3D_LOG(fn);
+#endif
+    }
+    if(freecb) (*freecb)(buff);
+    if(!model->texture[i].d)
+        model->errcode = M3D_ERR_UNKIMG;
+    return i;
+}
+
+/* helper function to load and generate a procedural surface */
+void _m3d_getpr(m3d_t *model, _unused m3dread_t readfilecb, _unused  m3dfree_t freecb, _unused char *fn)
+{
+#ifdef M3D_PR_INTERP
+    unsigned int i, len = 0;
+    unsigned char *buff = readfilecb ? (*readfilecb)(fn, &len) : NULL;
+
+    if(!buff && model->inlined) {
+        for(i = 0; i < model->numinlined; i++)
+            if(!strcmp(fn, model->inlined[i].name)) {
+                buff = model->inlined[i].data;
+                len = model->inlined[i].length;
+                freecb = NULL;
+                break;
+            }
+    }
+    if(!buff || !len || (model->errcode = M3D_PR_INTERP(fn, buff, len, model)) != M3D_SUCCESS) {
+        M3D_LOG("Unable to generate procedural surface");
+        M3D_LOG(fn);
+        model->errcode = M3D_ERR_UNKIMG;
+    }
+    if(freecb && buff) (*freecb)(buff);
+#else
+    M3D_LOG("Unimplemented interpreter");
+    M3D_LOG(fn);
+    model->errcode = M3D_ERR_UNIMPL;
+#endif
+}
+/* helpers to read indices from data stream */
+#define M3D_GETSTR(x) do{offs=0;data=_m3d_getidx(data,model->si_s,&offs);x=offs?((char*)model->raw+16+offs):NULL;}while(0)
+_inline static unsigned char *_m3d_getidx(unsigned char *data, char type, M3D_INDEX *idx)
+{
+    switch(type) {
+        case 1: *idx = data[0] > 253 ? (int8_t)data[0] : data[0]; data++; break;
+        case 2: *idx = *((uint16_t*)data) > 65533 ? *((int16_t*)data) : *((uint16_t*)data); data += 2; break;
+        case 4: *idx = *((int32_t*)data); data += 4; break;
+    }
+    return data;
+}
+
+#ifndef M3D_NOANIMATION
+/* multiply 4 x 4 matrices. Do not use float *r[16] as argument, because some compilers misinterpret that as
+ * 16 pointers each pointing to a float, but we need a single pointer to 16 floats. */
+void _m3d_mul(M3D_FLOAT *r, M3D_FLOAT *a, M3D_FLOAT *b)
+{
+    r[ 0] = b[ 0] * a[ 0] + b[ 4] * a[ 1] + b[ 8] * a[ 2] + b[12] * a[ 3];
+    r[ 1] = b[ 1] * a[ 0] + b[ 5] * a[ 1] + b[ 9] * a[ 2] + b[13] * a[ 3];
+    r[ 2] = b[ 2] * a[ 0] + b[ 6] * a[ 1] + b[10] * a[ 2] + b[14] * a[ 3];
+    r[ 3] = b[ 3] * a[ 0] + b[ 7] * a[ 1] + b[11] * a[ 2] + b[15] * a[ 3];
+    r[ 4] = b[ 0] * a[ 4] + b[ 4] * a[ 5] + b[ 8] * a[ 6] + b[12] * a[ 7];
+    r[ 5] = b[ 1] * a[ 4] + b[ 5] * a[ 5] + b[ 9] * a[ 6] + b[13] * a[ 7];
+    r[ 6] = b[ 2] * a[ 4] + b[ 6] * a[ 5] + b[10] * a[ 6] + b[14] * a[ 7];
+    r[ 7] = b[ 3] * a[ 4] + b[ 7] * a[ 5] + b[11] * a[ 6] + b[15] * a[ 7];
+    r[ 8] = b[ 0] * a[ 8] + b[ 4] * a[ 9] + b[ 8] * a[10] + b[12] * a[11];
+    r[ 9] = b[ 1] * a[ 8] + b[ 5] * a[ 9] + b[ 9] * a[10] + b[13] * a[11];
+    r[10] = b[ 2] * a[ 8] + b[ 6] * a[ 9] + b[10] * a[10] + b[14] * a[11];
+    r[11] = b[ 3] * a[ 8] + b[ 7] * a[ 9] + b[11] * a[10] + b[15] * a[11];
+    r[12] = b[ 0] * a[12] + b[ 4] * a[13] + b[ 8] * a[14] + b[12] * a[15];
+    r[13] = b[ 1] * a[12] + b[ 5] * a[13] + b[ 9] * a[14] + b[13] * a[15];
+    r[14] = b[ 2] * a[12] + b[ 6] * a[13] + b[10] * a[14] + b[14] * a[15];
+    r[15] = b[ 3] * a[12] + b[ 7] * a[13] + b[11] * a[14] + b[15] * a[15];
+}
+/* calculate 4 x 4 matrix inverse */
+void _m3d_inv(M3D_FLOAT *m)
+{
+    M3D_FLOAT r[16];
+    M3D_FLOAT det =
+          m[ 0]*m[ 5]*m[10]*m[15] - m[ 0]*m[ 5]*m[11]*m[14] + m[ 0]*m[ 6]*m[11]*m[13] - m[ 0]*m[ 6]*m[ 9]*m[15]
+        + m[ 0]*m[ 7]*m[ 9]*m[14] - m[ 0]*m[ 7]*m[10]*m[13] - m[ 1]*m[ 6]*m[11]*m[12] + m[ 1]*m[ 6]*m[ 8]*m[15]
+        - m[ 1]*m[ 7]*m[ 8]*m[14] + m[ 1]*m[ 7]*m[10]*m[12] - m[ 1]*m[ 4]*m[10]*m[15] + m[ 1]*m[ 4]*m[11]*m[14]
+        + m[ 2]*m[ 7]*m[ 8]*m[13] - m[ 2]*m[ 7]*m[ 9]*m[12] + m[ 2]*m[ 4]*m[ 9]*m[15] - m[ 2]*m[ 4]*m[11]*m[13]
+        + m[ 2]*m[ 5]*m[11]*m[12] - m[ 2]*m[ 5]*m[ 8]*m[15] - m[ 3]*m[ 4]*m[ 9]*m[14] + m[ 3]*m[ 4]*m[10]*m[13]
+        - m[ 3]*m[ 5]*m[10]*m[12] + m[ 3]*m[ 5]*m[ 8]*m[14] - m[ 3]*m[ 6]*m[ 8]*m[13] + m[ 3]*m[ 6]*m[ 9]*m[12];
+    if(det == (M3D_FLOAT)0.0 || det == (M3D_FLOAT)-0.0) det = (M3D_FLOAT)1.0; else det = (M3D_FLOAT)1.0 / det;
+    r[ 0] = det *(m[ 5]*(m[10]*m[15] - m[11]*m[14]) + m[ 6]*(m[11]*m[13] - m[ 9]*m[15]) + m[ 7]*(m[ 9]*m[14] - m[10]*m[13]));
+    r[ 1] = -det*(m[ 1]*(m[10]*m[15] - m[11]*m[14]) + m[ 2]*(m[11]*m[13] - m[ 9]*m[15]) + m[ 3]*(m[ 9]*m[14] - m[10]*m[13]));
+    r[ 2] = det *(m[ 1]*(m[ 6]*m[15] - m[ 7]*m[14]) + m[ 2]*(m[ 7]*m[13] - m[ 5]*m[15]) + m[ 3]*(m[ 5]*m[14] - m[ 6]*m[13]));
+    r[ 3] = -det*(m[ 1]*(m[ 6]*m[11] - m[ 7]*m[10]) + m[ 2]*(m[ 7]*m[ 9] - m[ 5]*m[11]) + m[ 3]*(m[ 5]*m[10] - m[ 6]*m[ 9]));
+    r[ 4] = -det*(m[ 4]*(m[10]*m[15] - m[11]*m[14]) + m[ 6]*(m[11]*m[12] - m[ 8]*m[15]) + m[ 7]*(m[ 8]*m[14] - m[10]*m[12]));
+    r[ 5] = det *(m[ 0]*(m[10]*m[15] - m[11]*m[14]) + m[ 2]*(m[11]*m[12] - m[ 8]*m[15]) + m[ 3]*(m[ 8]*m[14] - m[10]*m[12]));
+    r[ 6] = -det*(m[ 0]*(m[ 6]*m[15] - m[ 7]*m[14]) + m[ 2]*(m[ 7]*m[12] - m[ 4]*m[15]) + m[ 3]*(m[ 4]*m[14] - m[ 6]*m[12]));
+    r[ 7] = det *(m[ 0]*(m[ 6]*m[11] - m[ 7]*m[10]) + m[ 2]*(m[ 7]*m[ 8] - m[ 4]*m[11]) + m[ 3]*(m[ 4]*m[10] - m[ 6]*m[ 8]));
+    r[ 8] = det *(m[ 4]*(m[ 9]*m[15] - m[11]*m[13]) + m[ 5]*(m[11]*m[12] - m[ 8]*m[15]) + m[ 7]*(m[ 8]*m[13] - m[ 9]*m[12]));
+    r[ 9] = -det*(m[ 0]*(m[ 9]*m[15] - m[11]*m[13]) + m[ 1]*(m[11]*m[12] - m[ 8]*m[15]) + m[ 3]*(m[ 8]*m[13] - m[ 9]*m[12]));
+    r[10] = det *(m[ 0]*(m[ 5]*m[15] - m[ 7]*m[13]) + m[ 1]*(m[ 7]*m[12] - m[ 4]*m[15]) + m[ 3]*(m[ 4]*m[13] - m[ 5]*m[12]));
+    r[11] = -det*(m[ 0]*(m[ 5]*m[11] - m[ 7]*m[ 9]) + m[ 1]*(m[ 7]*m[ 8] - m[ 4]*m[11]) + m[ 3]*(m[ 4]*m[ 9] - m[ 5]*m[ 8]));
+    r[12] = -det*(m[ 4]*(m[ 9]*m[14] - m[10]*m[13]) + m[ 5]*(m[10]*m[12] - m[ 8]*m[14]) + m[ 6]*(m[ 8]*m[13] - m[ 9]*m[12]));
+    r[13] = det *(m[ 0]*(m[ 9]*m[14] - m[10]*m[13]) + m[ 1]*(m[10]*m[12] - m[ 8]*m[14]) + m[ 2]*(m[ 8]*m[13] - m[ 9]*m[12]));
+    r[14] = -det*(m[ 0]*(m[ 5]*m[14] - m[ 6]*m[13]) + m[ 1]*(m[ 6]*m[12] - m[ 4]*m[14]) + m[ 2]*(m[ 4]*m[13] - m[ 5]*m[12]));
+    r[15] = det *(m[ 0]*(m[ 5]*m[10] - m[ 6]*m[ 9]) + m[ 1]*(m[ 6]*m[ 8] - m[ 4]*m[10]) + m[ 2]*(m[ 4]*m[ 9] - m[ 5]*m[ 8]));
+    memcpy(m, &r, sizeof(r));
+}
+/* compose a coloumn major 4 x 4 matrix from vec3 position and vec4 orientation/rotation quaternion */
+void _m3d_mat(M3D_FLOAT *r, m3dv_t *p, m3dv_t *q)
+{
+    if(q->x == (M3D_FLOAT)0.0 && q->y == (M3D_FLOAT)0.0 && q->z >=(M3D_FLOAT) 0.7071065 && q->z <= (M3D_FLOAT)0.7071075 &&
+        q->w == (M3D_FLOAT)0.0) {
+        r[ 1] = r[ 2] = r[ 4] = r[ 6] = r[ 8] = r[ 9] = (M3D_FLOAT)0.0;
+        r[ 0] = r[ 5] = r[10] = (M3D_FLOAT)-1.0;
+    } else {
+        r[ 0] = 1 - 2 * (q->y * q->y + q->z * q->z); if(r[ 0]>-M3D_EPSILON && r[ 0]<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)
+/* fast inverse square root calculation. returns 1/sqrt(x) */
+static M3D_FLOAT _m3d_rsq(M3D_FLOAT x)
+{
+#ifdef M3D_DOUBLE
+    return ((M3D_FLOAT)15.0/(M3D_FLOAT)8.0) + ((M3D_FLOAT)-5.0/(M3D_FLOAT)4.0)*x + ((M3D_FLOAT)3.0/(M3D_FLOAT)8.0)*x*x;
+#else
+    /* John Carmack's */
+    float x2 = x * 0.5f;
+    *((uint32_t*)&x) = (0x5f3759df - (*((uint32_t*)&x) >> 1));
+    return x * (1.5f - (x2 * x * x));
+#endif
+}
+#endif
+
+/**
+ * Function to decode a Model 3D into in-memory format
+ */
+m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d_t *mtllib)
+{
+    unsigned char *end, *chunk, *buff, weights[8];
+    unsigned int i, j, k, l, n, am, len = 0, reclen, offs;
+    char *name, *lang;
+    float f;
+    m3d_t *model;
+    M3D_INDEX mi;
+    M3D_FLOAT w;
+    m3dcd_t *cd;
+    m3dtx_t *tx;
+    m3dh_t *h;
+    m3dm_t *m;
+    m3da_t *a;
+    m3di_t *t;
+#ifndef M3D_NONORMALS
+    m3dv_t *norm = NULL, *v0, *v1, *v2, va, vb;
+#endif
+#ifndef M3D_NOANIMATION
+    M3D_FLOAT r[16];
+#endif
+#if !defined(M3D_NOWEIGHTS) || !defined(M3D_NOANIMATION)
+    m3db_t *b;
+#endif
+#ifndef M3D_NOWEIGHTS
+    m3ds_t *sk;
+#endif
+#ifdef M3D_ASCII
+    m3ds_t s;
+    M3D_INDEX bi[M3D_BONEMAXLEVEL+1], level;
+    const char *ol;
+    char *ptr, *pe, *fn;
+#endif
+
+    if(!data || (!M3D_CHUNKMAGIC(data, '3','D','M','O')
+#ifdef M3D_ASCII
+        && !M3D_CHUNKMAGIC(data, '3','d','m','o')
+#endif
+        )) return NULL;
+    model = (m3d_t*)M3D_MALLOC(sizeof(m3d_t));
+    if(!model) {
+        M3D_LOG("Out of memory");
+        return NULL;
+    }
+    memset(model, 0, sizeof(m3d_t));
+
+    if(mtllib) {
+        model->nummaterial = mtllib->nummaterial;
+        model->material = mtllib->material;
+        model->numtexture = mtllib->numtexture;
+        model->texture = mtllib->texture;
+        model->flags |= M3D_FLG_MTLLIB;
+    }
+#ifdef M3D_ASCII
+    /* ASCII variant? */
+    if(M3D_CHUNKMAGIC(data, '3','d','m','o')) {
+        model->errcode = M3D_ERR_BADFILE;
+        model->flags |= M3D_FLG_FREESTR;
+        model->raw = (m3dhdr_t*)data;
+        ptr = (char*)data;
+        ol = setlocale(LC_NUMERIC, NULL);
+        setlocale(LC_NUMERIC, "C");
+        /* parse header. Don't use sscanf, that's incredibly slow */
+        ptr = _m3d_findarg(ptr);
+        if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+        pe = _m3d_findnl(ptr);
+        model->scale = (float)strtod(ptr, NULL); ptr = pe;
+        if(model->scale <= (M3D_FLOAT)0.0) model->scale = (M3D_FLOAT)1.0;
+        model->name = _m3d_safestr(ptr, 2); ptr = _m3d_findnl(ptr);
+        if(!*ptr) goto asciiend;
+        model->license = _m3d_safestr(ptr, 2); ptr = _m3d_findnl(ptr);
+        if(!*ptr) goto asciiend;
+        model->author = _m3d_safestr(ptr, 2); ptr = _m3d_findnl(ptr);
+        if(!*ptr) goto asciiend;
+        if(*ptr != '\r' && *ptr != '\n')
+            model->desc = _m3d_safestr(ptr, 3);
+        while(*ptr) {
+            while(*ptr && *ptr!='\n') ptr++;
+            ptr++; if(*ptr=='\r') ptr++;
+            if(*ptr == '\n') break;
+        }
+
+        /* the main chunk reader loop */
+        while(*ptr) {
+            while(*ptr && (*ptr == '\r' || *ptr == '\n')) ptr++;
+            if(!*ptr || (ptr[0]=='E' && ptr[1]=='n' && ptr[2]=='d')) break;
+            /* make sure there's at least one data row */
+            pe = ptr; ptr = _m3d_findnl(ptr);
+            if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+            /* Preview chunk */
+            if(!memcmp(pe, "Preview", 7)) {
+                if(readfilecb) {
+                    pe = _m3d_safestr(ptr, 0);
+                    if(!pe || !*pe) goto asciiend;
+                    model->preview.data = (*readfilecb)(pe, &model->preview.length);
+                    M3D_FREE(pe);
+                }
+                while(*ptr && *ptr != '\r' && *ptr != '\n')
+                    ptr = _m3d_findnl(ptr);
+            } else
+            /* texture map chunk */
+            if(!memcmp(pe, "Textmap", 7)) {
+                if(model->tmap) { M3D_LOG("More texture map chunks, should be unique"); goto asciiend; }
+                while(*ptr && *ptr != '\r' && *ptr != '\n') {
+                    i = model->numtmap++;
+                    model->tmap = (m3dti_t*)M3D_REALLOC(model->tmap, model->numtmap * sizeof(m3dti_t));
+                    if(!model->tmap) goto memerr;
+                    ptr = _m3d_getfloat(ptr, &model->tmap[i].u);
+                    if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+                    _m3d_getfloat(ptr, &model->tmap[i].v);
+                    ptr = _m3d_findnl(ptr);
+                }
+            } else
+            /* vertex chunk */
+            if(!memcmp(pe, "Vertex", 6)) {
+                if(model->vertex) { M3D_LOG("More vertex chunks, should be unique"); goto asciiend; }
+                while(*ptr && *ptr != '\r' && *ptr != '\n') {
+                    i = model->numvertex++;
+                    model->vertex = (m3dv_t*)M3D_REALLOC(model->vertex, model->numvertex * sizeof(m3dv_t));
+                    if(!model->vertex) goto memerr;
+                    memset(&model->vertex[i], 0, sizeof(m3dv_t));
+                    model->vertex[i].skinid = (M3D_INDEX)-1U;
+                    model->vertex[i].color = 0;
+                    model->vertex[i].w = (M3D_FLOAT)1.0;
+                    ptr = _m3d_getfloat(ptr, &model->vertex[i].x);
+                    if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+                    ptr = _m3d_getfloat(ptr, &model->vertex[i].y);
+                    if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+                    ptr = _m3d_getfloat(ptr, &model->vertex[i].z);
+                    if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+                    ptr = _m3d_getfloat(ptr, &model->vertex[i].w);
+                    if(!*ptr) goto asciiend;
+                    if(*ptr == '#') {
+                        ptr = _m3d_gethex(ptr, &model->vertex[i].color);
+                        if(!*ptr) goto asciiend;
+                    }
+                    /* parse skin */
+                    memset(&s, 0, sizeof(m3ds_t));
+                    for(j = 0, w = (M3D_FLOAT)0.0; j < M3D_NUMBONE && *ptr && *ptr != '\r' && *ptr != '\n'; j++) {
+                        ptr = _m3d_findarg(ptr);
+                        if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+                        ptr = _m3d_getint(ptr, &k);
+                        s.boneid[j] = (M3D_INDEX)k;
+                        if(*ptr == ':') {
+                            ptr++;
+                            ptr = _m3d_getfloat(ptr, &s.weight[j]);
+                            w += s.weight[j];
+                        } else if(!j)
+                            s.weight[j] = (M3D_FLOAT)1.0;
+                        if(!*ptr) goto asciiend;
+                    }
+                    if(s.boneid[0] != (M3D_INDEX)-1U && s.weight[0] > (M3D_FLOAT)0.0) {
+                        if(w != (M3D_FLOAT)1.0 && w != (M3D_FLOAT)0.0)
+                            for(j = 0; j < M3D_NUMBONE && s.weight[j] > (M3D_FLOAT)0.0; j++)
+                                s.weight[j] /= w;
+                        k = -1U;
+                        if(model->skin) {
+                            for(j = 0; j < model->numskin; j++)
+                                if(!memcmp(&model->skin[j], &s, sizeof(m3ds_t))) { k = j; break; }
+                        }
+                        if(k == -1U) {
+                            k = model->numskin++;
+                            model->skin = (m3ds_t*)M3D_REALLOC(model->skin, model->numskin * sizeof(m3ds_t));
+                            memcpy(&model->skin[k], &s, sizeof(m3ds_t));
+                        }
+                        model->vertex[i].skinid = (M3D_INDEX)k;
+                    }
+                    ptr = _m3d_findnl(ptr);
+                }
+            } else
+            /* Skeleton, bone hierarchy */
+            if(!memcmp(pe, "Bones", 5)) {
+                if(model->bone) { M3D_LOG("More bones chunks, should be unique"); goto asciiend; }
+                bi[0] = (M3D_INDEX)-1U;
+                while(*ptr && *ptr != '\r' && *ptr != '\n') {
+                    i = model->numbone++;
+                    model->bone = (m3db_t*)M3D_REALLOC(model->bone, model->numbone * sizeof(m3db_t));
+                    if(!model->bone) goto memerr;
+                    for(level = 0; *ptr == '/'; ptr++, level++);
+                    if(level > M3D_BONEMAXLEVEL || !*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+                    bi[level+1] = i;
+                    model->bone[i].numweight = 0;
+                    model->bone[i].weight = NULL;
+                    model->bone[i].parent = bi[level];
+                    ptr = _m3d_getint(ptr, &k);
+                    ptr = _m3d_findarg(ptr);
+                    if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+                    model->bone[i].pos = (M3D_INDEX)k;
+                    ptr = _m3d_getint(ptr, &k);
+                    ptr = _m3d_findarg(ptr);
+                    if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+                    model->bone[i].ori = (M3D_INDEX)k;
+                    model->vertex[k].skinid = (M3D_INDEX)-2U;
+                    pe = _m3d_safestr(ptr, 0);
+                    if(!pe || !*pe) goto asciiend;
+                    model->bone[i].name = pe;
+                    ptr = _m3d_findnl(ptr);
+                }
+            } else
+            /* material chunk */
+            if(!memcmp(pe, "Material", 8)) {
+                pe = _m3d_findarg(pe);
+                if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+                pe = _m3d_safestr(pe, 0);
+                if(!pe || !*pe) goto asciiend;
+                for(i = 0; i < model->nummaterial; i++)
+                    if(!strcmp(pe, model->material[i].name)) {
+                        M3D_LOG("Multiple definitions for material");
+                        M3D_LOG(pe);
+                        M3D_FREE(pe);
+                        pe = NULL;
+                        while(*ptr && *ptr != '\r' && *ptr != '\n') ptr = _m3d_findnl(ptr);
+                        break;
+                    }
+                if(!pe) continue;
+                i = model->nummaterial++;
+                if(model->flags & M3D_FLG_MTLLIB) {
+                    m = model->material;
+                    model->material = (m3dm_t*)M3D_MALLOC(model->nummaterial * sizeof(m3dm_t));
+                    if(!model->material) goto memerr;
+                    memcpy(model->material, m, (model->nummaterial - 1) * sizeof(m3dm_t));
+                    if(model->texture) {
+                        tx = model->texture;
+                        model->texture = (m3dtx_t*)M3D_MALLOC(model->numtexture * sizeof(m3dtx_t));
+                        if(!model->texture) goto memerr;
+                        memcpy(model->texture, tx, model->numtexture * sizeof(m3dm_t));
+                    }
+                    model->flags &= ~M3D_FLG_MTLLIB;
+                } else {
+                    model->material = (m3dm_t*)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t));
+                    if(!model->material) goto memerr;
+                }
+                m = &model->material[i];
+                m->name = pe;
+                m->numprop = 0;
+                m->prop = NULL;
+                while(*ptr && *ptr != '\r' && *ptr != '\n') {
+                    k = n = 256;
+                    if(*ptr == 'm' && *(ptr+1) == 'a' && *(ptr+2) == 'p' && *(ptr+3) == '_') {
+                        k = m3dpf_map;
+                        ptr += 4;
+                    }
+                    for(j = 0; j < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); j++)
+                        if(!memcmp(ptr, m3d_propertytypes[j].key, strlen(m3d_propertytypes[j].key))) {
+                            n = m3d_propertytypes[j].id;
+                            if(k != m3dpf_map) k = m3d_propertytypes[j].format;
+                            break;
+                        }
+                    if(n != 256 && k != 256) {
+                        ptr = _m3d_findarg(ptr);
+                        if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+                        j = m->numprop++;
+                        m->prop = (m3dp_t*)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t));
+                        if(!m->prop) goto memerr;
+                        m->prop[j].type = n + (k == m3dpf_map && n < 128 ? 128 : 0);
+                        switch(k) {
+                            case m3dpf_color: ptr = _m3d_gethex(ptr, &m->prop[j].value.color); break;
+                            case m3dpf_uint8:
+                            case m3dpf_uint16:
+                            case m3dpf_uint32: ptr = _m3d_getint(ptr, &m->prop[j].value.num); break;
+                            case m3dpf_float:  ptr = _m3d_getfloat(ptr, &m->prop[j].value.fnum); break;
+                            case m3dpf_map:
+                                pe = _m3d_safestr(ptr, 0);
+                                if(!pe || !*pe) goto asciiend;
+                                m->prop[j].value.textureid = _m3d_gettx(model, readfilecb, freecb, pe);
+                                if(model->errcode == M3D_ERR_ALLOC) { M3D_FREE(pe); goto memerr; }
+                                if(m->prop[j].value.textureid == (M3D_INDEX)-1U) {
+                                    M3D_LOG("Texture not found");
+                                    M3D_LOG(pe);
+                                    m->numprop--;
+                                }
+                                M3D_FREE(pe);
+                            break;
+                        }
+                    } else {
+                        M3D_LOG("Unknown material property in");
+                        M3D_LOG(m->name);
+                        model->errcode = M3D_ERR_UNKPROP;
+                    }
+                    ptr = _m3d_findnl(ptr);
+                }
+                if(!m->numprop) model->nummaterial--;
+            } else
+            /* procedural */
+            if(!memcmp(pe, "Procedural", 10)) {
+                pe = _m3d_safestr(ptr, 0);
+                _m3d_getpr(model, readfilecb, freecb, pe);
+                M3D_FREE(pe);
+                while(*ptr && *ptr != '\r' && *ptr != '\n') ptr = _m3d_findnl(ptr);
+            } else
+            /* mesh */
+            if(!memcmp(pe, "Mesh", 4)) {
+                mi = (M3D_INDEX)-1U;
+                while(*ptr && *ptr != '\r' && *ptr != '\n') {
+                    if(*ptr == 'u') {
+                        ptr = _m3d_findarg(ptr);
+                        if(!*ptr) goto asciiend;
+                        mi = (M3D_INDEX)-1U;
+                        if(*ptr != '\r' && *ptr != '\n') {
+                            pe = _m3d_safestr(ptr, 0);
+                            if(!pe || !*pe) goto asciiend;
+                            for(j = 0; j < model->nummaterial; j++)
+                                if(!strcmp(pe, model->material[j].name)) { mi = (M3D_INDEX)j; break; }
+                            if(mi == (M3D_INDEX)-1U && !(model->flags & M3D_FLG_MTLLIB)) {
+                                mi = model->nummaterial++;
+                                model->material = (m3dm_t*)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t));
+                                if(!model->material) goto memerr;
+                                model->material[mi].name = pe;
+                                model->material[mi].numprop = 1;
+                                model->material[mi].prop = NULL;
+                            } else
+                                M3D_FREE(pe);
+                        }
+                    } else {
+                        i = model->numface++;
+                        model->face = (m3df_t*)M3D_REALLOC(model->face, model->numface * sizeof(m3df_t));
+                        if(!model->face) goto memerr;
+                        memset(&model->face[i], 255, sizeof(m3df_t)); /* set all index to -1 by default */
+                        model->face[i].materialid = mi;
+                        /* hardcoded triangles. */
+                        for(j = 0; j < 3; j++) {
+                            /* vertex */
+                            ptr = _m3d_getint(ptr, &k);
+                            model->face[i].vertex[j] = (M3D_INDEX)k;
+                            if(!*ptr) goto asciiend;
+                            if(*ptr == '/') {
+                                ptr++;
+                                if(*ptr != '/') {
+                                    /* texcoord */
+                                    ptr = _m3d_getint(ptr, &k);
+                                    model->face[i].texcoord[j] = (M3D_INDEX)k;
+                                    if(!*ptr) goto asciiend;
+                                }
+                                if(*ptr == '/') {
+                                    ptr++;
+                                    /* normal */
+                                    ptr = _m3d_getint(ptr, &k);
+                                    model->face[i].normal[j] = (M3D_INDEX)k;
+                                    if(!*ptr) goto asciiend;
+                                }
+                            }
+                            ptr = _m3d_findarg(ptr);
+                        }
+                    }
+                    ptr = _m3d_findnl(ptr);
+                }
+            } else
+            /* mathematical shape */
+            if(!memcmp(pe, "Shape", 5)) {
+                pe = _m3d_findarg(pe);
+                if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+                pe = _m3d_safestr(pe, 0);
+                if(!pe || !*pe) goto asciiend;
+                i = model->numshape++;
+                model->shape = (m3dh_t*)M3D_REALLOC(model->shape, model->numshape * sizeof(m3ds_t));
+                if(!model->shape) goto memerr;
+                h = &model->shape[i];
+                h->name = pe;
+                h->group = (M3D_INDEX)-1U;
+                h->numcmd = 0;
+                h->cmd = NULL;
+                while(*ptr && *ptr != '\r' && *ptr != '\n') {
+                    if(!memcmp(ptr, "group", 5)) {
+                        ptr = _m3d_findarg(ptr);
+                        ptr = _m3d_getint(ptr, &h->group);
+                        ptr = _m3d_findnl(ptr);
+                        if(h->group != (M3D_INDEX)-1U && h->group >= model->numbone) {
+                            M3D_LOG("Unknown bone id as shape group in shape");
+                            M3D_LOG(pe);
+                            h->group = (M3D_INDEX)-1U;
+                            model->errcode = M3D_ERR_SHPE;
+                        }
+                        continue;
+                    }
+                    for(cd = NULL, k = 0; k < (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])); k++) {
+                        j = strlen(m3d_commandtypes[k].key);
+                        if(!memcmp(ptr, m3d_commandtypes[k].key, j) && (ptr[j] == ' ' || ptr[j] == '\r' || ptr[j] == '\n'))
+                            { cd = &m3d_commandtypes[k]; break; }
+                    }
+                    if(cd) {
+                        j = h->numcmd++;
+                        h->cmd = (m3dc_t*)M3D_REALLOC(h->cmd, h->numcmd * sizeof(m3dc_t));
+                        if(!h->cmd) goto memerr;
+                        h->cmd[j].type = k;
+                        h->cmd[j].arg = (uint32_t*)M3D_MALLOC(cd->p * sizeof(uint32_t));
+                        if(!h->cmd[j].arg) goto memerr;
+                        memset(h->cmd[j].arg, 0, cd->p * sizeof(uint32_t));
+                        for(k = n = 0, l = cd->p; k < l; k++) {
+                            ptr = _m3d_findarg(ptr);
+                            if(!*ptr) goto asciiend;
+                            if(*ptr == '[') {
+                                ptr = _m3d_findarg(ptr + 1);
+                                if(!*ptr) goto asciiend;
+                            }
+                            if(*ptr == ']' || *ptr == '\r' || *ptr == '\n') break;
+                            switch(cd->a[((k - n) % (cd->p - n)) + n]) {
+                                case m3dcp_mi_t:
+                                    mi = (M3D_INDEX)-1U;
+                                    if(*ptr != '\r' && *ptr != '\n') {
+                                        pe = _m3d_safestr(ptr, 0);
+                                        if(!pe || !*pe) goto asciiend;
+                                        for(n = 0; n < model->nummaterial; n++)
+                                            if(!strcmp(pe, model->material[n].name)) { mi = (M3D_INDEX)n; break; }
+                                        if(mi == (M3D_INDEX)-1U && !(model->flags & M3D_FLG_MTLLIB)) {
+                                            mi = model->nummaterial++;
+                                            model->material = (m3dm_t*)M3D_REALLOC(model->material,
+                                                model->nummaterial * sizeof(m3dm_t));
+                                            if(!model->material) goto memerr;
+                                            model->material[mi].name = pe;
+                                            model->material[mi].numprop = 1;
+                                            model->material[mi].prop = NULL;
+                                        } else
+                                            M3D_FREE(pe);
+                                    }
+                                    h->cmd[j].arg[k] = mi;
+                                break;
+                                case m3dcp_vc_t:
+                                    _m3d_getfloat(ptr, &w);
+                                    h->cmd[j].arg[k] = *((uint32_t*)&w);
+                                break;
+                                case m3dcp_va_t:
+                                    ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]);
+                                    n = k + 1; l += (h->cmd[j].arg[k] - 1) * (cd->p - k - 1);
+                                    h->cmd[j].arg = (uint32_t*)M3D_REALLOC(h->cmd[j].arg, l * sizeof(uint32_t));
+                                    if(!h->cmd[j].arg) goto memerr;
+                                    memset(&h->cmd[j].arg[k + 1], 0, (l - k - 1) * sizeof(uint32_t));
+                                break;
+                                case m3dcp_qi_t:
+                                    ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]);
+                                    model->vertex[h->cmd[i].arg[k]].skinid = (M3D_INDEX)-2U;
+                                break;
+                                default:
+                                    ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]);
+                                break;
+                            }
+                        }
+                    } else {
+                        M3D_LOG("Unknown shape command in");
+                        M3D_LOG(h->name);
+                        model->errcode = M3D_ERR_UNKCMD;
+                    }
+                    ptr = _m3d_findnl(ptr);
+                }
+                if(!h->numcmd) model->numshape--;
+            } else
+            /* annotation labels */
+            if(!memcmp(pe, "Labels", 6)) {
+                pe = _m3d_findarg(pe);
+                if(!*pe) goto asciiend;
+                if(*pe == '\r' || *pe == '\n') pe = NULL;
+                else pe = _m3d_safestr(pe, 0);
+                k = 0; fn = NULL;
+                while(*ptr && *ptr != '\r' && *ptr != '\n') {
+                    if(*ptr == 'c') {
+                        ptr = _m3d_findarg(ptr);
+                        if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+                        ptr = _m3d_gethex(ptr, &k);
+                    } else
+                    if(*ptr == 'l') {
+                        ptr = _m3d_findarg(ptr);
+                        if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+                        fn = _m3d_safestr(ptr, 2);
+                    } else {
+                        i = model->numlabel++;
+                        model->label = (m3dl_t*)M3D_REALLOC(model->label, model->numlabel * sizeof(m3dl_t));
+                        if(!model->label) goto memerr;
+                        model->label[i].name = pe;
+                        model->label[i].lang = fn;
+                        model->label[i].color = k;
+                        ptr = _m3d_getint(ptr, &j);
+                        model->label[i].vertexid = (M3D_INDEX)j;
+                        ptr = _m3d_findarg(ptr);
+                        if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+                        model->label[i].text = _m3d_safestr(ptr, 2);
+                    }
+                    ptr = _m3d_findnl(ptr);
+                }
+            } else
+            /* action */
+            if(!memcmp(pe, "Action", 6)) {
+                pe = _m3d_findarg(pe);
+                if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+                pe = _m3d_getint(pe, &k);
+                pe = _m3d_findarg(pe);
+                if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+                pe = _m3d_safestr(pe, 0);
+                if(!pe || !*pe) goto asciiend;
+                i = model->numaction++;
+                model->action = (m3da_t*)M3D_REALLOC(model->action, model->numaction * sizeof(m3da_t));
+                if(!model->action) goto memerr;
+                a = &model->action[i];
+                a->name = pe;
+                a->durationmsec = k;
+                /* skip the first frame marker as there's always at least one frame */
+                a->numframe = 1;
+                a->frame = (m3dfr_t*)M3D_MALLOC(sizeof(m3dfr_t));
+                if(!a->frame) goto memerr;
+                a->frame[0].msec = 0;
+                a->frame[0].numtransform = 0;
+                a->frame[0].transform = NULL;
+                i = 0;
+                if(*ptr == 'f')
+                    ptr = _m3d_findnl(ptr);
+                while(*ptr && *ptr != '\r' && *ptr != '\n') {
+                    if(*ptr == 'f') {
+                        i = a->numframe++;
+                        a->frame = (m3dfr_t*)M3D_REALLOC(a->frame, a->numframe * sizeof(m3dfr_t));
+                        if(!a->frame) goto memerr;
+                        ptr = _m3d_findarg(ptr);
+                        ptr = _m3d_getint(ptr, &a->frame[i].msec);
+                        a->frame[i].numtransform = 0;
+                        a->frame[i].transform = NULL;
+                    } else {
+                        j = a->frame[i].numtransform++;
+                        a->frame[i].transform = (m3dtr_t*)M3D_REALLOC(a->frame[i].transform,
+                            a->frame[i].numtransform * sizeof(m3dtr_t));
+                        if(!a->frame[i].transform) goto memerr;
+                        ptr = _m3d_getint(ptr, &k);
+                        ptr = _m3d_findarg(ptr);
+                        if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+                        a->frame[i].transform[j].boneid = (M3D_INDEX)k;
+                        ptr = _m3d_getint(ptr, &k);
+                        ptr = _m3d_findarg(ptr);
+                        if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+                        a->frame[i].transform[j].pos = (M3D_INDEX)k;
+                        ptr = _m3d_getint(ptr, &k);
+                        if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+                        a->frame[i].transform[j].ori = (M3D_INDEX)k;
+                        model->vertex[k].skinid = (M3D_INDEX)-2U;
+                    }
+                    ptr = _m3d_findnl(ptr);
+                }
+            } else
+            /* inlined assets chunk */
+            if(!memcmp(pe, "Assets", 6)) {
+                while(*ptr && *ptr != '\r' && *ptr != '\n') {
+                    if(readfilecb) {
+                        pe = _m3d_safestr(ptr, 2);
+                        if(!pe || !*pe) goto asciiend;
+                        i = model->numinlined++;
+                        model->inlined = (m3di_t*)M3D_REALLOC(model->inlined, model->numinlined * sizeof(m3di_t));
+                        if(!model->inlined) goto memerr;
+                        t = &model->inlined[i];
+                        model->inlined[i].data = (*readfilecb)(pe, &model->inlined[i].length);
+                        if(model->inlined[i].data) {
+                            fn = strrchr(pe, '.');
+                            if(fn && (fn[1] == 'p' || fn[1] == 'P') && (fn[2] == 'n' || fn[2] == 'N') &&
+                                (fn[3] == 'g' || fn[3] == 'G')) *fn = 0;
+                            fn = strrchr(pe, '/');
+                            if(!fn) fn = strrchr(pe, '\\');
+                            if(!fn) fn = pe; else fn++;
+                            model->inlined[i].name = _m3d_safestr(fn, 0);
+                        } else
+                            model->numinlined--;
+                        M3D_FREE(pe);
+                    }
+                    ptr = _m3d_findnl(ptr);
+                }
+            } else
+            /* extra chunks */
+            if(!memcmp(pe, "Extra", 5)) {
+                pe = _m3d_findarg(pe);
+                if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+                buff = (unsigned char*)_m3d_findnl(ptr);
+                k = ((uint32_t)((uint64_t)buff - (uint64_t)ptr) / 3) + 1;
+                i = model->numextra++;
+                model->extra = (m3dchunk_t**)M3D_REALLOC(model->extra, model->numextra * sizeof(m3dchunk_t*));
+                if(!model->extra) goto memerr;
+                model->extra[i] = (m3dchunk_t*)M3D_MALLOC(k + sizeof(m3dchunk_t));
+                if(!model->extra[i]) goto memerr;
+                memcpy(&model->extra[i]->magic, pe, 4);
+                model->extra[i]->length = sizeof(m3dchunk_t);
+                pe = (char*)model->extra[i] + sizeof(m3dchunk_t);
+                while(*ptr && *ptr != '\r' && *ptr != '\n') {
+                    ptr = _m3d_gethex(ptr, &k);
+                    *pe++ = (uint8_t)k;
+                    model->extra[i]->length++;
+                }
+            } else
+                goto asciiend;
+        }
+        model->errcode = M3D_SUCCESS;
+asciiend:
+        setlocale(LC_NUMERIC, ol);
+        goto postprocess;
+    }
+    /* Binary variant */
+#endif
+    if(!M3D_CHUNKMAGIC(data + 8, 'H','E','A','D')) {
+        buff = (unsigned char *)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)data+8, ((m3dchunk_t*)data)->length-8,
+            4096, (int*)&len, 1);
+        if(!buff || !len || !M3D_CHUNKMAGIC(buff, 'H','E','A','D')) {
+            if(buff) M3D_FREE(buff);
+            M3D_FREE(model);
+            return NULL;
+        }
+        buff = (unsigned char*)M3D_REALLOC(buff, len);
+        model->flags |= M3D_FLG_FREERAW; /* mark that we have to free the raw buffer */
+        data = buff;
+    } else {
+        len = ((m3dhdr_t*)data)->length;
+        data += 8;
+    }
+    model->raw = (m3dhdr_t*)data;
+    end = data + len;
+
+    /* parse header */
+    data += sizeof(m3dhdr_t);
+    M3D_LOG(data);
+    model->name = (char*)data;
+    for(; data < end && *data; data++) {}; data++;
+    model->license = (char*)data;
+    for(; data < end && *data; data++) {}; data++;
+    model->author = (char*)data;
+    for(; data < end && *data; data++) {}; data++;
+    model->desc = (char*)data;
+    chunk = (unsigned char*)model->raw + model->raw->length;
+    model->scale = (M3D_FLOAT)model->raw->scale;
+    if(model->scale <= (M3D_FLOAT)0.0) model->scale = (M3D_FLOAT)1.0;
+    model->vc_s = 1 << ((model->raw->types >> 0) & 3);  /* vertex coordinate size */
+    model->vi_s = 1 << ((model->raw->types >> 2) & 3);  /* vertex index size */
+    model->si_s = 1 << ((model->raw->types >> 4) & 3);  /* string offset size */
+    model->ci_s = 1 << ((model->raw->types >> 6) & 3);  /* color index size */
+    model->ti_s = 1 << ((model->raw->types >> 8) & 3);  /* tmap index size */
+    model->bi_s = 1 << ((model->raw->types >>10) & 3);  /* bone index size */
+    model->nb_s = 1 << ((model->raw->types >>12) & 3);  /* number of bones per vertex */
+    model->sk_s = 1 << ((model->raw->types >>14) & 3);  /* skin index size */
+    model->fc_s = 1 << ((model->raw->types >>16) & 3);  /* frame counter size */
+    model->hi_s = 1 << ((model->raw->types >>18) & 3);  /* shape index size */
+    model->fi_s = 1 << ((model->raw->types >>20) & 3);  /* face index size */
+    if(model->ci_s == 8) model->ci_s = 0;               /* optional indices */
+    if(model->ti_s == 8) model->ti_s = 0;
+    if(model->bi_s == 8) model->bi_s = 0;
+    if(model->sk_s == 8) model->sk_s = 0;
+    if(model->fc_s == 8) model->fc_s = 0;
+    if(model->hi_s == 8) model->hi_s = 0;
+    if(model->fi_s == 8) model->fi_s = 0;
+
+    /* variable limit checks */
+    if(sizeof(M3D_FLOAT) == 4 && model->vc_s > 4) {
+        M3D_LOG("Double precision coordinates not supported, truncating to float...");
+        model->errcode = M3D_ERR_TRUNC;
+    }
+    if(sizeof(M3D_INDEX) == 2 && (model->vi_s > 2 || model->si_s > 2 || model->ci_s > 2 || model->ti_s > 2 ||
+        model->bi_s > 2 || model->sk_s > 2 || model->fc_s > 2 || model->hi_s > 2 || model->fi_s > 2)) {
+        M3D_LOG("32 bit indices not supported, unable to load model");
+        M3D_FREE(model);
+        return NULL;
+    }
+    if(model->vi_s > 4 || model->si_s > 4) {
+        M3D_LOG("Invalid index size, unable to load model");
+        M3D_FREE(model);
+        return NULL;
+    }
+    if(model->nb_s > M3D_NUMBONE) {
+        M3D_LOG("Model has more bones per vertex than what importer configured to support");
+        model->errcode = M3D_ERR_TRUNC;
+    }
+
+    /* look for inlined assets in advance, material and procedural chunks may need them */
+    buff = chunk;
+    while(buff < end && !M3D_CHUNKMAGIC(buff, 'O','M','D','3')) {
+        data = buff;
+        len = ((m3dchunk_t*)data)->length;
+        if(len < sizeof(m3dchunk_t)) {
+            M3D_LOG("Invalid chunk size");
+            break;
+        }
+        buff += len;
+        len -= sizeof(m3dchunk_t) + model->si_s;
+
+        /* inlined assets */
+        if(M3D_CHUNKMAGIC(data, 'A','S','E','T') && len > 0) {
+            M3D_LOG("Inlined asset");
+            i = model->numinlined++;
+            model->inlined = (m3di_t*)M3D_REALLOC(model->inlined, model->numinlined * sizeof(m3di_t));
+            if(!model->inlined) {
+memerr:         M3D_LOG("Out of memory");
+                model->errcode = M3D_ERR_ALLOC;
+                return model;
+            }
+            data += sizeof(m3dchunk_t);
+            t = &model->inlined[i];
+            M3D_GETSTR(t->name);
+            M3D_LOG(t->name);
+            t->data = (uint8_t*)data;
+            t->length = len;
+        }
+    }
+
+    /* parse chunks */
+    while(chunk < end && !M3D_CHUNKMAGIC(chunk, 'O','M','D','3')) {
+        data = chunk;
+        len = ((m3dchunk_t*)chunk)->length;
+        if(len < sizeof(m3dchunk_t)) {
+            M3D_LOG("Invalid chunk size");
+            break;
+        }
+        chunk += len;
+        len -= sizeof(m3dchunk_t);
+
+        /* preview chunk */
+        if(M3D_CHUNKMAGIC(data, 'P','R','V','W') && len > 0) {
+            model->preview.length = len;
+            model->preview.data = data + sizeof(m3dchunk_t);
+        } else
+        /* color map */
+        if(M3D_CHUNKMAGIC(data, 'C','M','A','P')) {
+            M3D_LOG("Color map");
+            if(model->cmap) { M3D_LOG("More color map chunks, should be unique"); model->errcode = M3D_ERR_CMAP; continue; }
+            if(!model->ci_s) { M3D_LOG("Color map chunk, shouldn't be any"); model->errcode = M3D_ERR_CMAP; continue; }
+            model->numcmap = len / sizeof(uint32_t);
+            model->cmap = (uint32_t*)(data + sizeof(m3dchunk_t));
+        } else
+        /* texture map */
+        if(M3D_CHUNKMAGIC(data, 'T','M','A','P')) {
+            M3D_LOG("Texture map");
+            if(model->tmap) { M3D_LOG("More texture map chunks, should be unique"); model->errcode = M3D_ERR_TMAP; continue; }
+            if(!model->ti_s) { M3D_LOG("Texture map chunk, shouldn't be any"); model->errcode = M3D_ERR_TMAP; continue; }
+            reclen = model->vc_s + model->vc_s;
+            model->numtmap = len / reclen;
+            model->tmap = (m3dti_t*)M3D_MALLOC(model->numtmap * sizeof(m3dti_t));
+            if(!model->tmap) goto memerr;
+            for(i = 0, data += sizeof(m3dchunk_t); data < chunk; i++) {
+                switch(model->vc_s) {
+                    case 1:
+                        model->tmap[i].u = (M3D_FLOAT)(data[0]) / 255;
+                        model->tmap[i].v = (M3D_FLOAT)(data[1]) / 255;
+                    break;
+                    case 2:
+                        model->tmap[i].u = (M3D_FLOAT)(*((int16_t*)(data+0))) / 65535;
+                        model->tmap[i].v = (M3D_FLOAT)(*((int16_t*)(data+2))) / 65535;
+                    break;
+                    case 4:
+                        model->tmap[i].u = (M3D_FLOAT)(*((float*)(data+0)));
+                        model->tmap[i].v = (M3D_FLOAT)(*((float*)(data+4)));
+                    break;
+                    case 8:
+                        model->tmap[i].u = (M3D_FLOAT)(*((double*)(data+0)));
+                        model->tmap[i].v = (M3D_FLOAT)(*((double*)(data+8)));
+                    break;
+                }
+                data += reclen;
+            }
+        } else
+        /* vertex list */
+        if(M3D_CHUNKMAGIC(data, 'V','R','T','S')) {
+            M3D_LOG("Vertex list");
+            if(model->vertex) { M3D_LOG("More vertex chunks, should be unique"); model->errcode = M3D_ERR_VRTS; continue; }
+            if(model->ci_s && model->ci_s < 4 && !model->cmap) model->errcode = M3D_ERR_CMAP;
+            reclen = model->ci_s + model->sk_s + 4 * model->vc_s;
+            model->numvertex = len / reclen;
+            model->vertex = (m3dv_t*)M3D_MALLOC(model->numvertex * sizeof(m3dv_t));
+            if(!model->vertex) goto memerr;
+            memset(model->vertex, 0, model->numvertex * sizeof(m3dv_t));
+            for(i = 0, data += sizeof(m3dchunk_t); data < chunk && i < model->numvertex; i++) {
+                switch(model->vc_s) {
+                    case 1:
+                        model->vertex[i].x = (M3D_FLOAT)((int8_t)data[0]) / 127;
+                        model->vertex[i].y = (M3D_FLOAT)((int8_t)data[1]) / 127;
+                        model->vertex[i].z = (M3D_FLOAT)((int8_t)data[2]) / 127;
+                        model->vertex[i].w = (M3D_FLOAT)((int8_t)data[3]) / 127;
+                        data += 4;
+                    break;
+                    case 2:
+                        model->vertex[i].x = (M3D_FLOAT)(*((int16_t*)(data+0))) / 32767;
+                        model->vertex[i].y = (M3D_FLOAT)(*((int16_t*)(data+2))) / 32767;
+                        model->vertex[i].z = (M3D_FLOAT)(*((int16_t*)(data+4))) / 32767;
+                        model->vertex[i].w = (M3D_FLOAT)(*((int16_t*)(data+6))) / 32767;
+                        data += 8;
+                    break;
+                    case 4:
+                        model->vertex[i].x = (M3D_FLOAT)(*((float*)(data+0)));
+                        model->vertex[i].y = (M3D_FLOAT)(*((float*)(data+4)));
+                        model->vertex[i].z = (M3D_FLOAT)(*((float*)(data+8)));
+                        model->vertex[i].w = (M3D_FLOAT)(*((float*)(data+12)));
+                        data += 16;
+                    break;
+                    case 8:
+                        model->vertex[i].x = (M3D_FLOAT)(*((double*)(data+0)));
+                        model->vertex[i].y = (M3D_FLOAT)(*((double*)(data+8)));
+                        model->vertex[i].z = (M3D_FLOAT)(*((double*)(data+16)));
+                        model->vertex[i].w = (M3D_FLOAT)(*((double*)(data+24)));
+                        data += 32;
+                    break;
+                }
+                switch(model->ci_s) {
+                    case 1: model->vertex[i].color = model->cmap ? model->cmap[data[0]] : 0; data++; break;
+                    case 2: model->vertex[i].color = model->cmap ? model->cmap[*((uint16_t*)data)] : 0; data += 2; break;
+                    case 4: model->vertex[i].color = *((uint32_t*)data); data += 4; break;
+                    /* case 8: break; */
+                }
+                model->vertex[i].skinid = (M3D_INDEX)-1U;
+                data = _m3d_getidx(data, model->sk_s, &model->vertex[i].skinid);
+            }
+        } else
+        /* skeleton: bone hierarchy and skin */
+        if(M3D_CHUNKMAGIC(data, 'B','O','N','E')) {
+            M3D_LOG("Skeleton");
+            if(model->bone) { M3D_LOG("More bone chunks, should be unique"); model->errcode = M3D_ERR_BONE; continue; }
+            if(!model->bi_s) { M3D_LOG("Bone chunk, shouldn't be any"); model->errcode=M3D_ERR_BONE; continue; }
+            if(!model->vertex) { M3D_LOG("No vertex chunk before bones"); model->errcode = M3D_ERR_VRTS; break; }
+            data += sizeof(m3dchunk_t);
+            model->numbone = 0;
+            data = _m3d_getidx(data, model->bi_s, &model->numbone);
+            if(model->numbone) {
+                model->bone = (m3db_t*)M3D_MALLOC(model->numbone * sizeof(m3db_t));
+                if(!model->bone) goto memerr;
+            }
+            model->numskin = 0;
+            data = _m3d_getidx(data, model->sk_s, &model->numskin);
+            /* read bone hierarchy */
+            for(i = 0; i < model->numbone; i++) {
+                data = _m3d_getidx(data, model->bi_s, &model->bone[i].parent);
+                M3D_GETSTR(model->bone[i].name);
+                data = _m3d_getidx(data, model->vi_s, &model->bone[i].pos);
+                data = _m3d_getidx(data, model->vi_s, &model->bone[i].ori);
+                model->bone[i].numweight = 0;
+                model->bone[i].weight = NULL;
+            }
+            /* read skin definitions */
+            if(model->numskin) {
+                model->skin = (m3ds_t*)M3D_MALLOC(model->numskin * sizeof(m3ds_t));
+                if(!model->skin) goto memerr;
+                for(i = 0; data < chunk && i < model->numskin; i++) {
+                    for(j = 0; j < M3D_NUMBONE; j++) {
+                        model->skin[i].boneid[j] = (M3D_INDEX)-1U;
+                        model->skin[i].weight[j] = (M3D_FLOAT)0.0;
+                    }
+                    memset(&weights, 0, sizeof(weights));
+                    if(model->nb_s == 1) weights[0] = 255;
+                    else {
+                        memcpy(&weights, data, model->nb_s);
+                        data += model->nb_s;
+                    }
+                    for(j = 0, w = (M3D_FLOAT)0.0; j < (unsigned int)model->nb_s; j++) {
+                        if(weights[j]) {
+                            if(j >= M3D_NUMBONE)
+                                data += model->bi_s;
+                            else {
+                                model->skin[i].weight[j] = (M3D_FLOAT)(weights[j]) / 255;
+                                w += model->skin[i].weight[j];
+                                data = _m3d_getidx(data, model->bi_s, &model->skin[i].boneid[j]);
+                            }
+                        }
+                    }
+                    /* this can occur if model has more bones than what the importer is configured to handle */
+                    if(w != (M3D_FLOAT)1.0 && w != (M3D_FLOAT)0.0) {
+                        for(j = 0; j < M3D_NUMBONE; j++)
+                            model->skin[i].weight[j] /= w;
+                    }
+                }
+            }
+        } else
+        /* material */
+        if(M3D_CHUNKMAGIC(data, 'M','T','R','L')) {
+            data += sizeof(m3dchunk_t);
+            M3D_GETSTR(name);
+            M3D_LOG("Material");
+            M3D_LOG(name);
+            if(model->ci_s < 4 && !model->numcmap) model->errcode = M3D_ERR_CMAP;
+            for(i = 0; i < model->nummaterial; i++)
+                if(!strcmp(name, model->material[i].name)) {
+                    model->errcode = M3D_ERR_MTRL;
+                    M3D_LOG("Multiple definitions for material");
+                    M3D_LOG(name);
+                    name = NULL;
+                    break;
+                }
+            if(name) {
+                i = model->nummaterial++;
+                if(model->flags & M3D_FLG_MTLLIB) {
+                    m = model->material;
+                    model->material = (m3dm_t*)M3D_MALLOC(model->nummaterial * sizeof(m3dm_t));
+                    if(!model->material) goto memerr;
+                    memcpy(model->material, m, (model->nummaterial - 1) * sizeof(m3dm_t));
+                    if(model->texture) {
+                        tx = model->texture;
+                        model->texture = (m3dtx_t*)M3D_MALLOC(model->numtexture * sizeof(m3dtx_t));
+                        if(!model->texture) goto memerr;
+                        memcpy(model->texture, tx, model->numtexture * sizeof(m3dm_t));
+                    }
+                    model->flags &= ~M3D_FLG_MTLLIB;
+                } else {
+                    model->material = (m3dm_t*)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t));
+                    if(!model->material) goto memerr;
+                }
+                m = &model->material[i];
+                m->numprop = 0;
+                m->name = name;
+                m->prop = (m3dp_t*)M3D_MALLOC((len / 2) * sizeof(m3dp_t));
+                if(!m->prop) goto memerr;
+                while(data < chunk) {
+                    i = m->numprop++;
+                    m->prop[i].type = *data++;
+                    m->prop[i].value.num = 0;
+                    if(m->prop[i].type >= 128)
+                        k = m3dpf_map;
+                    else {
+                        for(k = 256, j = 0; j < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); j++)
+                            if(m->prop[i].type == m3d_propertytypes[j].id) { k = m3d_propertytypes[j].format; break; }
+                    }
+                    switch(k) {
+                        case m3dpf_color:
+                            switch(model->ci_s) {
+                                case 1: m->prop[i].value.color = model->cmap ? model->cmap[data[0]] : 0; data++; break;
+                                case 2: m->prop[i].value.color = model->cmap ? model->cmap[*((uint16_t*)data)] : 0; data += 2; break;
+                                case 4: m->prop[i].value.color = *((uint32_t*)data); data += 4; break;
+                            }
+                        break;
+
+                        case m3dpf_uint8: m->prop[i].value.num = *data++; break;
+                        case m3dpf_uint16:m->prop[i].value.num = *((uint16_t*)data); data += 2; break;
+                        case m3dpf_uint32:m->prop[i].value.num = *((uint32_t*)data); data += 4; break;
+                        case m3dpf_float: m->prop[i].value.fnum = *((float*)data); data += 4; break;
+
+                        case m3dpf_map:
+                            M3D_GETSTR(name);
+                            m->prop[i].value.textureid = _m3d_gettx(model, readfilecb, freecb, name);
+                            if(model->errcode == M3D_ERR_ALLOC) goto memerr;
+                            if(m->prop[i].value.textureid == (M3D_INDEX)-1U) {
+                                M3D_LOG("Texture not found");
+                                M3D_LOG(m->name);
+                                m->numprop--;
+                            }
+                        break;
+
+                        default:
+                            M3D_LOG("Unknown material property in");
+                            M3D_LOG(m->name);
+                            model->errcode = M3D_ERR_UNKPROP;
+                            data = chunk;
+                        break;
+                    }
+                }
+                m->prop = (m3dp_t*)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t));
+                if(!m->prop) goto memerr;
+            }
+        } else
+        /* face */
+        if(M3D_CHUNKMAGIC(data, 'P','R','O','C')) {
+            /* procedural surface */
+            M3D_GETSTR(name);
+            M3D_LOG("Procedural surface");
+            M3D_LOG(name);
+            _m3d_getpr(model, readfilecb, freecb, name);
+        } else
+        if(M3D_CHUNKMAGIC(data, 'M','E','S','H')) {
+            M3D_LOG("Mesh data");
+            /* mesh */
+            data += sizeof(m3dchunk_t);
+            mi = (M3D_INDEX)-1U;
+            am = model->numface;
+            while(data < chunk) {
+                k = *data++;
+                n = k >> 4;
+                k &= 15;
+                if(!n) {
+                    /* use material */
+                    mi = (M3D_INDEX)-1U;
+                    M3D_GETSTR(name);
+                    if(name) {
+                        for(j = 0; j < model->nummaterial; j++)
+                            if(!strcmp(name, model->material[j].name)) {
+                                mi = (M3D_INDEX)j;
+                                break;
+                            }
+                        if(mi == (M3D_INDEX)-1U) model->errcode = M3D_ERR_MTRL;
+                    }
+                    continue;
+                }
+                if(n != 3) { M3D_LOG("Only triangle mesh supported for now"); model->errcode = M3D_ERR_UNKMESH; return model; }
+                i = model->numface++;
+                if(model->numface > am) {
+                    am = model->numface + 4095;
+                    model->face = (m3df_t*)M3D_REALLOC(model->face, am * sizeof(m3df_t));
+                    if(!model->face) goto memerr;
+                }
+                memset(&model->face[i], 255, sizeof(m3df_t)); /* set all index to -1 by default */
+                model->face[i].materialid = mi;
+                for(j = 0; j < n; j++) {
+                    /* vertex */
+                    data = _m3d_getidx(data, model->vi_s, &model->face[i].vertex[j]);
+                    /* texcoord */
+                    if(k & 1)
+                        data = _m3d_getidx(data, model->ti_s, &model->face[i].texcoord[j]);
+                    /* normal */
+                    if(k & 2)
+                        data = _m3d_getidx(data, model->vi_s, &model->face[i].normal[j]);
+                }
+            }
+            model->face = (m3df_t*)M3D_REALLOC(model->face, model->numface * sizeof(m3df_t));
+        } else
+        if(M3D_CHUNKMAGIC(data, 'S','H','P','E')) {
+            /* mathematical shape */
+            data += sizeof(m3dchunk_t);
+            M3D_GETSTR(name);
+            M3D_LOG("Mathematical Shape");
+            M3D_LOG(name);
+            i = model->numshape++;
+            model->shape = (m3dh_t*)M3D_REALLOC(model->shape, model->numshape * sizeof(m3dh_t));
+            if(!model->shape) goto memerr;
+            h = &model->shape[i];
+            h->numcmd = 0;
+            h->cmd = NULL;
+            h->name = name;
+            h->group = (M3D_INDEX)-1U;
+            data = _m3d_getidx(data, model->bi_s, &h->group);
+            if(h->group != (M3D_INDEX)-1U && h->group >= model->numbone) {
+                M3D_LOG("Unknown bone id as shape group in shape");
+                M3D_LOG(name);
+                h->group = (M3D_INDEX)-1U;
+                model->errcode = M3D_ERR_SHPE;
+            }
+            while(data < chunk) {
+                i = h->numcmd++;
+                h->cmd = (m3dc_t*)M3D_REALLOC(h->cmd, h->numcmd * sizeof(m3dc_t));
+                if(!h->cmd) goto memerr;
+                h->cmd[i].type = *data++;
+                if(h->cmd[i].type & 0x80) {
+                    h->cmd[i].type &= 0x7F;
+                    h->cmd[i].type |= (*data++ << 7);
+                }
+                if(h->cmd[i].type >= (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0]))) {
+                    M3D_LOG("Unknown shape command in");
+                    M3D_LOG(h->name);
+                    model->errcode = M3D_ERR_UNKCMD;
+                    break;
+                }
+                cd = &m3d_commandtypes[h->cmd[i].type];
+                h->cmd[i].arg = (uint32_t*)M3D_MALLOC(cd->p * sizeof(uint32_t));
+                if(!h->cmd[i].arg) goto memerr;
+                memset(h->cmd[i].arg, 0, cd->p * sizeof(uint32_t));
+                for(k = n = 0, l = cd->p; k < l; k++)
+                    switch(cd->a[((k - n) % (cd->p - n)) + n]) {
+                        case m3dcp_mi_t:
+                            h->cmd[i].arg[k] = -1U;
+                            M3D_GETSTR(name);
+                            if(name) {
+                                for(n = 0; n < model->nummaterial; n++)
+                                    if(!strcmp(name, model->material[n].name)) {
+                                        h->cmd[i].arg[k] = n;
+                                        break;
+                                    }
+                                if(h->cmd[i].arg[k] == -1U) model->errcode = M3D_ERR_MTRL;
+                            }
+                        break;
+                        case m3dcp_vc_t:
+                            f = 0.0f;
+                            switch(model->vc_s) {
+                                case 1: f = (float)((int8_t)data[0]) / 127; break;
+                                case 2: f = (float)(*((int16_t*)(data+0))) / 32767; break;
+                                case 4: f = (float)(*((float*)(data+0))); break;
+                                case 8: f = (float)(*((double*)(data+0))); break;
+                            }
+                            h->cmd[i].arg[k] = *((uint32_t*)&f);
+                            data += model->vc_s;
+                        break;
+                        case m3dcp_hi_t: data = _m3d_getidx(data, model->hi_s, &h->cmd[i].arg[k]); break;
+                        case m3dcp_fi_t: data = _m3d_getidx(data, model->fi_s, &h->cmd[i].arg[k]); break;
+                        case m3dcp_ti_t: data = _m3d_getidx(data, model->ti_s, &h->cmd[i].arg[k]); break;
+                        case m3dcp_qi_t:
+                        case m3dcp_vi_t: data = _m3d_getidx(data, model->vi_s, &h->cmd[i].arg[k]); break;
+                        case m3dcp_i1_t: data = _m3d_getidx(data, 1, &h->cmd[i].arg[k]); break;
+                        case m3dcp_i2_t: data = _m3d_getidx(data, 2, &h->cmd[i].arg[k]); break;
+                        case m3dcp_i4_t: data = _m3d_getidx(data, 4, &h->cmd[i].arg[k]); break;
+                        case m3dcp_va_t: data = _m3d_getidx(data, 4, &h->cmd[i].arg[k]);
+                            n = k + 1; l += (h->cmd[i].arg[k] - 1) * (cd->p - k - 1);
+                            h->cmd[i].arg = (uint32_t*)M3D_REALLOC(h->cmd[i].arg, l * sizeof(uint32_t));
+                            if(!h->cmd[i].arg) goto memerr;
+                            memset(&h->cmd[i].arg[k + 1], 0, (l - k - 1) * sizeof(uint32_t));
+                        break;
+                    }
+            }
+        } else
+        /* annotation label list */
+        if(M3D_CHUNKMAGIC(data, 'L','B','L','S')) {
+            data += sizeof(m3dchunk_t);
+            M3D_GETSTR(name);
+            M3D_GETSTR(lang);
+            M3D_LOG("Label list");
+            if(name) { M3D_LOG(name); }
+            if(lang) { M3D_LOG(lang); }
+            if(model->ci_s && model->ci_s < 4 && !model->cmap) model->errcode = M3D_ERR_CMAP;
+            k = 0;
+            switch(model->ci_s) {
+                case 1: k = model->cmap ? model->cmap[data[0]] : 0; data++; break;
+                case 2: k = model->cmap ? model->cmap[*((uint16_t*)data)] : 0; data += 2; break;
+                case 4: k = *((uint32_t*)data); data += 4; break;
+                /* case 8: break; */
+            }
+            reclen = model->vi_s + model->si_s;
+            i = model->numlabel; model->numlabel += len / reclen;
+            model->label = (m3dl_t*)M3D_REALLOC(model->label, model->numlabel * sizeof(m3dl_t));
+            if(!model->label) goto memerr;
+            memset(&model->label[i], 0, (model->numlabel - i) * sizeof(m3dl_t));
+            for(; data < chunk && i < model->numlabel; i++) {
+                model->label[i].name = name;
+                model->label[i].lang = lang;
+                model->label[i].color = k;
+                data = _m3d_getidx(data, model->vi_s, &model->label[i].vertexid);
+                M3D_GETSTR(model->label[i].text);
+            }
+        } else
+        /* action */
+        if(M3D_CHUNKMAGIC(data, 'A','C','T','N')) {
+            M3D_LOG("Action");
+            i = model->numaction++;
+            model->action = (m3da_t*)M3D_REALLOC(model->action, model->numaction * sizeof(m3da_t));
+            if(!model->action) goto memerr;
+            a = &model->action[i];
+            data += sizeof(m3dchunk_t);
+            M3D_GETSTR(a->name);
+            M3D_LOG(a->name);
+            a->numframe = *((uint16_t*)data); data += 2;
+            if(a->numframe < 1) {
+                model->numaction--;
+            } else {
+                a->durationmsec = *((uint32_t*)data); data += 4;
+                a->frame = (m3dfr_t*)M3D_MALLOC(a->numframe * sizeof(m3dfr_t));
+                if(!a->frame) goto memerr;
+                for(i = 0; data < chunk && i < a->numframe; i++) {
+                    a->frame[i].msec = *((uint32_t*)data); data += 4;
+                    a->frame[i].numtransform = 0; a->frame[i].transform = NULL;
+                    data = _m3d_getidx(data, model->fc_s, &a->frame[i].numtransform);
+                    if(a->frame[i].numtransform > 0) {
+                        a->frame[i].transform = (m3dtr_t*)M3D_MALLOC(a->frame[i].numtransform * sizeof(m3dtr_t));
+                        for(j = 0; j < a->frame[i].numtransform; j++) {
+                            data = _m3d_getidx(data, model->bi_s, &a->frame[i].transform[j].boneid);
+                            data = _m3d_getidx(data, model->vi_s, &a->frame[i].transform[j].pos);
+                            data = _m3d_getidx(data, model->vi_s, &a->frame[i].transform[j].ori);
+                        }
+                    }
+                }
+            }
+        } else {
+            i = model->numextra++;
+            model->extra = (m3dchunk_t**)M3D_REALLOC(model->extra, model->numextra * sizeof(m3dchunk_t*));
+            if(!model->extra) goto memerr;
+            model->extra[i] = (m3dchunk_t*)data;
+        }
+    }
+    /* calculate normals, normalize skin weights, create bone/vertex cross-references and calculate transform matrices */
+#ifdef M3D_ASCII
+postprocess:
+#endif
+    if(model) {
+        M3D_LOG("Post-process");
+#ifndef M3D_NONORMALS
+        if(model->numface && model->face) {
+            /* if they are missing, calculate triangle normals into a temporary buffer */
+            for(i = 0, n = model->numvertex; i < model->numface; i++)
+                if(model->face[i].normal[0] == -1U) {
+                    v0 = &model->vertex[model->face[i].vertex[0]];
+                    v1 = &model->vertex[model->face[i].vertex[1]];
+                    v2 = &model->vertex[model->face[i].vertex[2]];
+                    va.x = v1->x - v0->x; va.y = v1->y - v0->y; va.z = v1->z - v0->z;
+                    vb.x = v2->x - v0->x; vb.y = v2->y - v0->y; vb.z = v2->z - v0->z;
+                    if(!norm) {
+                        norm = (m3dv_t*)M3D_MALLOC(model->numface * sizeof(m3dv_t));
+                        if(!norm) goto memerr;
+                    }
+                    v0 = &norm[i];
+                    v0->x = (va.y * vb.z) - (va.z * vb.y);
+                    v0->y = (va.z * vb.x) - (va.x * vb.z);
+                    v0->z = (va.x * vb.y) - (va.y * vb.x);
+                    w = _m3d_rsq((v0->x * v0->x) + (v0->y * v0->y) + (v0->z * v0->z));
+                    v0->x *= w; v0->y *= w; v0->z *= w;
+                    model->face[i].normal[0] = model->face[i].vertex[0] + n;
+                    model->face[i].normal[1] = model->face[i].vertex[1] + n;
+                    model->face[i].normal[2] = model->face[i].vertex[2] + n;
+                }
+            /* this is the fast way, we don't care if a normal is repeated in model->vertex */
+            if(norm) {
+                M3D_LOG("Generating normals");
+                model->flags |= M3D_FLG_GENNORM;
+                model->numvertex <<= 1;
+                model->vertex = (m3dv_t*)M3D_REALLOC(model->vertex, model->numvertex * sizeof(m3dv_t));
+                if(!model->vertex) goto memerr;
+                memset(&model->vertex[n], 0, n * sizeof(m3dv_t));
+                for(i = 0; i < model->numface; i++)
+                    for(j = 0; j < 3; j++) {
+                        v0 = &model->vertex[model->face[i].vertex[j] + n];
+                        v0->x += norm[i].x;
+                        v0->y += norm[i].y;
+                        v0->z += norm[i].z;
+                    }
+                /* for each vertex, take the average of the temporary normals and use that */
+                for(i = 0, v0 = &model->vertex[n]; i < n; i++, v0++) {
+                    w = _m3d_rsq((v0->x * v0->x) + (v0->y * v0->y) + (v0->z * v0->z));
+                    v0->x *= w; v0->y *= w; v0->z *= w;
+                    v0->skinid = -1U;
+                }
+                M3D_FREE(norm);
+            }
+        }
+#endif
+        if(model->numbone && model->bone && model->numskin && model->skin && model->numvertex && model->vertex) {
+#ifndef M3D_NOWEIGHTS
+            M3D_LOG("Generating weight cross-reference");
+            for(i = 0; i < model->numvertex; i++) {
+                if(model->vertex[i].skinid < model->numskin) {
+                    sk = &model->skin[model->vertex[i].skinid];
+                    w = (M3D_FLOAT)0.0;
+                    for(j = 0; j < M3D_NUMBONE && sk->boneid[j] != (M3D_INDEX)-1U && sk->weight[j] > (M3D_FLOAT)0.0; j++)
+                        w += sk->weight[j];
+                    for(j = 0; j < M3D_NUMBONE && sk->boneid[j] != (M3D_INDEX)-1U && sk->weight[j] > (M3D_FLOAT)0.0; j++) {
+                        sk->weight[j] /= w;
+                        b = &model->bone[sk->boneid[j]];
+                        k = b->numweight++;
+                        b->weight = (m3dw_t*)M3D_REALLOC(b->weight, b->numweight * sizeof(m3da_t));
+                        if(!b->weight) goto memerr;
+                        b->weight[k].vertexid = i;
+                        b->weight[k].weight = sk->weight[j];
+                    }
+                }
+            }
+#endif
+#ifndef M3D_NOANIMATION
+            M3D_LOG("Calculating bone transformation matrices");
+            for(i = 0; i < model->numbone; i++) {
+                b = &model->bone[i];
+                if(model->bone[i].parent == (M3D_INDEX)-1U) {
+                    _m3d_mat((M3D_FLOAT*)&b->mat4, &model->vertex[b->pos], &model->vertex[b->ori]);
+                } else {
+                    _m3d_mat((M3D_FLOAT*)&r, &model->vertex[b->pos], &model->vertex[b->ori]);
+                    _m3d_mul((M3D_FLOAT*)&b->mat4, (M3D_FLOAT*)&model->bone[b->parent].mat4, (M3D_FLOAT*)&r);
+                }
+            }
+            for(i = 0; i < model->numbone; i++)
+                _m3d_inv((M3D_FLOAT*)&model->bone[i].mat4);
+#endif
+        }
+    }
+    return model;
+}
+
+/**
+ * Calculates skeletons for animation frames, returns a working copy (should be freed after use)
+ */
+m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t *skeleton)
+{
+    unsigned int i;
+    M3D_INDEX s = frameid;
+    m3dfr_t *fr;
+
+    if(!model || !model->numbone || !model->bone || (actionid != (M3D_INDEX)-1U && (!model->action ||
+        actionid >= model->numaction || frameid >= model->action[actionid].numframe))) {
+            model->errcode = M3D_ERR_UNKFRAME;
+            return skeleton;
+    }
+    model->errcode = M3D_SUCCESS;
+    if(!skeleton) {
+        skeleton = (m3dtr_t*)M3D_MALLOC(model->numbone * sizeof(m3dtr_t));
+        if(!skeleton) {
+            model->errcode = M3D_ERR_ALLOC;
+            return NULL;
+        }
+        goto gen;
+    }
+    if(actionid == (M3D_INDEX)-1U || !frameid) {
+gen:    s = 0;
+        for(i = 0; i < model->numbone; i++) {
+            skeleton[i].boneid = i;
+            skeleton[i].pos = model->bone[i].pos;
+            skeleton[i].ori = model->bone[i].ori;
+        }
+    }
+    if(actionid < model->numaction && (frameid || !model->action[actionid].frame[0].msec)) {
+        for(; s <= frameid; s++) {
+            fr = &model->action[actionid].frame[s];
+            for(i = 0; i < fr->numtransform; i++) {
+                skeleton[fr->transform[i].boneid].pos = fr->transform[i].pos;
+                skeleton[fr->transform[i].boneid].ori = fr->transform[i].ori;
+            }
+        }
+    }
+    return skeleton;
+}
+
+#ifndef M3D_NOANIMATION
+/**
+ * Returns interpolated animation-pose, a working copy (should be freed after use)
+ */
+m3db_t *m3d_pose(m3d_t *model, M3D_INDEX actionid, uint32_t msec)
+{
+    unsigned int i, j, l;
+    M3D_FLOAT r[16], t, c, d, s;
+    m3db_t *ret;
+    m3dv_t *v, *p, *f;
+    m3dtr_t *tmp;
+    m3dfr_t *fr;
+
+    if(!model || !model->numbone || !model->bone) {
+        model->errcode = M3D_ERR_UNKFRAME;
+        return NULL;
+    }
+    ret = (m3db_t*)M3D_MALLOC(model->numbone * sizeof(m3db_t));
+    if(!ret) {
+        model->errcode = M3D_ERR_ALLOC;
+        return NULL;
+    }
+    memcpy(ret, model->bone, model->numbone * sizeof(m3db_t));
+    for(i = 0; i < model->numbone; i++)
+        _m3d_inv((M3D_FLOAT*)&ret[i].mat4);
+    if(!model->action || actionid >= model->numaction) {
+        model->errcode = M3D_ERR_UNKFRAME;
+        return ret;
+    }
+    msec %= model->action[actionid].durationmsec;
+    model->errcode = M3D_SUCCESS;
+    fr = &model->action[actionid].frame[0];
+    for(j = l = 0; j < model->action[actionid].numframe && model->action[actionid].frame[j].msec <= msec; j++) {
+        fr = &model->action[actionid].frame[j];
+        l = fr->msec;
+        for(i = 0; i < fr->numtransform; i++) {
+            ret[fr->transform[i].boneid].pos = fr->transform[i].pos;
+            ret[fr->transform[i].boneid].ori = fr->transform[i].ori;
+        }
+    }
+    if(l != msec) {
+        model->vertex = (m3dv_t*)M3D_REALLOC(model->vertex, (model->numvertex + 2 * model->numbone) * sizeof(m3dv_t));
+        if(!model->vertex) {
+            free(ret);
+            model->errcode = M3D_ERR_ALLOC;
+            return NULL;
+        }
+        tmp = (m3dtr_t*)M3D_MALLOC(model->numbone * sizeof(m3dtr_t));
+        if(tmp) {
+            for(i = 0; i < model->numbone; i++) {
+                tmp[i].pos = ret[i].pos;
+                tmp[i].ori = ret[i].ori;
+            }
+            fr = &model->action[actionid].frame[j % model->action[actionid].numframe];
+            t = l >= fr->msec ? (M3D_FLOAT)1.0 : (M3D_FLOAT)(msec - l) / (M3D_FLOAT)(fr->msec - l);
+            for(i = 0; i < fr->numtransform; i++) {
+                tmp[fr->transform[i].boneid].pos = fr->transform[i].pos;
+                tmp[fr->transform[i].boneid].ori = fr->transform[i].ori;
+            }
+            for(i = 0, j = model->numvertex; i < model->numbone; i++) {
+                /* interpolation of position */
+                if(ret[i].pos != tmp[i].pos) {
+                    p = &model->vertex[ret[i].pos];
+                    f = &model->vertex[tmp[i].pos];
+                    v = &model->vertex[j];
+                    v->x = p->x + t * (f->x - p->x);
+                    v->y = p->y + t * (f->y - p->y);
+                    v->z = p->z + t * (f->z - p->z);
+                    ret[i].pos = j++;
+                }
+                /* interpolation of orientation */
+                if(ret[i].ori != tmp[i].ori) {
+                    p = &model->vertex[ret[i].ori];
+                    f = &model->vertex[tmp[i].ori];
+                    v = &model->vertex[j];
+                    d = p->w * f->w + p->x * f->x + p->y * f->y + p->z * f->z;
+                    if(d < 0) { d = -d; s = (M3D_FLOAT)-1.0; } else s = (M3D_FLOAT)1.0;
+#if 0
+                    /* don't use SLERP, requires two more variables, libm linkage and it is slow (but nice) */
+                    a = (M3D_FLOAT)1.0 - t; b = t;
+                    if(d < (M3D_FLOAT)0.999999) { c = acosf(d); b = 1 / sinf(c); a = sinf(a * c) * b; b *= sinf(t * c) * s; }
+                    v->x = p->x * a + f->x * b;
+                    v->y = p->y * a + f->y * b;
+                    v->z = p->z * a + f->z * b;
+                    v->w = p->w * a + f->w * b;
+#else
+                    /* approximated NLERP, original approximation by Arseny Kapoulkine, heavily optimized by me */
+                    c = t - (M3D_FLOAT)0.5; t += t * c * (t - (M3D_FLOAT)1.0) * (((M3D_FLOAT)1.0904 + d * ((M3D_FLOAT)-3.2452 +
+                        d * ((M3D_FLOAT)3.55645 - d * (M3D_FLOAT)1.43519))) * c * c + ((M3D_FLOAT)0.848013 + d *
+                        ((M3D_FLOAT)-1.06021 + d * (M3D_FLOAT)0.215638)));
+                    v->x = p->x + t * (s * f->x - p->x);
+                    v->y = p->y + t * (s * f->y - p->y);
+                    v->z = p->z + t * (s * f->z - p->z);
+                    v->w = p->w + t * (s * f->w - p->w);
+                    d = _m3d_rsq(v->w * v->w + v->x * v->x + v->y * v->y + v->z * v->z);
+                    v->x *= d; v->y *= d; v->z *= d; v->w *= d;
+#endif
+                    ret[i].ori = j++;
+                }
+            }
+            M3D_FREE(tmp);
+        }
+    }
+    for(i = 0; i < model->numbone; i++) {
+        if(ret[i].parent == (M3D_INDEX)-1U) {
+            _m3d_mat((M3D_FLOAT*)&ret[i].mat4, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]);
+        } else {
+            _m3d_mat((M3D_FLOAT*)&r, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]);
+            _m3d_mul((M3D_FLOAT*)&ret[i].mat4, (M3D_FLOAT*)&ret[ret[i].parent].mat4, (M3D_FLOAT*)&r);
+        }
+    }
+    return ret;
+}
+
+#endif /* M3D_NOANIMATION */
+
+#endif /* M3D_IMPLEMENTATION */
+
+#if !defined(M3D_NODUP) && (!defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER))
+/**
+ * Free the in-memory model
+ */
+void m3d_free(m3d_t *model)
+{
+    unsigned int i, j;
+
+    if(!model) return;
+#ifdef M3D_ASCII
+    /* if model imported from ASCII, we have to free all strings as well */
+    if(model->flags & M3D_FLG_FREESTR) {
+        if(model->name) M3D_FREE(model->name);
+        if(model->license) M3D_FREE(model->license);
+        if(model->author) M3D_FREE(model->author);
+        if(model->desc) M3D_FREE(model->desc);
+        if(model->bone)
+            for(i = 0; i < model->numbone; i++)
+                if(model->bone[i].name)
+                    M3D_FREE(model->bone[i].name);
+        if(model->shape)
+            for(i = 0; i < model->numshape; i++)
+                if(model->shape[i].name)
+                    M3D_FREE(model->shape[i].name);
+        if(model->material)
+            for(i = 0; i < model->nummaterial; i++)
+                if(model->material[i].name)
+                    M3D_FREE(model->material[i].name);
+        if(model->action)
+            for(i = 0; i < model->numaction; i++)
+                if(model->action[i].name)
+                    M3D_FREE(model->action[i].name);
+        if(model->texture)
+            for(i = 0; i < model->numtexture; i++)
+                if(model->texture[i].name)
+                    M3D_FREE(model->texture[i].name);
+        if(model->inlined)
+            for(i = 0; i < model->numinlined; i++) {
+                if(model->inlined[i].name)
+                    M3D_FREE(model->inlined[i].name);
+                if(model->inlined[i].data)
+                    M3D_FREE(model->inlined[i].data);
+            }
+        if(model->extra)
+            for(i = 0; i < model->numextra; i++)
+                if(model->extra[i])
+                    M3D_FREE(model->extra[i]);
+        if(model->label)
+            for(i = 0; i < model->numlabel; i++) {
+                if(model->label[i].name) {
+                    for(j = i + 1; j < model->numlabel; j++)
+                        if(model->label[j].name == model->label[i].name)
+                            model->label[j].name = NULL;
+                    M3D_FREE(model->label[i].name);
+                }
+                if(model->label[i].lang) {
+                    for(j = i + 1; j < model->numlabel; j++)
+                        if(model->label[j].lang == model->label[i].lang)
+                            model->label[j].lang = NULL;
+                    M3D_FREE(model->label[i].lang);
+                }
+                if(model->label[i].text)
+                    M3D_FREE(model->label[i].text);
+            }
+        if(model->preview.data)
+            M3D_FREE(model->preview.data);
+    }
+#endif
+    if(model->flags & M3D_FLG_FREERAW) M3D_FREE(model->raw);
+
+    if(model->tmap) M3D_FREE(model->tmap);
+    if(model->bone) {
+        for(i = 0; i < model->numbone; i++)
+            if(model->bone[i].weight)
+                M3D_FREE(model->bone[i].weight);
+        M3D_FREE(model->bone);
+    }
+    if(model->skin) M3D_FREE(model->skin);
+    if(model->vertex) M3D_FREE(model->vertex);
+    if(model->face) M3D_FREE(model->face);
+    if(model->shape) {
+        for(i = 0; i < model->numshape; i++) {
+            if(model->shape[i].cmd) {
+                for(j = 0; j < model->shape[i].numcmd; j++)
+                    if(model->shape[i].cmd[j].arg) M3D_FREE(model->shape[i].cmd[j].arg);
+                M3D_FREE(model->shape[i].cmd);
+            }
+        }
+        M3D_FREE(model->shape);
+    }
+    if(model->material && !(model->flags & M3D_FLG_MTLLIB)) {
+        for(i = 0; i < model->nummaterial; i++)
+            if(model->material[i].prop) M3D_FREE(model->material[i].prop);
+        M3D_FREE(model->material);
+    }
+    if(model->texture) {
+        for(i = 0; i < model->numtexture; i++)
+            if(model->texture[i].d) M3D_FREE(model->texture[i].d);
+        M3D_FREE(model->texture);
+    }
+    if(model->action) {
+        for(i = 0; i < model->numaction; i++) {
+            if(model->action[i].frame) {
+                for(j = 0; j < model->action[i].numframe; j++)
+                    if(model->action[i].frame[j].transform) M3D_FREE(model->action[i].frame[j].transform);
+                M3D_FREE(model->action[i].frame);
+            }
+        }
+        M3D_FREE(model->action);
+    }
+    if(model->label) M3D_FREE(model->label);
+    if(model->inlined) M3D_FREE(model->inlined);
+    if(model->extra) M3D_FREE(model->extra);
+    free(model);
+}
+#endif
+
+#ifdef M3D_EXPORTER
+typedef struct {
+    char *str;
+    uint32_t offs;
+} m3dstr_t;
+
+typedef struct {
+    m3dti_t data;
+    M3D_INDEX oldidx;
+    M3D_INDEX newidx;
+} m3dtisave_t;
+
+typedef struct {
+    m3dv_t data;
+    M3D_INDEX oldidx;
+    M3D_INDEX newidx;
+    unsigned char norm;
+} m3dvsave_t;
+
+typedef struct {
+    m3ds_t data;
+    M3D_INDEX oldidx;
+    M3D_INDEX newidx;
+} m3dssave_t;
+
+typedef struct {
+    m3df_t data;
+    int group;
+    uint8_t opacity;
+} m3dfsave_t;
+
+/* create unique list of strings */
+static m3dstr_t *_m3d_addstr(m3dstr_t *str, uint32_t *numstr, char *s)
+{
+    uint32_t i;
+    if(!s || !*s) return str;
+    if(str) {
+        for(i = 0; i < *numstr; i++)
+            if(str[i].str == s || !strcmp(str[i].str, s)) return str;
+    }
+    str = (m3dstr_t*)M3D_REALLOC(str, ((*numstr) + 1) * sizeof(m3dstr_t));
+    str[*numstr].str = s;
+    str[*numstr].offs = 0;
+    (*numstr)++;
+    return str;
+}
+
+/* add strings to header */
+m3dhdr_t *_m3d_addhdr(m3dhdr_t *h, m3dstr_t *s)
+{
+    int i;
+    char *safe = _m3d_safestr(s->str, 0);
+    i = strlen(safe);
+    h = (m3dhdr_t*)M3D_REALLOC(h, h->length + i+1);
+    if(!h) { M3D_FREE(safe); return NULL; }
+    memcpy((uint8_t*)h + h->length, safe, i+1);
+    s->offs = h->length - 16;
+    h->length += i+1;
+    M3D_FREE(safe);
+    return h;
+}
+
+/* return offset of string */
+static uint32_t _m3d_stridx(m3dstr_t *str, uint32_t numstr, char *s)
+{
+    uint32_t i;
+    char *safe;
+    if(!s || !*s) return 0;
+    if(str) {
+        safe = _m3d_safestr(s, 0);
+        if(!safe) return 0;
+        if(!*safe) {
+            free(safe);
+            return 0;
+        }
+        for(i = 0; i < numstr; i++)
+            if(!strcmp(str[i].str, s)) {
+                free(safe);
+                return str[i].offs;
+            }
+        free(safe);
+    }
+    return 0;
+}
+
+/* compare to faces by their material */
+static int _m3d_facecmp(const void *a, const void *b) {
+    const m3dfsave_t *A = (const m3dfsave_t*)a, *B = (const m3dfsave_t*)b;
+    return A->group != B->group ? A->group - B->group : (A->opacity != B->opacity ? (int)B->opacity - (int)A->opacity :
+        (int)A->data.materialid - (int)B->data.materialid);
+}
+/* compare face groups */
+static int _m3d_grpcmp(const void *a, const void *b) { return *((uint32_t*)a) - *((uint32_t*)b); }
+/* compare UVs */
+static int _m3d_ticmp(const void *a, const void *b) { return memcmp(a, b, sizeof(m3dti_t)); }
+/* compare skin groups */
+static int _m3d_skincmp(const void *a, const void *b) { return memcmp(a, b, sizeof(m3ds_t)); }
+/* compare vertices */
+static int _m3d_vrtxcmp(const void *a, const void *b) {
+    int c = memcmp(a, b, 3 * sizeof(M3D_FLOAT));
+    if(c) return c;
+    c = ((m3dvsave_t*)a)->norm - ((m3dvsave_t*)b)->norm;
+    if(c) return c;
+    return memcmp(a, b, sizeof(m3dv_t));
+}
+/* compare labels */
+static _inline int _m3d_strcmp(char *a, char *b)
+{
+    if(a == NULL && b != NULL) return -1;
+    if(a != NULL && b == NULL) return 1;
+    if(a == NULL && b == NULL) return 0;
+    return strcmp(a, b);
+}
+static int _m3d_lblcmp(const void *a, const void *b) {
+    const m3dl_t *A = (const m3dl_t*)a, *B = (const m3dl_t*)b;
+    int c = _m3d_strcmp(A->lang, B->lang);
+    if(!c) c = _m3d_strcmp(A->name, B->name);
+    if(!c) c = _m3d_strcmp(A->text, B->text);
+    return c;
+}
+/* compare two colors by HSV value */
+_inline static int _m3d_cmapcmp(const void *a, const void *b)
+{
+    uint8_t *A = (uint8_t*)a,  *B = (uint8_t*)b;
+    _register int m, vA, vB;
+    /* get HSV value for A */
+    m = A[2] < A[1]? A[2] : A[1]; if(A[0] < m) m = A[0];
+    vA = A[2] > A[1]? A[2] : A[1]; if(A[0] > vA) vA = A[0];
+    /* get HSV value for B */
+    m = B[2] < B[1]? B[2] : B[1]; if(B[0] < m) m = B[0];
+    vB = B[2] > B[1]? B[2] : B[1]; if(B[0] > vB) vB = B[0];
+    return vA - vB;
+}
+
+/* create sorted list of colors */
+static uint32_t *_m3d_addcmap(uint32_t *cmap, uint32_t *numcmap, uint32_t color)
+{
+    uint32_t i;
+    if(cmap) {
+        for(i = 0; i < *numcmap; i++)
+            if(cmap[i] == color) return cmap;
+    }
+    cmap = (uint32_t*)M3D_REALLOC(cmap, ((*numcmap) + 1) * sizeof(uint32_t));
+    for(i = 0; i < *numcmap && _m3d_cmapcmp(&color, &cmap[i]) > 0; i++);
+    if(i < *numcmap) memmove(&cmap[i+1], &cmap[i], ((*numcmap) - i)*sizeof(uint32_t));
+    cmap[i] = color;
+    (*numcmap)++;
+    return cmap;
+}
+
+/* look up a color and return its index */
+static uint32_t _m3d_cmapidx(uint32_t *cmap, uint32_t numcmap, uint32_t color)
+{
+    uint32_t i;
+    if(numcmap >= 65536)
+        return color;
+    for(i = 0; i < numcmap; i++)
+        if(cmap[i] == color) return i;
+    return 0;
+}
+
+/* add index to output */
+static unsigned char *_m3d_addidx(unsigned char *out, char type, uint32_t idx) {
+    switch(type) {
+        case 1: *out++ = (uint8_t)(idx); break;
+        case 2: *((uint16_t*)out) = (uint16_t)(idx); out += 2; break;
+        case 4: *((uint32_t*)out) = (uint32_t)(idx); out += 4; break;
+        /* case 0: case 8: break; */
+    }
+    return out;
+}
+
+/* round a vertex position */
+static void _m3d_round(int quality, m3dv_t *src, m3dv_t *dst)
+{
+    _register int t;
+    /* copy additional attributes */
+    if(src != dst) memcpy(dst, src, sizeof(m3dv_t));
+    /* round according to quality */
+    switch(quality) {
+        case M3D_EXP_INT8:
+            t = src->x * 127 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->x = (M3D_FLOAT)t / 127;
+            t = src->y * 127 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->y = (M3D_FLOAT)t / 127;
+            t = src->z * 127 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->z = (M3D_FLOAT)t / 127;
+            t = src->w * 127 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->w = (M3D_FLOAT)t / 127;
+        break;
+        case M3D_EXP_INT16:
+            t = src->x * 32767 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->x = (M3D_FLOAT)t / 32767;
+            t = src->y * 32767 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->y = (M3D_FLOAT)t / 32767;
+            t = src->z * 32767 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->z = (M3D_FLOAT)t / 32767;
+            t = src->w * 32767 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->w = (M3D_FLOAT)t / 32767;
+        break;
+    }
+    if(dst->x == (M3D_FLOAT)-0.0) dst->x = (M3D_FLOAT)0.0;
+    if(dst->y == (M3D_FLOAT)-0.0) dst->y = (M3D_FLOAT)0.0;
+    if(dst->z == (M3D_FLOAT)-0.0) dst->z = (M3D_FLOAT)0.0;
+    if(dst->w == (M3D_FLOAT)-0.0) dst->w = (M3D_FLOAT)0.0;
+}
+
+#ifdef M3D_ASCII
+/* add a bone to ascii output */
+static char *_m3d_prtbone(char *ptr, m3db_t *bone, M3D_INDEX numbone, M3D_INDEX parent, uint32_t level, M3D_INDEX *vrtxidx)
+{
+    uint32_t i, j;
+    char *sn;
+
+    if(level > M3D_BONEMAXLEVEL || !bone) return ptr;
+    for(i = 0; i < numbone; i++) {
+        if(bone[i].parent == parent) {
+            for(j = 0; j < level; j++) *ptr++ = '/';
+            sn = _m3d_safestr(bone[i].name, 0);
+            ptr += sprintf(ptr, "%d %d %s\r\n", vrtxidx[bone[i].pos], vrtxidx[bone[i].ori], sn);
+            M3D_FREE(sn);
+            ptr = _m3d_prtbone(ptr, bone, numbone, i, level + 1, vrtxidx);
+        }
+    }
+    return ptr;
+}
+#endif
+
+/**
+ * Function to encode an in-memory model into on storage Model 3D format
+ */
+unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size)
+{
+#ifdef M3D_ASCII
+    const char *ol;
+    char *ptr;
+#endif
+    char vc_s, vi_s, si_s, ci_s, ti_s, bi_s, nb_s, sk_s, fc_s, hi_s, fi_s;
+    char *sn = NULL, *sl = NULL, *sa = NULL, *sd = NULL;
+    unsigned char *out = NULL, *z = NULL, weights[M3D_NUMBONE], *norm = NULL;
+    unsigned int i, j, k, l, n, len, chunklen, *length;
+    M3D_FLOAT scale = (M3D_FLOAT)0.0, min_x, max_x, min_y, max_y, min_z, max_z;
+    M3D_INDEX last, *vrtxidx = NULL, *mtrlidx = NULL, *tmapidx = NULL, *skinidx = NULL;
+    uint32_t idx, numcmap = 0, *cmap = NULL, numvrtx = 0, maxvrtx = 0, numtmap = 0, maxtmap = 0, numproc = 0;
+    uint32_t numskin = 0, maxskin = 0, numstr = 0, maxt = 0, maxbone = 0, numgrp = 0, maxgrp = 0, *grpidx = NULL;
+    uint8_t *opa;
+    m3dcd_t *cd;
+    m3dc_t *cmd;
+    m3dstr_t *str = NULL;
+    m3dvsave_t *vrtx = NULL, vertex;
+    m3dtisave_t *tmap = NULL, tcoord;
+    m3dssave_t *skin = NULL, sk;
+    m3dfsave_t *face = NULL;
+    m3dhdr_t *h = NULL;
+    m3dm_t *m;
+    m3da_t *a;
+
+    if(!model) {
+        if(size) *size = 0;
+        return NULL;
+    }
+    model->errcode = M3D_SUCCESS;
+#ifdef M3D_ASCII
+    if(flags & M3D_EXP_ASCII) quality = M3D_EXP_DOUBLE;
+#endif
+    vrtxidx = (M3D_INDEX*)M3D_MALLOC(model->numvertex * sizeof(M3D_INDEX));
+    if(!vrtxidx) goto memerr;
+    memset(vrtxidx, 255, model->numvertex * sizeof(M3D_INDEX));
+    if(model->numvertex && !(flags & M3D_EXP_NONORMAL)){
+        norm = (unsigned char*)M3D_MALLOC(model->numvertex * sizeof(unsigned char));
+        if(!norm) goto memerr;
+        memset(norm, 0, model->numvertex * sizeof(unsigned char));
+    }
+    if(model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) {
+        mtrlidx = (M3D_INDEX*)M3D_MALLOC(model->nummaterial * sizeof(M3D_INDEX));
+        if(!mtrlidx) goto memerr;
+        memset(mtrlidx, 255, model->nummaterial * sizeof(M3D_INDEX));
+        opa = (uint8_t*)M3D_MALLOC(model->nummaterial * 2 * sizeof(M3D_INDEX));
+        if(!opa) goto memerr;
+        memset(opa, 255, model->nummaterial * 2 * sizeof(M3D_INDEX));
+    }
+    if(model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) {
+        tmapidx = (M3D_INDEX*)M3D_MALLOC(model->numtmap * sizeof(M3D_INDEX));
+        if(!tmapidx) goto memerr;
+        memset(tmapidx, 255, model->numtmap * sizeof(M3D_INDEX));
+    }
+    /** collect array elements that are actually referenced **/
+    if(!(flags & M3D_EXP_NOFACE)) {
+        /* face */
+        if(model->numface && model->face) {
+            M3D_LOG("Processing mesh face");
+            face = (m3dfsave_t*)M3D_MALLOC(model->numface * sizeof(m3dfsave_t));
+            if(!face) goto memerr;
+            for(i = 0; i < model->numface; i++) {
+                memcpy(&face[i].data, &model->face[i], sizeof(m3df_t));
+                face[i].group = 0;
+                face[i].opacity = 255;
+                if(!(flags & M3D_EXP_NOMATERIAL) && model->face[i].materialid < model->nummaterial) {
+                    if(model->material[model->face[i].materialid].numprop) {
+                        mtrlidx[model->face[i].materialid] = 0;
+                        if(opa[model->face[i].materialid * 2]) {
+                            m = &model->material[model->face[i].materialid];
+                            for(j = 0; j < m->numprop; j++)
+                                if(m->prop[j].type == m3dp_Kd) {
+                                    opa[model->face[i].materialid * 2 + 1] = ((uint8_t*)&m->prop[j].value.color)[3];
+                                    break;
+                                }
+                            for(j = 0; j < m->numprop; j++)
+                                if(m->prop[j].type == m3dp_d) {
+                                    opa[model->face[i].materialid * 2 + 1] = (uint8_t)(m->prop[j].value.fnum * 255);
+                                    break;
+                                }
+                            opa[model->face[i].materialid * 2] = 0;
+                        }
+                        face[i].opacity = opa[model->face[i].materialid * 2 + 1];
+                    } else
+                        face[i].data.materialid = (M3D_INDEX)-1U;
+                }
+                for(j = 0; j < 3; j++) {
+                    k = model->face[i].vertex[j];
+                    if(k < model->numvertex)
+                        vrtxidx[k] = 0;
+                    if(!(flags & M3D_EXP_NOCMAP)) {
+                        cmap = _m3d_addcmap(cmap, &numcmap, model->vertex[k].color);
+                        if(!cmap) goto memerr;
+                    }
+                    k = model->face[i].normal[j];
+                    if(k < model->numvertex && !(flags & M3D_EXP_NONORMAL)) {
+                        vrtxidx[k] = 0;
+                        norm[k] = 1;
+                    }
+                    k = model->face[i].texcoord[j];
+                    if(k < model->numtmap && !(flags & M3D_EXP_NOTXTCRD))
+                        tmapidx[k] = 0;
+                }
+                /* convert from CW to CCW */
+                if(flags & M3D_EXP_IDOSUCK) {
+                    j = face[i].data.vertex[1];
+                    face[i].data.vertex[1] = face[i].data.vertex[2];
+                    face[i].data.vertex[2] = face[i].data.vertex[1];
+                    j = face[i].data.normal[1];
+                    face[i].data.normal[1] = face[i].data.normal[2];
+                    face[i].data.normal[2] = face[i].data.normal[1];
+                    j = face[i].data.texcoord[1];
+                    face[i].data.texcoord[1] = face[i].data.texcoord[2];
+                    face[i].data.texcoord[2] = face[i].data.texcoord[1];
+                }
+            }
+        }
+        if(model->numshape && model->shape) {
+            M3D_LOG("Processing shape face");
+            for(i = 0; i < model->numshape; i++) {
+                if(!model->shape[i].numcmd) continue;
+                str = _m3d_addstr(str, &numstr, model->shape[i].name);
+                if(!str) goto memerr;
+                for(j = 0; j < model->shape[i].numcmd; j++) {
+                    cmd = &model->shape[i].cmd[j];
+                    if(cmd->type >= (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])) || !cmd->arg)
+                        continue;
+                    if(cmd->type == m3dc_mesh) {
+                        if(numgrp + 2 < maxgrp) {
+                            maxgrp += 1024;
+                            grpidx = (uint32_t*)realloc(grpidx, maxgrp * sizeof(uint32_t));
+                            if(!grpidx) goto memerr;
+                            if(!numgrp) {
+                                grpidx[0] = 0;
+                                grpidx[1] = model->numface;
+                                numgrp += 2;
+                            }
+                        }
+                        grpidx[numgrp + 0] = cmd->arg[0];
+                        grpidx[numgrp + 1] = cmd->arg[0] + cmd->arg[1];
+                        numgrp += 2;
+                    }
+                    cd = &m3d_commandtypes[cmd->type];
+                    for(k = n = 0, l = cd->p; k < l; k++)
+                        switch(cd->a[((k - n) % (cd->p - n)) + n]) {
+                            case m3dcp_mi_t:
+                                if(!(flags & M3D_EXP_NOMATERIAL) && cmd->arg[k] < model->nummaterial)
+                                    mtrlidx[cmd->arg[k]] = 0;
+                            break;
+                            case m3dcp_ti_t:
+                                if(!(flags & M3D_EXP_NOTXTCRD) && cmd->arg[k] < model->numtmap)
+                                    tmapidx[cmd->arg[k]] = 0;
+                            break;
+                            case m3dcp_qi_t:
+                            case m3dcp_vi_t:
+                                if(cmd->arg[k] < model->numvertex)
+                                    vrtxidx[cmd->arg[k]] = 0;
+                            break;
+                            case m3dcp_va_t:
+                                n = k + 1; l += (cmd->arg[k] - 1) * (cd->p - k - 1);
+                            break;
+                        }
+                }
+            }
+        }
+        if(model->numface && face) {
+            if(numgrp && grpidx) {
+                qsort(grpidx, numgrp, sizeof(uint32_t), _m3d_grpcmp);
+                for(i = j = 0; i < model->numface && j < numgrp; i++) {
+                    while(j < numgrp && grpidx[j] < i) j++;
+                    face[i].group = j;
+                }
+            }
+            qsort(face, model->numface, sizeof(m3dfsave_t), _m3d_facecmp);
+        }
+        if(grpidx) { M3D_FREE(grpidx); grpidx = NULL; }
+        if(model->numlabel && model->label) {
+            M3D_LOG("Processing annotation labels");
+            for(i = 0; i < model->numlabel; i++) {
+                str = _m3d_addstr(str, &numstr, model->label[i].name);
+                str = _m3d_addstr(str, &numstr, model->label[i].lang);
+                str = _m3d_addstr(str, &numstr, model->label[i].text);
+                if(!(flags & M3D_EXP_NOCMAP)) {
+                    cmap = _m3d_addcmap(cmap, &numcmap, model->label[i].color);
+                    if(!cmap) goto memerr;
+                }
+                if(model->label[i].vertexid < model->numvertex)
+                    vrtxidx[model->label[i].vertexid] = 0;
+            }
+            qsort(model->label, model->numlabel, sizeof(m3dl_t), _m3d_lblcmp);
+        }
+    } else if(!(flags & M3D_EXP_NOMATERIAL)) {
+        /* without a face, simply add all materials, because it can be an mtllib */
+        for(i = 0; i < model->nummaterial; i++)
+            mtrlidx[i] = i;
+    }
+    /* bind-pose skeleton */
+    if(model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) {
+        M3D_LOG("Processing bones");
+        for(i = 0; i < model->numbone; i++) {
+            str = _m3d_addstr(str, &numstr, model->bone[i].name);
+            if(!str) goto memerr;
+            k = model->bone[i].pos;
+            if(k < model->numvertex)
+                vrtxidx[k] = 0;
+            k = model->bone[i].ori;
+            if(k < model->numvertex)
+                vrtxidx[k] = 0;
+        }
+    }
+    /* actions, animated skeleton poses */
+    if(model->numaction && model->action && !(flags & M3D_EXP_NOACTION)) {
+        M3D_LOG("Processing action list");
+        for(j = 0; j < model->numaction; j++) {
+            a = &model->action[j];
+            str = _m3d_addstr(str, &numstr, a->name);
+            if(!str) goto memerr;
+            if(a->numframe > 65535) a->numframe = 65535;
+            for(i = 0; i < a->numframe; i++) {
+                for(l = 0; l < a->frame[i].numtransform; l++) {
+                    k = a->frame[i].transform[l].pos;
+                    if(k < model->numvertex)
+                        vrtxidx[k] = 0;
+                    k = a->frame[i].transform[l].ori;
+                    if(k < model->numvertex)
+                        vrtxidx[k] = 0;
+                }
+                if(l > maxt) maxt = l;
+            }
+        }
+    }
+    /* add colors to color map and texture names to string table */
+    if(!(flags & M3D_EXP_NOMATERIAL)) {
+        M3D_LOG("Processing materials");
+        for(i = k = 0; i < model->nummaterial; i++) {
+            if(mtrlidx[i] == (M3D_INDEX)-1U || !model->material[i].numprop) continue;
+            mtrlidx[i] = k++;
+            m = &model->material[i];
+            str = _m3d_addstr(str, &numstr, m->name);
+            if(!str) goto memerr;
+            if(m->prop)
+                for(j = 0; j < m->numprop; j++) {
+                    if(!(flags & M3D_EXP_NOCMAP) && m->prop[j].type < 128) {
+                        for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) {
+                            if(m->prop[j].type == m3d_propertytypes[l].id && m3d_propertytypes[l].format == m3dpf_color) {
+                                ((uint8_t*)&m->prop[j].value.color)[3] = opa[i * 2 + 1];
+                                cmap = _m3d_addcmap(cmap, &numcmap, m->prop[j].value.color);
+                                if(!cmap) goto memerr;
+                                break;
+                            }
+                        }
+                    }
+                    if(m->prop[j].type >= 128 && m->prop[j].value.textureid < model->numtexture &&
+                        model->texture[m->prop[j].value.textureid].name) {
+                        str = _m3d_addstr(str, &numstr, model->texture[m->prop[j].value.textureid].name);
+                        if(!str) goto memerr;
+                    }
+                }
+        }
+    }
+    /* if there's only one black color, don't store it */
+    if(numcmap == 1 && cmap && !cmap[0]) numcmap = 0;
+
+    /** compress lists **/
+    if(model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) {
+        M3D_LOG("Compressing tmap");
+        tmap = (m3dtisave_t*)M3D_MALLOC(model->numtmap * sizeof(m3dtisave_t));
+        if(!tmap) goto memerr;
+        for(i = 0; i < model->numtmap; i++) {
+            if(tmapidx[i] == (M3D_INDEX)-1U) continue;
+            switch(quality) {
+                case M3D_EXP_INT8:
+                    l = model->tmap[i].u * 255; tcoord.data.u = (M3D_FLOAT)l / 255;
+                    l = model->tmap[i].v * 255; tcoord.data.v = (M3D_FLOAT)l / 255;
+                break;
+                case M3D_EXP_INT16:
+                    l = model->tmap[i].u * 65535; tcoord.data.u = (M3D_FLOAT)l / 65535;
+                    l = model->tmap[i].v * 65535; tcoord.data.v = (M3D_FLOAT)l / 65535;
+                break;
+                default:
+                    tcoord.data.u = model->tmap[i].u;
+                    tcoord.data.v = model->tmap[i].v;
+                break;
+            }
+            if(flags & M3D_EXP_FLIPTXTCRD)
+                tcoord.data.v = (M3D_FLOAT)1.0 - tcoord.data.v;
+            tcoord.oldidx = i;
+            memcpy(&tmap[numtmap++], &tcoord, sizeof(m3dtisave_t));
+        }
+        if(numtmap) {
+            qsort(tmap, numtmap, sizeof(m3dtisave_t), _m3d_ticmp);
+            memcpy(&tcoord.data, &tmap[0], sizeof(m3dti_t));
+            for(i = 0; i < numtmap; i++) {
+                if(memcmp(&tcoord.data, &tmap[i].data, sizeof(m3dti_t))) {
+                    memcpy(&tcoord.data, &tmap[i].data, sizeof(m3dti_t));
+                    maxtmap++;
+                }
+                tmap[i].newidx = maxtmap;
+                tmapidx[tmap[i].oldidx] = maxtmap;
+            }
+            maxtmap++;
+        }
+    }
+    if(model->numskin && model->skin && !(flags & M3D_EXP_NOBONE)) {
+        M3D_LOG("Compressing skin");
+        skinidx = (M3D_INDEX*)M3D_MALLOC(model->numskin * sizeof(M3D_INDEX));
+        if(!skinidx) goto memerr;
+        skin = (m3dssave_t*)M3D_MALLOC(model->numskin * sizeof(m3dssave_t));
+        if(!skin) goto memerr;
+        memset(skinidx, 255, model->numskin * sizeof(M3D_INDEX));
+        for(i = 0; i < model->numvertex; i++) {
+            if(vrtxidx[i] != (M3D_INDEX)-1U && model->vertex[i].skinid < model->numskin)
+                skinidx[model->vertex[i].skinid] = 0;
+        }
+        for(i = 0; i < model->numskin; i++) {
+            if(skinidx[i] == (M3D_INDEX)-1U) continue;
+            memset(&sk, 0, sizeof(m3dssave_t));
+            for(j = 0, min_x = (M3D_FLOAT)0.0; j < M3D_NUMBONE && model->skin[i].boneid[j] != (M3D_INDEX)-1U &&
+                model->skin[i].weight[j] > (M3D_FLOAT)0.0; j++) {
+                    sk.data.boneid[j] = model->skin[i].boneid[j];
+                    sk.data.weight[j] = model->skin[i].weight[j];
+                    min_x += sk.data.weight[j];
+            }
+            if(j > maxbone) maxbone = j;
+            if(min_x != (M3D_FLOAT)1.0 && min_x != (M3D_FLOAT)0.0)
+                for(j = 0; j < M3D_NUMBONE && sk.data.weight[j] > (M3D_FLOAT)0.0; j++)
+                    sk.data.weight[j] /= min_x;
+            sk.oldidx = i;
+            memcpy(&skin[numskin++], &sk, sizeof(m3dssave_t));
+        }
+        if(numskin) {
+            qsort(skin, numskin, sizeof(m3dssave_t), _m3d_skincmp);
+            memcpy(&sk.data, &skin[0].data, sizeof(m3ds_t));
+            for(i = 0; i < numskin; i++) {
+                if(memcmp(&sk.data, &skin[i].data, sizeof(m3ds_t))) {
+                    memcpy(&sk.data, &skin[i].data, sizeof(m3ds_t));
+                    maxskin++;
+                }
+                skin[i].newidx = maxskin;
+                skinidx[skin[i].oldidx] = maxskin;
+            }
+            maxskin++;
+        }
+    }
+
+    M3D_LOG("Compressing vertex list");
+    min_x = min_y = min_z = (M3D_FLOAT)1e10;
+    max_x = max_y = max_z = (M3D_FLOAT)-1e10;
+    if(vrtxidx) {
+        vrtx = (m3dvsave_t*)M3D_MALLOC(model->numvertex * sizeof(m3dvsave_t));
+        if(!vrtx) goto memerr;
+        for(i = numvrtx = 0; i < model->numvertex; i++) {
+            if(vrtxidx[i] == (M3D_INDEX)-1U) continue;
+            _m3d_round(quality, &model->vertex[i], &vertex.data);
+            vertex.norm = norm ? norm[i] : 0;
+            if(vertex.data.skinid != (M3D_INDEX)-2U && !vertex.norm) {
+                vertex.data.skinid = vertex.data.skinid != (M3D_INDEX)-1U && skinidx ? skinidx[vertex.data.skinid] : (M3D_INDEX)-1U;
+                if(vertex.data.x > max_x) max_x = vertex.data.x;
+                if(vertex.data.x < min_x) min_x = vertex.data.x;
+                if(vertex.data.y > max_y) max_y = vertex.data.y;
+                if(vertex.data.y < min_y) min_y = vertex.data.y;
+                if(vertex.data.z > max_z) max_z = vertex.data.z;
+                if(vertex.data.z < min_z) min_z = vertex.data.z;
+            }
+#ifdef M3D_VERTEXTYPE
+            vertex.data.type = 0;
+#endif
+            vertex.oldidx = i;
+            memcpy(&vrtx[numvrtx++], &vertex, sizeof(m3dvsave_t));
+        }
+        if(numvrtx) {
+            qsort(vrtx, numvrtx, sizeof(m3dvsave_t), _m3d_vrtxcmp);
+            memcpy(&vertex.data, &vrtx[0].data, sizeof(m3dv_t));
+            for(i = 0; i < numvrtx; i++) {
+                if(memcmp(&vertex.data, &vrtx[i].data, vrtx[i].norm ? 3 * sizeof(M3D_FLOAT) : sizeof(m3dv_t))) {
+                    memcpy(&vertex.data, &vrtx[i].data, sizeof(m3dv_t));
+                    maxvrtx++;
+                }
+                vrtx[i].newidx = maxvrtx;
+                vrtxidx[vrtx[i].oldidx] = maxvrtx;
+            }
+            maxvrtx++;
+        }
+    }
+    if(skinidx) { M3D_FREE(skinidx); skinidx = NULL; }
+    if(norm) { M3D_FREE(norm); norm = NULL; }
+
+    /* normalize to bounding cube */
+    if(numvrtx && !(flags & M3D_EXP_NORECALC)) {
+        M3D_LOG("Normalizing coordinates");
+        if(min_x < (M3D_FLOAT)0.0) min_x = -min_x;
+        if(max_x < (M3D_FLOAT)0.0) max_x = -max_x;
+        if(min_y < (M3D_FLOAT)0.0) min_y = -min_y;
+        if(max_y < (M3D_FLOAT)0.0) max_y = -max_y;
+        if(min_z < (M3D_FLOAT)0.0) min_z = -min_z;
+        if(max_z < (M3D_FLOAT)0.0) max_z = -max_z;
+        scale = min_x;
+        if(max_x > scale) scale = max_x;
+        if(min_y > scale) scale = min_y;
+        if(max_y > scale) scale = max_y;
+        if(min_z > scale) scale = min_z;
+        if(max_z > scale) scale = max_z;
+        if(scale == (M3D_FLOAT)0.0) scale = (M3D_FLOAT)1.0;
+        if(scale != (M3D_FLOAT)1.0) {
+            for(i = 0; i < numvrtx; i++) {
+                if(vrtx[i].data.skinid == (M3D_INDEX)-2U) continue;
+                vrtx[i].data.x /= scale;
+                vrtx[i].data.y /= scale;
+                vrtx[i].data.z /= scale;
+            }
+        }
+    }
+    if(model->scale > (M3D_FLOAT)0.0) scale = model->scale;
+    if(scale <= (M3D_FLOAT)0.0) scale = (M3D_FLOAT)1.0;
+
+    /* meta info */
+    sn = _m3d_safestr(model->name && *model->name ? model->name : (char*)"(noname)", 2);
+    sl = _m3d_safestr(model->license ? model->license : (char*)"MIT", 2);
+    sa = _m3d_safestr(model->author ? model->author : getenv("LOGNAME"), 2);
+    if(!sn || !sl || !sa) {
+memerr: if(vrtxidx) M3D_FREE(vrtxidx);
+        if(mtrlidx) M3D_FREE(mtrlidx);
+        if(tmapidx) M3D_FREE(tmapidx);
+        if(skinidx) M3D_FREE(skinidx);
+        if(grpidx) M3D_FREE(grpidx);
+        if(norm) M3D_FREE(norm);
+        if(face) M3D_FREE(face);
+        if(cmap) M3D_FREE(cmap);
+        if(tmap) M3D_FREE(tmap);
+        if(skin) M3D_FREE(skin);
+        if(str) M3D_FREE(str);
+        if(vrtx) M3D_FREE(vrtx);
+        if(sn) M3D_FREE(sn);
+        if(sl) M3D_FREE(sl);
+        if(sa) M3D_FREE(sa);
+        if(sd) M3D_FREE(sd);
+        if(out) M3D_FREE(out);
+        if(h) M3D_FREE(h);
+        M3D_LOG("Out of memory");
+        model->errcode = M3D_ERR_ALLOC;
+        return NULL;
+    }
+
+    M3D_LOG("Serializing model");
+#ifdef M3D_ASCII
+    if(flags & M3D_EXP_ASCII) {
+        /* use CRLF to make model creators on Win happy... */
+        sd = _m3d_safestr(model->desc, 1);
+        if(!sd) goto memerr;
+        ol = setlocale(LC_NUMERIC, NULL);
+        setlocale(LC_NUMERIC, "C");
+        /* header */
+        len = 64 + strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd);
+        out = (unsigned char*)M3D_MALLOC(len);
+        if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+        ptr = (char*)out;
+        ptr += sprintf(ptr, "3dmodel %g\r\n%s\r\n%s\r\n%s\r\n%s\r\n\r\n", scale,
+            sn, sl, sa, sd);
+        M3D_FREE(sl); M3D_FREE(sa); M3D_FREE(sd);
+        sl = sa = sd = NULL;
+        /* preview chunk */
+        if(model->preview.data && model->preview.length) {
+            sl = _m3d_safestr(sn, 0);
+            if(sl) {
+                ptr -= (uint64_t)out; len = (uint64_t)ptr + 20;
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out;
+                if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                ptr += sprintf(ptr, "Preview\r\n%s.png\r\n\r\n", sl);
+                M3D_FREE(sl); sl = NULL;
+            }
+        }
+        M3D_FREE(sn);  sn = NULL;
+        /* texture map */
+        if(numtmap && tmap && !(flags & M3D_EXP_NOTXTCRD) && !(flags & M3D_EXP_NOFACE)) {
+            ptr -= (uint64_t)out; len = (uint64_t)ptr + maxtmap * 32 + 12;
+            out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out;
+            if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+            ptr += sprintf(ptr, "Textmap\r\n");
+            last = (M3D_INDEX)-1U;
+            for(i = 0; i < numtmap; i++) {
+                if(tmap[i].newidx == last) continue;
+                last = tmap[i].newidx;
+                ptr += sprintf(ptr, "%g %g\r\n", tmap[i].data.u, tmap[i].data.v);
+            }
+            ptr += sprintf(ptr, "\r\n");
+        }
+        /* vertex chunk */
+        if(numvrtx && vrtx && !(flags & M3D_EXP_NOFACE)) {
+            ptr -= (uint64_t)out; len = (uint64_t)ptr + maxvrtx * 128 + 10;
+            out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out;
+            if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+            ptr += sprintf(ptr, "Vertex\r\n");
+            last = (M3D_INDEX)-1U;
+            for(i = 0; i < numvrtx; i++) {
+                if(vrtx[i].newidx == last) continue;
+                last = vrtx[i].newidx;
+                ptr += sprintf(ptr, "%g %g %g %g", vrtx[i].data.x, vrtx[i].data.y, vrtx[i].data.z, vrtx[i].data.w);
+                if(!(flags & M3D_EXP_NOCMAP) && vrtx[i].data.color)
+                    ptr += sprintf(ptr, " #%08x", vrtx[i].data.color);
+                if(!(flags & M3D_EXP_NOBONE) && model->numbone && maxskin && vrtx[i].data.skinid < M3D_INDEXMAX) {
+                    if(skin[vrtx[i].data.skinid].data.weight[0] == (M3D_FLOAT)1.0)
+                        ptr += sprintf(ptr, " %d", skin[vrtx[i].data.skinid].data.boneid[0]);
+                    else
+                        for(j = 0; j < M3D_NUMBONE && skin[vrtx[i].data.skinid].data.boneid[j] != (M3D_INDEX)-1U &&
+                            skin[vrtx[i].data.skinid].data.weight[j] > (M3D_FLOAT)0.0; j++)
+                            ptr += sprintf(ptr, " %d:%g", skin[vrtx[i].data.skinid].data.boneid[j],
+                                skin[vrtx[i].data.skinid].data.weight[j]);
+                }
+                ptr += sprintf(ptr, "\r\n");
+            }
+            ptr += sprintf(ptr, "\r\n");
+        }
+        /* bones chunk */
+        if(model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) {
+            ptr -= (uint64_t)out; len = (uint64_t)ptr + 9;
+            for(i = 0; i < model->numbone; i++) {
+                len += strlen(model->bone[i].name) + 128;
+            }
+            out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out;
+            if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+            ptr += sprintf(ptr, "Bones\r\n");
+            ptr = _m3d_prtbone(ptr, model->bone, model->numbone, (M3D_INDEX)-1U, 0, vrtxidx);
+            ptr += sprintf(ptr, "\r\n");
+        }
+        /* materials */
+        if(model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) {
+            for(j = 0; j < model->nummaterial; j++) {
+                if(mtrlidx[j] == (M3D_INDEX)-1U || !model->material[j].numprop || !model->material[j].prop) continue;
+                m = &model->material[j];
+                sn = _m3d_safestr(m->name, 0);
+                if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(sn) + 12;
+                for(i = 0; i < m->numprop; i++) {
+                    if(m->prop[i].type < 128)
+                        len += 32;
+                    else if(m->prop[i].value.textureid < model->numtexture && model->texture[m->prop[i].value.textureid].name)
+                        len += strlen(model->texture[m->prop[i].value.textureid].name) + 16;
+                }
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out;
+                if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                ptr += sprintf(ptr, "Material %s\r\n", sn);
+                M3D_FREE(sn); sn = NULL;
+                for(i = 0; i < m->numprop; i++) {
+                    k = 256;
+                    if(m->prop[i].type >= 128) {
+                        for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++)
+                            if(m->prop[i].type == m3d_propertytypes[l].id) {
+                                sn = m3d_propertytypes[l].key;
+                                break;
+                            }
+                        if(!sn)
+                            for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++)
+                                if(m->prop[i].type - 128 == m3d_propertytypes[l].id) {
+                                    sn = m3d_propertytypes[l].key;
+                                    break;
+                                }
+                        k = sn ? m3dpf_map : 256;
+                    } else {
+                        for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++)
+                            if(m->prop[i].type == m3d_propertytypes[l].id) {
+                                sn = m3d_propertytypes[l].key;
+                                k = m3d_propertytypes[l].format;
+                                break;
+                            }
+                    }
+                    switch(k) {
+                        case m3dpf_color: ptr += sprintf(ptr, "%s #%08x\r\n", sn, m->prop[i].value.color); break;
+                        case m3dpf_uint8:
+                        case m3dpf_uint16:
+                        case m3dpf_uint32: ptr += sprintf(ptr, "%s %d\r\n", sn, m->prop[i].value.num); break;
+                        case m3dpf_float:  ptr += sprintf(ptr, "%s %g\r\n", sn, m->prop[i].value.fnum); break;
+                        case m3dpf_map:
+                            if(m->prop[i].value.textureid < model->numtexture &&
+                                model->texture[m->prop[i].value.textureid].name) {
+                                sl = _m3d_safestr(model->texture[m->prop[i].value.textureid].name, 0);
+                                if(!sl) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                                if(*sl)
+                                    ptr += sprintf(ptr, "map_%s %s\r\n", sn, sl);
+                                M3D_FREE(sn); M3D_FREE(sl); sl = NULL;
+                            }
+                        break;
+                    }
+                    sn = NULL;
+                }
+                ptr += sprintf(ptr, "\r\n");
+            }
+        }
+        /* procedural face */
+        if(model->numinlined && model->inlined && !(flags & M3D_EXP_NOFACE)) {
+            /* all inlined assets which are not textures should be procedural surfaces */
+            for(j = 0; j < model->numinlined; j++) {
+                if(!model->inlined[j].name || !*model->inlined[j].name || !model->inlined[j].length || !model->inlined[j].data ||
+                 (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G'))
+                    continue;
+                for(i = k = 0; i < model->numtexture; i++) {
+                    if(!strcmp(model->inlined[j].name, model->texture[i].name)) { k = 1; break; }
+                }
+                if(k) continue;
+                sn = _m3d_safestr(model->inlined[j].name, 0);
+                if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(sn) + 18;
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out;
+                if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                ptr += sprintf(ptr, "Procedural\r\n%s\r\n\r\n", sn);
+                M3D_FREE(sn); sn = NULL;
+            }
+        }
+        /* mesh face */
+        if(model->numface && face && !(flags & M3D_EXP_NOFACE)) {
+            ptr -= (uint64_t)out; len = (uint64_t)ptr + model->numface * 128 + 6;
+            last = (M3D_INDEX)-1U;
+            if(!(flags & M3D_EXP_NOMATERIAL))
+                for(i = 0; i < model->numface; i++) {
+                    j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : (M3D_INDEX)-1U;
+                    if(j != last) {
+                        last = j;
+                        if(last < model->nummaterial)
+                            len += strlen(model->material[last].name);
+                        len += 6;
+                    }
+                }
+            out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out;
+            if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+            ptr += sprintf(ptr, "Mesh\r\n");
+            last = (M3D_INDEX)-1U;
+            for(i = 0; i < model->numface; i++) {
+                j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : (M3D_INDEX)-1U;
+                if(!(flags & M3D_EXP_NOMATERIAL) && j != last) {
+                    last = j;
+                    if(last < model->nummaterial) {
+                        sn = _m3d_safestr(model->material[last].name, 0);
+                        if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                        ptr += sprintf(ptr, "use %s\r\n", sn);
+                        M3D_FREE(sn); sn = NULL;
+                    } else
+                        ptr += sprintf(ptr, "use\r\n");
+                }
+                /* hardcoded triangles. Should be repeated as many times as the number of edges in polygon */
+                for(j = 0; j < 3; j++) {
+                    ptr += sprintf(ptr, "%s%d", j?" ":"", vrtxidx[face[i].data.vertex[j]]);
+                    k = -1U;
+                    if(!(flags & M3D_EXP_NOTXTCRD) && (face[i].data.texcoord[j] != (M3D_INDEX)-1U) &&
+                        (tmapidx[face[i].data.texcoord[j]] != (M3D_INDEX)-1U)) {
+                            k = tmapidx[face[i].data.texcoord[j]];
+                            ptr += sprintf(ptr, "/%d", k);
+                    }
+                    if(!(flags & M3D_EXP_NONORMAL) && (face[i].data.normal[j] != (M3D_INDEX)-1U))
+                        ptr += sprintf(ptr, "%s/%d", k == -1U? "/" : "", vrtxidx[face[i].data.normal[j]]);
+                }
+                ptr += sprintf(ptr, "\r\n");
+            }
+            ptr += sprintf(ptr, "\r\n");
+        }
+        /* mathematical shapes face */
+        if(model->numshape && model->numshape && !(flags & M3D_EXP_NOFACE)) {
+            for(j = 0; j < model->numshape; j++) {
+                sn = _m3d_safestr(model->shape[j].name, 0);
+                if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(sn) + 33;
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out;
+                if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                ptr += sprintf(ptr, "Shape %s\r\n", sn);
+                M3D_FREE(sn); sn = NULL;
+                if(model->shape[j].group != (M3D_INDEX)-1U && !(flags & M3D_EXP_NOBONE))
+                    ptr += sprintf(ptr, "group %d\r\n", model->shape[j].group);
+                for(i = 0; i < model->shape[j].numcmd; i++) {
+                    cmd = &model->shape[j].cmd[i];
+                    if(cmd->type >= (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])) || !cmd->arg)
+                        continue;
+                    cd = &m3d_commandtypes[cmd->type];
+                    ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(cd->key) + 3;
+                    for(k = 0; k < cd->p; k++)
+                        switch(cd->a[k]) {
+                            case m3dcp_mi_t: if(cmd->arg[k] != -1U) { len += strlen(model->material[cmd->arg[k]].name) + 1; } break;
+                            case m3dcp_va_t: len += cmd->arg[k] * (cd->p - k - 1) * 16; k = cd->p; break;
+                            default: len += 16; break;
+                        }
+                    out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out;
+                    if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                    ptr += sprintf(ptr, "%s", cd->key);
+                    for(k = n = 0, l = cd->p; k < l; k++) {
+                        switch(cd->a[((k - n) % (cd->p - n)) + n]) {
+                            case m3dcp_mi_t:
+                                if(cmd->arg[k] != -1U) {
+                                    sn = _m3d_safestr(model->material[cmd->arg[k]].name, 0);
+                                    if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                                    ptr += sprintf(ptr, " %s", sn);
+                                    M3D_FREE(sn); sn = NULL;
+                                }
+                            break;
+                            case m3dcp_vc_t: ptr += sprintf(ptr, " %g", *((float*)&cmd->arg[k])); break;
+                            case m3dcp_va_t: ptr += sprintf(ptr, " %d[", cmd->arg[k]);
+                                n = k + 1; l += (cmd->arg[k] - 1) * (cd->p - k - 1);
+                            break;
+                            default: ptr += sprintf(ptr, " %d", cmd->arg[k]); break;
+                        }
+                    }
+                    ptr += sprintf(ptr, "%s\r\n", l > cd->p ? " ]" : "");
+                }
+                ptr += sprintf(ptr, "\r\n");
+            }
+        }
+        /* annotation labels */
+        if(model->numlabel && model->label && !(flags & M3D_EXP_NOFACE)) {
+            for(i = 0, j = 3, length = NULL; i < model->numlabel; i++) {
+                if(model->label[i].name) j += strlen(model->label[i].name);
+                if(model->label[i].lang) j += strlen(model->label[i].lang);
+                if(model->label[i].text) j += strlen(model->label[i].text);
+                j += 40;
+            }
+            ptr -= (uint64_t)out; len = (uint64_t)ptr + j;
+            out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out;
+            if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+            for(i = 0; i < model->numlabel; i++) {
+                if(!i || _m3d_strcmp(sl, model->label[i].lang) || _m3d_strcmp(sn, model->label[i].name)) {
+                    sl = model->label[i].lang;
+                    sn = model->label[i].name;
+                    sd = _m3d_safestr(sn, 0);
+                    if(!sd) { setlocale(LC_NUMERIC, ol); sn = sl = NULL; goto memerr; }
+                    if(i) ptr += sprintf(ptr, "\r\n");
+                    ptr += sprintf(ptr, "Labels %s\r\n", sd);
+                    M3D_FREE(sd); sd = NULL;
+                    if(model->label[i].color)
+                        ptr += sprintf(ptr, "color #0x%08x\r\n", model->label[i].color);
+                    if(sl && *sl) {
+                        sd = _m3d_safestr(sl, 0);
+                        if(!sd) { setlocale(LC_NUMERIC, ol); sn = sl = NULL; goto memerr; }
+                        ptr += sprintf(ptr, "lang %s\r\n", sd);
+                        M3D_FREE(sd); sd = NULL;
+                    }
+                }
+                sd = _m3d_safestr(model->label[i].text, 2);
+                if(!sd) { setlocale(LC_NUMERIC, ol); sn = sl = NULL; goto memerr; }
+                ptr += sprintf(ptr, "%d %s\r\n", model->label[i].vertexid, sd);
+                M3D_FREE(sd); sd = NULL;
+            }
+            ptr += sprintf(ptr, "\r\n");
+            sn = sl = NULL;
+        }
+        /* actions */
+        if(model->numaction && model->action && !(flags & M3D_EXP_NOACTION)) {
+            for(j = 0; j < model->numaction; j++) {
+                a = &model->action[j];
+                sn = _m3d_safestr(a->name, 0);
+                if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(sn) + 48;
+                for(i = 0; i < a->numframe; i++)
+                    len += a->frame[i].numtransform * 128 + 8;
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out;
+                if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                ptr += sprintf(ptr, "Action %d %s\r\n", a->durationmsec, sn);
+                M3D_FREE(sn); sn = NULL;
+                for(i = 0; i < a->numframe; i++) {
+                    ptr += sprintf(ptr, "frame %d\r\n", a->frame[i].msec);
+                    for(k = 0; k < a->frame[i].numtransform; k++) {
+                        ptr += sprintf(ptr, "%d %d %d\r\n", a->frame[i].transform[k].boneid,
+                            vrtxidx[a->frame[i].transform[k].pos], vrtxidx[a->frame[i].transform[k].ori]);
+                    }
+                }
+                ptr += sprintf(ptr, "\r\n");
+            }
+        }
+        /* inlined assets */
+        if(model->numinlined && model->inlined) {
+            for(i = j = 0; i < model->numinlined; i++)
+                if(model->inlined[i].name)
+                    j += strlen(model->inlined[i].name) + 6;
+            if(j > 0) {
+                ptr -= (uint64_t)out; len = (uint64_t)ptr + j + 16;
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out;
+                if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                ptr += sprintf(ptr, "Assets\r\n");
+                for(i = 0; i < model->numinlined; i++)
+                    if(model->inlined[i].name)
+                        ptr += sprintf(ptr, "%s%s\r\n", model->inlined[i].name, strrchr(model->inlined[i].name, '.') ? "" : ".png");
+                ptr += sprintf(ptr, "\r\n");
+            }
+        }
+        /* extra info */
+        if(model->numextra && (flags & M3D_EXP_EXTRA)) {
+            for(i = 0; i < model->numextra; i++) {
+                if(model->extra[i]->length < 9) continue;
+                ptr -= (uint64_t)out; len = (uint64_t)ptr + 17 + model->extra[i]->length * 3;
+                out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out;
+                if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
+                ptr += sprintf(ptr, "Extra %c%c%c%c\r\n",
+                    model->extra[i]->magic[0] > ' ' ? model->extra[i]->magic[0] : '_',
+                    model->extra[i]->magic[1] > ' ' ? model->extra[i]->magic[1] : '_',
+                    model->extra[i]->magic[2] > ' ' ? model->extra[i]->magic[2] : '_',
+                    model->extra[i]->magic[3] > ' ' ? model->extra[i]->magic[3] : '_');
+                for(j = 0; j < model->extra[i]->length; j++)
+                    ptr += sprintf(ptr, "%02x ", *((unsigned char *)model->extra + sizeof(m3dchunk_t) + j));
+                ptr--;
+                ptr += sprintf(ptr, "\r\n\r\n");
+            }
+        }
+        setlocale(LC_NUMERIC, ol);
+        len = (uint64_t)ptr - (uint64_t)out;
+        out = (unsigned char*)M3D_REALLOC(out, len + 1);
+        if(!out) goto memerr;
+        out[len] = 0;
+    } else
+#endif
+    {
+        /* stricly only use LF (newline) in binary */
+        sd = _m3d_safestr(model->desc, 3);
+        if(!sd) goto memerr;
+        /* header */
+        h = (m3dhdr_t*)M3D_MALLOC(sizeof(m3dhdr_t) + strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd) + 4);
+        if(!h) goto memerr;
+        memcpy((uint8_t*)h, "HEAD", 4);
+        h->length = sizeof(m3dhdr_t);
+        h->scale = scale;
+        i = strlen(sn); memcpy((uint8_t*)h + h->length, sn, i+1); h->length += i+1; M3D_FREE(sn);
+        i = strlen(sl); memcpy((uint8_t*)h + h->length, sl, i+1); h->length += i+1; M3D_FREE(sl);
+        i = strlen(sa); memcpy((uint8_t*)h + h->length, sa, i+1); h->length += i+1; M3D_FREE(sa);
+        i = strlen(sd); memcpy((uint8_t*)h + h->length, sd, i+1); h->length += i+1; M3D_FREE(sd);
+        sn = sl = sa = sd = NULL;
+        if(model->inlined)
+            for(i = 0; i < model->numinlined; i++) {
+                if(model->inlined[i].name && *model->inlined[i].name && model->inlined[i].length > 0) {
+                    str = _m3d_addstr(str, &numstr, model->inlined[i].name);
+                    if(!str) goto memerr;
+                }
+            }
+        if(str)
+            for(i = 0; i < numstr; i++) {
+                h = _m3d_addhdr(h, &str[i]);
+                if(!h) goto memerr;
+            }
+        vc_s = quality == M3D_EXP_INT8? 1 : (quality == M3D_EXP_INT16? 2 : (quality == M3D_EXP_DOUBLE? 8 : 4));
+        vi_s = maxvrtx < 254 ? 1 : (maxvrtx < 65534 ? 2 : 4);
+        si_s = h->length - 16 < 254 ? 1 : (h->length - 16 < 65534 ? 2 : 4);
+        ci_s = !numcmap || !cmap ? 0 : (numcmap < 254 ? 1 : (numcmap < 65534 ? 2 : 4));
+        ti_s = !maxtmap || !tmap ? 0 : (maxtmap < 254 ? 1 : (maxtmap < 65534 ? 2 : 4));
+        bi_s = !model->numbone || !model->bone || (flags & M3D_EXP_NOBONE)? 0 : (model->numbone < 254 ? 1 :
+            (model->numbone < 65534 ? 2 : 4));
+        nb_s = maxbone < 2 ? 1 : (maxbone == 2 ? 2 : (maxbone <= 4 ? 4 : 8));
+        sk_s = !bi_s || !maxskin || !skin ? 0 : (maxskin < 254 ? 1 : (maxskin < 65534 ? 2 : 4));
+        fc_s = maxt < 254 ? 1 : (maxt < 65534 ? 2 : 4);
+        hi_s = !model->numshape || !model->shape || (flags & M3D_EXP_NOFACE)? 0 : (model->numshape < 254 ? 1 :
+            (model->numshape < 65534 ? 2 : 4));
+        fi_s = !model->numface || !model->face || (flags & M3D_EXP_NOFACE)? 0 : (model->numface < 254 ? 1 :
+            (model->numface < 65534 ? 2 : 4));
+        h->types =  (vc_s == 8 ? (3<<0) : (vc_s == 2 ? (1<<0) : (vc_s == 1 ? (0<<0) : (2<<0)))) |
+                    (vi_s == 2 ? (1<<2) : (vi_s == 1 ? (0<<2) : (2<<2))) |
+                    (si_s == 2 ? (1<<4) : (si_s == 1 ? (0<<4) : (2<<4))) |
+                    (ci_s == 2 ? (1<<6) : (ci_s == 1 ? (0<<6) : (ci_s == 4 ? (2<<6) : (3<<6)))) |
+                    (ti_s == 2 ? (1<<8) : (ti_s == 1 ? (0<<8) : (ti_s == 4 ? (2<<8) : (3<<8)))) |
+                    (bi_s == 2 ? (1<<10): (bi_s == 1 ? (0<<10): (bi_s == 4 ? (2<<10) : (3<<10)))) |
+                    (nb_s == 2 ? (1<<12): (nb_s == 1 ? (0<<12): (2<<12))) |
+                    (sk_s == 2 ? (1<<14): (sk_s == 1 ? (0<<14): (sk_s == 4 ? (2<<14) : (3<<14)))) |
+                    (fc_s == 2 ? (1<<16): (fc_s == 1 ? (0<<16): (2<<16))) |
+                    (hi_s == 2 ? (1<<18): (hi_s == 1 ? (0<<18): (hi_s == 4 ? (2<<18) : (3<<18)))) |
+                    (fi_s == 2 ? (1<<20): (fi_s == 1 ? (0<<20): (fi_s == 4 ? (2<<20) : (3<<20))));
+        len = h->length;
+        /* preview image chunk, must be the first if exists */
+        if(model->preview.data && model->preview.length) {
+            chunklen = 8 + model->preview.length;
+            h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen);
+            if(!h) goto memerr;
+            memcpy((uint8_t*)h + len, "PRVW", 4);
+            *((uint32_t*)((uint8_t*)h + len + 4)) = chunklen;
+            memcpy((uint8_t*)h + len + 8, model->preview.data, model->preview.length);
+            len += chunklen;
+        }
+        /* color map */
+        if(numcmap && cmap && ci_s < 4 && !(flags & M3D_EXP_NOCMAP)) {
+            chunklen = 8 + numcmap * sizeof(uint32_t);
+            h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen);
+            if(!h) goto memerr;
+            memcpy((uint8_t*)h + len, "CMAP", 4);
+            *((uint32_t*)((uint8_t*)h + len + 4)) = chunklen;
+            memcpy((uint8_t*)h + len + 8, cmap, chunklen - 8);
+            len += chunklen;
+        } else numcmap = 0;
+        /* texture map */
+        if(numtmap && tmap && !(flags & M3D_EXP_NOTXTCRD) && !(flags & M3D_EXP_NOFACE)) {
+            chunklen = 8 + maxtmap * vc_s * 2;
+            h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen);
+            if(!h) goto memerr;
+            memcpy((uint8_t*)h + len, "TMAP", 4);
+            length = (uint32_t*)((uint8_t*)h + len + 4);
+            out = (uint8_t*)h + len + 8;
+            last = (M3D_INDEX)-1U;
+            for(i = 0; i < numtmap; i++) {
+                if(tmap[i].newidx == last) continue;
+                last = tmap[i].newidx;
+                switch(vc_s) {
+                    case 1: *out++ = (uint8_t)(tmap[i].data.u * 255); *out++ = (uint8_t)(tmap[i].data.v * 255); break;
+                    case 2:
+                        *((uint16_t*)out) = (uint16_t)(tmap[i].data.u * 65535); out += 2;
+                        *((uint16_t*)out) = (uint16_t)(tmap[i].data.v * 65535); out += 2;
+                    break;
+                    case 4:  *((float*)out) = tmap[i].data.u; out += 4;  *((float*)out) = tmap[i].data.v; out += 4; break;
+                    case 8: *((double*)out) = tmap[i].data.u; out += 8; *((double*)out) = tmap[i].data.v; out += 8; break;
+                }
+            }
+            *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len);
+            out = NULL;
+            len += *length;
+        }
+        /* vertex */
+        if(numvrtx && vrtx) {
+            chunklen = 8 + maxvrtx * (ci_s + sk_s + 4 * vc_s);
+            h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen);
+            if(!h) goto memerr;
+            memcpy((uint8_t*)h + len, "VRTS", 4);
+            length = (uint32_t*)((uint8_t*)h + len + 4);
+            out = (uint8_t*)h + len + 8;
+            last = (M3D_INDEX)-1U;
+            for(i = 0; i < numvrtx; i++) {
+                if(vrtx[i].newidx == last) continue;
+                last = vrtx[i].newidx;
+                switch(vc_s) {
+                    case 1:
+                        *out++ = (int8_t)(vrtx[i].data.x * 127);
+                        *out++ = (int8_t)(vrtx[i].data.y * 127);
+                        *out++ = (int8_t)(vrtx[i].data.z * 127);
+                        *out++ = (int8_t)(vrtx[i].data.w * 127);
+                    break;
+                    case 2:
+                        *((int16_t*)out) = (int16_t)(vrtx[i].data.x * 32767); out += 2;
+                        *((int16_t*)out) = (int16_t)(vrtx[i].data.y * 32767); out += 2;
+                        *((int16_t*)out) = (int16_t)(vrtx[i].data.z * 32767); out += 2;
+                        *((int16_t*)out) = (int16_t)(vrtx[i].data.w * 32767); out += 2;
+                    break;
+                    case 4:
+                        *((float*)out) = vrtx[i].data.x; out += 4;
+                        *((float*)out) = vrtx[i].data.y; out += 4;
+                        *((float*)out) = vrtx[i].data.z; out += 4;
+                        *((float*)out) = vrtx[i].data.w; out += 4;
+                    break;
+                    case 8:
+                        *((double*)out) = vrtx[i].data.x; out += 8;
+                        *((double*)out) = vrtx[i].data.y; out += 8;
+                        *((double*)out) = vrtx[i].data.z; out += 8;
+                        *((double*)out) = vrtx[i].data.w; out += 8;
+                    break;
+                }
+                idx = _m3d_cmapidx(cmap, numcmap, vrtx[i].data.color);
+                switch(ci_s) {
+                    case 1: *out++ = (uint8_t)(idx); break;
+                    case 2: *((uint16_t*)out) = (uint16_t)(idx); out += 2; break;
+                    case 4: *((uint32_t*)out) = vrtx[i].data.color; out += 4; break;
+                }
+                out = _m3d_addidx(out, sk_s, vrtx[i].data.skinid);
+            }
+            *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len);
+            out = NULL;
+            len += *length;
+        }
+        /* bones chunk */
+        if(model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) {
+            i = 8 + bi_s + sk_s + model->numbone * (bi_s + si_s + 2*vi_s);
+            chunklen = i + numskin * nb_s * (bi_s + 1);
+            h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen);
+            if(!h) goto memerr;
+            memcpy((uint8_t*)h + len, "BONE", 4);
+            length = (uint32_t*)((uint8_t*)h + len + 4);
+            out = (uint8_t*)h + len + 8;
+            out = _m3d_addidx(out, bi_s, model->numbone);
+            out = _m3d_addidx(out, sk_s, maxskin);
+            for(i = 0; i < model->numbone; i++) {
+                out = _m3d_addidx(out, bi_s, model->bone[i].parent);
+                out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->bone[i].name));
+                out = _m3d_addidx(out, vi_s, vrtxidx[model->bone[i].pos]);
+                out = _m3d_addidx(out, vi_s, vrtxidx[model->bone[i].ori]);
+            }
+            if(numskin && skin && sk_s) {
+                last = (M3D_INDEX)-1U;
+                for(i = 0; i < numskin; i++) {
+                    if(skin[i].newidx == last) continue;
+                    last = skin[i].newidx;
+                    memset(&weights, 0, nb_s);
+                    for(j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != (M3D_INDEX)-1U &&
+                        skin[i].data.weight[j] > (M3D_FLOAT)0.0; j++)
+                            weights[j] = (uint8_t)(skin[i].data.weight[j] * 255);
+                    switch(nb_s) {
+                        case 1: weights[0] = 255; break;
+                        case 2: *((uint16_t*)out) = *((uint16_t*)&weights[0]); out += 2; break;
+                        case 4: *((uint32_t*)out) = *((uint32_t*)&weights[0]); out += 4; break;
+                        case 8: *((uint64_t*)out) = *((uint64_t*)&weights[0]); out += 8; break;
+                    }
+                    for(j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != (M3D_INDEX)-1U && weights[j]; j++) {
+                        out = _m3d_addidx(out, bi_s, skin[i].data.boneid[j]);
+                        *length += bi_s;
+                    }
+                }
+            }
+            *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len);
+            out = NULL;
+            len += *length;
+        }
+        /* materials */
+        if(model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) {
+            for(j = 0; j < model->nummaterial; j++) {
+                if(mtrlidx[j] == (M3D_INDEX)-1U || !model->material[j].numprop || !model->material[j].prop) continue;
+                m = &model->material[j];
+                chunklen = 12 + si_s + m->numprop * 5;
+                h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen);
+                if(!h) goto memerr;
+                memcpy((uint8_t*)h + len, "MTRL", 4);
+                length = (uint32_t*)((uint8_t*)h + len + 4);
+                out = (uint8_t*)h + len + 8;
+                out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, m->name));
+                for(i = 0; i < m->numprop; i++) {
+                    if(m->prop[i].type >= 128) {
+                        if(m->prop[i].value.textureid >= model->numtexture ||
+                            !model->texture[m->prop[i].value.textureid].name) continue;
+                        k = m3dpf_map;
+                    } else {
+                        for(k = 256, l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++)
+                            if(m->prop[i].type == m3d_propertytypes[l].id) { k = m3d_propertytypes[l].format; break; }
+                    }
+                    if(k == 256) continue;
+                    *out++ = m->prop[i].type;
+                    switch(k) {
+                        case m3dpf_color:
+                            if(!(flags & M3D_EXP_NOCMAP)) {
+                                idx = _m3d_cmapidx(cmap, numcmap, m->prop[i].value.color);
+                                switch(ci_s) {
+                                    case 1: *out++ = (uint8_t)(idx); break;
+                                    case 2: *((uint16_t*)out) = (uint16_t)(idx); out += 2; break;
+                                    case 4: *((uint32_t*)out) = (uint32_t)(m->prop[i].value.color); out += 4; break;
+                                }
+                            } else out--;
+                        break;
+                        case m3dpf_uint8:  *out++ = m->prop[i].value.num; break;
+                        case m3dpf_uint16: *((uint16_t*)out) = m->prop[i].value.num; out += 2; break;
+                        case m3dpf_uint32: *((uint32_t*)out) = m->prop[i].value.num; out += 4; break;
+                        case m3dpf_float:  *((float*)out) = m->prop[i].value.fnum; out += 4; break;
+
+                        case m3dpf_map:
+                            idx = _m3d_stridx(str, numstr, model->texture[m->prop[i].value.textureid].name);
+                            out = _m3d_addidx(out, si_s, idx);
+                        break;
+                    }
+                }
+                *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len);
+                len += *length;
+                out = NULL;
+            }
+        }
+        /* procedural face */
+        if(model->numinlined && model->inlined && !(flags & M3D_EXP_NOFACE)) {
+            /* all inlined assets which are not textures should be procedural surfaces */
+            for(j = 0; j < model->numinlined; j++) {
+                if(!model->inlined[j].name || !model->inlined[j].name[0] || model->inlined[j].length < 4 ||
+                    !model->inlined[j].data || (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' &&
+                    model->inlined[j].data[3] == 'G'))
+                    continue;
+                for(i = k = 0; i < model->numtexture; i++) {
+                    if(!strcmp(model->inlined[j].name, model->texture[i].name)) { k = 1; break; }
+                }
+                if(k) continue;
+                numproc++;
+                chunklen = 8 + si_s;
+                h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen);
+                if(!h) goto memerr;
+                memcpy((uint8_t*)h + len, "PROC", 4);
+                *((uint32_t*)((uint8_t*)h + len + 4)) = chunklen;
+                out = (uint8_t*)h + len + 8;
+                out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->inlined[j].name));
+                out = NULL;
+                len += chunklen;
+            }
+        }
+        /* mesh face */
+        if(model->numface && face && !(flags & M3D_EXP_NOFACE)) {
+            chunklen = 8 + si_s + model->numface * (6 * vi_s + 3 * ti_s + si_s + 1);
+            h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen);
+            if(!h) goto memerr;
+            memcpy((uint8_t*)h + len, "MESH", 4);
+            length = (uint32_t*)((uint8_t*)h + len + 4);
+            out = (uint8_t*)h + len + 8;
+            last = (M3D_INDEX)-1U;
+            for(i = 0; i < model->numface; i++) {
+                if(!(flags & M3D_EXP_NOMATERIAL) && face[i].data.materialid != last) {
+                    last = face[i].data.materialid;
+                    idx = last < model->nummaterial ? _m3d_stridx(str, numstr, model->material[last].name) : 0;
+                    *out++ = 0;
+                    out = _m3d_addidx(out, si_s, idx);
+                }
+                /* hardcoded triangles. */
+                k = (3 << 4) |
+                    (((flags & M3D_EXP_NOTXTCRD) || !ti_s || face[i].data.texcoord[0] == (M3D_INDEX)-1U ||
+                    face[i].data.texcoord[1] == (M3D_INDEX)-1U || face[i].data.texcoord[2] == (M3D_INDEX)-1U) ? 0 : 1) |
+                    (((flags & M3D_EXP_NONORMAL) || face[i].data.normal[0] == (M3D_INDEX)-1U ||
+                    face[i].data.normal[1] == (M3D_INDEX)-1U || face[i].data.normal[2] == (M3D_INDEX)-1U) ? 0 : 2);
+                *out++ = k;
+                for(j = 0; j < 3; j++) {
+                    out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.vertex[j]]);
+                    if(k & 1)
+                        out = _m3d_addidx(out, ti_s, tmapidx[face[i].data.texcoord[j]]);
+                    if(k & 2)
+                        out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.normal[j]]);
+                }
+            }
+            *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len);
+            len += *length;
+            out = NULL;
+        }
+        /* mathematical shapes face */
+        if(model->numshape && model->shape && !(flags & M3D_EXP_NOFACE)) {
+            for(j = 0; j < model->numshape; j++) {
+                chunklen = 12 + si_s + model->shape[j].numcmd * (M3D_CMDMAXARG + 1) * 4;
+                h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen);
+                if(!h) goto memerr;
+                memcpy((uint8_t*)h + len, "SHPE", 4);
+                length = (uint32_t*)((uint8_t*)h + len + 4);
+                out = (uint8_t*)h + len + 8;
+                out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->shape[j].name));
+                out = _m3d_addidx(out, bi_s, model->shape[j].group);
+                for(i = 0; i < model->shape[j].numcmd; i++) {
+                    cmd = &model->shape[j].cmd[i];
+                    if(cmd->type >= (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])) || !cmd->arg)
+                        continue;
+                    cd = &m3d_commandtypes[cmd->type];
+                    *out++ = (cmd->type & 0x7F) | (cmd->type > 127 ? 0x80 : 0);
+                    if(cmd->type > 127) *out++ = (cmd->type >> 7) & 0xff;
+                    for(k = n = 0, l = cd->p; k < l; k++) {
+                        switch(cd->a[((k - n) % (cd->p - n)) + n]) {
+                            case m3dcp_mi_t:
+                                out = _m3d_addidx(out, si_s, cmd->arg[k] < model->nummaterial ?
+                                    _m3d_stridx(str, numstr, model->material[cmd->arg[k]].name) : 0);
+                            break;
+                            case m3dcp_vc_t:
+                                min_x = *((float*)&cmd->arg[k]);
+                                switch(vc_s) {
+                                    case 1: *out++ = (int8_t)(min_x * 127); break;
+                                    case 2: *((int16_t*)out) = (int16_t)(min_x * 32767); out += 2; break;
+                                    case 4: *((float*)out) = min_x; out += 4; break;
+                                    case 8: *((double*)out) = min_x; out += 8; break;
+                                }
+                            break;
+                            case m3dcp_hi_t: out = _m3d_addidx(out, hi_s, cmd->arg[k]); break;
+                            case m3dcp_fi_t: out = _m3d_addidx(out, fi_s, cmd->arg[k]); break;
+                            case m3dcp_ti_t: out = _m3d_addidx(out, ti_s, cmd->arg[k]); break;
+                            case m3dcp_qi_t:
+                            case m3dcp_vi_t: out = _m3d_addidx(out, vi_s, cmd->arg[k]); break;
+                            case m3dcp_i1_t: out = _m3d_addidx(out, 1, cmd->arg[k]); break;
+                            case m3dcp_i2_t: out = _m3d_addidx(out, 2, cmd->arg[k]); break;
+                            case m3dcp_i4_t: out = _m3d_addidx(out, 4, cmd->arg[k]); break;
+                            case m3dcp_va_t: out = _m3d_addidx(out, 4, cmd->arg[k]);
+                                n = k + 1; l += (cmd->arg[k] - 1) * (cd->p - k - 1);
+                            break;
+                        }
+                    }
+                }
+                *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len);
+                len += *length;
+                out = NULL;
+            }
+        }
+        /* annotation labels */
+        if(model->numlabel && model->label) {
+            for(i = 0, length = NULL; i < model->numlabel; i++) {
+                if(!i || _m3d_strcmp(sl, model->label[i].lang) || _m3d_strcmp(sn, model->label[i].name)) {
+                    sl = model->label[i].lang;
+                    sn = model->label[i].name;
+                    if(length) {
+                        *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len);
+                        len += *length;
+                    }
+                    chunklen = 8 + 2 * si_s + ci_s + model->numlabel * (vi_s + si_s);
+                    h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen);
+                    if(!h) { sn = NULL; sl = NULL; goto memerr; }
+                    memcpy((uint8_t*)h + len, "LBLS", 4);
+                    length = (uint32_t*)((uint8_t*)h + len + 4);
+                    out = (uint8_t*)h + len + 8;
+                    out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].name));
+                    out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].lang));
+                    idx = _m3d_cmapidx(cmap, numcmap, model->label[i].color);
+                    switch(ci_s) {
+                        case 1: *out++ = (uint8_t)(idx); break;
+                        case 2: *((uint16_t*)out) = (uint16_t)(idx); out += 2; break;
+                        case 4: *((uint32_t*)out) = model->label[i].color; out += 4; break;
+                    }
+                }
+                out = _m3d_addidx(out, vi_s, vrtxidx[model->label[i].vertexid]);
+                out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].text));
+            }
+            if(length) {
+                *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len);
+                len += *length;
+            }
+            out = NULL;
+            sn = sl = NULL;
+        }
+        /* actions */
+        if(model->numaction && model->action && model->numbone && model->bone && !(flags & M3D_EXP_NOACTION)) {
+            for(j = 0; j < model->numaction; j++) {
+                a = &model->action[j];
+                chunklen = 14 + si_s + a->numframe * (4 + fc_s + maxt * (bi_s + 2 * vi_s));
+                h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen);
+                if(!h) goto memerr;
+                memcpy((uint8_t*)h + len, "ACTN", 4);
+                length = (uint32_t*)((uint8_t*)h + len + 4);
+                out = (uint8_t*)h + len + 8;
+                out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, a->name));
+                *((uint16_t*)out) = (uint16_t)(a->numframe); out += 2;
+                *((uint32_t*)out) = (uint32_t)(a->durationmsec); out += 4;
+                for(i = 0; i < a->numframe; i++) {
+                    *((uint32_t*)out) = (uint32_t)(a->frame[i].msec); out += 4;
+                    out = _m3d_addidx(out, fc_s, a->frame[i].numtransform);
+                    for(k = 0; k < a->frame[i].numtransform; k++) {
+                        out = _m3d_addidx(out, bi_s, a->frame[i].transform[k].boneid);
+                        out = _m3d_addidx(out, vi_s, vrtxidx[a->frame[i].transform[k].pos]);
+                        out = _m3d_addidx(out, vi_s, vrtxidx[a->frame[i].transform[k].ori]);
+                    }
+                }
+                *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len);
+                len += *length;
+                out = NULL;
+            }
+        }
+        /* inlined assets */
+        if(model->numinlined && model->inlined && (numproc || (flags & M3D_EXP_INLINE))) {
+            for(j = 0; j < model->numinlined; j++) {
+                if(!model->inlined[j].name || !model->inlined[j].name[0] || model->inlined[j].length<4 || !model->inlined[j].data)
+                    continue;
+                if(!(flags & M3D_EXP_INLINE)) {
+                    if(model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G')
+                        continue;
+                    for(i = k = 0; i < model->numtexture; i++) {
+                        if(!strcmp(model->inlined[j].name, model->texture[i].name)) { k = 1; break; }
+                    }
+                    if(k) continue;
+                }
+                chunklen = 8 + si_s + model->inlined[j].length;
+                h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen);
+                if(!h) goto memerr;
+                memcpy((uint8_t*)h + len, "ASET", 4);
+                *((uint32_t*)((uint8_t*)h + len + 4)) = chunklen;
+                out = (uint8_t*)h + len + 8;
+                out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->inlined[j].name));
+                memcpy(out, model->inlined[j].data, model->inlined[j].length);
+                out = NULL;
+                len += chunklen;
+            }
+        }
+        /* extra chunks */
+        if(model->numextra && model->extra && (flags & M3D_EXP_EXTRA)) {
+            for(j = 0; j < model->numextra; j++) {
+                if(!model->extra[j] || model->extra[j]->length < 8)
+                    continue;
+                chunklen = model->extra[j]->length;
+                h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen);
+                if(!h) goto memerr;
+                memcpy((uint8_t*)h + len, model->extra[j], chunklen);
+                len += chunklen;
+            }
+        }
+        /* add end chunk */
+        h = (m3dhdr_t*)M3D_REALLOC(h, len + 4);
+        if(!h) goto memerr;
+        memcpy((uint8_t*)h + len, "OMD3", 4);
+        len += 4;
+        /* zlib compress */
+        if(!(flags & M3D_EXP_NOZLIB)) {
+            M3D_LOG("Deflating chunks");
+            z = stbi_zlib_compress((unsigned char *)h, len, (int*)&l, 9);
+            if(z && l > 0 && l < len) { len = l; M3D_FREE(h); h = (m3dhdr_t*)z; }
+        }
+        /* add file header at the begining */
+        len += 8;
+        out = (unsigned char*)M3D_MALLOC(len);
+        if(!out) goto memerr;
+        memcpy(out, "3DMO", 4);
+        *((uint32_t*)(out + 4)) = len;
+        memcpy(out + 8, h, len - 8);
+    }
+    if(size) *size = out ? len : 0;
+    if(vrtxidx) M3D_FREE(vrtxidx);
+    if(mtrlidx) M3D_FREE(mtrlidx);
+    if(tmapidx) M3D_FREE(tmapidx);
+    if(skinidx) M3D_FREE(skinidx);
+    if(norm) M3D_FREE(norm);
+    if(face) M3D_FREE(face);
+    if(cmap) M3D_FREE(cmap);
+    if(tmap) M3D_FREE(tmap);
+    if(skin) M3D_FREE(skin);
+    if(str) M3D_FREE(str);
+    if(vrtx) M3D_FREE(vrtx);
+    if(h) M3D_FREE(h);
+    return out;
+}
+#endif
+
+#endif
+
+#ifdef  __cplusplus
+}
+#ifdef M3D_CPPWRAPPER
+#include <vector>
+#include <string>
+#include <memory>
+
+/*** C++ wrapper class ***/
+namespace M3D {
+#ifdef M3D_IMPLEMENTATION
+
+    class Model {
+        public:
+            m3d_t *model;
+
+        public:
+            Model() {
+                this->model = (m3d_t*)malloc(sizeof(m3d_t)); memset(this->model, 0, sizeof(m3d_t));
+            }
+            Model(_unused const std::string &data, _unused m3dread_t ReadFileCB,
+                _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) {
+#ifndef M3D_NOIMPORTER
+                this->model = m3d_load((unsigned char *)data.data(), ReadFileCB, FreeCB, mtllib.model);
+#else
+                Model();
+#endif
+            }
+            Model(_unused const std::vector<unsigned char> data, _unused m3dread_t ReadFileCB,
+                _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) {
+#ifndef M3D_NOIMPORTER
+                this->model = m3d_load((unsigned char *)&data[0], ReadFileCB, FreeCB, mtllib.model);
+#else
+                Model();
+#endif
+            }
+            Model(_unused const unsigned char *data, _unused m3dread_t ReadFileCB,
+                _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) {
+#ifndef M3D_NOIMPORTER
+                this->model = m3d_load((unsigned char*)data, ReadFileCB, FreeCB, mtllib.model);
+#else
+                Model();
+#endif
+            }
+            ~Model() { m3d_free(this->model); }
+
+        public:
+            m3d_t *getCStruct() { return this->model; }
+            std::string getName() { return std::string(this->model->name); }
+            void setName(std::string name) { this->model->name = (char*)name.c_str(); }
+            std::string getLicense() { return std::string(this->model->license); }
+            void setLicense(std::string license) { this->model->license = (char*)license.c_str(); }
+            std::string getAuthor() { return std::string(this->model->author); }
+            void setAuthor(std::string author) { this->model->author = (char*)author.c_str(); }
+            std::string getDescription() { return std::string(this->model->desc); }
+            void setDescription(std::string desc) { this->model->desc = (char*)desc.c_str(); }
+            float getScale() { return this->model->scale; }
+            void setScale(float scale) { this->model->scale = scale; }
+            std::vector<unsigned char> getPreview() { return this->model->preview.data ?
+                std::vector<unsigned char>(this->model->preview.data, this->model->preview.data + this->model->preview.length) :
+                std::vector<unsigned char>(); }
+            std::vector<uint32_t> getColorMap() { return this->model->cmap ? std::vector<uint32_t>(this->model->cmap,
+                this->model->cmap + this->model->numcmap) : std::vector<uint32_t>(); }
+            std::vector<m3dti_t> getTextureMap() { return this->model->tmap ? std::vector<m3dti_t>(this->model->tmap,
+                this->model->tmap + this->model->numtmap) : std::vector<m3dti_t>(); }
+            std::vector<m3dtx_t> getTextures() { return this->model->texture ? std::vector<m3dtx_t>(this->model->texture,
+                this->model->texture + this->model->numtexture) : std::vector<m3dtx_t>(); }
+            std::string getTextureName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numtexture ?
+                std::string(this->model->texture[idx].name) : nullptr; }
+            std::vector<m3db_t> getBones() { return this->model->bone ? std::vector<m3db_t>(this->model->bone, this->model->bone +
+                this->model->numbone) : std::vector<m3db_t>(); }
+            std::string getBoneName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numbone ?
+                std::string(this->model->bone[idx].name) : nullptr; }
+            std::vector<m3dm_t> getMaterials() { return this->model->material ? std::vector<m3dm_t>(this->model->material,
+                this->model->material + this->model->nummaterial) : std::vector<m3dm_t>(); }
+            std::string getMaterialName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->nummaterial ?
+                std::string(this->model->material[idx].name) : nullptr; }
+            int getMaterialPropertyInt(int idx, int type) {
+                    if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 0 || type >= 127 ||
+                        !this->model->material[idx].prop) return -1;
+                    for (int i = 0; i < this->model->material[idx].numprop; i++) {
+                        if (this->model->material[idx].prop[i].type == type)
+                            return this->model->material[idx].prop[i].value.num;
+                    }
+                    return -1;
+                }
+            uint32_t getMaterialPropertyColor(int idx, int type) { return this->getMaterialPropertyInt(idx, type); }
+            float getMaterialPropertyFloat(int idx, int type) {
+                    if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 0 || type >= 127 ||
+                        !this->model->material[idx].prop) return -1.0f;
+                    for (int i = 0; i < this->model->material[idx].numprop; i++) {
+                        if (this->model->material[idx].prop[i].type == type)
+                            return this->model->material[idx].prop[i].value.fnum;
+                    }
+                    return -1.0f;
+                }
+            m3dtx_t* getMaterialPropertyMap(int idx, int type) {
+                    if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 128 || type > 255 ||
+                        !this->model->material[idx].prop) return nullptr;
+                    for (int i = 0; i < this->model->material[idx].numprop; i++) {
+                        if (this->model->material[idx].prop[i].type == type)
+                            return this->model->material[idx].prop[i].value.textureid < this->model->numtexture ?
+                                &this->model->texture[this->model->material[idx].prop[i].value.textureid] : nullptr;
+                    }
+                    return nullptr;
+                }
+            std::vector<m3dv_t> getVertices() { return this->model->vertex ? std::vector<m3dv_t>(this->model->vertex,
+                this->model->vertex + this->model->numvertex) : std::vector<m3dv_t>(); }
+            std::vector<m3df_t> getFace() { return this->model->face ? std::vector<m3df_t>(this->model->face, this->model->face +
+                this->model->numface) : std::vector<m3df_t>(); }
+            std::vector<m3dh_t> getShape() { return this->model->shape ? std::vector<m3dh_t>(this->model->shape,
+                this->model->shape + this->model->numshape) : std::vector<m3dh_t>(); }
+            std::string getShapeName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape &&
+                this->model->shape[idx].name && this->model->shape[idx].name[0] ?
+                std::string(this->model->shape[idx].name) : nullptr; }
+            unsigned int getShapeGroup(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape ?
+                this->model->shape[idx].group : 0xFFFFFFFF; }
+            std::vector<m3dc_t> getShapeCommands(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape &&
+                this->model->shape[idx].cmd ? std::vector<m3dc_t>(this->model->shape[idx].cmd, this->model->shape[idx].cmd +
+                this->model->shape[idx].numcmd) : std::vector<m3dc_t>(); }
+            std::vector<m3dl_t> getAnnotationLabels() { return this->model->label ? std::vector<m3dl_t>(this->model->label,
+                this->model->label + this->model->numlabel) : std::vector<m3dl_t>(); }
+            std::vector<m3ds_t> getSkin() { return this->model->skin ? std::vector<m3ds_t>(this->model->skin, this->model->skin +
+                this->model->numskin) : std::vector<m3ds_t>(); }
+            std::vector<m3da_t> getActions() { return this->model->action ? std::vector<m3da_t>(this->model->action,
+                this->model->action + this->model->numaction) : std::vector<m3da_t>(); }
+            std::string getActionName(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ?
+                std::string(this->model->action[aidx].name) : nullptr; }
+            unsigned int getActionDuration(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ?
+                this->model->action[aidx].durationmsec : 0; }
+            std::vector<m3dfr_t> getActionFrames(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ?
+                std::vector<m3dfr_t>(this->model->action[aidx].frame, this->model->action[aidx].frame +
+                this->model->action[aidx].numframe) : std::vector<m3dfr_t>(); }
+            unsigned int getActionFrameTimestamp(int aidx, int fidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction?
+                    (fidx >= 0 && (unsigned int)fidx < this->model->action[aidx].numframe ?
+                    this->model->action[aidx].frame[fidx].msec : 0) : 0; }
+            std::vector<m3dtr_t> getActionFrameTransforms(int aidx, int fidx) {
+                return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? (
+                    fidx >= 0 && (unsigned int)fidx < this->model->action[aidx].numframe ?
+                    std::vector<m3dtr_t>(this->model->action[aidx].frame[fidx].transform,
+                    this->model->action[aidx].frame[fidx].transform + this->model->action[aidx].frame[fidx].numtransform) :
+                    std::vector<m3dtr_t>()) : std::vector<m3dtr_t>(); }
+            std::vector<m3dtr_t> getActionFrame(int aidx, int fidx, std::vector<m3dtr_t> skeleton) {
+                m3dtr_t *pose = m3d_frame(this->model, (unsigned int)aidx, (unsigned int)fidx,
+                    skeleton.size() ? &skeleton[0] : nullptr);
+                return std::vector<m3dtr_t>(pose, pose + this->model->numbone); }
+            std::vector<m3db_t> getActionPose(int aidx, unsigned int msec) {
+                m3db_t *pose = m3d_pose(this->model, (unsigned int)aidx, (unsigned int)msec);
+                return std::vector<m3db_t>(pose, pose + this->model->numbone); }
+            std::vector<m3di_t> getInlinedAssets() { return this->model->inlined ? std::vector<m3di_t>(this->model->inlined,
+                this->model->inlined + this->model->numinlined) : std::vector<m3di_t>(); }
+            std::vector<std::unique_ptr<m3dchunk_t>> getExtras() { return this->model->extra ?
+                std::vector<std::unique_ptr<m3dchunk_t>>(this->model->extra,
+                this->model->extra + this->model->numextra) : std::vector<std::unique_ptr<m3dchunk_t>>(); }
+            std::vector<unsigned char> Save(_unused int quality, _unused int flags) {
+#ifdef M3D_EXPORTER
+                unsigned int size;
+                unsigned char *ptr = m3d_save(this->model, quality, flags, &size);
+                return ptr && size ? std::vector<unsigned char>(ptr, ptr + size) : std::vector<unsigned char>();
+#else
+                return std::vector<unsigned char>();
+#endif
+            }
+    };
+
+#else
+    class Model {
+        public:
+            m3d_t *model;
+
+        public:
+            Model(const std::string &data, m3dread_t ReadFileCB, m3dfree_t FreeCB);
+            Model(const std::vector<unsigned char> data, m3dread_t ReadFileCB, m3dfree_t FreeCB);
+            Model(const unsigned char *data, m3dread_t ReadFileCB, m3dfree_t FreeCB);
+            Model();
+            ~Model();
+
+        public:
+            m3d_t *getCStruct();
+            std::string getName();
+            void setName(std::string name);
+            std::string getLicense();
+            void setLicense(std::string license);
+            std::string getAuthor();
+            void setAuthor(std::string author);
+            std::string getDescription();
+            void setDescription(std::string desc);
+            float getScale();
+            void setScale(float scale);
+            std::vector<unsigned char> getPreview();
+            std::vector<uint32_t> getColorMap();
+            std::vector<m3dti_t> getTextureMap();
+            std::vector<m3dtx_t> getTextures();
+            std::string getTextureName(int idx);
+            std::vector<m3db_t> getBones();
+            std::string getBoneName(int idx);
+            std::vector<m3dm_t> getMaterials();
+            std::string getMaterialName(int idx);
+            int getMaterialPropertyInt(int idx, int type);
+            uint32_t getMaterialPropertyColor(int idx, int type);
+            float getMaterialPropertyFloat(int idx, int type);
+            m3dtx_t* getMaterialPropertyMap(int idx, int type);
+            std::vector<m3dv_t> getVertices();
+            std::vector<m3df_t> getFace();
+            std::vector<m3dh_t> getShape();
+            std::string getShapeName(int idx);
+            unsigned int getShapeGroup(int idx);
+            std::vector<m3dc_t> getShapeCommands(int idx);
+            std::vector<m3dl_t> getAnnotationLabels();
+            std::vector<m3ds_t> getSkin();
+            std::vector<m3da_t> getActions();
+            std::string getActionName(int aidx);
+            unsigned int getActionDuration(int aidx);
+            std::vector<m3dfr_t> getActionFrames(int aidx);
+            unsigned int getActionFrameTimestamp(int aidx, int fidx);
+            std::vector<m3dtr_t> getActionFrameTransforms(int aidx, int fidx);
+            std::vector<m3dtr_t> getActionFrame(int aidx, int fidx, std::vector<m3dtr_t> skeleton);
+            std::vector<m3db_t> getActionPose(int aidx, unsigned int msec);
+            std::vector<m3di_t> getInlinedAssets();
+            std::vector<std::unique_ptr<m3dchunk_t>> getExtras();
+            std::vector<unsigned char> Save(int quality, int flags);
+    };
+
+#endif /* impl */
+}
+#endif
+
+#endif /* __cplusplus */
+
+#endif

+ 1 - 1
code/MD2/MD2Loader.cpp

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

+ 2 - 1
code/MD5/MD5Loader.cpp

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

+ 1 - 1
code/MD5/MD5Parser.cpp

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

+ 16 - 26
code/MDL/MDLLoader.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2019, assimp team
 Copyright (c) 2006-2019, assimp team
 
 
-
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -54,7 +52,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "MDL/MDLDefaultColorMap.h"
 #include "MDL/MDLDefaultColorMap.h"
 #include "MD2/MD2FileData.h"
 #include "MD2/MD2FileData.h"
 
 
-#include <assimp/Macros.h>
 #include <assimp/qnan.h>
 #include <assimp/qnan.h>
 #include <assimp/StringUtils.h>
 #include <assimp/StringUtils.h>
 #include <assimp/Importer.hpp>
 #include <assimp/Importer.hpp>
@@ -94,23 +91,24 @@ static const aiImporterDesc desc = {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 // Constructor to be privately used by Importer
 MDLImporter::MDLImporter()
 MDLImporter::MDLImporter()
-    : configFrameID(),
-    mBuffer(),
-    iGSFileVersion(),
-    pIOHandler(),
-    pScene(),
-    iFileSize()
-{}
+: configFrameID()
+, mBuffer()
+, iGSFileVersion()
+, pIOHandler()
+, pScene()
+, iFileSize() {
+    // empty
+}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
 // Destructor, private as well
-MDLImporter::~MDLImporter()
-{}
+MDLImporter::~MDLImporter() {
+    // empty
+}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 // Returns whether the class can handle the format of the given file.
-bool MDLImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
-{
+bool MDLImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
     const std::string extension = GetExtension(pFile);
     const std::string extension = GetExtension(pFile);
 
 
     // if check for extension is not enough, check for the magic tokens
     // if check for extension is not enough, check for the magic tokens
@@ -404,23 +402,15 @@ void MDLImporter::InternReadFile_Quake1() {
 
 
     // now get a pointer to the first frame in the file
     // now get a pointer to the first frame in the file
     BE_NCONST MDL::Frame* pcFrames = (BE_NCONST MDL::Frame*)szCurrent;
     BE_NCONST MDL::Frame* pcFrames = (BE_NCONST MDL::Frame*)szCurrent;
-    BE_NCONST MDL::SimpleFrame* pcFirstFrame;
+    MDL::SimpleFrame* pcFirstFrame;
 
 
     if (0 == pcFrames->type) {
     if (0 == pcFrames->type) {
         // get address of single frame
         // get address of single frame
-        pcFirstFrame = &pcFrames->frame;
+        pcFirstFrame =( MDL::SimpleFrame*) &pcFrames->frame;
     } else {
     } else {
         // get the first frame in the group
         // get the first frame in the group
-
-#if 1
-        // FIXME: the cast is wrong and cause a warning on clang 5.0
-        // disable this code for now, fix it later
-        ai_assert(false && "Bad pointer cast");
-        pcFirstFrame = nullptr; // Workaround: msvc++ C4703 error
-#else
-        BE_NCONST MDL::GroupFrame* pcFrames2 = (BE_NCONST MDL::GroupFrame*)pcFrames;
-        pcFirstFrame = (BE_NCONST MDL::SimpleFrame*)(&pcFrames2->time + pcFrames->type);
-#endif
+        BE_NCONST MDL::GroupFrame* pcFrames2 = (BE_NCONST MDL::GroupFrame*) pcFrames;
+        pcFirstFrame = &(pcFrames2->frames[0]);
     }
     }
     BE_NCONST MDL::Vertex* pcVertices = (BE_NCONST MDL::Vertex*) ((pcFirstFrame->name) + sizeof(pcFirstFrame->name));
     BE_NCONST MDL::Vertex* pcVertices = (BE_NCONST MDL::Vertex*) ((pcFirstFrame->name) + sizeof(pcFirstFrame->name));
     VALIDATE_FILE_SIZE((const unsigned char*)(pcVertices + pcHeader->num_verts));
     VALIDATE_FILE_SIZE((const unsigned char*)(pcVertices + pcHeader->num_verts));

+ 1 - 17
code/MDL/MDLLoader.h

@@ -89,16 +89,12 @@ public:
     MDLImporter();
     MDLImporter();
     ~MDLImporter();
     ~MDLImporter();
 
 
-
-public:
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Returns whether the class can handle the format of the given file.
     /** Returns whether the class can handle the format of the given file.
     * See BaseImporter::CanRead() for details.  */
     * See BaseImporter::CanRead() for details.  */
     bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
     bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
         bool checkSig) const;
         bool checkSig) const;
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Called prior to ReadFile().
     /** Called prior to ReadFile().
     * The function is a request to the importer to update its configuration
     * The function is a request to the importer to update its configuration
@@ -107,8 +103,6 @@ public:
     void SetupProperties(const Importer* pImp);
     void SetupProperties(const Importer* pImp);
 
 
 protected:
 protected:
-
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Return importer meta information.
     /** Return importer meta information.
      * See #BaseImporter::GetInfo for the details
      * See #BaseImporter::GetInfo for the details
@@ -122,8 +116,6 @@ protected:
     void InternReadFile( const std::string& pFile, aiScene* pScene,
     void InternReadFile( const std::string& pFile, aiScene* pScene,
         IOSystem* pIOHandler);
         IOSystem* pIOHandler);
 
 
-protected:
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Import a quake 1 MDL file (IDPO)
     /** Import a quake 1 MDL file (IDPO)
     */
     */
@@ -154,7 +146,6 @@ protected:
     void SizeCheck(const void* szPos);
     void SizeCheck(const void* szPos);
     void SizeCheck(const void* szPos, const char* szFile, unsigned int iLine);
     void SizeCheck(const void* szPos, const char* szFile, unsigned int iLine);
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Validate the header data structure of a game studio MDL7 file
     /** Validate the header data structure of a game studio MDL7 file
      * \param pcHeader Input header to be validated
      * \param pcHeader Input header to be validated
@@ -167,7 +158,6 @@ protected:
      */
      */
     void ValidateHeader_Quake1(const MDL::Header* pcHeader);
     void ValidateHeader_Quake1(const MDL::Header* pcHeader);
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Try to load a  palette from the current directory (colormap.lmp)
     /** Try to load a  palette from the current directory (colormap.lmp)
      *  If it is not found the default palette of Quake1 is returned
      *  If it is not found the default palette of Quake1 is returned
@@ -179,9 +169,8 @@ protected:
      */
      */
     void FreePalette(const unsigned char* pszColorMap);
     void FreePalette(const unsigned char* pszColorMap);
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
-    /** Load a paletized texture from the file and convert it to 32bpp
+    /** Load a palletized texture from the file and convert it to 32bpp
     */
     */
     void CreateTextureARGB8_3DGS_MDL3(const unsigned char* szData);
     void CreateTextureARGB8_3DGS_MDL3(const unsigned char* szData);
 
 
@@ -195,7 +184,6 @@ protected:
         unsigned int iType,
         unsigned int iType,
         unsigned int* piSkip);
         unsigned int* piSkip);
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Used to load textures from MDL5
     /** Used to load textures from MDL5
      * \param szData Input data
      * \param szData Input data
@@ -206,7 +194,6 @@ protected:
         unsigned int iType,
         unsigned int iType,
         unsigned int* piSkip);
         unsigned int* piSkip);
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Checks whether a texture can be replaced with a single color
     /** Checks whether a texture can be replaced with a single color
      * This is useful for all file formats before MDL7 (all those
      * This is useful for all file formats before MDL7 (all those
@@ -218,14 +205,12 @@ protected:
     */
     */
     aiColor4D ReplaceTextureWithColor(const aiTexture* pcTexture);
     aiColor4D ReplaceTextureWithColor(const aiTexture* pcTexture);
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Converts the absolute texture coordinates in MDL5 files to
     /** Converts the absolute texture coordinates in MDL5 files to
      *  relative in a range between 0 and 1
      *  relative in a range between 0 and 1
     */
     */
     void CalculateUVCoordinates_MDL5();
     void CalculateUVCoordinates_MDL5();
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Read an UV coordinate from the file. If the file format is not
     /** Read an UV coordinate from the file. If the file format is not
      * MDL5, the function calculates relative texture coordinates
      * MDL5, the function calculates relative texture coordinates
@@ -245,7 +230,6 @@ protected:
      */
      */
     void SetupMaterialProperties_3DGS_MDL5_Quake1( );
     void SetupMaterialProperties_3DGS_MDL5_Quake1( );
 
 
-
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Parse a skin lump in a MDL7/HMP7 file with all of its features
     /** Parse a skin lump in a MDL7/HMP7 file with all of its features
      *  variant 1: Current cursor position is the beginning of the skin header
      *  variant 1: Current cursor position is the beginning of the skin header

+ 4 - 21
code/Material/MaterialSystem.cpp

@@ -51,7 +51,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/types.h>
 #include <assimp/types.h>
 #include <assimp/material.h>
 #include <assimp/material.h>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/DefaultLogger.hpp>
-#include <assimp/Macros.h>
 
 
 using namespace Assimp;
 using namespace Assimp;
 
 
@@ -472,12 +471,12 @@ aiReturn aiMaterial::AddBinaryProperty (const void* pInput,
     aiPropertyTypeInfo pType
     aiPropertyTypeInfo pType
     )
     )
 {
 {
-    ai_assert( pInput != NULL );
-    ai_assert( pKey != NULL );
+    ai_assert( pInput != nullptr );
+	ai_assert(pKey != nullptr );
     ai_assert( 0 != pSizeInBytes );
     ai_assert( 0 != pSizeInBytes );
 
 
     if ( 0 == pSizeInBytes ) {
     if ( 0 == pSizeInBytes ) {
-
+		return AI_FAILURE;
     }
     }
 
 
     // first search the list whether there is already an entry with this key
     // first search the list whether there is already an entry with this key
@@ -545,23 +544,7 @@ aiReturn aiMaterial::AddProperty (const aiString* pInput,
     unsigned int type,
     unsigned int type,
     unsigned int index)
     unsigned int index)
 {
 {
-    // We don't want to add the whole buffer .. write a 32 bit length
-    // prefix followed by the zero-terminated UTF8 string.
-    // (HACK) I don't want to break the ABI now, but we definitely
-    // ought to change aiString::mLength to uint32_t one day.
-    if (sizeof(size_t) == 8) {
-        aiString copy = *pInput;
-        uint32_t* s = reinterpret_cast<uint32_t*>(&copy.length);
-        s[1] = static_cast<uint32_t>(pInput->length);
-
-        return AddBinaryProperty(s+1,
-            static_cast<unsigned int>(pInput->length+1+4),
-            pKey,
-            type,
-            index,
-            aiPTI_String);
-    }
-    ai_assert(sizeof(size_t)==4);
+    ai_assert(sizeof(ai_uint32)==4);
     return AddBinaryProperty(pInput,
     return AddBinaryProperty(pInput,
         static_cast<unsigned int>(pInput->length+1+4),
         static_cast<unsigned int>(pInput->length+1+4),
         pKey,
         pKey,

+ 1 - 4
code/Obj/ObjFileImporter.cpp

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

+ 3 - 3
code/Obj/ObjFileParser.cpp

@@ -118,7 +118,7 @@ void ObjFileParser::parseFile( IOStreamBuffer<char> &streamBuffer ) {
     size_t lastFilePos( 0 );
     size_t lastFilePos( 0 );
 
 
     std::vector<char> buffer;
     std::vector<char> buffer;
-    while ( streamBuffer.getNextDataLine( buffer, '\0' ) ) {
+    while ( streamBuffer.getNextDataLine( buffer, '\\' ) ) {
         m_DataIt = buffer.begin();
         m_DataIt = buffer.begin();
         m_DataItEnd = buffer.end();
         m_DataItEnd = buffer.end();
 
 
@@ -244,8 +244,8 @@ void ObjFileParser::copyNextWord(char *pBuffer, size_t length) {
     size_t index = 0;
     size_t index = 0;
     m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
     m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
     if ( *m_DataIt == '\\' ) {
     if ( *m_DataIt == '\\' ) {
-        m_DataIt++;
-        m_DataIt++;
+        ++m_DataIt;
+        ++m_DataIt;
         m_DataIt = getNextWord<DataArrayIt>( m_DataIt, m_DataItEnd );
         m_DataIt = getNextWord<DataArrayIt>( m_DataIt, m_DataItEnd );
     }
     }
     while( m_DataIt != m_DataItEnd && !IsSpaceOrNewLine( *m_DataIt ) ) {
     while( m_DataIt != m_DataItEnd && !IsSpaceOrNewLine( *m_DataIt ) ) {

+ 0 - 1
code/Ply/PlyLoader.cpp

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

+ 268 - 0
code/PostProcessing/ArmaturePopulate.cpp

@@ -0,0 +1,268 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+#include "ArmaturePopulate.h"
+
+#include <assimp/BaseImporter.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <iostream>
+
+namespace Assimp {
+
+/// The default class constructor.
+ArmaturePopulate::ArmaturePopulate() : BaseProcess()
+{}
+
+/// The class destructor.
+ArmaturePopulate::~ArmaturePopulate() 
+{}
+
+bool ArmaturePopulate::IsActive(unsigned int pFlags) const {
+  return (pFlags & aiProcess_PopulateArmatureData) != 0;
+}
+
+void ArmaturePopulate::SetupProperties(const Importer *pImp) {
+  // do nothing
+}
+
+void ArmaturePopulate::Execute(aiScene *out) {
+
+  // Now convert all bone positions to the correct mOffsetMatrix
+  std::vector<aiBone *> bones;
+  std::vector<aiNode *> nodes;
+  std::map<aiBone *, aiNode *> bone_stack;
+  BuildBoneList(out->mRootNode, out->mRootNode, out, bones);
+  BuildNodeList(out->mRootNode, nodes);
+
+  BuildBoneStack(out->mRootNode, out->mRootNode, out, bones, bone_stack, nodes);
+
+  ASSIMP_LOG_DEBUG_F("Bone stack size: ", bone_stack.size());
+
+  for (std::pair<aiBone *, aiNode *> kvp : bone_stack) {
+    aiBone *bone = kvp.first;
+    aiNode *bone_node = kvp.second;
+    ASSIMP_LOG_DEBUG_F("active node lookup: ", bone->mName.C_Str());
+    // lcl transform grab - done in generate_nodes :)
+
+    // bone->mOffsetMatrix = bone_node->mTransformation;
+    aiNode *armature = GetArmatureRoot(bone_node, bones);
+
+    ai_assert(armature);
+
+    // set up bone armature id
+    bone->mArmature = armature;
+
+    // set this bone node to be referenced properly
+    ai_assert(bone_node);
+    bone->mNode = bone_node;
+  }
+}
+
+
+/* Reprocess all nodes to calculate bone transforms properly based on the REAL
+ * mOffsetMatrix not the local. */
+/* Before this would use mesh transforms which is wrong for bone transforms */
+/* Before this would work for simple character skeletons but not complex meshes
+ * with multiple origins */
+/* Source: sketch fab log cutter fbx */
+void ArmaturePopulate::BuildBoneList(aiNode *current_node,
+                                     const aiNode *root_node,
+                                     const aiScene *scene,
+                                     std::vector<aiBone *> &bones) {
+  ai_assert(scene);
+  for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) {
+    aiNode *child = current_node->mChildren[nodeId];
+    ai_assert(child);
+
+    // check for bones
+    for (unsigned int meshId = 0; meshId < child->mNumMeshes; ++meshId) {
+      ai_assert(child->mMeshes);
+      unsigned int mesh_index = child->mMeshes[meshId];
+      aiMesh *mesh = scene->mMeshes[mesh_index];
+      ai_assert(mesh);
+
+      for (unsigned int boneId = 0; boneId < mesh->mNumBones; ++boneId) {
+        aiBone *bone = mesh->mBones[boneId];
+        ai_assert(bone);
+
+        // duplicate meshes exist with the same bones sometimes :)
+        // so this must be detected
+        if (std::find(bones.begin(), bones.end(), bone) == bones.end()) {
+          // add the element once
+          bones.push_back(bone);
+        }
+      }
+
+      // find mesh and get bones
+      // then do recursive lookup for bones in root node hierarchy
+    }
+
+    BuildBoneList(child, root_node, scene, bones);
+  }
+}
+
+/* Prepare flat node list which can be used for non recursive lookups later */
+void ArmaturePopulate::BuildNodeList(const aiNode *current_node,
+                                     std::vector<aiNode *> &nodes) {
+  ai_assert(current_node);
+
+  for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) {
+    aiNode *child = current_node->mChildren[nodeId];
+    ai_assert(child);
+
+    nodes.push_back(child);
+
+    BuildNodeList(child, nodes);
+  }
+}
+
+/* A bone stack allows us to have multiple armatures, with the same bone names
+ * A bone stack allows us also to retrieve bones true transform even with
+ * duplicate names :)
+ */
+void ArmaturePopulate::BuildBoneStack(aiNode *current_node,
+                                      const aiNode *root_node,
+                                      const aiScene *scene,
+                                      const std::vector<aiBone *> &bones,
+                                      std::map<aiBone *, aiNode *> &bone_stack,
+                                      std::vector<aiNode *> &node_stack) {
+  ai_assert(scene);
+  ai_assert(root_node);
+  ai_assert(!node_stack.empty());
+
+  for (aiBone *bone : bones) {
+    ai_assert(bone);
+    aiNode *node = GetNodeFromStack(bone->mName, node_stack);
+    if (node == nullptr) {
+      node_stack.clear();
+      BuildNodeList(root_node, node_stack);
+      ASSIMP_LOG_DEBUG_F("Resetting bone stack: nullptr element ", bone->mName.C_Str());
+
+      node = GetNodeFromStack(bone->mName, node_stack);
+
+      if (!node) {
+        ASSIMP_LOG_ERROR("serious import issue node for bone was not detected");
+        continue;
+      }
+    }
+
+    ASSIMP_LOG_DEBUG_F("Successfully added bone[", bone->mName.C_Str(), "] to stack and bone node is: ", node->mName.C_Str());
+
+    bone_stack.insert(std::pair<aiBone *, aiNode *>(bone, node));
+  }
+}
+
+
+/* Returns the armature root node */
+/* This is required to be detected for a bone initially, it will recurse up
+ * until it cannot find another bone and return the node No known failure
+ * points. (yet)
+ */
+aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node,
+                                          std::vector<aiBone *> &bone_list) {
+  while (bone_node) {
+    if (!IsBoneNode(bone_node->mName, bone_list)) {
+      ASSIMP_LOG_DEBUG_F("GetArmatureRoot() Found valid armature: ", bone_node->mName.C_Str());
+      return bone_node;
+    }
+
+    bone_node = bone_node->mParent;
+  }
+  
+  ASSIMP_LOG_ERROR("GetArmatureRoot() can't find armature!");
+  
+  return nullptr;
+}
+
+
+
+/* Simple IsBoneNode check if this could be a bone */
+bool ArmaturePopulate::IsBoneNode(const aiString &bone_name,
+                                  std::vector<aiBone *> &bones) {
+  for (aiBone *bone : bones) {
+    if (bone->mName == bone_name) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/* Pop this node by name from the stack if found */
+/* Used in multiple armature situations with duplicate node / bone names */
+/* Known flaw: cannot have nodes with bone names, will be fixed in later release
+ */
+/* (serious to be fixed) Known flaw: nodes which have more than one bone could
+ * be prematurely dropped from stack */
+aiNode *ArmaturePopulate::GetNodeFromStack(const aiString &node_name,
+                                           std::vector<aiNode *> &nodes) {
+  std::vector<aiNode *>::iterator iter;
+  aiNode *found = nullptr;
+  for (iter = nodes.begin(); iter < nodes.end(); ++iter) {
+    aiNode *element = *iter;
+    ai_assert(element);
+    // node valid and node name matches
+    if (element->mName == node_name) {
+      found = element;
+      break;
+    }
+  }
+
+  if (found != nullptr) {
+    ASSIMP_LOG_INFO_F("Removed node from stack: ", found->mName.C_Str());
+    // now pop the element from the node list
+    nodes.erase(iter);
+
+    return found;
+  }
+
+  // unique names can cause this problem
+  ASSIMP_LOG_ERROR("[Serious] GetNodeFromStack() can't find node from stack!");
+
+  return nullptr;
+}
+
+
+
+
+} // Namespace Assimp

+ 112 - 0
code/PostProcessing/ArmaturePopulate.h

@@ -0,0 +1,112 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+#ifndef ARMATURE_POPULATE_H_
+#define ARMATURE_POPULATE_H_
+
+#include "Common/BaseProcess.h"
+#include <assimp/BaseImporter.h>
+#include <vector>
+#include <map>
+
+
+struct aiNode;
+struct aiBone;
+
+namespace Assimp {
+
+// ---------------------------------------------------------------------------
+/** Armature Populate: This is a post process designed
+ * To save you time when importing models into your game engines
+ * This was originally designed only for fbx but will work with other formats
+ * it is intended to auto populate aiBone data with armature and the aiNode
+ * This is very useful when dealing with skinned meshes
+ * or when dealing with many different skeletons
+ * It's off by default but recommend that you try it and use it
+ * It should reduce down any glue code you have in your
+ * importers
+ * You can contact RevoluPowered <[email protected]>
+ * For more info about this
+*/
+class ASSIMP_API ArmaturePopulate : public BaseProcess {
+public:
+    /// The default class constructor.
+    ArmaturePopulate();
+
+    /// The class destructor.
+    virtual ~ArmaturePopulate();
+
+    /// Overwritten, @see BaseProcess
+    virtual bool IsActive( unsigned int pFlags ) const;
+
+    /// Overwritten, @see BaseProcess
+    virtual void SetupProperties( const Importer* pImp );
+
+    /// Overwritten, @see BaseProcess
+    virtual void Execute( aiScene* pScene );
+
+    static aiNode *GetArmatureRoot(aiNode *bone_node,
+                                      std::vector<aiBone *> &bone_list);
+
+    static bool IsBoneNode(const aiString &bone_name,
+                              std::vector<aiBone *> &bones);
+
+    static aiNode *GetNodeFromStack(const aiString &node_name,
+                                       std::vector<aiNode *> &nodes);
+
+    static void BuildNodeList(const aiNode *current_node,
+                                 std::vector<aiNode *> &nodes);
+
+    static void BuildBoneList(aiNode *current_node, const aiNode *root_node,
+                                 const aiScene *scene,
+                                 std::vector<aiBone *> &bones);                        
+
+    static void BuildBoneStack(aiNode *current_node, const aiNode *root_node,
+                                  const aiScene *scene,
+                                  const std::vector<aiBone *> &bones,
+                                  std::map<aiBone *, aiNode *> &bone_stack,
+                                  std::vector<aiNode *> &node_stack);
+};
+
+} // Namespace Assimp
+
+
+#endif // SCALE_PROCESS_H_

+ 3 - 3
code/PostProcessing/CalcTangentsProcess.cpp

@@ -212,7 +212,7 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
             // project tangent and bitangent into the plane formed by the vertex' normal
             // project tangent and bitangent into the plane formed by the vertex' normal
             aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]);
             aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]);
             aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]);
             aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]);
-            localTangent.Normalize(); localBitangent.Normalize();
+            localTangent.NormalizeSafe(); localBitangent.NormalizeSafe();
 
 
             // reconstruct tangent/bitangent according to normal and bitangent/tangent when it's infinite or NaN.
             // reconstruct tangent/bitangent according to normal and bitangent/tangent when it's infinite or NaN.
             bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z);
             bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z);
@@ -220,10 +220,10 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
             if (invalid_tangent != invalid_bitangent) {
             if (invalid_tangent != invalid_bitangent) {
                 if (invalid_tangent) {
                 if (invalid_tangent) {
                     localTangent = meshNorm[p] ^ localBitangent;
                     localTangent = meshNorm[p] ^ localBitangent;
-                    localTangent.Normalize();
+                    localTangent.NormalizeSafe();
                 } else {
                 } else {
                     localBitangent = localTangent ^ meshNorm[p];
                     localBitangent = localTangent ^ meshNorm[p];
-                    localBitangent.Normalize();
+                    localBitangent.NormalizeSafe();
                 }
                 }
             }
             }
 
 

+ 3 - 3
code/PostProcessing/ComputeUVMappingProcess.cpp

@@ -354,12 +354,12 @@ void ComputeUVMappingProcess::ComputePlaneMapping(aiMesh* mesh,const aiVector3D&
     }
     }
     else if (axis * base_axis_z >= angle_epsilon)   {
     else if (axis * base_axis_z >= angle_epsilon)   {
         FindMeshCenter(mesh, center, min, max);
         FindMeshCenter(mesh, center, min, max);
-        diffu = max.y - min.y;
-        diffv = max.z - min.z;
+        diffu = max.x - min.x;
+        diffv = max.y - min.y;
 
 
         for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
         for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
             const aiVector3D& pos = mesh->mVertices[pnt];
             const aiVector3D& pos = mesh->mVertices[pnt];
-            out[pnt].Set((pos.y - min.y) / diffu,(pos.x - min.x) / diffv,0.0);
+            out[pnt].Set((pos.x - min.x) / diffu,(pos.y - min.y) / diffv,0.0);
         }
         }
     }
     }
     // slower code path in case the mapping axis is not one of the coordinate system axes
     // slower code path in case the mapping axis is not one of the coordinate system axes

+ 0 - 1
code/PostProcessing/FindInvalidDataProcess.cpp

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

+ 0 - 25
code/PostProcessing/JoinVerticesProcess.cpp

@@ -431,31 +431,6 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
             bone->mWeights = new aiVertexWeight[bone->mNumWeights];
             bone->mWeights = new aiVertexWeight[bone->mNumWeights];
             memcpy( bone->mWeights, &newWeights[0], bone->mNumWeights * sizeof( aiVertexWeight));
             memcpy( bone->mWeights, &newWeights[0], bone->mNumWeights * sizeof( aiVertexWeight));
         }
         }
-        else {
-
-            /*  NOTE:
-             *
-             *  In the algorithm above we're assuming that there are no vertices
-             *  with a different bone weight setup at the same position. That wouldn't
-             *  make sense, but it is not absolutely impossible. SkeletonMeshBuilder
-             *  for example generates such input data if two skeleton points
-             *  share the same position. Again this doesn't make sense but is
-             *  reality for some model formats (MD5 for example uses these special
-             *  nodes as attachment tags for its weapons).
-             *
-             *  Then it is possible that a bone has no weights anymore .... as a quick
-             *  workaround, we're just removing these bones. If they're animated,
-             *  model geometry might be modified but at least there's no risk of a crash.
-             */
-            delete bone;
-            --pMesh->mNumBones;
-            for (unsigned int n = a; n < pMesh->mNumBones; ++n)  {
-                pMesh->mBones[n] = pMesh->mBones[n+1];
-            }
-
-            --a;
-            ASSIMP_LOG_WARN("Removing bone -> no weights remaining");
-        }
     }
     }
     return pMesh->mNumVertices;
     return pMesh->mNumVertices;
 }
 }

+ 29 - 0
code/PostProcessing/MakeVerboseFormat.cpp

@@ -224,3 +224,32 @@ bool MakeVerboseFormatProcess::MakeVerboseFormat(aiMesh* pcMesh)
     }
     }
     return (pcMesh->mNumVertices != iOldNumVertices);
     return (pcMesh->mNumVertices != iOldNumVertices);
 }
 }
+
+
+// ------------------------------------------------------------------------------------------------
+bool IsMeshInVerboseFormat(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 MakeVerboseFormatProcess::IsVerboseFormat(const aiScene* pScene) {
+    for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
+        if(!IsMeshInVerboseFormat(pScene->mMeshes[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}

+ 7 - 0
code/PostProcessing/MakeVerboseFormat.h

@@ -94,6 +94,13 @@ public:
     * @param pScene The imported data to work at. */
     * @param pScene The imported data to work at. */
     void Execute( aiScene* pScene);
     void Execute( aiScene* pScene);
 
 
+public:
+
+    // -------------------------------------------------------------------
+    /** Checks whether the scene is already in verbose format.
+    * @param pScene The data to check. 
+    * @return true if the scene is already in verbose format. */
+    static bool IsVerboseFormat(const aiScene* pScene);
 
 
 private:
 private:
 
 

+ 115 - 10
code/PostProcessing/ScaleProcess.cpp

@@ -39,19 +39,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 */
 */
-#ifndef ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS
-
 #include "ScaleProcess.h"
 #include "ScaleProcess.h"
 
 
 #include <assimp/scene.h>
 #include <assimp/scene.h>
 #include <assimp/postprocess.h>
 #include <assimp/postprocess.h>
+#include <assimp/BaseImporter.h>
 
 
 namespace Assimp {
 namespace Assimp {
 
 
 ScaleProcess::ScaleProcess()
 ScaleProcess::ScaleProcess()
 : BaseProcess()
 : BaseProcess()
 , mScale( AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT ) {
 , mScale( AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT ) {
-    // empty
 }
 }
 
 
 ScaleProcess::~ScaleProcess() {
 ScaleProcess::~ScaleProcess() {
@@ -71,10 +69,26 @@ bool ScaleProcess::IsActive( unsigned int pFlags ) const {
 }
 }
 
 
 void ScaleProcess::SetupProperties( const Importer* pImp ) {
 void ScaleProcess::SetupProperties( const Importer* pImp ) {
-    mScale = pImp->GetPropertyFloat( AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY, 0 );
+    // User scaling
+    mScale = pImp->GetPropertyFloat( AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY, 1.0f );
+
+    // File scaling * Application Scaling
+    float importerScale = pImp->GetPropertyFloat( AI_CONFIG_APP_SCALE_KEY, 1.0f );
+
+    // apply scale to the scale 
+    // helps prevent bugs with backward compatibility for anyone using normal scaling.
+    mScale *= importerScale;
 }
 }
 
 
 void ScaleProcess::Execute( aiScene* pScene ) {
 void ScaleProcess::Execute( aiScene* pScene ) {
+    if(mScale == 1.0f)  {
+        return; // nothing to scale
+    }
+    
+    ai_assert( mScale != 0 );
+    ai_assert( nullptr != pScene );
+    ai_assert( nullptr != pScene->mRootNode );
+
     if ( nullptr == pScene ) {
     if ( nullptr == pScene ) {
         return;
         return;
     }
     }
@@ -82,22 +96,113 @@ void ScaleProcess::Execute( aiScene* pScene ) {
     if ( nullptr == pScene->mRootNode ) {
     if ( nullptr == pScene->mRootNode ) {
         return;
         return;
     }
     }
+    
+    // Process animations and update position transform to new unit system
+    for( unsigned int animationID = 0; animationID < pScene->mNumAnimations; animationID++ )
+    {
+        aiAnimation* animation = pScene->mAnimations[animationID];
+
+        for( unsigned int animationChannel = 0; animationChannel < animation->mNumChannels; animationChannel++)
+        {
+            aiNodeAnim* anim = animation->mChannels[animationChannel];
+            
+            for( unsigned int posKey = 0; posKey < anim->mNumPositionKeys; posKey++)
+            {
+                aiVectorKey& vectorKey = anim->mPositionKeys[posKey];
+                vectorKey.mValue *= mScale;
+            }
+        }
+    }
+
+    for( unsigned int meshID = 0; meshID < pScene->mNumMeshes; meshID++)
+    {
+        aiMesh *mesh = pScene->mMeshes[meshID]; 
+        
+        // Reconstruct mesh vertexes to the new unit system
+        for( unsigned int vertexID = 0; vertexID < mesh->mNumVertices; vertexID++)
+        {
+            aiVector3D& vertex = mesh->mVertices[vertexID];
+            vertex *= mScale;
+        }
+
+
+        // bone placement / scaling
+        for( unsigned int boneID = 0; boneID < mesh->mNumBones; boneID++)
+        {
+            // Reconstruct matrix by transform rather than by scale 
+            // This prevent scale values being changed which can
+            // be meaningful in some cases 
+            // like when you want the modeller to see 1:1 compatibility.
+            aiBone* bone = mesh->mBones[boneID];
+
+            aiVector3D pos, scale;
+            aiQuaternion rotation;
+
+            bone->mOffsetMatrix.Decompose( scale, rotation, pos);
+            
+            aiMatrix4x4 translation;
+            aiMatrix4x4::Translation( pos * mScale, translation );
+            
+            aiMatrix4x4 scaling;
+            aiMatrix4x4::Scaling( aiVector3D(scale), scaling );
+
+            aiMatrix4x4 RotMatrix = aiMatrix4x4 (rotation.GetMatrix());
+
+            bone->mOffsetMatrix = translation * RotMatrix * scaling;
+        }
+
+
+        // animation mesh processing
+        // convert by position rather than scale.
+        for( unsigned int animMeshID = 0; animMeshID < mesh->mNumAnimMeshes; animMeshID++)
+        {
+            aiAnimMesh * animMesh = mesh->mAnimMeshes[animMeshID];
+            
+            for( unsigned int vertexID = 0; vertexID < animMesh->mNumVertices; vertexID++)
+            {
+                aiVector3D& vertex = animMesh->mVertices[vertexID];
+                vertex *= mScale;
+            }
+        }
+    }
 
 
     traverseNodes( pScene->mRootNode );
     traverseNodes( pScene->mRootNode );
 }
 }
 
 
-void ScaleProcess::traverseNodes( aiNode *node ) {
+void ScaleProcess::traverseNodes( aiNode *node, unsigned int nested_node_id ) {    
     applyScaling( node );
     applyScaling( node );
+
+    for( size_t i = 0; i < node->mNumChildren; i++)
+    {
+        // recurse into the tree until we are done!
+        traverseNodes( node->mChildren[i], nested_node_id+1 ); 
+    }
 }
 }
 
 
 void ScaleProcess::applyScaling( aiNode *currentNode ) {
 void ScaleProcess::applyScaling( aiNode *currentNode ) {
     if ( nullptr != currentNode ) {
     if ( nullptr != currentNode ) {
-        currentNode->mTransformation.a1 = currentNode->mTransformation.a1 * mScale;
-        currentNode->mTransformation.b2 = currentNode->mTransformation.b2 * mScale;
-        currentNode->mTransformation.c3 = currentNode->mTransformation.c3 * mScale;
+        // Reconstruct matrix by transform rather than by scale 
+        // This prevent scale values being changed which can
+        // be meaningful in some cases 
+        // like when you want the modeller to 
+        // see 1:1 compatibility.
+        
+        aiVector3D pos, scale;
+        aiQuaternion rotation;
+        currentNode->mTransformation.Decompose( scale, rotation, pos);
+        
+        aiMatrix4x4 translation;
+        aiMatrix4x4::Translation( pos * mScale, translation );
+        
+        aiMatrix4x4 scaling;
+
+        // note: we do not use mScale here, this is on purpose.
+        aiMatrix4x4::Scaling( scale, scaling );
+
+        aiMatrix4x4 RotMatrix = aiMatrix4x4 (rotation.GetMatrix());
+
+        currentNode->mTransformation = translation * RotMatrix * scaling;
     }
     }
 }
 }
 
 
 } // Namespace Assimp
 } // Namespace Assimp
-
-#endif // !! ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS

+ 11 - 2
code/PostProcessing/ScaleProcess.h

@@ -39,7 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 */
 */
-#pragma once
+#ifndef SCALE_PROCESS_H_
+#define SCALE_PROCESS_H_
 
 
 #include "Common/BaseProcess.h"
 #include "Common/BaseProcess.h"
 
 
@@ -53,6 +54,11 @@ namespace Assimp {
 
 
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
 /** ScaleProcess: Class to rescale the whole model.
 /** ScaleProcess: Class to rescale the whole model.
+ * Now rescales animations, bones, and blend shapes properly.
+ * Please note this will not write to 'scale' transform it will rewrite mesh 
+ * and matrixes so that your scale values 
+ * from your model package are preserved, so this is completely intentional
+ * bugs should be reported as soon as they are found.
 */
 */
 class ASSIMP_API ScaleProcess : public BaseProcess {
 class ASSIMP_API ScaleProcess : public BaseProcess {
 public:
 public:
@@ -78,7 +84,7 @@ public:
     virtual void Execute( aiScene* pScene );
     virtual void Execute( aiScene* pScene );
 
 
 private:
 private:
-    void traverseNodes( aiNode *currentNode );
+    void traverseNodes( aiNode *currentNode, unsigned int nested_node_id = 0 );
     void applyScaling( aiNode *currentNode );
     void applyScaling( aiNode *currentNode );
 
 
 private:
 private:
@@ -86,3 +92,6 @@ private:
 };
 };
 
 
 } // Namespace Assimp
 } // Namespace Assimp
+
+
+#endif // SCALE_PROCESS_H_

+ 67 - 9
code/PostProcessing/ValidateDataStructure.cpp

@@ -538,13 +538,17 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation)
 {
 {
     Validate(&pAnimation->mName);
     Validate(&pAnimation->mName);
 
 
-    // validate all materials
-    if (pAnimation->mNumChannels)
+    // validate all animations
+    if (pAnimation->mNumChannels || pAnimation->mNumMorphMeshChannels)
     {
     {
-        if (!pAnimation->mChannels) {
+        if (!pAnimation->mChannels && pAnimation->mNumChannels) {
             ReportError("aiAnimation::mChannels is NULL (aiAnimation::mNumChannels is %i)",
             ReportError("aiAnimation::mChannels is NULL (aiAnimation::mNumChannels is %i)",
                 pAnimation->mNumChannels);
                 pAnimation->mNumChannels);
         }
         }
+        if (!pAnimation->mMorphMeshChannels && pAnimation->mNumMorphMeshChannels) {
+            ReportError("aiAnimation::mMorphMeshChannels is NULL (aiAnimation::mNumMorphMeshChannels is %i)",
+                pAnimation->mNumMorphMeshChannels);
+        }
         for (unsigned int i = 0; i < pAnimation->mNumChannels;++i)
         for (unsigned int i = 0; i < pAnimation->mNumChannels;++i)
         {
         {
             if (!pAnimation->mChannels[i])
             if (!pAnimation->mChannels[i])
@@ -554,6 +558,15 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation)
             }
             }
             Validate(pAnimation, pAnimation->mChannels[i]);
             Validate(pAnimation, pAnimation->mChannels[i]);
         }
         }
+        for (unsigned int i = 0; i < pAnimation->mNumMorphMeshChannels;++i)
+        {
+            if (!pAnimation->mMorphMeshChannels[i])
+            {
+                ReportError("aiAnimation::mMorphMeshChannels[%i] is NULL (aiAnimation::mNumMorphMeshChannels is %i)",
+                    i, pAnimation->mNumMorphMeshChannels);
+            }
+            Validate(pAnimation, pAnimation->mMorphMeshChannels[i]);
+        }
     }
     }
     else {
     else {
     	ReportError("aiAnimation::mNumChannels is 0. At least one node animation channel must be there.");
     	ReportError("aiAnimation::mNumChannels is 0. At least one node animation channel must be there.");
@@ -590,15 +603,18 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
         ReportError("%s #%i is set, but there are only %i %s textures",
         ReportError("%s #%i is set, but there are only %i %s textures",
             szType,iIndex,iNumIndices,szType);
             szType,iIndex,iNumIndices,szType);
     }
     }
-    if (!iNumIndices)return;
+	if (!iNumIndices) {
+		return;
+	}
     std::vector<aiTextureMapping> mappings(iNumIndices);
     std::vector<aiTextureMapping> mappings(iNumIndices);
 
 
     // Now check whether all UV indices are valid ...
     // Now check whether all UV indices are valid ...
     bool bNoSpecified = true;
     bool bNoSpecified = true;
-    for (unsigned int i = 0; i < pMaterial->mNumProperties;++i)
-    {
+    for (unsigned int i = 0; i < pMaterial->mNumProperties;++i) {
         aiMaterialProperty* prop = pMaterial->mProperties[i];
         aiMaterialProperty* prop = pMaterial->mProperties[i];
-        if (prop->mSemantic != type)continue;
+		if (prop->mSemantic != type) {
+			continue;
+		}
 
 
         if ((int)prop->mIndex >= iNumIndices)
         if ((int)prop->mIndex >= iNumIndices)
         {
         {
@@ -621,7 +637,7 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
                 ReportError("Material property %s%i is expected to be 5 floats large (size is %i)",
                 ReportError("Material property %s%i is expected to be 5 floats large (size is %i)",
                     prop->mKey.data,prop->mIndex, prop->mDataLength);
                     prop->mKey.data,prop->mIndex, prop->mDataLength);
             }
             }
-            mappings[prop->mIndex] = *((aiTextureMapping*)prop->mData);
+			//mappings[prop->mIndex] = ((aiUVTransform*)prop->mData);
         }
         }
         else if (!::strcmp(prop->mKey.data,"$tex.uvwsrc")) {
         else if (!::strcmp(prop->mKey.data,"$tex.uvwsrc")) {
             if (aiPTI_Integer != prop->mType || sizeof(int) > prop->mDataLength)
             if (aiPTI_Integer != prop->mType || sizeof(int) > prop->mDataLength)
@@ -903,6 +919,48 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation,
     }
     }
 }
 }
 
 
+void ValidateDSProcess::Validate( const aiAnimation* pAnimation,
+     const aiMeshMorphAnim* pMeshMorphAnim)
+{
+    Validate(&pMeshMorphAnim->mName);
+
+    if (!pMeshMorphAnim->mNumKeys) {
+        ReportError("Empty mesh morph animation channel");
+    }
+
+    // otherwise check whether one of the keys exceeds the total duration of the animation
+    if (pMeshMorphAnim->mNumKeys)
+    {
+        if (!pMeshMorphAnim->mKeys)
+        {
+            ReportError("aiMeshMorphAnim::mKeys is NULL (aiMeshMorphAnim::mNumKeys is %i)",
+                pMeshMorphAnim->mNumKeys);
+        }
+        double dLast = -10e10;
+        for (unsigned int i = 0; i < pMeshMorphAnim->mNumKeys;++i)
+        {
+            // ScenePreprocessor will compute the duration if still the default value
+            // (Aramis) Add small epsilon, comparison tended to fail if max_time == duration,
+            //  seems to be due the compilers register usage/width.
+            if (pAnimation->mDuration > 0. && pMeshMorphAnim->mKeys[i].mTime > pAnimation->mDuration+0.001)
+            {
+                ReportError("aiMeshMorphAnim::mKeys[%i].mTime (%.5f) is larger "
+                    "than aiAnimation::mDuration (which is %.5f)",i,
+                    (float)pMeshMorphAnim->mKeys[i].mTime,
+                    (float)pAnimation->mDuration);
+            }
+            if (i && pMeshMorphAnim->mKeys[i].mTime <= dLast)
+            {
+                ReportWarning("aiMeshMorphAnim::mKeys[%i].mTime (%.5f) is smaller "
+                    "than aiMeshMorphAnim::mKeys[%i] (which is %.5f)",i,
+                    (float)pMeshMorphAnim->mKeys[i].mTime,
+                    i-1, (float)dLast);
+            }
+            dLast = pMeshMorphAnim->mKeys[i].mTime;
+        }
+    }
+}
+
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void ValidateDSProcess::Validate( const aiNode* pNode)
 void ValidateDSProcess::Validate( const aiNode* pNode)
 {
 {
@@ -958,7 +1016,7 @@ void ValidateDSProcess::Validate( const aiString* pString)
 {
 {
     if (pString->length > MAXLEN)
     if (pString->length > MAXLEN)
     {
     {
-        ReportError("aiString::length is too large (%lu, maximum is %lu)",
+        ReportError("aiString::length is too large (%u, maximum is %lu)",
             pString->length,MAXLEN);
             pString->length,MAXLEN);
     }
     }
     const char* sz = pString->data;
     const char* sz = pString->data;

+ 8 - 0
code/PostProcessing/ValidateDataStructure.h

@@ -55,6 +55,7 @@ struct aiBone;
 struct aiMesh;
 struct aiMesh;
 struct aiAnimation;
 struct aiAnimation;
 struct aiNodeAnim;
 struct aiNodeAnim;
+struct aiMeshMorphAnim;
 struct aiTexture;
 struct aiTexture;
 struct aiMaterial;
 struct aiMaterial;
 struct aiNode;
 struct aiNode;
@@ -150,6 +151,13 @@ protected:
     void Validate( const aiAnimation* pAnimation,
     void Validate( const aiAnimation* pAnimation,
         const aiNodeAnim* pBoneAnim);
         const aiNodeAnim* pBoneAnim);
 
 
+    /** Validates a mesh morph animation channel.
+     * @param pAnimation Input animation.
+     * @param pMeshMorphAnim Mesh morph animation channel.
+     * */
+    void Validate( const aiAnimation* pAnimation,
+        const aiMeshMorphAnim* pMeshMorphAnim);
+
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Validates a node and all of its subnodes
     /** Validates a node and all of its subnodes
      * @param Node Input node*/
      * @param Node Input node*/

+ 8 - 8
code/Q3BSP/Q3BSPFileImporter.cpp

@@ -43,7 +43,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #ifndef ASSIMP_BUILD_NO_Q3BSP_IMPORTER
 #ifndef ASSIMP_BUILD_NO_Q3BSP_IMPORTER
 
 
 #include "Q3BSPFileImporter.h"
 #include "Q3BSPFileImporter.h"
-#include "Q3BSPZipArchive.h"
 #include "Q3BSPFileParser.h"
 #include "Q3BSPFileParser.h"
 #include "Q3BSPFileData.h"
 #include "Q3BSPFileData.h"
 
 
@@ -60,6 +59,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/scene.h>
 #include <assimp/scene.h>
 #include <assimp/ai_assert.h>
 #include <assimp/ai_assert.h>
 #include <assimp/DefaultIOSystem.h>
 #include <assimp/DefaultIOSystem.h>
+#include <assimp/ZipArchiveIOSystem.h>
 #include <assimp/importerdesc.h>
 #include <assimp/importerdesc.h>
 #include <vector>
 #include <vector>
 #include <sstream>
 #include <sstream>
@@ -181,7 +181,7 @@ const aiImporterDesc* Q3BSPFileImporter::GetInfo () const {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 //  Import method.
 //  Import method.
 void Q3BSPFileImporter::InternReadFile(const std::string &rFile, aiScene* scene, IOSystem* ioHandler) {
 void Q3BSPFileImporter::InternReadFile(const std::string &rFile, aiScene* scene, IOSystem* ioHandler) {
-    Q3BSPZipArchive Archive( ioHandler, rFile );
+    ZipArchiveIOSystem Archive( ioHandler, rFile );
     if ( !Archive.isOpen() ) {
     if ( !Archive.isOpen() ) {
         throw DeadlyImportError( "Failed to open file " + rFile + "." );
         throw DeadlyImportError( "Failed to open file " + rFile + "." );
     }
     }
@@ -223,10 +223,10 @@ void Q3BSPFileImporter::separateMapName( const std::string &importName, std::str
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 //  Returns the first map in the map archive.
 //  Returns the first map in the map archive.
-bool Q3BSPFileImporter::findFirstMapInArchive( Q3BSPZipArchive &bspArchive, std::string &mapName ) {
+bool Q3BSPFileImporter::findFirstMapInArchive(ZipArchiveIOSystem &bspArchive, std::string &mapName ) {
     mapName = "";
     mapName = "";
     std::vector<std::string> fileList;
     std::vector<std::string> fileList;
-    bspArchive.getFileList( fileList );
+    bspArchive.getFileListExtension( fileList, "bsp" );
     if (fileList.empty()) {
     if (fileList.empty()) {
         return false;
         return false;
     }
     }
@@ -249,7 +249,7 @@ bool Q3BSPFileImporter::findFirstMapInArchive( Q3BSPZipArchive &bspArchive, std:
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 //  Creates the assimp specific data.
 //  Creates the assimp specific data.
 void Q3BSPFileImporter::CreateDataFromImport( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene,
 void Q3BSPFileImporter::CreateDataFromImport( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene,
-        Q3BSPZipArchive *pArchive ) {
+    ZipArchiveIOSystem *pArchive ) {
     if (nullptr == pModel || nullptr == pScene) {
     if (nullptr == pModel || nullptr == pScene) {
         return;
         return;
     }
     }
@@ -418,7 +418,7 @@ void Q3BSPFileImporter::createTriangleTopology( const Q3BSP::Q3BSPModel *pModel,
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 //  Creates all referenced materials.
 //  Creates all referenced materials.
 void Q3BSPFileImporter::createMaterials( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene,
 void Q3BSPFileImporter::createMaterials( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene,
-        Q3BSPZipArchive *pArchive ) {
+    ZipArchiveIOSystem *pArchive ) {
     if ( m_MaterialLookupMap.empty() ) {
     if ( m_MaterialLookupMap.empty() ) {
         return;
         return;
     }
     }
@@ -564,7 +564,7 @@ aiFace *Q3BSPFileImporter::getNextFace( aiMesh *mesh, unsigned int &faceIdx ) {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 //  Imports a texture file.
 //  Imports a texture file.
 bool Q3BSPFileImporter::importTextureFromArchive( const Q3BSP::Q3BSPModel *model,
 bool Q3BSPFileImporter::importTextureFromArchive( const Q3BSP::Q3BSPModel *model,
-                                                 Q3BSP::Q3BSPZipArchive *archive, aiScene*,
+                                                 ZipArchiveIOSystem *archive, aiScene*,
                                                  aiMaterial *pMatHelper, int textureId ) {
                                                  aiMaterial *pMatHelper, int textureId ) {
     if (nullptr == archive || nullptr == pMatHelper ) {
     if (nullptr == archive || nullptr == pMatHelper ) {
         return false;
         return false;
@@ -669,7 +669,7 @@ bool Q3BSPFileImporter::importLightmap( const Q3BSP::Q3BSPModel *pModel, aiScene
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 //  Will search for a supported extension.
 //  Will search for a supported extension.
-bool Q3BSPFileImporter::expandFile(  Q3BSP::Q3BSPZipArchive *pArchive, const std::string &rFilename,
+bool Q3BSPFileImporter::expandFile(ZipArchiveIOSystem *pArchive, const std::string &rFilename,
                                    const std::vector<std::string> &rExtList, std::string &rFile,
                                    const std::vector<std::string> &rExtList, std::string &rFile,
                                    std::string &rExt )
                                    std::string &rExt )
 {
 {

+ 6 - 6
code/Q3BSP/Q3BSPFileImporter.h

@@ -54,9 +54,9 @@ struct aiMaterial;
 struct aiTexture;
 struct aiTexture;
 
 
 namespace Assimp {
 namespace Assimp {
+    class ZipArchiveIOSystem;
 
 
 namespace Q3BSP {
 namespace Q3BSP {
-    class Q3BSPZipArchive;
     struct Q3BSPModel;
     struct Q3BSPModel;
     struct sQ3BSPFace;
     struct sQ3BSPFace;
 }
 }
@@ -85,24 +85,24 @@ protected:
     const aiImporterDesc* GetInfo () const;
     const aiImporterDesc* GetInfo () const;
     void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler);
     void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler);
     void separateMapName( const std::string &rImportName, std::string &rArchiveName, std::string &rMapName );
     void separateMapName( const std::string &rImportName, std::string &rArchiveName, std::string &rMapName );
-    bool findFirstMapInArchive( Q3BSP::Q3BSPZipArchive &rArchive, std::string &rMapName );
-    void CreateDataFromImport( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, Q3BSP::Q3BSPZipArchive *pArchive );
+    bool findFirstMapInArchive(ZipArchiveIOSystem &rArchive, std::string &rMapName );
+    void CreateDataFromImport( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, ZipArchiveIOSystem *pArchive );
     void CreateNodes( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, aiNode *pParent );
     void CreateNodes( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, aiNode *pParent );
     aiNode *CreateTopology( const Q3BSP::Q3BSPModel *pModel, unsigned int materialIdx,
     aiNode *CreateTopology( const Q3BSP::Q3BSPModel *pModel, unsigned int materialIdx,
         std::vector<Q3BSP::sQ3BSPFace*> &rArray, aiMesh  **pMesh );
         std::vector<Q3BSP::sQ3BSPFace*> &rArray, aiMesh  **pMesh );
     void createTriangleTopology( const Q3BSP::Q3BSPModel *pModel, Q3BSP::sQ3BSPFace *pQ3BSPFace, aiMesh* pMesh, unsigned int &rFaceIdx,
     void createTriangleTopology( const Q3BSP::Q3BSPModel *pModel, Q3BSP::sQ3BSPFace *pQ3BSPFace, aiMesh* pMesh, unsigned int &rFaceIdx,
         unsigned int &rVertIdx  );
         unsigned int &rVertIdx  );
-    void createMaterials( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, Q3BSP::Q3BSPZipArchive *pArchive );
+    void createMaterials( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, ZipArchiveIOSystem *pArchive );
     size_t countData( const std::vector<Q3BSP::sQ3BSPFace*> &rArray ) const;
     size_t countData( const std::vector<Q3BSP::sQ3BSPFace*> &rArray ) const;
     size_t countFaces( const std::vector<Q3BSP::sQ3BSPFace*> &rArray ) const;
     size_t countFaces( const std::vector<Q3BSP::sQ3BSPFace*> &rArray ) const;
     size_t countTriangles( const std::vector<Q3BSP::sQ3BSPFace*> &rArray ) const;
     size_t countTriangles( const std::vector<Q3BSP::sQ3BSPFace*> &rArray ) const;
     void createMaterialMap( const Q3BSP::Q3BSPModel *pModel);
     void createMaterialMap( const Q3BSP::Q3BSPModel *pModel);
     aiFace *getNextFace( aiMesh *pMesh, unsigned int &rFaceIdx );
     aiFace *getNextFace( aiMesh *pMesh, unsigned int &rFaceIdx );
-    bool importTextureFromArchive( const Q3BSP::Q3BSPModel *pModel, Q3BSP::Q3BSPZipArchive *pArchive, aiScene* pScene,
+    bool importTextureFromArchive( const Q3BSP::Q3BSPModel *pModel, ZipArchiveIOSystem *pArchive, aiScene* pScene,
         aiMaterial *pMatHelper, int textureId );
         aiMaterial *pMatHelper, int textureId );
     bool importLightmap( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, aiMaterial *pMatHelper, int lightmapId );
     bool importLightmap( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene, aiMaterial *pMatHelper, int lightmapId );
     bool importEntities( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene );
     bool importEntities( const Q3BSP::Q3BSPModel *pModel, aiScene* pScene );
-    bool expandFile(  Q3BSP::Q3BSPZipArchive *pArchive, const std::string &rFilename, const std::vector<std::string> &rExtList,
+    bool expandFile(ZipArchiveIOSystem *pArchive, const std::string &rFilename, const std::vector<std::string> &rExtList,
         std::string &rFile, std::string &rExt );
         std::string &rFile, std::string &rExt );
 
 
 private:
 private:

+ 3 - 2
code/Q3BSP/Q3BSPFileParser.cpp

@@ -45,9 +45,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 #include "Q3BSPFileParser.h"
 #include "Q3BSPFileParser.h"
 #include "Q3BSPFileData.h"
 #include "Q3BSPFileData.h"
-#include "Q3BSPZipArchive.h"
 #include <vector>
 #include <vector>
 #include <assimp/DefaultIOSystem.h>
 #include <assimp/DefaultIOSystem.h>
+#include <assimp/ZipArchiveIOSystem.h>
 #include <assimp/ai_assert.h>
 #include <assimp/ai_assert.h>
 
 
 namespace Assimp {
 namespace Assimp {
@@ -55,7 +55,7 @@ namespace Assimp {
 using namespace Q3BSP;
 using namespace Q3BSP;
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-Q3BSPFileParser::Q3BSPFileParser( const std::string &mapName, Q3BSPZipArchive *pZipArchive ) :
+Q3BSPFileParser::Q3BSPFileParser( const std::string &mapName, ZipArchiveIOSystem *pZipArchive ) :
     m_sOffset( 0 ),
     m_sOffset( 0 ),
     m_Data(),
     m_Data(),
     m_pModel(nullptr),
     m_pModel(nullptr),
@@ -101,6 +101,7 @@ bool Q3BSPFileParser::readData( const std::string &rMapName ) {
     const size_t readSize = pMapFile->Read( &m_Data[0], sizeof( char ), size );
     const size_t readSize = pMapFile->Read( &m_Data[0], sizeof( char ), size );
     if ( readSize != size ) {
     if ( readSize != size ) {
         m_Data.clear();
         m_Data.clear();
+        m_pZipArchive->Close(pMapFile);
         return false;
         return false;
     }
     }
     m_pZipArchive->Close( pMapFile );
     m_pZipArchive->Close( pMapFile );

+ 6 - 7
code/Q3BSP/Q3BSPFileParser.h

@@ -48,13 +48,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 namespace Assimp
 namespace Assimp
 {
 {
+    class ZipArchiveIOSystem;
+
 namespace Q3BSP
 namespace Q3BSP
 {
 {
-
-class Q3BSPZipArchive;
-struct Q3BSPModel;
-class ZipFile;
-
+    struct Q3BSPModel;
+    class ZipFile;
 }
 }
 
 
 // -------------------------------------------------------------------
 // -------------------------------------------------------------------
@@ -62,7 +61,7 @@ class ZipFile;
 class Q3BSPFileParser
 class Q3BSPFileParser
 {
 {
 public:
 public:
-    Q3BSPFileParser( const std::string &rMapName, Q3BSP::Q3BSPZipArchive *pZipArchive );
+    Q3BSPFileParser( const std::string &rMapName, ZipArchiveIOSystem *pZipArchive );
     ~Q3BSPFileParser();
     ~Q3BSPFileParser();
     Q3BSP::Q3BSPModel *getModel() const;
     Q3BSP::Q3BSPModel *getModel() const;
 
 
@@ -83,7 +82,7 @@ private:
     size_t m_sOffset;
     size_t m_sOffset;
     std::vector<char> m_Data;
     std::vector<char> m_Data;
     Q3BSP::Q3BSPModel *m_pModel;
     Q3BSP::Q3BSPModel *m_pModel;
-    Q3BSP::Q3BSPZipArchive *m_pZipArchive;
+    ZipArchiveIOSystem *m_pZipArchive;
 };
 };
 
 
 } // Namespace Assimp
 } // Namespace Assimp

+ 0 - 325
code/Q3BSP/Q3BSPZipArchive.cpp

@@ -1,325 +0,0 @@
-/*
-Open Asset Import Library (assimp)
-----------------------------------------------------------------------
-
-Copyright (c) 2006-2019, assimp team
-
-
-All rights reserved.
-
-Redistribution and use of this software in source and binary forms,
-with or without modification, are permitted provided that the
-following conditions are met:
-
-* Redistributions of source code must retain the above
-  copyright notice, this list of conditions and the
-  following disclaimer.
-
-* Redistributions in binary form must reproduce the above
-  copyright notice, this list of conditions and the
-  following disclaimer in the documentation and/or other
-  materials provided with the distribution.
-
-* Neither the name of the assimp team, nor the names of its
-  contributors may be used to endorse or promote products
-  derived from this software without specific prior
-  written permission of the assimp team.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-----------------------------------------------------------------------
-*/
-
-#ifndef ASSIMP_BUILD_NO_Q3BSP_IMPORTER
-
-#include "Q3BSPZipArchive.h"
-#include <cassert>
-#include <cstdlib>
-#include <assimp/ai_assert.h>
-
-namespace Assimp {
-namespace Q3BSP {
-
-voidpf IOSystem2Unzip::open(voidpf opaque, const char* filename, int mode) {
-    IOSystem* io_system = (IOSystem*) opaque;
-
-    const char* mode_fopen = NULL;
-    if((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) {
-        mode_fopen = "rb";
-    } else {
-        if(mode & ZLIB_FILEFUNC_MODE_EXISTING) {
-            mode_fopen = "r+b";
-        } else {
-            if(mode & ZLIB_FILEFUNC_MODE_CREATE) {
-                mode_fopen = "wb";
-            }
-        }
-    }
-
-    return (voidpf) io_system->Open(filename, mode_fopen);
-}
-
-uLong IOSystem2Unzip::read(voidpf /*opaque*/, voidpf stream, void* buf, uLong size) {
-    IOStream* io_stream = (IOStream*) stream;
-
-    return static_cast<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 = (voidpf) pIOHandler;
-
-    return mapping;
-}
-
-ZipFile::ZipFile(size_t size) : m_Size(size) {
-    ai_assert(m_Size != 0);
-
-    m_Buffer = malloc(m_Size);
-}
-
-ZipFile::~ZipFile() {
-    free(m_Buffer);
-    m_Buffer = NULL;
-}
-
-size_t ZipFile::Read(void* pvBuffer, size_t pSize, size_t pCount) {
-    const size_t size = pSize * pCount;
-    assert(size <= m_Size);
-
-    std::memcpy(pvBuffer, m_Buffer, size);
-
-    return size;
-}
-
-size_t ZipFile::Write(const void* /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/) {
-    return 0;
-}
-
-size_t ZipFile::FileSize() const {
-    return m_Size;
-}
-
-aiReturn ZipFile::Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/) {
-    return aiReturn_FAILURE;
-}
-
-size_t ZipFile::Tell() const {
-    return 0;
-}
-
-void ZipFile::Flush() {
-    // empty
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Constructor.
-Q3BSPZipArchive::Q3BSPZipArchive(IOSystem* pIOHandler, const std::string& rFile) : m_ZipFileHandle(NULL), m_ArchiveMap() {
-    if (! rFile.empty()) {
-        zlib_filefunc_def mapping = IOSystem2Unzip::get(pIOHandler);
-
-        m_ZipFileHandle = unzOpen2(rFile.c_str(), &mapping);
-
-        if(m_ZipFileHandle != nullptr) {
-            mapArchive();
-        }
-    }
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Destructor.
-Q3BSPZipArchive::~Q3BSPZipArchive() {
-    for(auto &file : m_ArchiveMap) {
-        delete file.second;
-    }
-    m_ArchiveMap.clear();
-
-    if(m_ZipFileHandle != nullptr) {
-        unzClose(m_ZipFileHandle);
-        m_ZipFileHandle = nullptr;
-    }
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Returns true, if the archive is already open.
-bool Q3BSPZipArchive::isOpen() const {
-    return (m_ZipFileHandle != nullptr);
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Returns true, if the filename is part of the archive.
-bool Q3BSPZipArchive::Exists(const char* pFile) const {
-    bool exist = false;
-    if (pFile != nullptr) {
-        std::string rFile(pFile);
-        std::map<std::string, ZipFile*>::const_iterator it = m_ArchiveMap.find(rFile);
-
-        if(it != m_ArchiveMap.end()) {
-            exist = true;
-        }
-    }
-
-    return exist;
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Returns the separator delimiter.
-char Q3BSPZipArchive::getOsSeparator() const {
-#ifndef _WIN32
-    return '/';
-#else
-    return '\\';
-#endif
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Opens a file, which is part of the archive.
-IOStream *Q3BSPZipArchive::Open(const char* pFile, const char* /*pMode*/) {
-    ai_assert(pFile != nullptr);
-
-    IOStream* result = nullptr;
-
-    std::map<std::string, ZipFile*>::iterator it = m_ArchiveMap.find(pFile);
-
-    if(it != m_ArchiveMap.end()) {
-        result = (IOStream*) it->second;
-    }
-
-    return result;
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Close a filestream.
-void Q3BSPZipArchive::Close(IOStream *pFile) {
-    (void)(pFile);
-    ai_assert(pFile != nullptr);
-
-    // We don't do anything in case the file would be opened again in the future
-}
-// ------------------------------------------------------------------------------------------------
-//  Returns the file-list of the archive.
-void Q3BSPZipArchive::getFileList(std::vector<std::string> &rFileList) {
-    rFileList.clear();
-
-    for(auto &file : m_ArchiveMap) {
-        rFileList.push_back(file.first);
-    }
-}
-
-// ------------------------------------------------------------------------------------------------
-//  Maps the archive content.
-bool Q3BSPZipArchive::mapArchive() {
-    bool success = false;
-
-    if(m_ZipFileHandle != nullptr) {
-        if(m_ArchiveMap.empty()) {
-            //  At first ensure file is already open
-            if(unzGoToFirstFile(m_ZipFileHandle) == UNZ_OK) {
-                // Loop over all files
-                do {
-                    char filename[FileNameSize];
-                    unz_file_info fileInfo;
-
-                    if(unzGetCurrentFileInfo(m_ZipFileHandle, &fileInfo, filename, FileNameSize, NULL, 0, NULL, 0) == UNZ_OK) {
-                        // The file has EXACTLY the size of uncompressed_size. In C
-                        // you need to mark the last character with '\0', so add
-                        // another character
-                        if(fileInfo.uncompressed_size != 0 && unzOpenCurrentFile(m_ZipFileHandle) == UNZ_OK) {
-                            std::pair<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;
-}
-
-// ------------------------------------------------------------------------------------------------
-
-} // Namespace Q3BSP
-} // Namespace Assimp
-
-#endif // ASSIMP_BUILD_NO_Q3BSP_IMPORTER

+ 0 - 135
code/Q3BSP/Q3BSPZipArchive.h

@@ -1,135 +0,0 @@
-/*
-Open Asset Import Library (assimp)
-----------------------------------------------------------------------
-
-Copyright (c) 2006-2019, assimp team
-
-
-All rights reserved.
-
-Redistribution and use of this software in source and binary forms,
-with or without modification, are permitted provided that the
-following conditions are met:
-
-* Redistributions of source code must retain the above
-  copyright notice, this list of conditions and the
-  following disclaimer.
-
-* Redistributions in binary form must reproduce the above
-  copyright notice, this list of conditions and the
-  following disclaimer in the documentation and/or other
-  materials provided with the distribution.
-
-* Neither the name of the assimp team, nor the names of its
-  contributors may be used to endorse or promote products
-  derived from this software without specific prior
-  written permission of the assimp team.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-----------------------------------------------------------------------
-*/
-#ifndef AI_Q3BSP_ZIPARCHIVE_H_INC
-#define AI_Q3BSP_ZIPARCHIVE_H_INC
-
-#ifdef ASSIMP_USE_HUNTER
-#  include <minizip/unzip.h>
-#else
-#  include <unzip.h>
-#endif
-#include <assimp/IOStream.hpp>
-#include <assimp/IOSystem.hpp>
-#include <vector>
-#include <map>
-#include <cassert>
-
-namespace Assimp {
-namespace Q3BSP {
-
-// ------------------------------------------------------------------------------------------------
-/// \class      IOSystem2Unzip
-/// \ingroup    Assimp::Q3BSP
-///
-/// \brief
-// ------------------------------------------------------------------------------------------------
-class IOSystem2Unzip {
-public:
-    static voidpf open(voidpf opaque, const char* filename, int mode);
-    static uLong read(voidpf opaque, voidpf stream, void* buf, uLong size);
-    static uLong write(voidpf opaque, voidpf stream, const void* buf, uLong size);
-    static long tell(voidpf opaque, voidpf stream);
-    static long seek(voidpf opaque, voidpf stream, uLong offset, int origin);
-    static int close(voidpf opaque, voidpf stream);
-    static int testerror(voidpf opaque, voidpf stream);
-    static zlib_filefunc_def get(IOSystem* pIOHandler);
-};
-
-// ------------------------------------------------------------------------------------------------
-/// \class      ZipFile
-/// \ingroup    Assimp::Q3BSP
-///
-/// \brief
-// ------------------------------------------------------------------------------------------------
-class ZipFile : public IOStream {
-    friend class Q3BSPZipArchive;
-
-public:
-    explicit ZipFile(size_t size);
-    ~ZipFile();
-    size_t Read(void* pvBuffer, size_t pSize, size_t pCount );
-    size_t Write(const void* /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/);
-    size_t FileSize() const;
-    aiReturn Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/);
-    size_t Tell() const;
-    void Flush();
-
-private:
-    void* m_Buffer;
-    size_t m_Size;
-};
-
-// ------------------------------------------------------------------------------------------------
-/// \class      Q3BSPZipArchive
-/// \ingroup    Assimp::Q3BSP
-///
-/// \brief  IMplements a zip archive like the WinZip archives. Will be also used to import data
-/// from a P3K archive ( Quake level format ).
-// ------------------------------------------------------------------------------------------------
-class Q3BSPZipArchive : public Assimp::IOSystem {
-public:
-    static const unsigned int FileNameSize = 256;
-
-public:
-    Q3BSPZipArchive(IOSystem* pIOHandler, const std::string & rFile);
-    ~Q3BSPZipArchive();
-    bool Exists(const char* pFile) const;
-    char getOsSeparator() const;
-    IOStream* Open(const char* pFile, const char* pMode = "rb");
-    void Close(IOStream* pFile);
-    bool isOpen() const;
-    void getFileList(std::vector<std::string> &rFileList);
-
-private:
-    bool mapArchive();
-
-private:
-    unzFile m_ZipFileHandle;
-    std::map<std::string, ZipFile*> m_ArchiveMap;
-};
-
-// ------------------------------------------------------------------------------------------------
-
-} // Namespace Q3BSP
-} // Namespace Assimp
-
-#endif // AI_Q3BSP_ZIPARCHIVE_H_INC

+ 1 - 1
code/STL/STLLoader.cpp

@@ -225,7 +225,7 @@ void STLImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS
     }
     }
     pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_DIFFUSE);
     pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_DIFFUSE);
     pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_SPECULAR);
     pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_SPECULAR);
-    clrDiffuse = aiColor4D( ai_real(1.0), ai_real(1.0), ai_real(1.0), ai_real(1.0));
+    clrDiffuse = aiColor4D( ai_real(0.05), ai_real(0.05), ai_real(0.05), ai_real(1.0));
     pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_AMBIENT);
     pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_AMBIENT);
 
 
     pScene->mNumMaterials = 1;
     pScene->mNumMaterials = 1;

+ 2 - 1
code/Unreal/UnrealLoader.h

@@ -127,7 +127,8 @@ inline void CompressVertex(const aiVector3D& v, uint32_t& out)
     n.X = (int32_t)v.x;
     n.X = (int32_t)v.x;
     n.Y = (int32_t)v.y;
     n.Y = (int32_t)v.y;
     n.Z = (int32_t)v.z;
     n.Z = (int32_t)v.z;
-    out = t;
+    ::memcpy( &out, &t, sizeof(int32_t));
+    //out = t;
 }
 }
 
 
     // UNREAL vertex decompression
     // UNREAL vertex decompression

+ 3 - 3
code/X/XFileParser.cpp

@@ -596,11 +596,11 @@ void XFileParser::ParseDataObjectMeshNormals( Mesh* pMesh)
     // do not crah when no face definitions are there
     // do not crah when no face definitions are there
     if (numFaces > 0) {
     if (numFaces > 0) {
         // normal face creation
         // normal face creation
-        pMesh->mNormFaces.resize( pMesh->mNormFaces.size() + numFaces );
+        pMesh->mNormFaces.resize( numFaces );
         for( unsigned int a = 0; a < numFaces; ++a ) {
         for( unsigned int a = 0; a < numFaces; ++a ) {
             unsigned int numIndices = ReadInt();
             unsigned int numIndices = ReadInt();
-            pMesh->mNormFaces.push_back( Face() );
-            Face& face = pMesh->mNormFaces.back();
+            pMesh->mNormFaces[a] = Face();
+            Face& face = pMesh->mNormFaces[a];
             for( unsigned int b = 0; b < numIndices; ++b ) {
             for( unsigned int b = 0; b < numIndices; ++b ) {
                 face.mIndices.push_back( ReadInt());
                 face.mIndices.push_back( ReadInt());
             }
             }

+ 1 - 1
code/X3D/X3DExporter.cpp

@@ -68,7 +68,7 @@ aiMatrix4x4 out_matr;
 	}
 	}
 
 
 	// multiplicate all matrices in reverse order
 	// multiplicate all matrices in reverse order
-	for(std::list<aiMatrix4x4>::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); rit++) out_matr = out_matr * (*rit);
+	for(std::list<aiMatrix4x4>::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); ++rit) out_matr = out_matr * (*rit);
 
 
 	return out_matr;
 	return out_matr;
 }
 }

+ 37 - 31
code/X3D/X3DImporter.cpp

@@ -80,7 +80,13 @@ const aiImporterDesc X3DImporter::Description = {
 //const std::regex X3DImporter::pattern_nws(R"([^, \t\r\n]+)");
 //const std::regex X3DImporter::pattern_nws(R"([^, \t\r\n]+)");
 //const std::regex X3DImporter::pattern_true(R"(^\s*(?:true|1)\s*$)", std::regex::icase);
 //const std::regex X3DImporter::pattern_true(R"(^\s*(?:true|1)\s*$)", std::regex::icase);
 
 
-struct WordIterator: public std::iterator<std::input_iterator_tag, const char*> {
+struct WordIterator {
+    using iterator_category = std::input_iterator_tag;
+    using value_type = const char*;
+    using difference_type = ptrdiff_t;
+    using pointer = value_type*;
+    using reference = value_type&;
+
     static const char *whitespace;
     static const char *whitespace;
     const char *start_, *end_;
     const char *start_, *end_;
     WordIterator(const char *start, const char *end): start_(start), end_(end) {
     WordIterator(const char *start, const char *end): start_(start), end_(end) {
@@ -130,8 +136,8 @@ X3DImporter::~X3DImporter() {
 void X3DImporter::Clear() {
 void X3DImporter::Clear() {
 	NodeElement_Cur = nullptr;
 	NodeElement_Cur = nullptr;
 	// Delete all elements
 	// Delete all elements
-	if(NodeElement_List.size()) {
-        for ( std::list<CX3DImporter_NodeElement*>::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); it++ ) {
+	if(!NodeElement_List.empty()) {
+        for ( std::list<CX3DImporter_NodeElement*>::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); ++it ) {
             delete *it;
             delete *it;
         }
         }
 		NodeElement_List.clear();
 		NodeElement_List.clear();
@@ -145,7 +151,7 @@ void X3DImporter::Clear() {
 
 
 bool X3DImporter::FindNodeElement_FromRoot(const std::string& pID, const CX3DImporter_NodeElement::EType pType, CX3DImporter_NodeElement** pElement)
 bool X3DImporter::FindNodeElement_FromRoot(const std::string& pID, const CX3DImporter_NodeElement::EType pType, CX3DImporter_NodeElement** pElement)
 {
 {
-	for(std::list<CX3DImporter_NodeElement*>::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); it++)
+	for(std::list<CX3DImporter_NodeElement*>::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); ++it)
 	{
 	{
 		if(((*it)->Type == pType) && ((*it)->ID == pID))
 		if(((*it)->Type == pType) && ((*it)->ID == pID))
 		{
 		{
@@ -176,7 +182,7 @@ bool X3DImporter::FindNodeElement_FromNode(CX3DImporter_NodeElement* pStartNode,
 	}// if((pStartNode->Type() == pType) && (pStartNode->ID() == pID))
 	}// if((pStartNode->Type() == pType) && (pStartNode->ID() == pID))
 
 
 	// Check childs of pStartNode.
 	// Check childs of pStartNode.
-	for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = pStartNode->Child.begin(); ch_it != pStartNode->Child.end(); ch_it++)
+	for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = pStartNode->Child.begin(); ch_it != pStartNode->Child.end(); ++ch_it)
 	{
 	{
 		found = FindNodeElement_FromNode(*ch_it, pID, pType, pElement);
 		found = FindNodeElement_FromNode(*ch_it, pID, pType, pElement);
         if ( found )
         if ( found )
@@ -608,10 +614,10 @@ void X3DImporter::XML_ReadNode_GetAttrVal_AsArrCol3f(const int pAttrIdx, std::ve
 
 
 	XML_ReadNode_GetAttrVal_AsListCol3f(pAttrIdx, tlist);// read as list
 	XML_ReadNode_GetAttrVal_AsListCol3f(pAttrIdx, tlist);// read as list
 	// and copy to array
 	// and copy to array
-	if(tlist.size() > 0)
+	if(!tlist.empty())
 	{
 	{
 		pValue.reserve(tlist.size());
 		pValue.reserve(tlist.size());
-		for(std::list<aiColor3D>::iterator it = tlist.begin(); it != tlist.end(); it++) pValue.push_back(*it);
+		for(std::list<aiColor3D>::iterator it = tlist.begin(); it != tlist.end(); ++it) pValue.push_back(*it);
 	}
 	}
 }
 }
 
 
@@ -641,10 +647,10 @@ void X3DImporter::XML_ReadNode_GetAttrVal_AsArrCol4f(const int pAttrIdx, std::ve
 
 
 	XML_ReadNode_GetAttrVal_AsListCol4f(pAttrIdx, tlist);// read as list
 	XML_ReadNode_GetAttrVal_AsListCol4f(pAttrIdx, tlist);// read as list
 	// and copy to array
 	// and copy to array
-	if(tlist.size() > 0)
+	if(!tlist.empty())
 	{
 	{
 		pValue.reserve(tlist.size());
 		pValue.reserve(tlist.size());
-        for ( std::list<aiColor4D>::iterator it = tlist.begin(); it != tlist.end(); it++ )
+        for ( std::list<aiColor4D>::iterator it = tlist.begin(); it != tlist.end(); ++it )
         {
         {
             pValue.push_back( *it );
             pValue.push_back( *it );
         }
         }
@@ -678,10 +684,10 @@ void X3DImporter::XML_ReadNode_GetAttrVal_AsArrVec2f(const int pAttrIdx, std::ve
 
 
 	XML_ReadNode_GetAttrVal_AsListVec2f(pAttrIdx, tlist);// read as list
 	XML_ReadNode_GetAttrVal_AsListVec2f(pAttrIdx, tlist);// read as list
 	// and copy to array
 	// and copy to array
-	if(tlist.size() > 0)
+	if(!tlist.empty())
 	{
 	{
 		pValue.reserve(tlist.size());
 		pValue.reserve(tlist.size());
-        for ( std::list<aiVector2D>::iterator it = tlist.begin(); it != tlist.end(); it++ )
+        for ( std::list<aiVector2D>::iterator it = tlist.begin(); it != tlist.end(); ++it )
         {
         {
             pValue.push_back( *it );
             pValue.push_back( *it );
         }
         }
@@ -716,10 +722,10 @@ void X3DImporter::XML_ReadNode_GetAttrVal_AsArrVec3f(const int pAttrIdx, std::ve
 
 
 	XML_ReadNode_GetAttrVal_AsListVec3f(pAttrIdx, tlist);// read as list
 	XML_ReadNode_GetAttrVal_AsListVec3f(pAttrIdx, tlist);// read as list
 	// and copy to array
 	// and copy to array
-	if(tlist.size() > 0)
+	if(!tlist.empty())
 	{
 	{
 		pValue.reserve(tlist.size());
 		pValue.reserve(tlist.size());
-        for ( std::list<aiVector3D>::iterator it = tlist.begin(); it != tlist.end(); it++ )
+        for ( std::list<aiVector3D>::iterator it = tlist.begin(); it != tlist.end(); ++it )
         {
         {
             pValue.push_back( *it );
             pValue.push_back( *it );
         }
         }
@@ -817,7 +823,7 @@ void X3DImporter::GeometryHelper_Extend_PointToLine(const std::list<aiVector3D>&
     std::list<aiVector3D>::const_iterator pit = pPoint.begin();
     std::list<aiVector3D>::const_iterator pit = pPoint.begin();
     std::list<aiVector3D>::const_iterator pit_last = pPoint.end();
     std::list<aiVector3D>::const_iterator pit_last = pPoint.end();
 
 
-	pit_last--;
+	--pit_last;
 
 
     if ( pPoint.size() < 2 )
     if ( pPoint.size() < 2 )
     {
     {
@@ -831,7 +837,7 @@ void X3DImporter::GeometryHelper_Extend_PointToLine(const std::list<aiVector3D>&
 	{
 	{
 		pLine.push_back(*pit);// second point of previous line
 		pLine.push_back(*pit);// second point of previous line
 		pLine.push_back(*pit);// first point of next line
 		pLine.push_back(*pit);// first point of next line
-		pit++;
+		++pit;
 	}
 	}
 	// add last point of last line
 	// add last point of last line
 	pLine.push_back(*pit);
 	pLine.push_back(*pit);
@@ -849,7 +855,7 @@ void X3DImporter::GeometryHelper_Extend_PolylineIdxToLineIdx(const std::list<int
 		{
 		{
 			std::list<int32_t>::const_iterator plit_next;
 			std::list<int32_t>::const_iterator plit_next;
 
 
-			plit_next = plit, plit_next++;
+			plit_next = plit, ++plit_next;
 			pLineCoordIdx.push_back(*plit);// second point of previous line.
 			pLineCoordIdx.push_back(*plit);// second point of previous line.
 			pLineCoordIdx.push_back(-1);// delimiter
 			pLineCoordIdx.push_back(-1);// delimiter
 			if((*plit_next == (-1)) || (plit_next == pPolylineCoordIdx.end())) break;// current polyline is finished
 			if((*plit_next == (-1)) || (plit_next == pPolylineCoordIdx.end())) break;// current polyline is finished
@@ -904,7 +910,7 @@ void X3DImporter::GeometryHelper_CoordIdxStr2FacesArr(const std::vector<int32_t>
 	pFaces.reserve(f_data.size() / 3);
 	pFaces.reserve(f_data.size() / 3);
 	inds.reserve(4);
 	inds.reserve(4);
     //PrintVectorSet("build. ci", pCoordIdx);
     //PrintVectorSet("build. ci", pCoordIdx);
-	for(std::vector<int32_t>::iterator it = f_data.begin(); it != f_data.end(); it++)
+	for(std::vector<int32_t>::iterator it = f_data.begin(); it != f_data.end(); ++it)
 	{
 	{
 		// when face is got count how many indices in it.
 		// when face is got count how many indices in it.
 		if(*it == (-1))
 		if(*it == (-1))
@@ -951,7 +957,7 @@ void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::list<aiColor3D
 std::list<aiColor4D> tcol;
 std::list<aiColor4D> tcol;
 
 
 	// create RGBA array from RGB.
 	// create RGBA array from RGB.
-	for(std::list<aiColor3D>::const_iterator it = pColors.begin(); it != pColors.end(); it++) tcol.push_back(aiColor4D((*it).r, (*it).g, (*it).b, 1));
+	for(std::list<aiColor3D>::const_iterator it = pColors.begin(); it != pColors.end(); ++it) tcol.push_back(aiColor4D((*it).r, (*it).g, (*it).b, 1));
 
 
 	// call existing function for adding RGBA colors
 	// call existing function for adding RGBA colors
 	MeshGeometry_AddColor(pMesh, tcol, pColorPerVertex);
 	MeshGeometry_AddColor(pMesh, tcol, pColorPerVertex);
@@ -991,7 +997,7 @@ void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::list<aiColor4D
                 pMesh.mColors[ 0 ][ pMesh.mFaces[ fi ].mIndices[ vi ] ] = *col_it;
                 pMesh.mColors[ 0 ][ pMesh.mFaces[ fi ].mIndices[ vi ] ] = *col_it;
             }
             }
 
 
-			col_it++;
+			++col_it;
 		}
 		}
 	}// if(pColorPerVertex) else
 	}// if(pColorPerVertex) else
 }
 }
@@ -1002,7 +1008,7 @@ void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::vector<int32_t
     std::list<aiColor4D> tcol;
     std::list<aiColor4D> tcol;
 
 
 	// create RGBA array from RGB.
 	// create RGBA array from RGB.
-    for ( std::list<aiColor3D>::const_iterator it = pColors.begin(); it != pColors.end(); it++ )
+    for ( std::list<aiColor3D>::const_iterator it = pColors.begin(); it != pColors.end(); ++it )
     {
     {
         tcol.push_back( aiColor4D( ( *it ).r, ( *it ).g, ( *it ).b, 1 ) );
         tcol.push_back( aiColor4D( ( *it ).r, ( *it ).g, ( *it ).b, 1 ) );
     }
     }
@@ -1025,7 +1031,7 @@ void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::vector<int32_t
 
 
 	// copy list to array because we are need indexed access to colors.
 	// copy list to array because we are need indexed access to colors.
 	col_arr_copy.reserve(pColors.size());
 	col_arr_copy.reserve(pColors.size());
-    for ( std::list<aiColor4D>::const_iterator it = pColors.begin(); it != pColors.end(); it++ )
+    for ( std::list<aiColor4D>::const_iterator it = pColors.begin(); it != pColors.end(); ++it )
     {
     {
         col_arr_copy.push_back( *it );
         col_arr_copy.push_back( *it );
     }
     }
@@ -1042,7 +1048,7 @@ void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::vector<int32_t
 			}
 			}
 			// create list with colors for every vertex.
 			// create list with colors for every vertex.
 			col_tgt_arr.resize(pMesh.mNumVertices);
 			col_tgt_arr.resize(pMesh.mNumVertices);
-			for(std::vector<int32_t>::const_iterator colidx_it = pColorIdx.begin(), coordidx_it = pCoordIdx.begin(); colidx_it != pColorIdx.end(); colidx_it++, coordidx_it++)
+			for(std::vector<int32_t>::const_iterator colidx_it = pColorIdx.begin(), coordidx_it = pCoordIdx.begin(); colidx_it != pColorIdx.end(); ++colidx_it, ++coordidx_it)
 			{
 			{
                 if ( *colidx_it == ( -1 ) )
                 if ( *colidx_it == ( -1 ) )
                 {
                 {
@@ -1115,7 +1121,7 @@ void X3DImporter::MeshGeometry_AddColor(aiMesh& pMesh, const std::vector<int32_t
 	}// if(pColorPerVertex) else
 	}// if(pColorPerVertex) else
 
 
 	// copy array to list for calling function that add colors.
 	// copy array to list for calling function that add colors.
-	for(std::vector<aiColor4D>::const_iterator it = col_tgt_arr.begin(); it != col_tgt_arr.end(); it++) col_tgt_list.push_back(*it);
+	for(std::vector<aiColor4D>::const_iterator it = col_tgt_arr.begin(); it != col_tgt_arr.end(); ++it) col_tgt_list.push_back(*it);
 	// add prepared colors list to mesh.
 	// add prepared colors list to mesh.
 	MeshGeometry_AddColor(pMesh, col_tgt_list, pColorPerVertex);
 	MeshGeometry_AddColor(pMesh, col_tgt_list, pColorPerVertex);
 }
 }
@@ -1128,7 +1134,7 @@ void X3DImporter::MeshGeometry_AddNormal(aiMesh& pMesh, const std::vector<int32_
 
 
 	// copy list to array because we are need indexed access to normals.
 	// copy list to array because we are need indexed access to normals.
 	norm_arr_copy.reserve(pNormals.size());
 	norm_arr_copy.reserve(pNormals.size());
-    for ( std::list<aiVector3D>::const_iterator it = pNormals.begin(); it != pNormals.end(); it++ )
+    for ( std::list<aiVector3D>::const_iterator it = pNormals.begin(); it != pNormals.end(); ++it )
     {
     {
         norm_arr_copy.push_back( *it );
         norm_arr_copy.push_back( *it );
     }
     }
@@ -1141,7 +1147,7 @@ void X3DImporter::MeshGeometry_AddNormal(aiMesh& pMesh, const std::vector<int32_
 			if(pNormalIdx.size() != pCoordIdx.size()) throw DeadlyImportError("Normals and Coords inidces count must be equal.");
 			if(pNormalIdx.size() != pCoordIdx.size()) throw DeadlyImportError("Normals and Coords inidces count must be equal.");
 
 
 			tind.reserve(pNormalIdx.size());
 			tind.reserve(pNormalIdx.size());
-			for(std::vector<int32_t>::const_iterator it = pNormalIdx.begin(); it != pNormalIdx.end(); it++)
+			for(std::vector<int32_t>::const_iterator it = pNormalIdx.begin(); it != pNormalIdx.end(); ++it)
 			{
 			{
 				if(*it != (-1)) tind.push_back(*it);
 				if(*it != (-1)) tind.push_back(*it);
 			}
 			}
@@ -1221,7 +1227,7 @@ void X3DImporter::MeshGeometry_AddNormal(aiMesh& pMesh, const std::list<aiVector
 			// apply color to all vertices of face
 			// apply color to all vertices of face
 			for(size_t vi = 0, vi_e = pMesh.mFaces[fi].mNumIndices; vi < vi_e; vi++) pMesh.mNormals[pMesh.mFaces[fi].mIndices[vi]] = *norm_it;
 			for(size_t vi = 0, vi_e = pMesh.mFaces[fi].mNumIndices; vi < vi_e; vi++) pMesh.mNormals[pMesh.mFaces[fi].mIndices[vi]] = *norm_it;
 
 
-			norm_it++;
+			++norm_it;
 		}
 		}
 	}// if(pNormalPerVertex) else
 	}// if(pNormalPerVertex) else
 }
 }
@@ -1235,7 +1241,7 @@ void X3DImporter::MeshGeometry_AddTexCoord(aiMesh& pMesh, const std::vector<int3
 
 
 	// copy list to array because we are need indexed access to normals.
 	// copy list to array because we are need indexed access to normals.
 	texcoord_arr_copy.reserve(pTexCoords.size());
 	texcoord_arr_copy.reserve(pTexCoords.size());
-	for(std::list<aiVector2D>::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); it++)
+	for(std::list<aiVector2D>::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); ++it)
 	{
 	{
 		texcoord_arr_copy.push_back(aiVector3D((*it).x, (*it).y, 0));
 		texcoord_arr_copy.push_back(aiVector3D((*it).x, (*it).y, 0));
 	}
 	}
@@ -1285,7 +1291,7 @@ void X3DImporter::MeshGeometry_AddTexCoord(aiMesh& pMesh, const std::list<aiVect
 
 
 	// copy list to array because we are need convert aiVector2D to aiVector3D and also get indexed access as a bonus.
 	// copy list to array because we are need convert aiVector2D to aiVector3D and also get indexed access as a bonus.
 	tc_arr_copy.reserve(pTexCoords.size());
 	tc_arr_copy.reserve(pTexCoords.size());
-    for ( std::list<aiVector2D>::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); it++ )
+    for ( std::list<aiVector2D>::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); ++it )
     {
     {
         tc_arr_copy.push_back( aiVector3D( ( *it ).x, ( *it ).y, 0 ) );
         tc_arr_copy.push_back( aiVector3D( ( *it ).x, ( *it ).y, 0 ) );
     }
     }
@@ -1693,7 +1699,7 @@ void X3DImporter::InternReadFile(const std::string& pFile, aiScene* pScene, IOSy
 		// create nodes tree
 		// create nodes tree
 		Postprocess_BuildNode(*NodeElement_Cur, *pScene->mRootNode, mesh_list, mat_list, light_list);
 		Postprocess_BuildNode(*NodeElement_Cur, *pScene->mRootNode, mesh_list, mat_list, light_list);
 		// copy needed data to scene
 		// copy needed data to scene
-		if(mesh_list.size() > 0)
+		if(!mesh_list.empty())
 		{
 		{
 			std::list<aiMesh*>::const_iterator it = mesh_list.begin();
 			std::list<aiMesh*>::const_iterator it = mesh_list.begin();
 
 
@@ -1702,7 +1708,7 @@ void X3DImporter::InternReadFile(const std::string& pFile, aiScene* pScene, IOSy
 			for(size_t i = 0; i < pScene->mNumMeshes; i++) pScene->mMeshes[i] = *it++;
 			for(size_t i = 0; i < pScene->mNumMeshes; i++) pScene->mMeshes[i] = *it++;
 		}
 		}
 
 
-		if(mat_list.size() > 0)
+		if(!mat_list.empty())
 		{
 		{
 			std::list<aiMaterial*>::const_iterator it = mat_list.begin();
 			std::list<aiMaterial*>::const_iterator it = mat_list.begin();
 
 
@@ -1711,7 +1717,7 @@ void X3DImporter::InternReadFile(const std::string& pFile, aiScene* pScene, IOSy
 			for(size_t i = 0; i < pScene->mNumMaterials; i++) pScene->mMaterials[i] = *it++;
 			for(size_t i = 0; i < pScene->mNumMaterials; i++) pScene->mMaterials[i] = *it++;
 		}
 		}
 
 
-		if(light_list.size() > 0)
+		if(!light_list.empty())
 		{
 		{
 			std::list<aiLight*>::const_iterator it = light_list.begin();
 			std::list<aiLight*>::const_iterator it = light_list.begin();
 
 

+ 3 - 3
code/X3D/X3DImporter_Geometry2D.cpp

@@ -356,7 +356,7 @@ void X3DImporter::ParseNode_Geometry2D_Polyline2D()
 		std::list<aiVector3D> tlist;
 		std::list<aiVector3D> tlist;
 
 
 		// convert vec2 to vec3
 		// convert vec2 to vec3
-		for(std::list<aiVector2D>::iterator it2 = lineSegments.begin(); it2 != lineSegments.end(); it2++) tlist.push_back(aiVector3D(it2->x, it2->y, 0));
+		for(std::list<aiVector2D>::iterator it2 = lineSegments.begin(); it2 != lineSegments.end(); ++it2) tlist.push_back(aiVector3D(it2->x, it2->y, 0));
 
 
 		// convert point set to line set
 		// convert point set to line set
 		GeometryHelper_Extend_PointToLine(tlist, ((CX3DImporter_NodeElement_Geometry2D*)ne)->Vertices);
 		GeometryHelper_Extend_PointToLine(tlist, ((CX3DImporter_NodeElement_Geometry2D*)ne)->Vertices);
@@ -399,7 +399,7 @@ void X3DImporter::ParseNode_Geometry2D_Polypoint2D()
 		if(!def.empty()) ne->ID = def;
 		if(!def.empty()) ne->ID = def;
 
 
 		// convert vec2 to vec3
 		// convert vec2 to vec3
-		for(std::list<aiVector2D>::iterator it2 = point.begin(); it2 != point.end(); it2++)
+		for(std::list<aiVector2D>::iterator it2 = point.begin(); it2 != point.end(); ++it2)
 		{
 		{
 			((CX3DImporter_NodeElement_Geometry2D*)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0));
 			((CX3DImporter_NodeElement_Geometry2D*)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0));
 		}
 		}
@@ -500,7 +500,7 @@ void X3DImporter::ParseNode_Geometry2D_TriangleSet2D()
 		if(!def.empty()) ne->ID = def;
 		if(!def.empty()) ne->ID = def;
 
 
 		// convert vec2 to vec3
 		// convert vec2 to vec3
-		for(std::list<aiVector2D>::iterator it2 = vertices.begin(); it2 != vertices.end(); it2++)
+		for(std::list<aiVector2D>::iterator it2 = vertices.begin(); it2 != vertices.end(); ++it2)
 		{
 		{
 			((CX3DImporter_NodeElement_Geometry2D*)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0));
 			((CX3DImporter_NodeElement_Geometry2D*)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0));
 		}
 		}

+ 7 - 7
code/X3D/X3DImporter_Geometry3D.cpp

@@ -153,11 +153,11 @@ void X3DImporter::ParseNode_Geometry3D_Cone()
 		{
 		{
 			StandardShapes::MakeCircle(bottomRadius, tess, tvec);
 			StandardShapes::MakeCircle(bottomRadius, tess, tvec);
 			height = -(height / 2);
 			height = -(height / 2);
-			for(std::vector<aiVector3D>::iterator it = tvec.begin(); it != tvec.end(); it++) it->y = height;// y - because circle made in oXZ.
+			for(std::vector<aiVector3D>::iterator it = tvec.begin(); it != tvec.end(); ++it) it->y = height;// y - because circle made in oXZ.
 		}
 		}
 
 
 		// copy data from temp array
 		// copy data from temp array
-		for(std::vector<aiVector3D>::iterator it = tvec.begin(); it != tvec.end(); it++) ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices.push_back(*it);
+		for(std::vector<aiVector3D>::iterator it = tvec.begin(); it != tvec.end(); ++it) ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices.push_back(*it);
 
 
 		((CX3DImporter_NodeElement_Geometry3D*)ne)->Solid = solid;
 		((CX3DImporter_NodeElement_Geometry3D*)ne)->Solid = solid;
 		((CX3DImporter_NodeElement_Geometry3D*)ne)->NumIndices = 3;
 		((CX3DImporter_NodeElement_Geometry3D*)ne)->NumIndices = 3;
@@ -226,11 +226,11 @@ void X3DImporter::ParseNode_Geometry3D_Cylinder()
 		// copy data from temp arrays
 		// copy data from temp arrays
 		std::list<aiVector3D>& vlist = ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices;// just short alias.
 		std::list<aiVector3D>& vlist = ((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices;// just short alias.
 
 
-		for(std::vector<aiVector3D>::iterator it = tside.begin(); it != tside.end(); it++) vlist.push_back(*it);
+		for(std::vector<aiVector3D>::iterator it = tside.begin(); it != tside.end(); ++it) vlist.push_back(*it);
 
 
 		if(top)
 		if(top)
 		{
 		{
-			for(std::vector<aiVector3D>::iterator it = tcir.begin(); it != tcir.end(); it++)
+			for(std::vector<aiVector3D>::iterator it = tcir.begin(); it != tcir.end(); ++it)
 			{
 			{
 				(*it).y = height;// y - because circle made in oXZ.
 				(*it).y = height;// y - because circle made in oXZ.
 				vlist.push_back(*it);
 				vlist.push_back(*it);
@@ -239,7 +239,7 @@ void X3DImporter::ParseNode_Geometry3D_Cylinder()
 
 
 		if(bottom)
 		if(bottom)
 		{
 		{
-			for(std::vector<aiVector3D>::iterator it = tcir.begin(); it != tcir.end(); it++)
+			for(std::vector<aiVector3D>::iterator it = tcir.begin(); it != tcir.end(); ++it)
 			{
 			{
 				(*it).y = -height;// y - because circle made in oXZ.
 				(*it).y = -height;// y - because circle made in oXZ.
 				vlist.push_back(*it);
 				vlist.push_back(*it);
@@ -336,7 +336,7 @@ void X3DImporter::ParseNode_Geometry3D_ElevationGrid()
 					aiVector3D tvec(xSpacing * xi, *he_it, zSpacing * zi);
 					aiVector3D tvec(xSpacing * xi, *he_it, zSpacing * zi);
 
 
 					grid_alias.Vertices.push_back(tvec);
 					grid_alias.Vertices.push_back(tvec);
-					he_it++;
+					++he_it;
 				}
 				}
 			}
 			}
 		}// END: create grid vertices list
 		}// END: create grid vertices list
@@ -977,7 +977,7 @@ void X3DImporter::ParseNode_Geometry3D_Sphere()
 
 
 		StandardShapes::MakeSphere(tess, tlist);
 		StandardShapes::MakeSphere(tess, tlist);
 		// copy data from temp array and apply scale
 		// copy data from temp array and apply scale
-		for(std::vector<aiVector3D>::iterator it = tlist.begin(); it != tlist.end(); it++)
+		for(std::vector<aiVector3D>::iterator it = tlist.begin(); it != tlist.end(); ++it)
 		{
 		{
 			((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices.push_back(*it * radius);
 			((CX3DImporter_NodeElement_Geometry3D*)ne)->Vertices.push_back(*it * radius);
 		}
 		}

+ 1 - 1
code/X3D/X3DImporter_Networking.cpp

@@ -93,7 +93,7 @@ void X3DImporter::ParseNode_Networking_Inline()
 		// at this place new group mode created and made current, so we can name it.
 		// at this place new group mode created and made current, so we can name it.
 		if(!def.empty()) NodeElement_Cur->ID = def;
 		if(!def.empty()) NodeElement_Cur->ID = def;
 
 
-		if(load && (url.size() > 0))
+		if(load && !url.empty())
 		{
 		{
 			std::string full_path = mpIOHandler->CurrentDirectory() + url.front();
 			std::string full_path = mpIOHandler->CurrentDirectory() + url.front();
 
 

+ 40 - 40
code/X3D/X3DImporter_Postprocess.cpp

@@ -81,7 +81,7 @@ aiMatrix4x4 X3DImporter::PostprocessHelper_Matrix_GlobalToCurrent() const
 	}
 	}
 
 
 	// multiplicate all matrices in reverse order
 	// multiplicate all matrices in reverse order
-	for(std::list<aiMatrix4x4>::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); rit++) out_matr = out_matr * (*rit);
+	for(std::list<aiMatrix4x4>::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); ++rit) out_matr = out_matr * (*rit);
 
 
 	return out_matr;
 	return out_matr;
 }
 }
@@ -89,7 +89,7 @@ aiMatrix4x4 X3DImporter::PostprocessHelper_Matrix_GlobalToCurrent() const
 void X3DImporter::PostprocessHelper_CollectMetadata(const CX3DImporter_NodeElement& pNodeElement, std::list<CX3DImporter_NodeElement*>& pList) const
 void X3DImporter::PostprocessHelper_CollectMetadata(const CX3DImporter_NodeElement& pNodeElement, std::list<CX3DImporter_NodeElement*>& pList) const
 {
 {
 	// walk through childs and find for metadata.
 	// walk through childs and find for metadata.
-	for(std::list<CX3DImporter_NodeElement*>::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); el_it++)
+	for(std::list<CX3DImporter_NodeElement*>::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); ++el_it)
 	{
 	{
 		if(((*el_it)->Type == CX3DImporter_NodeElement::ENET_MetaBoolean) || ((*el_it)->Type == CX3DImporter_NodeElement::ENET_MetaDouble) ||
 		if(((*el_it)->Type == CX3DImporter_NodeElement::ENET_MetaBoolean) || ((*el_it)->Type == CX3DImporter_NodeElement::ENET_MetaDouble) ||
 			((*el_it)->Type == CX3DImporter_NodeElement::ENET_MetaFloat) || ((*el_it)->Type == CX3DImporter_NodeElement::ENET_MetaInteger) ||
 			((*el_it)->Type == CX3DImporter_NodeElement::ENET_MetaFloat) || ((*el_it)->Type == CX3DImporter_NodeElement::ENET_MetaInteger) ||
@@ -194,7 +194,7 @@ void X3DImporter::Postprocess_BuildMaterial(const CX3DImporter_NodeElement& pNod
 	aiMaterial& taimat = **pMaterial;// creating alias for convenience.
 	aiMaterial& taimat = **pMaterial;// creating alias for convenience.
 
 
 	// at this point pNodeElement point to <Appearance> node. Walk through childs and add all stored data.
 	// at this point pNodeElement point to <Appearance> node. Walk through childs and add all stored data.
-	for(std::list<CX3DImporter_NodeElement*>::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); el_it++)
+	for(std::list<CX3DImporter_NodeElement*>::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); ++el_it)
 	{
 	{
 		if((*el_it)->Type == CX3DImporter_NodeElement::ENET_Material)
 		if((*el_it)->Type == CX3DImporter_NodeElement::ENET_Material)
 		{
 		{
@@ -255,7 +255,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		std::vector<aiVector3D> tarr;
 		std::vector<aiVector3D> tarr;
 
 
 		tarr.reserve(tnemesh.Vertices.size());
 		tarr.reserve(tnemesh.Vertices.size());
-		for(std::list<aiVector3D>::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); it++) tarr.push_back(*it);
+		for(std::list<aiVector3D>::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); ++it) tarr.push_back(*it);
 		*pMesh = StandardShapes::MakeMesh(tarr, static_cast<unsigned int>(tnemesh.NumIndices));// create mesh from vertices using Assimp help.
 		*pMesh = StandardShapes::MakeMesh(tarr, static_cast<unsigned int>(tnemesh.NumIndices));// create mesh from vertices using Assimp help.
 
 
 		return;// mesh is build, nothing to do anymore.
 		return;// mesh is build, nothing to do anymore.
@@ -273,7 +273,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		std::vector<aiVector3D> tarr;
 		std::vector<aiVector3D> tarr;
 
 
 		tarr.reserve(tnemesh.Vertices.size());
 		tarr.reserve(tnemesh.Vertices.size());
-		for(std::list<aiVector3D>::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); it++) tarr.push_back(*it);
+		for(std::list<aiVector3D>::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); ++it) tarr.push_back(*it);
 
 
 		*pMesh = StandardShapes::MakeMesh(tarr, static_cast<unsigned int>(tnemesh.NumIndices));// create mesh from vertices using Assimp help.
 		*pMesh = StandardShapes::MakeMesh(tarr, static_cast<unsigned int>(tnemesh.NumIndices));// create mesh from vertices using Assimp help.
 
 
@@ -289,7 +289,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		// at first create mesh from existing vertices.
 		// at first create mesh from existing vertices.
 		*pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIdx, tnemesh.Vertices);
 		*pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIdx, tnemesh.Vertices);
 		// copy additional information from children
 		// copy additional information from children
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
 				MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color*)*ch_it)->Value, tnemesh.ColorPerVertex);
 				MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color*)*ch_it)->Value, tnemesh.ColorPerVertex);
@@ -301,7 +301,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 				MeshGeometry_AddTexCoord(**pMesh, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value);
 				MeshGeometry_AddTexCoord(**pMesh, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value);
 			else
 			else
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of ElevationGrid: " + to_string((*ch_it)->Type) + ".");
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of ElevationGrid: " + to_string((*ch_it)->Type) + ".");
-		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 
 
 		return;// mesh is build, nothing to do anymore.
 		return;// mesh is build, nothing to do anymore.
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_ElevationGrid)
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_ElevationGrid)
@@ -313,7 +313,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		CX3DImporter_NodeElement_IndexedSet& tnemesh = *((CX3DImporter_NodeElement_IndexedSet*)&pNodeElement);// create alias for convenience
 		CX3DImporter_NodeElement_IndexedSet& tnemesh = *((CX3DImporter_NodeElement_IndexedSet*)&pNodeElement);// create alias for convenience
 
 
 		// at first search for <Coordinate> node and create mesh.
 		// at first search for <Coordinate> node and create mesh.
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			{
 			{
@@ -322,7 +322,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		}
 		}
 
 
 		// copy additional information from children
 		// copy additional information from children
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
 				MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_Color*)*ch_it)->Value, tnemesh.ColorPerVertex);
 				MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_Color*)*ch_it)->Value, tnemesh.ColorPerVertex);
@@ -338,7 +338,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 				MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value);
 				MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value);
 			else
 			else
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedFaceSet: " + to_string((*ch_it)->Type) + ".");
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedFaceSet: " + to_string((*ch_it)->Type) + ".");
-		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 
 
 		return;// mesh is build, nothing to do anymore.
 		return;// mesh is build, nothing to do anymore.
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedFaceSet)
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedFaceSet)
@@ -348,7 +348,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		CX3DImporter_NodeElement_IndexedSet& tnemesh = *((CX3DImporter_NodeElement_IndexedSet*)&pNodeElement);// create alias for convenience
 		CX3DImporter_NodeElement_IndexedSet& tnemesh = *((CX3DImporter_NodeElement_IndexedSet*)&pNodeElement);// create alias for convenience
 
 
 		// at first search for <Coordinate> node and create mesh.
 		// at first search for <Coordinate> node and create mesh.
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			{
 			{
@@ -357,7 +357,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		}
 		}
 
 
 		// copy additional information from children
 		// copy additional information from children
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			ai_assert(*pMesh);
 			ai_assert(*pMesh);
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
@@ -369,7 +369,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 				{} // skip because already read when mesh created.
 				{} // skip because already read when mesh created.
 			else
 			else
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedLineSet: " + to_string((*ch_it)->Type) + ".");
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedLineSet: " + to_string((*ch_it)->Type) + ".");
-		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 
 
 		return;// mesh is build, nothing to do anymore.
 		return;// mesh is build, nothing to do anymore.
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedLineSet)
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedLineSet)
@@ -381,7 +381,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		CX3DImporter_NodeElement_IndexedSet& tnemesh = *((CX3DImporter_NodeElement_IndexedSet*)&pNodeElement);// create alias for convenience
 		CX3DImporter_NodeElement_IndexedSet& tnemesh = *((CX3DImporter_NodeElement_IndexedSet*)&pNodeElement);// create alias for convenience
 
 
 		// at first search for <Coordinate> node and create mesh.
 		// at first search for <Coordinate> node and create mesh.
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			{
 			{
@@ -390,7 +390,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		}
 		}
 
 
 		// copy additional information from children
 		// copy additional information from children
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			ai_assert(*pMesh);
 			ai_assert(*pMesh);
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
@@ -408,7 +408,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 			else
 			else
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedTriangleSet or IndexedTriangleFanSet, or \
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedTriangleSet or IndexedTriangleFanSet, or \
 																	IndexedTriangleStripSet: " + to_string((*ch_it)->Type) + ".");
 																	IndexedTriangleStripSet: " + to_string((*ch_it)->Type) + ".");
-		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 
 
 		return;// mesh is build, nothing to do anymore.
 		return;// mesh is build, nothing to do anymore.
 	}// if((pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleFanSet) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleStripSet))
 	}// if((pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleFanSet) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleStripSet))
@@ -430,7 +430,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience
 		CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience
 
 
 		// at first search for <Coordinate> node and create mesh.
 		// at first search for <Coordinate> node and create mesh.
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			{
 			{
@@ -438,7 +438,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 
 
 				vec_copy.reserve(((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.size());
 				vec_copy.reserve(((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.size());
 				for(std::list<aiVector3D>::const_iterator it = ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.begin();
 				for(std::list<aiVector3D>::const_iterator it = ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.begin();
-					it != ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.end(); it++)
+					it != ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.end(); ++it)
 				{
 				{
 					vec_copy.push_back(*it);
 					vec_copy.push_back(*it);
 				}
 				}
@@ -448,7 +448,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		}
 		}
 
 
 		// copy additional information from children
 		// copy additional information from children
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			ai_assert(*pMesh);
 			ai_assert(*pMesh);
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
@@ -459,7 +459,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 				{} // skip because already read when mesh created.
 				{} // skip because already read when mesh created.
 			else
 			else
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of PointSet: " + to_string((*ch_it)->Type) + ".");
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of PointSet: " + to_string((*ch_it)->Type) + ".");
-		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 
 
 		return;// mesh is build, nothing to do anymore.
 		return;// mesh is build, nothing to do anymore.
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_PointSet)
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_PointSet)
@@ -469,7 +469,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience
 		CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience
 
 
 		// at first search for <Coordinate> node and create mesh.
 		// at first search for <Coordinate> node and create mesh.
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			{
 			{
@@ -478,7 +478,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		}
 		}
 
 
 		// copy additional information from children
 		// copy additional information from children
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			ai_assert(*pMesh);
 			ai_assert(*pMesh);
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
@@ -489,7 +489,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 				{} // skip because already read when mesh created.
 				{} // skip because already read when mesh created.
 			else
 			else
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of LineSet: " + to_string((*ch_it)->Type) + ".");
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of LineSet: " + to_string((*ch_it)->Type) + ".");
-		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 
 
 		return;// mesh is build, nothing to do anymore.
 		return;// mesh is build, nothing to do anymore.
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_LineSet)
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_LineSet)
@@ -499,7 +499,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience
 		CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience
 
 
 		// at first search for <Coordinate> node and create mesh.
 		// at first search for <Coordinate> node and create mesh.
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			{
 			{
@@ -508,7 +508,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		}
 		}
 
 
 		// copy additional information from children
 		// copy additional information from children
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			if ( nullptr == *pMesh ) {
 			if ( nullptr == *pMesh ) {
 				break;
 				break;
@@ -526,7 +526,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 				MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value);
 				MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value);
 			else
 			else
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeFanSet: " + to_string((*ch_it)->Type) + ".");
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeFanSet: " + to_string((*ch_it)->Type) + ".");
-		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 
 
 		return;// mesh is build, nothing to do anymore.
 		return;// mesh is build, nothing to do anymore.
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleFanSet)
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleFanSet)
@@ -536,7 +536,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience
 		CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience
 
 
 		// at first search for <Coordinate> node and create mesh.
 		// at first search for <Coordinate> node and create mesh.
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			{
 			{
@@ -544,7 +544,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 
 
 				vec_copy.reserve(((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.size());
 				vec_copy.reserve(((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.size());
 				for(std::list<aiVector3D>::const_iterator it = ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.begin();
 				for(std::list<aiVector3D>::const_iterator it = ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.begin();
-					it != ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.end(); it++)
+					it != ((CX3DImporter_NodeElement_Coordinate*)*ch_it)->Value.end(); ++it)
 				{
 				{
 					vec_copy.push_back(*it);
 					vec_copy.push_back(*it);
 				}
 				}
@@ -554,7 +554,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		}
 		}
 
 
 		// copy additional information from children
 		// copy additional information from children
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			ai_assert(*pMesh);
 			ai_assert(*pMesh);
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
@@ -570,7 +570,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 				MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value);
 				MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value);
 			else
 			else
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeSet: " + to_string((*ch_it)->Type) + ".");
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeSet: " + to_string((*ch_it)->Type) + ".");
-		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 
 
 		return;// mesh is build, nothing to do anymore.
 		return;// mesh is build, nothing to do anymore.
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleSet)
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleSet)
@@ -580,7 +580,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience
 		CX3DImporter_NodeElement_Set& tnemesh = *((CX3DImporter_NodeElement_Set*)&pNodeElement);// create alias for convenience
 
 
 		// at first search for <Coordinate> node and create mesh.
 		// at first search for <Coordinate> node and create mesh.
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate)
 			{
 			{
@@ -589,7 +589,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 		}
 		}
 
 
 		// copy additional information from children
 		// copy additional information from children
-		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 		{
 		{
 			ai_assert(*pMesh);
 			ai_assert(*pMesh);
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
 			if((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color)
@@ -605,7 +605,7 @@ void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement& pNodeEle
 				MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value);
 				MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate*)*ch_it)->Value);
 			else
 			else
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TriangleStripSet: " + to_string((*ch_it)->Type) + ".");
 				throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TriangleStripSet: " + to_string((*ch_it)->Type) + ".");
-		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ch_it++)
+		}// for(std::list<CX3DImporter_NodeElement*>::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it)
 
 
 		return;// mesh is build, nothing to do anymore.
 		return;// mesh is build, nothing to do anymore.
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleStripSet)
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleStripSet)
@@ -639,16 +639,16 @@ void X3DImporter::Postprocess_BuildNode(const CX3DImporter_NodeElement& pNodeEle
 			}
 			}
 			else
 			else
 			{
 			{
-				for(size_t i = 0; i < (size_t)tne_group.Choice; i++) chit_begin++;// forward iterator to chosen node.
+				for(size_t i = 0; i < (size_t)tne_group.Choice; i++) ++chit_begin;// forward iterator to chosen node.
 
 
 				chit_end = chit_begin;
 				chit_end = chit_begin;
-				chit_end++;// point end iterator to next element after chosen node.
+				++chit_end;// point end iterator to next element after chosen node.
 			}
 			}
 		}// if(tne_group.UseChoice)
 		}// if(tne_group.UseChoice)
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_Group)
 	}// if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_Group)
 
 
 	// Reserve memory for fast access and check children.
 	// Reserve memory for fast access and check children.
-	for(std::list<CX3DImporter_NodeElement*>::const_iterator it = chit_begin; it != chit_end; it++)
+	for(std::list<CX3DImporter_NodeElement*>::const_iterator it = chit_begin; it != chit_end; ++it)
 	{// in this loop we do not read metadata because it's already read at begin.
 	{// in this loop we do not read metadata because it's already read at begin.
 		if((*it)->Type == CX3DImporter_NodeElement::ENET_Group)
 		if((*it)->Type == CX3DImporter_NodeElement::ENET_Group)
 		{
 		{
@@ -677,7 +677,7 @@ void X3DImporter::Postprocess_BuildNode(const CX3DImporter_NodeElement& pNodeEle
 	}// for(std::list<CX3DImporter_NodeElement*>::const_iterator it = chit_begin; it != chit_end; it++)
 	}// for(std::list<CX3DImporter_NodeElement*>::const_iterator it = chit_begin; it != chit_end; it++)
 
 
 	// copy data about children and meshes to aiNode.
 	// copy data about children and meshes to aiNode.
-	if(SceneNode_Child.size() > 0)
+	if(!SceneNode_Child.empty())
 	{
 	{
 		std::list<aiNode*>::const_iterator it = SceneNode_Child.begin();
 		std::list<aiNode*>::const_iterator it = SceneNode_Child.begin();
 
 
@@ -686,7 +686,7 @@ void X3DImporter::Postprocess_BuildNode(const CX3DImporter_NodeElement& pNodeEle
 		for(size_t i = 0; i < pSceneNode.mNumChildren; i++) pSceneNode.mChildren[i] = *it++;
 		for(size_t i = 0; i < pSceneNode.mNumChildren; i++) pSceneNode.mChildren[i] = *it++;
 	}
 	}
 
 
-	if(SceneNode_Mesh.size() > 0)
+	if(!SceneNode_Mesh.empty())
 	{
 	{
 		std::list<unsigned int>::const_iterator it = SceneNode_Mesh.begin();
 		std::list<unsigned int>::const_iterator it = SceneNode_Mesh.begin();
 
 
@@ -706,7 +706,7 @@ void X3DImporter::Postprocess_BuildShape(const CX3DImporter_NodeElement_Shape& p
     CX3DImporter_NodeElement::EType mesh_type = CX3DImporter_NodeElement::ENET_Invalid;
     CX3DImporter_NodeElement::EType mesh_type = CX3DImporter_NodeElement::ENET_Invalid;
     unsigned int mat_ind = 0;
     unsigned int mat_ind = 0;
 
 
-	for(std::list<CX3DImporter_NodeElement*>::const_iterator it = pShapeNodeElement.Child.begin(); it != pShapeNodeElement.Child.end(); it++)
+	for(std::list<CX3DImporter_NodeElement*>::const_iterator it = pShapeNodeElement.Child.begin(); it != pShapeNodeElement.Child.end(); ++it)
 	{
 	{
 		if(PostprocessHelper_ElementIsMesh((*it)->Type))
 		if(PostprocessHelper_ElementIsMesh((*it)->Type))
 		{
 		{
@@ -779,7 +779,7 @@ void X3DImporter::Postprocess_CollectMetadata(const CX3DImporter_NodeElement& pN
 		// copy collected metadata to output node.
 		// copy collected metadata to output node.
         pSceneNode.mMetaData = aiMetadata::Alloc( static_cast<unsigned int>(meta_list.size()) );
         pSceneNode.mMetaData = aiMetadata::Alloc( static_cast<unsigned int>(meta_list.size()) );
 		meta_idx = 0;
 		meta_idx = 0;
-		for(std::list<CX3DImporter_NodeElement*>::const_iterator it = meta_list.begin(); it != meta_list.end(); it++, meta_idx++)
+		for(std::list<CX3DImporter_NodeElement*>::const_iterator it = meta_list.begin(); it != meta_list.end(); ++it, ++meta_idx)
 		{
 		{
 			CX3DImporter_NodeElement_Meta* cur_meta = (CX3DImporter_NodeElement_Meta*)*it;
 			CX3DImporter_NodeElement_Meta* cur_meta = (CX3DImporter_NodeElement_Meta*)*it;
 
 

+ 6 - 6
code/X3D/X3DImporter_Rendering.cpp

@@ -295,7 +295,7 @@ void X3DImporter::ParseNode_Rendering_IndexedTriangleFanSet()
 		ne_alias.CoordIndex.clear();
 		ne_alias.CoordIndex.clear();
 		int counter = 0;
 		int counter = 0;
 		int32_t idx[3];
 		int32_t idx[3];
-		for(std::vector<int32_t>::const_iterator idx_it = index.begin(); idx_it != index.end(); idx_it++)
+		for(std::vector<int32_t>::const_iterator idx_it = index.begin(); idx_it != index.end(); ++idx_it)
 		{
 		{
 			idx[2] = *idx_it;
 			idx[2] = *idx_it;
 			if (idx[2] < 0)
 			if (idx[2] < 0)
@@ -413,7 +413,7 @@ void X3DImporter::ParseNode_Rendering_IndexedTriangleSet()
 		ne_alias.CoordIndex.clear();
 		ne_alias.CoordIndex.clear();
 		int counter = 0;
 		int counter = 0;
 		int32_t idx[3];
 		int32_t idx[3];
-		for(std::vector<int32_t>::const_iterator idx_it = index.begin(); idx_it != index.end(); idx_it++)
+		for(std::vector<int32_t>::const_iterator idx_it = index.begin(); idx_it != index.end(); ++idx_it)
 		{
 		{
 			idx[counter++] = *idx_it;
 			idx[counter++] = *idx_it;
 			if (counter > 2)
 			if (counter > 2)
@@ -519,7 +519,7 @@ void X3DImporter::ParseNode_Rendering_IndexedTriangleStripSet()
 		ne_alias.CoordIndex.clear();
 		ne_alias.CoordIndex.clear();
 		int counter = 0;
 		int counter = 0;
 		int32_t idx[3];
 		int32_t idx[3];
-		for(std::vector<int32_t>::const_iterator idx_it = index.begin(); idx_it != index.end(); idx_it++)
+		for(std::vector<int32_t>::const_iterator idx_it = index.begin(); idx_it != index.end(); ++idx_it)
 		{
 		{
 			idx[2] = *idx_it;
 			idx[2] = *idx_it;
 			if (idx[2] < 0)
 			if (idx[2] < 0)
@@ -617,7 +617,7 @@ void X3DImporter::ParseNode_Rendering_LineSet()
 		size_t coord_num = 0;
 		size_t coord_num = 0;
 
 
 		ne_alias.CoordIndex.clear();
 		ne_alias.CoordIndex.clear();
-		for(std::vector<int32_t>::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); vc_it++)
+		for(std::vector<int32_t>::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); ++vc_it)
 		{
 		{
 			if(*vc_it < 2) throw DeadlyImportError("LineSet. vertexCount shall be greater than or equal to two.");
 			if(*vc_it < 2) throw DeadlyImportError("LineSet. vertexCount shall be greater than or equal to two.");
 
 
@@ -765,7 +765,7 @@ void X3DImporter::ParseNode_Rendering_TriangleFanSet()
 		// assign indices for first triangle
 		// assign indices for first triangle
 		coord_num_first = 0;
 		coord_num_first = 0;
 		coord_num_prev = 1;
 		coord_num_prev = 1;
-		for(std::vector<int32_t>::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); vc_it++)
+		for(std::vector<int32_t>::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); ++vc_it)
 		{
 		{
 			if(*vc_it < 3) throw DeadlyImportError("TriangleFanSet. fanCount shall be greater than or equal to three.");
 			if(*vc_it < 3) throw DeadlyImportError("TriangleFanSet. fanCount shall be greater than or equal to three.");
 
 
@@ -956,7 +956,7 @@ void X3DImporter::ParseNode_Rendering_TriangleStripSet()
 
 
 		ne_alias.CoordIndex.clear();
 		ne_alias.CoordIndex.clear();
 		coord_num_sb = 0;
 		coord_num_sb = 0;
-		for(std::vector<int32_t>::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); vc_it++)
+		for(std::vector<int32_t>::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); ++vc_it)
 		{
 		{
 			if(*vc_it < 3) throw DeadlyImportError("TriangleStripSet. stripCount shall be greater than or equal to three.");
 			if(*vc_it < 3) throw DeadlyImportError("TriangleStripSet. stripCount shall be greater than or equal to three.");
 
 

+ 1 - 1
code/X3D/X3DImporter_Texturing.cpp

@@ -89,7 +89,7 @@ void X3DImporter::ParseNode_Texturing_ImageTexture()
 		((CX3DImporter_NodeElement_ImageTexture*)ne)->RepeatS = repeatS;
 		((CX3DImporter_NodeElement_ImageTexture*)ne)->RepeatS = repeatS;
 		((CX3DImporter_NodeElement_ImageTexture*)ne)->RepeatT = repeatT;
 		((CX3DImporter_NodeElement_ImageTexture*)ne)->RepeatT = repeatT;
 		// Attribute "url" can contain list of strings. But we need only one - first.
 		// Attribute "url" can contain list of strings. But we need only one - first.
-		if(url.size() > 0)
+		if(!url.empty())
 			((CX3DImporter_NodeElement_ImageTexture*)ne)->URL = url.front();
 			((CX3DImporter_NodeElement_ImageTexture*)ne)->URL = url.front();
 		else
 		else
 			((CX3DImporter_NodeElement_ImageTexture*)ne)->URL = "";
 			((CX3DImporter_NodeElement_ImageTexture*)ne)->URL = "";

+ 8 - 61
code/glTF/glTFAsset.h

@@ -92,38 +92,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #   endif
 #   endif
 #endif
 #endif
 
 
+#include "glTF/glTFCommon.h"
+
 namespace glTF
 namespace glTF
 {
 {
-#ifdef ASSIMP_API
-    using Assimp::IOStream;
-    using Assimp::IOSystem;
-    using std::shared_ptr;
-#else
-    using std::shared_ptr;
-
-    typedef std::runtime_error DeadlyImportError;
-    typedef std::runtime_error DeadlyExportError;
-
-    enum aiOrigin { aiOrigin_SET = 0, aiOrigin_CUR = 1, aiOrigin_END = 2 };
-    class IOSystem;
-    class IOStream
-    {
-        FILE* f;
-    public:
-        IOStream(FILE* file) : f(file) {}
-        ~IOStream() { fclose(f); f = 0; }
-
-        size_t Read(void* b, size_t sz, size_t n) { return fread(b, sz, n, f); }
-        size_t Write(const void* b, size_t sz, size_t n) { return fwrite(b, sz, n, f); }
-        int    Seek(size_t off, aiOrigin orig) { return fseek(f, off, int(orig)); }
-        size_t Tell() const { return ftell(f); }
-
-        size_t FileSize() {
-            long p = Tell(), len = (Seek(0, aiOrigin_END), Tell());
-            return size_t((Seek(p, aiOrigin_SET), len));
-        }
-    };
-#endif
+    using glTFCommon::shared_ptr;
+    using glTFCommon::IOSystem;
+    using glTFCommon::IOStream;
 
 
     using rapidjson::Value;
     using rapidjson::Value;
     using rapidjson::Document;
     using rapidjson::Document;
@@ -136,37 +111,9 @@ namespace glTF
     struct Light;
     struct Light;
     struct Skin;
     struct Skin;
 
 
-
-    // Vec/matrix types, as raw float arrays
-    typedef float (vec3)[3];
-    typedef float (vec4)[4];
-    typedef float (mat4)[16];
-
-
-    namespace Util
-    {
-        void EncodeBase64(const uint8_t* in, size_t inLength, std::string& out);
-
-        size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out);
-
-        inline size_t DecodeBase64(const char* in, uint8_t*& out)
-        {
-            return DecodeBase64(in, strlen(in), out);
-        }
-
-        struct DataURI
-        {
-            const char* mediaType;
-            const char* charset;
-            bool base64;
-            const char* data;
-            size_t dataLength;
-        };
-
-        //! Check if a uri is a data URI
-        inline bool ParseDataURI(const char* uri, size_t uriLen, DataURI& out);
-    }
-
+    using glTFCommon::vec3;
+    using glTFCommon::vec4;
+    using glTFCommon::mat4;
 
 
     //! Magic number for GLB files
     //! Magic number for GLB files
     #define AI_GLB_MAGIC_NUMBER "glTF"
     #define AI_GLB_MAGIC_NUMBER "glTF"

部分文件因为文件数量过多而无法显示