Browse Source

Merge branch 'master' into isue_1621

Kim Kulling 7 years ago
parent
commit
483541ee25
100 changed files with 6648 additions and 2131 deletions
  1. 58 59
      CMakeLists.txt
  2. 16 1
      CREDITS
  3. 24 22
      Readme.md
  4. 2 21
      assimp-config.cmake.in
  5. 1 1
      code/3DSConverter.cpp
  6. 1 1
      code/3DSExporter.cpp
  7. 1 1
      code/3DSExporter.h
  8. 12 10
      code/3DSLoader.cpp
  9. 15 1
      code/3MFXmlTags.h
  10. 5 4
      code/AMFImporter_Postprocess.cpp
  11. 1 1
      code/ASELoader.cpp
  12. 13 13
      code/ASEParser.cpp
  13. 14 13
      code/BVHLoader.cpp
  14. 4 1
      code/BVHLoader.h
  15. 19 13
      code/BaseImporter.cpp
  16. 5 6
      code/BlenderDNA.cpp
  17. 1 1
      code/BlenderDNA.h
  18. 1 1
      code/BlenderDNA.inl
  19. 6 2
      code/BlenderIntermediate.h
  20. 5 13
      code/BlenderLoader.cpp
  21. 1 1
      code/BlenderScene.cpp
  22. 15 5
      code/CMakeLists.txt
  23. 6 3
      code/COBLoader.cpp
  24. 2 2
      code/CSMLoader.cpp
  25. 1 1
      code/CalcTangentsProcess.cpp
  26. 9 5
      code/ColladaExporter.cpp
  27. 14 4
      code/ColladaLoader.cpp
  28. 1 0
      code/ColladaLoader.h
  29. 16 0
      code/ColladaParser.cpp
  30. 3 0
      code/ColladaParser.h
  31. 17 9
      code/ConvertToLHProcess.cpp
  32. 75 6
      code/D3MFExporter.cpp
  33. 3 1
      code/D3MFExporter.h
  34. 222 75
      code/D3MFImporter.cpp
  35. 39 29
      code/D3MFOpcPackage.cpp
  36. 7 5
      code/D3MFOpcPackage.h
  37. 0 1
      code/DXFHelper.h
  38. 3 3
      code/DefaultIOSystem.cpp
  39. 56 68
      code/DefaultLogger.cpp
  40. 6 6
      code/EmbedTexturesProcess.cpp
  41. 9 1
      code/Exporter.cpp
  42. 86 0
      code/FBXCommon.h
  43. 158 109
      code/FBXConverter.cpp
  44. 15 26
      code/FBXConverter.h
  45. 3 1
      code/FBXDocument.cpp
  46. 2 2
      code/FBXDocument.h
  47. 568 0
      code/FBXExportNode.cpp
  48. 258 0
      code/FBXExportNode.h
  49. 364 0
      code/FBXExportProperty.cpp
  50. 129 0
      code/FBXExportProperty.h
  51. 2475 0
      code/FBXExporter.cpp
  52. 178 0
      code/FBXExporter.h
  53. 6 3
      code/FBXMaterial.cpp
  54. 20 21
      code/FBXMeshGeometry.cpp
  55. 2 3
      code/FBXMeshGeometry.h
  56. 108 63
      code/FileSystemFilter.h
  57. 7 7
      code/FindInstancesProcess.h
  58. 8 12
      code/IRRLoader.cpp
  59. 2 14
      code/Importer.cpp
  60. 4 4
      code/Importer/IFC/IFCLoader.cpp
  61. 3 2
      code/Importer/IFC/IFCReaderGen_4.cpp
  62. 575 572
      code/Importer/IFC/IFCReaderGen_4.h
  63. 7 7
      code/LWOLoader.cpp
  64. 5 1
      code/LimitBoneWeightsProcess.h
  65. 2 2
      code/MDLFileData.h
  66. 1 1
      code/MDLLoader.cpp
  67. 13 14
      code/ObjExporter.cpp
  68. 10 20
      code/ObjFileImporter.cpp
  69. 3 2
      code/ObjFileMtlImporter.cpp
  70. 4 3
      code/ObjFileParser.cpp
  71. 23 32
      code/OgreParsingUtils.h
  72. 10 11
      code/OgreXmlSerializer.cpp
  73. 27 29
      code/OpenGEXImporter.cpp
  74. 4 6
      code/OpenGEXImporter.h
  75. 11 0
      code/PlyExporter.cpp
  76. 142 185
      code/PlyLoader.cpp
  77. 0 1
      code/PlyLoader.h
  78. 44 24
      code/PlyParser.cpp
  79. 18 25
      code/PlyParser.h
  80. 7 2
      code/PostStepRegistry.cpp
  81. 6 3
      code/PretransformVertices.cpp
  82. 6 2
      code/STLExporter.cpp
  83. 25 8
      code/STLLoader.cpp
  84. 4 7
      code/ScaleProcess.cpp
  85. 41 32
      code/SceneCombiner.cpp
  86. 23 10
      code/ScenePrivate.h
  87. 1 1
      code/SpatialSort.cpp
  88. 6 9
      code/VertexTriangleAdjacency.cpp
  89. 9 20
      code/VertexTriangleAdjacency.h
  90. 152 187
      code/XFileImporter.cpp
  91. 166 146
      code/XFileParser.cpp
  92. 26 33
      code/XFileParser.h
  93. 5 16
      code/XGLLoader.cpp
  94. 2 27
      code/glTF2Asset.h
  95. 14 1
      code/glTF2Asset.inl
  96. 3 5
      code/glTF2Exporter.cpp
  97. 8 8
      code/glTFAsset.inl
  98. 9 2
      code/glTFImporter.cpp
  99. 78 0
      code/simd.cpp
  100. 53 0
      code/simd.h

+ 58 - 59
CMakeLists.txt

@@ -110,7 +110,6 @@ if (WIN32)
 endif()
 
 IF(MSVC)
-  SET (CMAKE_PREFIX_PATH "D:\\libs\\devil")
   OPTION( ASSIMP_INSTALL_PDB
     "Install MSVC debug files."
     ON
@@ -139,8 +138,7 @@ SET (PROJECT_VERSION "${ASSIMP_VERSION}")
 
 SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" )
 
-# Needed for openddl_parser config, no use of c++11 at this moment
-ADD_DEFINITIONS( -DOPENDDL_NO_USE_CPP11 )
+# Enable C++1 globally
 set_property( GLOBAL PROPERTY CXX_STANDARD 11 )
 
 # Get the current working branch
@@ -162,7 +160,7 @@ EXECUTE_PROCESS(
 )
 
 IF(NOT GIT_COMMIT_HASH)
-    SET(GIT_COMMIT_HASH 0)
+  SET(GIT_COMMIT_HASH 0)
 ENDIF(NOT GIT_COMMIT_HASH)
 
 IF(ASSIMP_DOUBLE_PRECISION)
@@ -180,10 +178,10 @@ CONFIGURE_FILE(
 )
 
 INCLUDE_DIRECTORIES(
-    ./
-	include
-    ${CMAKE_CURRENT_BINARY_DIR}
-    ${CMAKE_CURRENT_BINARY_DIR}/include
+  ./
+  include
+  ${CMAKE_CURRENT_BINARY_DIR}
+  ${CMAKE_CURRENT_BINARY_DIR}/include
 )
 
 LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules" )
@@ -193,14 +191,6 @@ SET(CPACK_COMPONENTS_ALL assimp-bin ${LIBASSIMP_COMPONENT} ${LIBASSIMP-DEV_COMPO
 SET(ASSIMP_LIBRARY_SUFFIX "" CACHE STRING "Suffix to append to library names")
 
 IF( UNIX )
-  # Ensure that we do not run into issues like http://www.tcm.phy.cam.ac.uk/sw/inodes64.html on 32 bit linux
-  IF( ${OPERATING_SYSTEM} MATCHES "Android")
-  ELSE()
-    IF ( CMAKE_SIZEOF_VOID_P EQUAL 4) # only necessary for 32-bit linux
-      #ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 )
-    ENDIF()
-  ENDIF()
-
   # Use GNUInstallDirs for Unix predefined directories
   INCLUDE(GNUInstallDirs)
 ENDIF( UNIX )
@@ -213,13 +203,13 @@ IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT CMAKE_COMPILER_IS_MINGW)
   SET(LIBSTDC++_LIBRARIES -lstdc++)
 ELSEIF(MSVC)
   # enable multi-core compilation with MSVC
-  add_compile_options(/MP)
-  if("${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)")
-    add_compile_options( /bigobj )
-  endif()
+  ADD_COMPILE_OPTIONS(/MP)
+  IF("${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)")
+    ADD_COMPILE_OPTIONS( /bigobj )
+  ENDIF()
   # disable "elements of array '' will be default initialized" warning on MSVC2013
   IF(MSVC12)
-    add_compile_options(/wd4351)
+    ADD_COMPILE_OPTIONS(/wd4351)
   ENDIF()
 ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" )
   SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fvisibility=hidden -fPIC -Wall -Wno-long-long -std=c++11" )
@@ -230,34 +220,39 @@ ELSEIF( CMAKE_COMPILER_IS_MINGW )
   ADD_DEFINITIONS( -U__STRICT_ANSI__ )
 ENDIF()
 
-if (ASSIMP_COVERALLS)
-    MESSAGE(STATUS "Coveralls enabled")
-    INCLUDE(Coveralls)
-    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
-    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
-endif()
+IF (IOS)
+  SET(CMAKE_C_FLAGS   "${CMAKE_C_FLAGS}   -fembed-bitcode -O3")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3")
+ENDIF()
+
+IF (ASSIMP_COVERALLS)
+  MESSAGE(STATUS "Coveralls enabled")
+  INCLUDE(Coveralls)
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
+ENDIF()
 
-if (ASSIMP_WERROR)
+IF (ASSIMP_WERROR)
   MESSAGE(STATUS "Treating warnings as errors")
   IF (MSVC)
-    add_compile_options(/WX)
+    ADD_COMPILE_OPTIONS(/WX)
   ELSE()
     SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
     SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror")
   ENDIF()
-endif()
+ENDIF()
 
-if (ASSIMP_ASAN)
-    MESSAGE(STATUS "AddressSanitizer enabled")
-    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
-    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
-endif()
+IF (ASSIMP_ASAN)
+  MESSAGE(STATUS "AddressSanitizer enabled")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
+ENDIF()
 
-if (ASSIMP_UBSAN)
-    MESSAGE(STATUS "Undefined Behavior sanitizer enabled")
-    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all")
-    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all")
-endif()
+IF (ASSIMP_UBSAN)
+  MESSAGE(STATUS "Undefined Behavior sanitizer enabled")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all")
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all")
+ENDIF()
 
 INCLUDE (FindPkgMacros)
 INCLUDE (PrecompiledHeader)
@@ -290,42 +285,42 @@ ENDIF()
 IF (NOT TARGET uninstall)
   # add make uninstall capability
   CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY)
-  add_custom_target(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
+  ADD_CUSTOM_TARGET(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
 ENDIF()
 
 # cmake configuration files
 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-version.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimp-config-version.cmake" @ONLY IMMEDIATE)
-install(FILES "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake"             "${CMAKE_CURRENT_BINARY_DIR}/assimp-config-version.cmake" DESTINATION "${ASSIMP_LIB_INSTALL_DIR}/cmake/assimp-${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}" COMPONENT ${LIBASSIMP-DEV_COMPONENT})
+INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake"             "${CMAKE_CURRENT_BINARY_DIR}/assimp-config-version.cmake" DESTINATION "${ASSIMP_LIB_INSTALL_DIR}/cmake/assimp-${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}" COMPONENT ${LIBASSIMP-DEV_COMPONENT})
 
 FIND_PACKAGE( DirectX )
 
 IF( BUILD_DOCS )
-    add_subdirectory(doc)
+  ADD_SUBDIRECTORY(doc)
 ENDIF( BUILD_DOCS )
 
 # Look for system installed irrXML
 IF ( SYSTEM_IRRXML )
-    find_package( IrrXML REQUIRED )
+  FIND_PACKAGE( IrrXML REQUIRED )
 ENDIF( SYSTEM_IRRXML )
 
 # Search for external dependencies, and build them from source if not found
 # Search for zlib
 IF ( NOT ASSIMP_BUILD_ZLIB )
-    find_package(ZLIB)
+  FIND_PACKAGE(ZLIB)
 ENDIF( NOT ASSIMP_BUILD_ZLIB )
 
 IF( NOT ZLIB_FOUND )
-  message(STATUS "compiling zlib from souces")
+  MESSAGE(STATUS "compiling zlib from souces")
   INCLUDE(CheckIncludeFile)
   INCLUDE(CheckTypeSize)
   INCLUDE(CheckFunctionExists)
   # compile from sources
-  add_subdirectory(contrib/zlib)
+  ADD_SUBDIRECTORY(contrib/zlib)
   SET(ZLIB_FOUND 1)
   SET(ZLIB_LIBRARIES zlibstatic)
   SET(ZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/contrib/zlib ${CMAKE_CURRENT_BINARY_DIR}/contrib/zlib)
-else(NOT ZLIB_FOUND)
+ELSE(NOT ZLIB_FOUND)
   ADD_DEFINITIONS(-DASSIMP_BUILD_NO_OWN_ZLIB)
   SET(ZLIB_LIBRARIES_LINKED -lz)
 ENDIF(NOT ZLIB_FOUND)
@@ -367,7 +362,9 @@ IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER)
     SET(C4D_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/contrib/Melange/includes")
 
     # pick the correct prebuilt library
-    IF(MSVC14)
+    IF(MSVC15)
+      SET(C4D_LIB_POSTFIX "_2017")
+    ELSEIF(MSVC14)
       SET(C4D_LIB_POSTFIX "_2015")
     ELSEIF(MSVC12)
       SET(C4D_LIB_POSTFIX "_2013")
@@ -408,7 +405,7 @@ ADD_SUBDIRECTORY(contrib)
 ADD_SUBDIRECTORY( code/ )
 IF ( ASSIMP_BUILD_ASSIMP_TOOLS )
   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 )
       ADD_SUBDIRECTORY( tools/assimp_view/ )
     ENDIF ( ASSIMP_BUILD_ASSIMP_VIEW )
@@ -472,8 +469,8 @@ IF(CMAKE_CPACK_COMMAND AND UNIX AND ASSIMP_OPT_BUILD_PACKAGES)
   SET(CPACK_PACKAGE_INSTALL_DIRECTORY       "assimp${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}")
   SET(CPACK_RESOURCE_FILE_LICENSE           "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
 
-  string(TOUPPER ${LIBASSIMP_COMPONENT}     "LIBASSIMP_COMPONENT_UPPER")
-  string(TOUPPER ${LIBASSIMP-DEV_COMPONENT} "LIBASSIMP-DEV_COMPONENT_UPPER")
+  STRING(TOUPPER ${LIBASSIMP_COMPONENT}     "LIBASSIMP_COMPONENT_UPPER")
+  STRING(TOUPPER ${LIBASSIMP-DEV_COMPONENT} "LIBASSIMP-DEV_COMPONENT_UPPER")
 
   SET(CPACK_COMPONENT_ASSIMP-BIN_DISPLAY_NAME                       "tools")
   SET(CPACK_COMPONENT_ASSIMP-BIN_DEPENDS                            "${LIBASSIMP_COMPONENT}" )
@@ -503,8 +500,8 @@ IF(CMAKE_CPACK_COMMAND AND UNIX AND ASSIMP_OPT_BUILD_PACKAGES)
     SET(CPACK_DEBIAN_DISTRIBUTION_RELEASES lucid maverick natty oneiric precise CACHE STRING "Release code-names of the distrubiton release")
   ENDIF()
   SET(DPUT_HOST "" CACHE STRING "PPA repository to upload the debian sources")
-  include(CPack)
-  include(DebSourcePPA)
+  INCLUDE(CPack)
+  INCLUDE(DebSourcePPA)
 ENDIF()
 
 if(WIN32)
@@ -516,14 +513,16 @@ if(WIN32)
     SET(LIB_DIR "${PROJECT_SOURCE_DIR}/lib32/")
   ENDIF()
 
-  if(MSVC12)
+  IF(MSVC12)
     SET(ASSIMP_MSVC_VERSION "vc120")
-  elseif(MSVC14)
+  ELSEIF(MSVC14)
     SET(ASSIMP_MSVC_VERSION "vc140")
+  ELSEIF(MSVC15)
+    SET(ASSIMP_MSVC_VERSION "vc141")
   ENDIF(MSVC12)
 
-  if(MSVC12 OR MSVC14)
-    add_custom_target(UpdateAssimpLibsDebugSymbolsAndDLLs COMMENT "Copying Assimp Libraries ..." VERBATIM)
+  IF(MSVC12 OR MSVC14 OR MSVC15 )
+    ADD_CUSTOM_TARGET(UpdateAssimpLibsDebugSymbolsAndDLLs COMMENT "Copying Assimp Libraries ..." VERBATIM)
     IF(CMAKE_GENERATOR MATCHES "^Visual Studio")
       ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.dll	${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.dll VERBATIM)
       ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.exp	${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.exp VERBATIM)
@@ -544,5 +543,5 @@ if(WIN32)
       ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM)
       ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM)
     ENDIF()
-  ENDIF(MSVC12 OR MSVC14)
+  ENDIF(MSVC12 OR MSVC14 OR MSVC15 )
 ENDIF (WIN32)

+ 16 - 1
CREDITS

@@ -157,12 +157,27 @@ Contributed ExportProperties interface
 Contributed X File exporter
 Contributed Step (stp) exporter
 
+- Thomas Iorns (mesilliac)
+Initial FBX Export support
+
 For a more detailed list just check: https://github.com/assimp/assimp/network/members
 
-Patreons:
+
+========
+Patreons
+========
+
+Huge thanks to our Patreons!
+
 - migenius
 - Marcus
 - Cort
 - elect
 - Steffen
 
+
+===================
+Commercial Sponsors
+===================
+
+- MyDidimo (mydidimo.com): Sponsored development of FBX Export support

+ 24 - 22
Readme.md

@@ -32,32 +32,32 @@ Please check our Wiki as well: https://github.com/assimp/assimp/wiki
 
 #### Supported file formats ####
 
-A full list [is here](http://assimp.org/main_features_formats.html).
 __Importers__:
+
 - 3D
-- 3DS
-- 3MF
+- [3DS](https://en.wikipedia.org/wiki/.3ds)
+- [3MF](https://en.wikipedia.org/wiki/3D_Manufacturing_Format)
 - AC
-- AC3D
+- [AC3D](https://en.wikipedia.org/wiki/AC3D)
 - ACC
 - AMJ
 - ASE
 - ASK
 - B3D
-- BLEND (Blender)
-- BVH
-- COB
+- [BLEND](https://en.wikipedia.org/wiki/.blend_(file_format))
+- [BVH](https://en.wikipedia.org/wiki/Biovision_Hierarchy)
 - CMS
-- DAE/Collada
-- DXF
+- COB
+- [DAE/Collada](https://en.wikipedia.org/wiki/COLLADA)
+- [DXF](https://en.wikipedia.org/wiki/AutoCAD_DXF)
 - ENFF
-- FBX
-- glTF 1.0 + GLB
-- glTF 2.0
+- [FBX](https://en.wikipedia.org/wiki/FBX)
+- [glTF 1.0](https://en.wikipedia.org/wiki/GlTF#glTF_1.0) + GLB
+- [glTF 2.0](https://en.wikipedia.org/wiki/GlTF#glTF_2.0)
 - HMB
 - IFC-STEP
 - IRR / IRRMESH
-- LWO
+- [LWO](https://en.wikipedia.org/wiki/LightWave_3D)
 - LWS
 - LXO
 - MD2
@@ -70,10 +70,10 @@ __Importers__:
 - MS3D
 - NDO
 - NFF
-- OBJ
-- OFF
-- OGEX
-- PLY
+- [OBJ](https://en.wikipedia.org/wiki/Wavefront_.obj_file)
+- [OFF](https://en.wikipedia.org/wiki/OFF_(file_format))
+- [OGEX](https://en.wikipedia.org/wiki/Open_Game_Engine_Exchange)
+- [PLY](https://en.wikipedia.org/wiki/PLY_(file_format))
 - PMX
 - PRJ
 - Q3O
@@ -82,19 +82,19 @@ __Importers__:
 - SCN
 - SIB
 - SMD
-- STL
-- STP
+- [STP](https://en.wikipedia.org/wiki/ISO_10303-21)
+- [STL](https://en.wikipedia.org/wiki/STL_(file_format))
 - TER
 - UC
 - VTA
 - X
-- X3D
+- [X3D](https://en.wikipedia.org/wiki/X3D)
 - XGL
 - ZGL
 
 Additionally, some formats are supported by dependency on non-free code or external SDKs (not built by default):
 
-- C4D (https://github.com/assimp/assimp/wiki/Cinema4D-&-Melange)
+- [C4D](https://en.wikipedia.org/wiki/Cinema_4D) (https://github.com/assimp/assimp/wiki/Cinema4D-&-Melange)
 
 __Exporters__:
 
@@ -109,6 +109,8 @@ __Exporters__:
 - STEP
 - glTF 1.0 (partial)
 - glTF 2.0 (partial)
+- 3MF ( experimental )
+- FBX ( experimental )
 
 ### Building ###
 Take a look into the `INSTALL` file. Our build system is CMake, if you used CMake before there is a good chance you know what to do.
@@ -120,7 +122,7 @@ Take a look into the `INSTALL` file. Our build system is CMake, if you used CMak
 * [Pascal](port/AssimpPascal/Readme.md)
 * [Javascript (Alpha)](https://github.com/makc/assimp2json)
 * [Unity 3d Plugin](https://www.assetstore.unity3d.com/en/#!/content/91777)
-* [JVM](https://github.com/kotlin-graphics/assimp) Full jvm port (currently supported obj, ply, stl, collada, md2)
+* [JVM](https://github.com/kotlin-graphics/assimp) Full jvm port (current [status](https://github.com/kotlin-graphics/assimp/wiki/Status))
 
 ### Other tools ###
 [open3mod](https://github.com/acgessler/open3mod) is a powerful 3D model viewer based on Assimp's import and export abilities.

+ 2 - 21
assimp-config.cmake.in

@@ -34,36 +34,18 @@ if( MSVC )
   else()
     set(MSVC_PREFIX "vc150")
   endif()
-  set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" FORCE)
+  set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" )
 else()
-  set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the openrave libraries" FORCE)
+  set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the openrave libraries" )
 endif()
 
 set( ASSIMP_CXX_FLAGS ) # dynamically linked library
-if( WIN32 )
-  # for visual studio linking, most of the time boost dlls will be used
-  set( ASSIMP_CXX_FLAGS " -DBOOST_ALL_DYN_LINK -DBOOST_ALL_NO_LIB")
-endif()
 set( ASSIMP_LINK_FLAGS "" )
 set( ASSIMP_LIBRARY_DIRS "${ASSIMP_ROOT_DIR}/@ASSIMP_LIB_INSTALL_DIR@")
 set( ASSIMP_INCLUDE_DIRS "${ASSIMP_ROOT_DIR}/@ASSIMP_INCLUDE_INSTALL_DIR@")
 set( ASSIMP_LIBRARIES assimp${ASSIMP_LIBRARY_SUFFIX})
 set( ASSIMP_LIBRARIES ${ASSIMP_LIBRARIES}@CMAKE_DEBUG_POSTFIX@)
 
-# search for the boost version assimp was compiled with
-#set(Boost_USE_MULTITHREAD ON)
-#set(Boost_USE_STATIC_LIBS OFF)
-#set(Boost_USE_STATIC_RUNTIME OFF)
-#find_package(Boost ${ASSIMP_Boost_VERSION} EXACT COMPONENTS thread date_time)
-#if(Boost_VERSION AND NOT "${Boost_VERSION}" STREQUAL "0")
-#	set( ASSIMP_INCLUDE_DIRS "${ASSIMP_INCLUDE_DIRS}" ${Boost_INCLUDE_DIRS})
-#else(Boost_VERSION AND NOT "${Boost_VERSION}" STREQUAL "0")
-#	message(WARNING "Failed to find Boost ${ASSIMP_Boost_VERSION} necessary for assimp")
-#endif(Boost_VERSION AND NOT "${Boost_VERSION}" STREQUAL "0")
-
-# the boost version assimp was compiled with
-set( ASSIMP_Boost_VERSION "@Boost_MAJOR_VERSION@.@Boost_MINOR_VERSION@")
-
 # for compatibility with pkg-config
 set(ASSIMP_CFLAGS_OTHER "${ASSIMP_CXX_FLAGS}")
 set(ASSIMP_LDFLAGS_OTHER "${ASSIMP_LINK_FLAGS}")
@@ -74,7 +56,6 @@ MARK_AS_ADVANCED(
   ASSIMP_LINK_FLAGS
   ASSIMP_INCLUDE_DIRS
   ASSIMP_LIBRARIES
-  ASSIMP_Boost_VERSION
   ASSIMP_CFLAGS_OTHER
   ASSIMP_LDFLAGS_OTHER
   ASSIMP_LIBRARY_SUFFIX

+ 1 - 1
code/3DSConverter.cpp

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

+ 1 - 1
code/3DSExporter.cpp

@@ -184,7 +184,7 @@ void ExportScene3DS(const char* pFile, IOSystem* pIOSystem, const aiScene* pScen
 } // end of namespace Assimp
 
 // ------------------------------------------------------------------------------------------------
-Discreet3DSExporter:: Discreet3DSExporter(std::shared_ptr<IOStream> outfile, const aiScene* scene)
+Discreet3DSExporter:: Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, const aiScene* scene)
 : scene(scene)
 , writer(outfile)
 {

+ 1 - 1
code/3DSExporter.h

@@ -67,7 +67,7 @@ namespace Assimp
 // ------------------------------------------------------------------------------------------------
 class Discreet3DSExporter {
 public:
-    Discreet3DSExporter(std::shared_ptr<IOStream> outfile, const aiScene* pScene);
+    Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, const aiScene* pScene);
     ~Discreet3DSExporter();
 
 private:

+ 12 - 10
code/3DSLoader.cpp

@@ -71,7 +71,7 @@ static const aiImporterDesc desc = {
     0,
     0,
     0,
-    "3ds prj"
+    "3ds prj 3DS PRJ"
 };
 
 
@@ -113,22 +113,24 @@ Discreet3DSImporter::Discreet3DSImporter()
 , mScene()
 , mMasterScale()
 , bHasBG()
-, bIsPrj()
-{}
+, bIsPrj() {
+    // empty
+}
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
-Discreet3DSImporter::~Discreet3DSImporter()
-{}
+Discreet3DSImporter::~Discreet3DSImporter() {
+    // empty
+}
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool Discreet3DSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
-{
+bool Discreet3DSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
     std::string extension = GetExtension(pFile);
-    if(extension == "3ds" || extension == "prj" ) {
+    if(extension == "3ds" || extension == "3DS" || extension == "prj"|| extension == "PRJ" ) {
         return true;
     }
+
     if (!extension.length() || checkSig) {
         uint16_t token[3];
         token[0] = 0x4d4d;
@@ -210,7 +212,7 @@ void Discreet3DSImporter::InternReadFile( const std::string& pFile,
     ConvertScene(pScene);
 
     // Generate the node graph for the scene. This is a little bit
-    // tricky since we'll need to split some meshes into submeshes
+    // tricky since we'll need to split some meshes into sub-meshes
     GenerateNodeGraph(pScene);
 
     // Now apply the master scaling factor to the scene
@@ -347,7 +349,7 @@ void Discreet3DSImporter::ParseObjectChunk()
     case Discreet3DS::CHUNK_MAT_MATERIAL:
 
         // Add a new material to the list
-        mScene->mMaterials.push_back(D3DS::Material(std::string("UNNAMED_" + std::to_string(mScene->mMaterials.size()))));
+        mScene->mMaterials.push_back(D3DS::Material(std::string("UNNAMED_" + to_string(mScene->mMaterials.size()))));
         ParseMaterialChunk();
         break;
 

+ 15 - 1
code/3MFXmlTags.h

@@ -45,6 +45,11 @@ namespace Assimp {
 namespace D3MF {
 
 namespace XmlTag {
+    // Meta-data
+    static const std::string meta = "metadata";
+    static const std::string meta_name = "name";
+
+    // Model-data specific tags
     static const std::string model = "model";
     static const std::string model_unit = "unit";
     static const std::string metadata = "metadata";
@@ -62,6 +67,8 @@ namespace XmlTag {
     static const std::string v2 = "v2";
     static const std::string v3 = "v3";
     static const std::string id = "id";
+    static const std::string pid = "pid";
+    static const std::string p1 = "p1";
     static const std::string name = "name";
     static const std::string type = "type";
     static const std::string build = "build";
@@ -69,6 +76,14 @@ namespace XmlTag {
     static const std::string objectid = "objectid";
     static const std::string transform = "transform";
 
+    // Material definitions
+    static const std::string basematerials = "basematerials";
+    static const std::string basematerials_id = "id";
+    static const std::string basematerials_base = "base";
+    static const std::string basematerials_name = "name";
+    static const std::string basematerials_displaycolor = "displaycolor";
+
+    // Meta info tags
     static const std::string CONTENT_TYPES_ARCHIVE = "[Content_Types].xml";
     static const std::string ROOT_RELATIONSHIPS_ARCHIVE = "_rels/.rels";
     static const std::string SCHEMA_CONTENTTYPES = "http://schemas.openxmlformats.org/package/2006/content-types";
@@ -83,7 +98,6 @@ namespace XmlTag {
     static const std::string PACKAGE_TEXTURE_RELATIONSHIP_TYPE = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dtexture";
     static const std::string PACKAGE_CORE_PROPERTIES_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
     static const std::string PACKAGE_THUMBNAIL_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail";
-
 }
 
 } // Namespace D3MF

+ 5 - 4
code/AMFImporter_Postprocess.cpp

@@ -156,10 +156,11 @@ size_t AMFImporter::PostprocessHelper_GetTextureID_Or_Create(const std::string&
 	TextureConverted_Index = 0;
 	for(const SPP_Texture& tex_convd: mTexture_Converted)
 	{
-		if(tex_convd.ID == TextureConverted_ID)
-			return TextureConverted_Index;
-		else
-			TextureConverted_Index++;
+        if ( tex_convd.ID == TextureConverted_ID ) {
+            return TextureConverted_Index;
+        } else {
+            ++TextureConverted_Index;
+        }
 	}
 
 	//

+ 1 - 1
code/ASELoader.cpp

@@ -583,7 +583,7 @@ void ASEImporter::AddNodes (const std::vector<BaseNode*>& nodes,
         node->mTransformation = mParentAdjust*snode->mTransform;
 
         // Add sub nodes - prevent stack overflow due to recursive parenting
-        if (node->mName != node->mParent->mName) {
+        if (node->mName != node->mParent->mName && node->mName != node->mParent->mParent->mName ) {
             AddNodes(nodes,node,node->mName.data,snode->mTransform);
         }
 

+ 13 - 13
code/ASEParser.cpp

@@ -267,7 +267,9 @@ void Parser::Parse()
                 // at the file extension (ASE, ASK, ASC)
                 // *************************************************************
 
-                if (fmt)iFileFormat = fmt;
+                if ( fmt ) {
+                    iFileFormat = fmt;
+                }
                 continue;
             }
             // main scene information
@@ -427,28 +429,25 @@ void Parser::ParseLV1SoftSkinBlock()
                         // Reserve enough storage
                         vert.mBoneWeights.reserve(numWeights);
 
-                        for (unsigned int w = 0; w < numWeights;++w)
-                        {
-                            std::string bone;
+                        std::string bone;
+                        for (unsigned int w = 0; w < numWeights;++w) {
+                            bone.clear();
                             ParseString(bone,"*MESH_SOFTSKINVERTS.Bone");
 
                             // Find the bone in the mesh's list
                             std::pair<int,ai_real> me;
                             me.first = -1;
 
-                            for (unsigned int n = 0; n < curMesh->mBones.size();++n)
-                            {
-                                if (curMesh->mBones[n].mName == bone)
-                                {
+                            for (unsigned int n = 0; n < curMesh->mBones.size();++n) {
+                                if (curMesh->mBones[n].mName == bone) {
                                     me.first = n;
                                     break;
                                 }
                             }
-                            if (-1 == me.first)
-                            {
+                            if (-1 == me.first) {
                                 // We don't have this bone yet, so add it to the list
-                                me.first = (int)curMesh->mBones.size();
-                                curMesh->mBones.push_back(ASE::Bone(bone));
+                                me.first = static_cast<int>( curMesh->mBones.size() );
+                                curMesh->mBones.push_back( ASE::Bone( bone ) );
                             }
                             ParseLV4MeshFloat( me.second );
 
@@ -745,6 +744,7 @@ void Parser::ParseLV3MapBlock(Texture& map)
     // empty the texture won't be used later.
     // ***********************************************************
     bool parsePath = true;
+    std::string temp;
     while (true)
     {
         if ('*' == *filePtr)
@@ -753,7 +753,7 @@ void Parser::ParseLV3MapBlock(Texture& map)
             // type of map
             if (TokenMatch(filePtr,"MAP_CLASS" ,9))
             {
-                std::string temp;
+                temp.clear();
                 if(!ParseString(temp,"*MAP_CLASS"))
                     SkipToNextToken();
                 if (temp != "Bitmap" && temp != "Normal Bump")

+ 14 - 13
code/BVHLoader.cpp

@@ -199,6 +199,7 @@ aiNode* BVHLoader::ReadNode()
     Node& internNode = mNodes.back();
 
     // now read the node's contents
+    std::string siteToken;
     while( 1)
     {
         std::string token = GetNextToken();
@@ -218,7 +219,8 @@ aiNode* BVHLoader::ReadNode()
         else if( token == "End")
         {
             // The real symbol is "End Site". Second part comes in a separate token
-            std::string siteToken = GetNextToken();
+            siteToken.clear();
+            siteToken = GetNextToken();
             if( siteToken != "Site")
                 ThrowException( format() << "Expected \"End Site\" keyword, but found \"" << token << " " << siteToken << "\"." );
 
@@ -262,21 +264,18 @@ aiNode* BVHLoader::ReadEndSite( const std::string& pParentName)
     aiNode* node = new aiNode( "EndSite_" + pParentName);
 
     // now read the node's contents. Only possible entry is "OFFSET"
-    while( 1)
-    {
-        std::string token = GetNextToken();
+    std::string token;
+    while( 1) {
+        token.clear();
+        token = GetNextToken();
 
         // end node's offset
-        if( token == "OFFSET")
-        {
+        if( token == "OFFSET") {
             ReadNodeOffset( node);
-        }
-        else if( token == "}")
-        {
+        } else if( token == "}") {
             // we're done with the end node
             break;
-        } else
-        {
+        } else {
             // everything else is a parse error
             ThrowException( format() << "Unknown keyword \"" << token << "\"." );
         }
@@ -296,8 +295,10 @@ void BVHLoader::ReadNodeOffset( aiNode* pNode)
     offset.z = GetNextTokenAsFloat();
 
     // build a transformation matrix from it
-    pNode->mTransformation = aiMatrix4x4( 1.0f, 0.0f, 0.0f, offset.x, 0.0f, 1.0f, 0.0f, offset.y,
-        0.0f, 0.0f, 1.0f, offset.z, 0.0f, 0.0f, 0.0f, 1.0f);
+    pNode->mTransformation = aiMatrix4x4( 1.0f, 0.0f, 0.0f, offset.x,
+                                          0.0f, 1.0f, 0.0f, offset.y,
+                                          0.0f, 0.0f, 1.0f, offset.z,
+                                          0.0f, 0.0f, 0.0f, 1.0f);
 }
 
 // ------------------------------------------------------------------------------------------------

+ 4 - 1
code/BVHLoader.h

@@ -84,7 +84,10 @@ class BVHLoader : public BaseImporter
         std::vector<ChannelType> mChannels;
         std::vector<float> mChannelValues; // motion data values for that node. Of size NumChannels * NumFrames
 
-        Node() { }
+        Node()
+        : mNode(nullptr)
+        { }
+
         explicit Node( const aiNode* pNode) : mNode( pNode) { }
     };
 

+ 19 - 13
code/BaseImporter.cpp

@@ -115,13 +115,12 @@ void BaseImporter::SetupProperties(const Importer* /*pImp*/)
 }
 
 // ------------------------------------------------------------------------------------------------
-void BaseImporter::GetExtensionList(std::set<std::string>& extensions)
-{
+void BaseImporter::GetExtensionList(std::set<std::string>& extensions) {
     const aiImporterDesc* desc = GetInfo();
-    ai_assert(desc != NULL);
+    ai_assert(desc != nullptr);
 
     const char* ext = desc->mFileExtensions;
-    ai_assert(ext != NULL);
+    ai_assert(ext != nullptr );
 
     const char* last = ext;
     do {
@@ -145,21 +144,19 @@ void BaseImporter::GetExtensionList(std::set<std::string>& extensions)
     unsigned int        searchBytes /* = 200 */,
     bool                tokensSol /* false */)
 {
-    ai_assert( NULL != tokens );
+    ai_assert( nullptr != tokens );
     ai_assert( 0 != numTokens );
     ai_assert( 0 != searchBytes);
 
-    if (!pIOHandler)
+    if ( nullptr == pIOHandler ) {
         return false;
+    }
 
     std::unique_ptr<IOStream> pStream (pIOHandler->Open(pFile));
     if (pStream.get() ) {
         // read 200 characters from the file
         std::unique_ptr<char[]> _buffer (new char[searchBytes+1 /* for the '\0' */]);
         char* buffer = _buffer.get();
-        if( NULL == buffer ) {
-            return false;
-        }
 
         const size_t read = pStream->Read(buffer,1,searchBytes);
         if( !read ) {
@@ -181,9 +178,17 @@ void BaseImporter::GetExtensionList(std::set<std::string>& extensions)
         }
         *cur2 = '\0';
 
-        for (unsigned int i = 0; i < numTokens;++i) {
-            ai_assert(NULL != tokens[i]);
-            const char* r = strstr(buffer,tokens[i]);
+        std::string token;
+        for (unsigned int i = 0; i < numTokens; ++i ) {
+            ai_assert( nullptr != tokens[i] );
+            const size_t len( strlen( tokens[ i ] ) );
+            token.clear();
+            const char *ptr( tokens[ i ] );
+            for ( size_t tokIdx = 0; tokIdx < len; ++tokIdx ) {
+                token.push_back( tolower( *ptr ) );
+                ++ptr;
+            }
+            const char* r = strstr( buffer, token.c_str() );
             if( !r ) {
                 continue;
             }
@@ -246,7 +251,8 @@ void BaseImporter::GetExtensionList(std::set<std::string>& extensions)
 /* static */ bool BaseImporter::CheckMagicToken(IOSystem* pIOHandler, const std::string& pFile,
     const void* _magic, unsigned int num, unsigned int offset, unsigned int size)
 {
-    ai_assert(size <= 16 && _magic);
+    ai_assert( size <= 16 );
+    ai_assert( _magic );
 
     if (!pIOHandler) {
         return false;

+ 5 - 6
code/BlenderDNA.cpp

@@ -58,12 +58,11 @@ using namespace Assimp::Formatter;
 
 static bool match4(StreamReaderAny& stream, const char* string) {
     ai_assert( nullptr != string );
-    char tmp[] = {
-        (const char)(stream).GetI1(),
-        (const char)(stream).GetI1(),
-        (const char)(stream).GetI1(),
-        (const char)(stream).GetI1()
-    };
+    char tmp[4];
+    tmp[ 0 ] = ( stream ).GetI1();
+    tmp[ 1 ] = ( stream ).GetI1();
+    tmp[ 2 ] = ( stream ).GetI1();
+    tmp[ 3 ] = ( stream ).GetI1();
     return (tmp[0]==string[0] && tmp[1]==string[1] && tmp[2]==string[2] && tmp[3]==string[3]);
 }
 

+ 1 - 1
code/BlenderDNA.h

@@ -205,7 +205,7 @@ enum ErrorPolicy {
 
 // -------------------------------------------------------------------------------
 /** Represents a data structure in a BLEND file. A Structure defines n fields
- *  and their locatios and encodings the input stream. Usually, every
+ *  and their locations and encodings the input stream. Usually, every
  *  Structure instance pertains to one equally-named data structure in the
  *  BlenderScene.h header. This class defines various utilities to map a
  *  binary `blob` read from the file to such a structure instance with

+ 1 - 1
code/BlenderDNA.inl

@@ -502,7 +502,7 @@ const FileBlockHead* Structure :: LocateFileBlockForAddress(const Pointer & ptrv
 {
     // the file blocks appear in list sorted by
     // with ascending base addresses so we can run a
-    // binary search to locate the pointee quickly.
+    // binary search to locate the pointer quickly.
 
     // NOTE: Blender seems to distinguish between side-by-side
     // data (stored in the same data block) and far pointers,

+ 6 - 2
code/BlenderIntermediate.h

@@ -122,9 +122,11 @@ namespace Blender {
 #   pragma warning(disable:4351)
 #endif
 
+    // As counter-intuitive as it may seem, a comparator must return false for equal values.
+    // The C++ standard defines and expects this behavior: true if lhs < rhs, false otherwise.
     struct ObjectCompare {
         bool operator() (const Object* left, const Object* right) const {
-            return ::strncmp(left->id.name, right->id.name, strlen( left->id.name ) ) == 0;
+            return ::strncmp(left->id.name, right->id.name, strlen( left->id.name ) ) < 0;
         }
     };
 
@@ -143,9 +145,11 @@ namespace Blender {
             , db(db)
         {}
 
+        // As counter-intuitive as it may seem, a comparator must return false for equal values.
+        // The C++ standard defines and expects this behavior: true if lhs < rhs, false otherwise.
         struct ObjectCompare {
             bool operator() (const Object* left, const Object* right) const {
-                return ::strncmp( left->id.name, right->id.name, strlen( left->id.name ) ) == 0;
+                return ::strncmp( left->id.name, right->id.name, strlen( left->id.name ) ) < 0;
             }
         };
 

+ 5 - 13
code/BlenderLoader.cpp

@@ -154,14 +154,6 @@ void BlenderImporter::SetupProperties(const Importer* /*pImp*/)
     // nothing to be done for the moment
 }
 
-struct free_it {
-    free_it(void* free) : free(free) {}
-    ~free_it() {
-        ::free(this->free);
-    }
-
-    void* free;
-};
 
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure.
@@ -169,8 +161,7 @@ void BlenderImporter::InternReadFile( const std::string& pFile,
     aiScene* pScene, IOSystem* pIOHandler)
 {
 #ifndef ASSIMP_BUILD_NO_COMPRESSED_BLEND
-    Bytef* dest = NULL;
-    free_it free_it_really(dest);
+    std::vector<Bytef> uncompressed;
 #endif
 
 
@@ -218,6 +209,7 @@ void BlenderImporter::InternReadFile( const std::string& pFile,
 
         size_t total = 0l;
 
+        // TODO: be smarter about this, decompress directly into heap buffer
         // and decompress the data .... do 1k chunks in the hope that we won't kill the stack
 #define MYBLOCK 1024
         Bytef block[MYBLOCK];
@@ -232,8 +224,8 @@ void BlenderImporter::InternReadFile( const std::string& pFile,
             }
             const size_t have = MYBLOCK - zstream.avail_out;
             total += have;
-            dest = reinterpret_cast<Bytef*>( realloc(dest,total) );
-            memcpy(dest + total - have,block,have);
+            uncompressed.resize(total);
+            memcpy(uncompressed.data() + total - have,block,have);
         }
         while (ret != Z_STREAM_END);
 
@@ -241,7 +233,7 @@ void BlenderImporter::InternReadFile( const std::string& pFile,
         inflateEnd(&zstream);
 
         // replace the input stream with a memory stream
-        stream.reset(new MemoryIOStream(reinterpret_cast<uint8_t*>(dest),total));
+        stream.reset(new MemoryIOStream(reinterpret_cast<uint8_t*>(uncompressed.data()),total));
 
         // .. and retry
         stream->Read(magic,7,1);

+ 1 - 1
code/BlenderScene.cpp

@@ -116,7 +116,7 @@ template <> void Structure :: Convert<MTex> (
     ReadField<ErrorPolicy_Igno>(temp,"projy",db);
     dest.projy = static_cast<Assimp::Blender::MTex::Projection>(temp);
     ReadField<ErrorPolicy_Igno>(temp,"projz",db);
-    dest.projx = static_cast<Assimp::Blender::MTex::Projection>(temp);
+    dest.projz = static_cast<Assimp::Blender::MTex::Projection>(temp);
     ReadField<ErrorPolicy_Igno>(dest.mapping,"mapping",db);
     ReadFieldArray<ErrorPolicy_Igno>(dest.ofs,"ofs",db);
     ReadFieldArray<ErrorPolicy_Igno>(dest.size,"size",db);

+ 15 - 5
code/CMakeLists.txt

@@ -72,6 +72,7 @@ SET( PUBLIC_HEADERS
   ${HEADER_PATH}/matrix4x4.h
   ${HEADER_PATH}/matrix4x4.inl
   ${HEADER_PATH}/mesh.h
+  ${HEADER_PATH}/pbrmaterial.h
   ${HEADER_PATH}/postprocess.h
   ${HEADER_PATH}/quaternion.h
   ${HEADER_PATH}/quaternion.inl
@@ -176,8 +177,6 @@ SET( Common_SRCS
   SkeletonMeshBuilder.cpp
   SplitByBoneCountProcess.cpp
   SplitByBoneCountProcess.h
-  ScaleProcess.cpp
-  ScaleProcess.h
   StandardShapes.cpp
   TargetAnimation.cpp
   TargetAnimation.h
@@ -187,6 +186,8 @@ SET( Common_SRCS
   Bitmap.cpp
   Version.cpp
   CreateAnimMesh.cpp
+  simd.h
+  simd.cpp
 )
 SOURCE_GROUP(Common FILES ${Common_SRCS})
 
@@ -484,9 +485,9 @@ ADD_ASSIMP_IMPORTER( IFC
 )
 if (ASSIMP_BUILD_IFC_IMPORTER)
   if (MSVC)
-    set_source_files_properties(IFCReaderGen1.cpp IFCReaderGen2.cpp PROPERTIES COMPILE_FLAGS "/bigobj")
+    set_source_files_properties(Importer/IFC/IFCReaderGen1_2x3.cpp Importer/IFC/IFCReaderGen2_2x3.cpp PROPERTIES COMPILE_FLAGS "/bigobj")
   elseif(CMAKE_COMPILER_IS_MINGW)
-    set_source_files_properties(IFCReaderGen1.cpp IFCReaderGen2.cpp PROPERTIES COMPILE_FLAGS "-O2 -Wa,-mbig-obj")
+    set_source_files_properties(Importer/IFC/IFCReaderGen1_2x3.cpp Importer/IFC/IFCReaderGen2_2x3.cpp PROPERTIES COMPILE_FLAGS "-O2 -Wa,-mbig-obj")
   endif()
 endif (ASSIMP_BUILD_IFC_IMPORTER)
 
@@ -522,6 +523,13 @@ ADD_ASSIMP_IMPORTER( FBX
   FBXDeformer.cpp
   FBXBinaryTokenizer.cpp
   FBXDocumentUtil.cpp
+  FBXExporter.h
+  FBXExporter.cpp
+  FBXExportNode.h
+  FBXExportNode.cpp
+  FBXExportProperty.h
+  FBXExportProperty.cpp
+  FBXCommon.h
 )
 
 SET( PostProcessing_SRCS
@@ -578,6 +586,8 @@ SET( PostProcessing_SRCS
   PolyTools.h
   MakeVerboseFormat.cpp
   MakeVerboseFormat.h
+  ScaleProcess.cpp
+  ScaleProcess.h
 )
 SOURCE_GROUP( PostProcessing FILES ${PostProcessing_SRCS})
 
@@ -641,7 +651,7 @@ ADD_ASSIMP_IMPORTER( X
   XFileExporter.cpp
 )
 
-ADD_ASSIMP_IMPORTER(X3D
+ADD_ASSIMP_IMPORTER( X3D
   X3DExporter.cpp
   X3DExporter.hpp
   X3DImporter.cpp

+ 6 - 3
code/COBLoader.cpp

@@ -47,11 +47,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #ifndef ASSIMP_BUILD_NO_COB_IMPORTER
 #include "COBLoader.h"
 #include "COBScene.h"
-
+#include "ConvertToLHProcess.h"
 #include <assimp/StreamReader.h>
 #include <assimp/ParsingUtils.h>
 #include <assimp/fast_atof.h>
-
 #include <assimp/LineSplitter.h>
 #include <assimp/TinyFormatter.h>
 #include <memory>
@@ -105,7 +104,7 @@ COBImporter::~COBImporter()
 bool COBImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
 {
     const std::string& extension = GetExtension(pFile);
-    if (extension == "cob" || extension == "scn") {
+    if (extension == "cob" || extension == "scn" || extension == "COB" || extension == "SCN") {
         return true;
     }
 
@@ -225,6 +224,9 @@ void COBImporter::InternReadFile( const std::string& pFile,
     }
 
     pScene->mRootNode = BuildNodes(*root.get(),scene,pScene);
+	//flip normals after import
+    FlipWindingOrderProcess flip;
+    flip.Execute( pScene );
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -1299,3 +1301,4 @@ void COBImporter::ReadUnit_Binary(COB::Scene& out, StreamReaderLE& reader, const
 
 
 #endif
+

+ 2 - 2
code/CSMLoader.cpp

@@ -136,7 +136,7 @@ void CSMImporter::InternReadFile( const std::string& pFile,
     TextFileToBuffer(file.get(),mBuffer2);
     const char* buffer = &mBuffer2[0];
 
-    aiAnimation* anim = new aiAnimation();
+    std::unique_ptr<aiAnimation> anim(new aiAnimation());
     int first = 0, last = 0x00ffffff;
 
     // now process the file and look out for '$' sections
@@ -294,8 +294,8 @@ void CSMImporter::InternReadFile( const std::string& pFile,
 
     // Store the one and only animation in the scene
     pScene->mAnimations    = new aiAnimation*[pScene->mNumAnimations=1];
-    pScene->mAnimations[0] = anim;
     anim->mName.Set("$CSM_MasterAnim");
+    pScene->mAnimations[0] = anim.release();
 
     // mark the scene as incomplete and run SkeletonMeshBuilder on it
     pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;

+ 1 - 1
code/CalcTangentsProcess.cpp

@@ -190,7 +190,7 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
         float tx = meshTex[p2].x - meshTex[p0].x, ty = meshTex[p2].y - meshTex[p0].y;
         float dirCorrection = (tx * sy - ty * sx) < 0.0f ? -1.0f : 1.0f;
         // when t1, t2, t3 in same position in UV space, just use default UV direction.
-        if ( 0 == sx && 0 ==sy && 0 == tx && 0 == ty ) {
+        if (  sx * ty == sy * tx ) {
             sx = 0.0; sy = 1.0;
             tx = 1.0; ty = 0.0;
         }

+ 9 - 5
code/ColladaExporter.cpp

@@ -1269,7 +1269,8 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 	
 	mOutput << startstr << "<animation id=\"" + idstrEscaped + "\" name=\"" + animation_name_escaped + "\">" << endstr;
 	PushTag();
-	
+
+    std::string node_idstr;
 	for (size_t a = 0; a < anim->mNumChannels; ++a) {
 		const aiNodeAnim * nodeAnim = anim->mChannels[a];
 		
@@ -1277,7 +1278,9 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 		if ( nodeAnim->mNumPositionKeys != nodeAnim->mNumScalingKeys ||  nodeAnim->mNumPositionKeys != nodeAnim->mNumRotationKeys ) continue;
 		
 		{
-			const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-input");
+            node_idstr.clear();
+            node_idstr += nodeAnim->mNodeName.data;
+            node_idstr += std::string( "_matrix-input" );
 
 			std::vector<ai_real> frames;
 			for( size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
@@ -1289,12 +1292,14 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 		}
 		
 		{
-			const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-output");
+            node_idstr.clear();
+
+            node_idstr += nodeAnim->mNodeName.data;
+            node_idstr += std::string("_matrix-output");
 			
 			std::vector<ai_real> keyframes;
 			keyframes.reserve(nodeAnim->mNumPositionKeys * 16);
 			for( size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
-				
 				aiVector3D Scaling = nodeAnim->mScalingKeys[i].mValue;
 				aiMatrix4x4 ScalingM;  // identity
 				ScalingM[0][0] = Scaling.x; ScalingM[1][1] = Scaling.y; ScalingM[2][2] = Scaling.z;
@@ -1361,7 +1366,6 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
 			PopTag();
 			mOutput << startstr << "</source>" << endstr;
 		}
-		
 	}
 	
 	for (size_t a = 0; a < anim->mNumChannels; ++a) {

+ 14 - 4
code/ColladaLoader.cpp

@@ -131,6 +131,7 @@ void ColladaLoader::SetupProperties(const Importer* pImp)
 {
     noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0;
     ignoreUpDirection = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION,0) != 0;
+    useColladaName = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES,0) != 0;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -1120,6 +1121,7 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars
             continue;
 
         // now check all channels if they affect the current node
+        std::string targetID, subElement;
         for( std::vector<Collada::AnimationChannel>::const_iterator cit = pSrcAnim->mChannels.begin();
             cit != pSrcAnim->mChannels.end(); ++cit)
         {
@@ -1146,7 +1148,9 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars
             }
             if( srcChannel.mTarget.find( '/', slashPos+1) != std::string::npos)
                 continue;
-            std::string targetID = srcChannel.mTarget.substr( 0, slashPos);
+
+            targetID.clear();
+            targetID = srcChannel.mTarget.substr( 0, slashPos);
             if( targetID != srcNode->mID)
                 continue;
 
@@ -1159,7 +1163,8 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars
 
                 entry.mTransformId = srcChannel.mTarget.substr( slashPos+1, dotPos - slashPos - 1);
 
-                std::string subElement = srcChannel.mTarget.substr( dotPos+1);
+                subElement.clear();
+                subElement = srcChannel.mTarget.substr( dotPos+1);
                 if( subElement == "ANGLE")
                     entry.mSubElement = 3; // last number in an Axis-Angle-Transform is the angle
                 else if( subElement == "X")
@@ -1180,7 +1185,8 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars
             if (bracketPos != std::string::npos)
             {
                 entry.mTransformId = srcChannel.mTarget.substr(slashPos + 1, bracketPos - slashPos - 1);
-                std::string subElement = srcChannel.mTarget.substr(bracketPos);
+                subElement.clear();
+                subElement = srcChannel.mTarget.substr(bracketPos);
 
                 if (subElement == "(0)(0)")
                     entry.mSubElement = 0;
@@ -1214,7 +1220,6 @@ void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pPars
                     entry.mSubElement = 14;
                 else if (subElement == "(3)(3)")
                     entry.mSubElement = 15;
-
             }
 
             // determine which transform step is affected by this channel
@@ -1913,6 +1918,11 @@ const Collada::Node* ColladaLoader::FindNodeBySID( const Collada::Node* pNode, c
 // The name must be unique for proper node-bone association.
 std::string ColladaLoader::FindNameForNode( const Collada::Node* pNode)
 {
+    // If explicitly requested, just use the collada name.
+    if (useColladaName) {
+        return pNode->mName;
+    }
+
     // Now setup the name of the assimp node. The collada name might not be
     // unique, so we use the collada ID.
     if (!pNode->mID.empty())

+ 1 - 0
code/ColladaLoader.h

@@ -248,6 +248,7 @@ protected:
 
     bool noSkeletonMesh;
     bool ignoreUpDirection;
+    bool useColladaName;
 
     /** Used by FindNameForNode() to generate unique node names */
     unsigned int mNodeNameCounter;

+ 16 - 0
code/ColladaParser.cpp

@@ -222,6 +222,7 @@ void ColladaParser::ReadStructure()
     }
 
 	PostProcessRootAnimations();
+    PostProcessControllers();
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -360,6 +361,21 @@ void ColladaParser::ReadAnimationClipLibrary()
 	}
 }
 
+void ColladaParser::PostProcessControllers()
+{
+  for (ControllerLibrary::iterator it = mControllerLibrary.begin(); it != mControllerLibrary.end(); ++it)
+  {
+    std::string meshId = it->second.mMeshId;
+    ControllerLibrary::iterator findItr = mControllerLibrary.find(meshId);
+    while(findItr != mControllerLibrary.end()) {
+      meshId = findItr->second.mMeshId;
+      findItr = mControllerLibrary.find(meshId);
+    }
+    
+    it->second.mMeshId = meshId;
+  }
+}
+
 // ------------------------------------------------------------------------------------------------
 // Re-build animations from animation clip library, if present, otherwise combine single-channel animations
 void ColladaParser::PostProcessRootAnimations()

+ 3 - 0
code/ColladaParser.h

@@ -87,6 +87,9 @@ namespace Assimp
 		/** Reads the animation clip library */
 		void ReadAnimationClipLibrary();
 
+        /** Unwrap controllers dependency hierarchy */
+        void PostProcessControllers();
+    
 		/** Re-build animations from animation clip library, if present, otherwise combine single-channel animations */
 		void PostProcessRootAnimations();
 

+ 17 - 9
code/ConvertToLHProcess.cpp

@@ -91,12 +91,14 @@ void MakeLeftHandedProcess::Execute( aiScene* pScene)
     ProcessNode( pScene->mRootNode, aiMatrix4x4());
 
     // process the meshes accordingly
-    for( unsigned int a = 0; a < pScene->mNumMeshes; ++a)
-        ProcessMesh( pScene->mMeshes[a]);
+    for ( unsigned int a = 0; a < pScene->mNumMeshes; ++a ) {
+        ProcessMesh( pScene->mMeshes[ a ] );
+    }
 
     // process the materials accordingly
-    for( unsigned int a = 0; a < pScene->mNumMaterials; ++a)
-        ProcessMaterial( pScene->mMaterials[a]);
+    for ( unsigned int a = 0; a < pScene->mNumMaterials; ++a ) {
+        ProcessMaterial( pScene->mMaterials[ a ] );
+    }
 
     // transform all animation channels as well
     for( unsigned int a = 0; a < pScene->mNumAnimations; a++)
@@ -136,8 +138,11 @@ void MakeLeftHandedProcess::ProcessNode( aiNode* pNode, const aiMatrix4x4& pPare
 
 // ------------------------------------------------------------------------------------------------
 // Converts a single mesh to left handed coordinates.
-void MakeLeftHandedProcess::ProcessMesh( aiMesh* pMesh)
-{
+void MakeLeftHandedProcess::ProcessMesh( aiMesh* pMesh) {
+    if ( nullptr == pMesh ) {
+        DefaultLogger::get()->error( "Nullptr to mesh found." );
+        return;
+    }
     // mirror positions, normals and stuff along the Z axis
     for( size_t a = 0; a < pMesh->mNumVertices; ++a)
     {
@@ -173,8 +178,12 @@ void MakeLeftHandedProcess::ProcessMesh( aiMesh* pMesh)
 
 // ------------------------------------------------------------------------------------------------
 // Converts a single material to left handed coordinates.
-void MakeLeftHandedProcess::ProcessMaterial( aiMaterial* _mat)
-{
+void MakeLeftHandedProcess::ProcessMaterial( aiMaterial* _mat) {
+    if ( nullptr == _mat ) {
+        DefaultLogger::get()->error( "Nullptr to aiMaterial found." );
+        return;
+    }
+
     aiMaterial* mat = (aiMaterial*)_mat;
     for (unsigned int a = 0; a < mat->mNumProperties;++a)   {
         aiMaterialProperty* prop = mat->mProperties[a];
@@ -183,7 +192,6 @@ void MakeLeftHandedProcess::ProcessMaterial( aiMaterial* _mat)
         if (!::strcmp( prop->mKey.data, "$tex.mapaxis"))    {
             ai_assert( prop->mDataLength >= sizeof(aiVector3D)); /* something is wrong with the validation if we end up here */
             aiVector3D* pff = (aiVector3D*)prop->mData;
-
             pff->z *= -1.f;
         }
     }

+ 75 - 6
code/D3MFExporter.cpp

@@ -49,8 +49,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/IOStream.hpp>
 #include <assimp/Exporter.hpp>
 #include <assimp/DefaultLogger.hpp>
-
+#include <assimp/StringUtils.h>
 #include <assimp/Exceptional.h>
+
 #include "3MFXmlTags.h"
 #include "D3MFOpcPackage.h"
 
@@ -116,6 +117,7 @@ bool D3MFExporter::exportArchive( const char *file ) {
     if ( nullptr == m_zipArchive ) {
         return false;
     }
+
     ok |= exportContentTypes();
     ok |= export3DModel();
     ok |= exportRelations();
@@ -126,7 +128,6 @@ bool D3MFExporter::exportArchive( const char *file ) {
     return ok;
 }
 
-
 bool D3MFExporter::exportContentTypes() {
     mContentOutput.clear();
 
@@ -153,7 +154,11 @@ bool D3MFExporter::exportRelations() {
     mRelOutput << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">";
 
     for ( size_t i = 0; i < mRelations.size(); ++i ) {
-        mRelOutput << "<Relationship Target=\"/" << mRelations[ i ]->target << "\" ";
+        if ( mRelations[ i ]->target[ 0 ] == '/' ) {
+            mRelOutput << "<Relationship Target=\"" << mRelations[ i ]->target << "\" ";
+        } else {
+            mRelOutput << "<Relationship Target=\"/" << mRelations[ i ]->target << "\" ";
+        }
         mRelOutput << "Id=\"" << mRelations[i]->id << "\" ";
         mRelOutput << "Type=\"" << mRelations[ i ]->type << "\" />";
         mRelOutput << std::endl;
@@ -177,6 +182,10 @@ bool D3MFExporter::export3DModel() {
     mModelOutput << "<" << XmlTag::resources << ">";
     mModelOutput << std::endl;
 
+    writeMetaData();
+
+    writeBaseMaterials();
+
     writeObjects();
 
 
@@ -203,6 +212,63 @@ void D3MFExporter::writeHeader() {
     mModelOutput << std::endl;
 }
 
+void D3MFExporter::writeMetaData() {
+    if ( nullptr == mScene->mMetaData ) {
+        return;
+    }
+
+    const unsigned int numMetaEntries( mScene->mMetaData->mNumProperties );
+    if ( 0 == numMetaEntries ) {
+        return;
+    }
+
+    const aiString *key;
+    const aiMetadataEntry *entry(nullptr);
+    for ( size_t i = 0; i < numMetaEntries; ++i ) {
+        mScene->mMetaData->Get( i, key, entry );
+        std::string k( key->C_Str() );
+        aiString value;
+        mScene->mMetaData->Get(  k, value );
+        mModelOutput << "<" << XmlTag::meta << " " << XmlTag::meta_name << "=\"" << key->C_Str() << "\">";
+        mModelOutput << value.C_Str();
+        mModelOutput << "</" << XmlTag::meta << ">" << std::endl;
+    }
+}
+
+void D3MFExporter::writeBaseMaterials() {
+    mModelOutput << "<basematerials id=\"1\">\n";
+    std::string strName, hexDiffuseColor , tmp;
+    for ( size_t i = 0; i < mScene->mNumMaterials; ++i ) {
+        aiMaterial *mat = mScene->mMaterials[ i ];
+        aiString name;
+        if ( mat->Get( AI_MATKEY_NAME, name ) != aiReturn_SUCCESS ) {
+            strName = "basemat_" + to_string( i );
+        } else {
+            strName = name.C_Str();
+        }
+        aiColor4D color;
+        if ( mat->Get( AI_MATKEY_COLOR_DIFFUSE, color ) == aiReturn_SUCCESS ) {
+            hexDiffuseColor.clear();
+            tmp.clear();
+            hexDiffuseColor = "#";
+            
+            tmp = DecimalToHexa( color.r );
+            hexDiffuseColor += tmp;
+            tmp = DecimalToHexa( color.g );
+            hexDiffuseColor += tmp;
+            tmp = DecimalToHexa( color.b );
+            hexDiffuseColor += tmp;
+            tmp = DecimalToHexa( color.a );
+            hexDiffuseColor += tmp;
+        } else {
+            hexDiffuseColor = "#FFFFFFFF";
+        }
+
+        mModelOutput << "<base name=\""+strName+"\" "+" displaycolor=\""+hexDiffuseColor+"\" />\n";
+    }
+    mModelOutput << "</basematerials>\n";
+}
+
 void D3MFExporter::writeObjects() {
     if ( nullptr == mScene->mRootNode ) {
         return;
@@ -242,7 +308,9 @@ void D3MFExporter::writeMesh( aiMesh *mesh ) {
     }
     mModelOutput << "</" << XmlTag::vertices << ">" << std::endl;
 
-    writeFaces( mesh );
+    const unsigned int matIdx( mesh->mMaterialIndex );
+
+    writeFaces( mesh, matIdx );
 
     mModelOutput << "</" << XmlTag::mesh << ">" << std::endl;
 }
@@ -252,7 +320,7 @@ void D3MFExporter::writeVertex( const aiVector3D &pos ) {
     mModelOutput << std::endl;
 }
 
-void D3MFExporter::writeFaces( aiMesh *mesh ) {
+void D3MFExporter::writeFaces( aiMesh *mesh, unsigned int matIdx ) {
     if ( nullptr == mesh ) {
         return;
     }
@@ -264,7 +332,8 @@ void D3MFExporter::writeFaces( aiMesh *mesh ) {
     for ( unsigned int i = 0; i < mesh->mNumFaces; ++i ) {
         aiFace &currentFace = mesh->mFaces[ i ];
         mModelOutput << "<" << XmlTag::triangle << " v1=\"" << currentFace.mIndices[ 0 ] << "\" v2=\""
-                << currentFace.mIndices[ 1 ] << "\" v3=\"" << currentFace.mIndices[ 2 ] << "\"/>";
+                << currentFace.mIndices[ 1 ] << "\" v3=\"" << currentFace.mIndices[ 2 ]
+                << "\" pid=\"1\" p1=\""+to_string(matIdx)+"\" />";
         mModelOutput << std::endl;
     }
     mModelOutput << "</" << XmlTag::triangles << ">";

+ 3 - 1
code/D3MFExporter.h

@@ -76,10 +76,12 @@ public:
 
 protected:
     void writeHeader();
+    void writeMetaData();
+    void writeBaseMaterials();
     void writeObjects();
     void writeMesh( aiMesh *mesh );
     void writeVertex( const aiVector3D &pos );
-    void writeFaces( aiMesh *mesh );
+    void writeFaces( aiMesh *mesh, unsigned int matIdx );
     void writeBuild();
     void exportContentTyp( const std::string &filename );
     void writeModelToArchive( const std::string &folder, const std::string &modelName );

+ 222 - 75
code/D3MFImporter.cpp

@@ -61,14 +61,24 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <unzip.h>
 #include <assimp/irrXMLWrapper.h>
 #include "3MFXmlTags.h"
+#include <assimp/fast_atof.h>
+
+#include <iomanip>
 
 namespace Assimp {
 namespace D3MF {
 
 class XmlSerializer {
 public:
+    using MatArray = std::vector<aiMaterial*>;
+    using MatId2MatArray = std::map<unsigned int, std::vector<unsigned int>>;
+
     XmlSerializer(XmlReader* xmlReader)
-    : xmlReader(xmlReader) {
+    : mMeshes()
+    , mMatArray()
+    , mActiveMatGroup( 99999999 )
+    , mMatId2MatArray()
+    , xmlReader(xmlReader){
 		// empty
     }
 
@@ -77,14 +87,24 @@ public:
     }
 
     void ImportXml(aiScene* scene) {
+        if ( nullptr == scene ) {
+            return;
+        }
+
         scene->mRootNode = new aiNode();
         std::vector<aiNode*> children;
 
+        std::string nodeName;
         while(ReadToEndElement(D3MF::XmlTag::model)) {
-            if(xmlReader->getNodeName() == D3MF::XmlTag::object) {
+            nodeName = xmlReader->getNodeName();
+            if( nodeName == D3MF::XmlTag::object) {
                 children.push_back(ReadObject(scene));
-            } else if(xmlReader->getNodeName() == D3MF::XmlTag::build) {
-
+            } else if( nodeName == D3MF::XmlTag::build) {
+                // 
+            } else if ( nodeName == D3MF::XmlTag::basematerials ) {
+                ReadBaseMaterials();
+            } else if ( nodeName == D3MF::XmlTag::meta ) {
+                ReadMetadata();
             }
         }
 
@@ -92,31 +112,47 @@ public:
             scene->mRootNode->mName.Set( "3MF" );
         }
 
-        scene->mNumMeshes = static_cast<unsigned int>(meshes.size());
+        // import the metadata
+        if ( !mMetaData.empty() ) {
+            const size_t numMeta( mMetaData.size() );
+            scene->mMetaData = aiMetadata::Alloc( numMeta );
+            for ( size_t i = 0; i < numMeta; ++i ) {
+                aiString val( mMetaData[ i ].value );
+                scene->mMetaData->Set( i, mMetaData[ i ].name, val );
+            }
+        }
+
+        // import the meshes
+        scene->mNumMeshes = static_cast<unsigned int>( mMeshes.size());
         scene->mMeshes = new aiMesh*[scene->mNumMeshes]();
+        std::copy( mMeshes.begin(), mMeshes.end(), scene->mMeshes);
 
-        std::copy(meshes.begin(), meshes.end(), scene->mMeshes);
+        // import the materials
+        scene->mNumMaterials = static_cast<unsigned int>( mMatArray.size() );
+        if ( 0 != scene->mNumMaterials ) {
+            scene->mMaterials = new aiMaterial*[ scene->mNumMaterials ];
+            std::copy( mMatArray.begin(), mMatArray.end(), scene->mMaterials );
+        }
 
+        // create the scenegraph
         scene->mRootNode->mNumChildren = static_cast<unsigned int>(children.size());
         scene->mRootNode->mChildren = new aiNode*[scene->mRootNode->mNumChildren]();
-
         std::copy(children.begin(), children.end(), scene->mRootNode->mChildren);
     }
 
 private:
-    aiNode* ReadObject(aiScene* scene)
-    {
+    aiNode* ReadObject(aiScene* scene) {
         std::unique_ptr<aiNode> node(new aiNode());
 
         std::vector<unsigned long> meshIds;
 
         const char *attrib( nullptr );
         std::string name, type;
-        attrib = xmlReader->getAttributeValue( D3MF::XmlTag::name.c_str() );
+        attrib = xmlReader->getAttributeValue( D3MF::XmlTag::id.c_str() );
         if ( nullptr != attrib ) {
             name = attrib;
         }
-        attrib = xmlReader->getAttributeValue( D3MF::XmlTag::name.c_str() );
+        attrib = xmlReader->getAttributeValue( D3MF::XmlTag::type.c_str() );
         if ( nullptr != attrib ) {
             type = attrib;
         }
@@ -124,19 +160,16 @@ private:
         node->mParent = scene->mRootNode;
         node->mName.Set(name);
 
-        size_t meshIdx = meshes.size();
+        size_t meshIdx = mMeshes.size();
 
-        while(ReadToEndElement(D3MF::XmlTag::object))
-        {
-            if(xmlReader->getNodeName() == D3MF::XmlTag::mesh)
-            {
+        while(ReadToEndElement(D3MF::XmlTag::object)) {
+            if(xmlReader->getNodeName() == D3MF::XmlTag::mesh) {
                 auto mesh = ReadMesh();
 
                 mesh->mName.Set(name);
-                meshes.push_back(mesh);
+                mMeshes.push_back(mesh);
                 meshIds.push_back(static_cast<unsigned long>(meshIdx));
-                meshIdx++;
-
+                ++meshIdx;
             }
         }
 
@@ -147,19 +180,14 @@ private:
         std::copy(meshIds.begin(), meshIds.end(), node->mMeshes);
 
         return node.release();
-
     }
 
-    aiMesh* ReadMesh() {
+    aiMesh *ReadMesh() {
         aiMesh* mesh = new aiMesh();
-        while(ReadToEndElement(D3MF::XmlTag::mesh))
-        {
-            if(xmlReader->getNodeName() == D3MF::XmlTag::vertices)
-            {
+        while(ReadToEndElement(D3MF::XmlTag::mesh)) {
+            if(xmlReader->getNodeName() == D3MF::XmlTag::vertices) {
                 ImportVertices(mesh);
-            }
-            else if(xmlReader->getNodeName() == D3MF::XmlTag::triangles)
-            {
+            } else if(xmlReader->getNodeName() == D3MF::XmlTag::triangles) {
                 ImportTriangles(mesh);
             }
         }
@@ -167,14 +195,25 @@ private:
         return mesh;
     }
 
-    void ImportVertices(aiMesh* mesh)
-    {
-        std::vector<aiVector3D> vertices;
+    void ReadMetadata() {
+        const std::string name = xmlReader->getAttributeValue( D3MF::XmlTag::meta_name.c_str() );
+        xmlReader->read();
+        const std::string value = xmlReader->getNodeData();
 
-        while(ReadToEndElement(D3MF::XmlTag::vertices))
-        {
-            if(xmlReader->getNodeName() == D3MF::XmlTag::vertex)
-            {
+        if ( name.empty() ) {
+            return;
+        }
+
+        MetaEntry entry;
+        entry.name = name;
+        entry.value = value;
+        mMetaData.push_back( entry );
+    }
+
+    void ImportVertices(aiMesh* mesh) {
+        std::vector<aiVector3D> vertices;
+        while(ReadToEndElement(D3MF::XmlTag::vertices)) {
+            if(xmlReader->getNodeName() == D3MF::XmlTag::vertex) {
                 vertices.push_back(ReadVertex());
             }
         }
@@ -182,11 +221,9 @@ private:
         mesh->mVertices = new aiVector3D[mesh->mNumVertices];
 
         std::copy(vertices.begin(), vertices.end(), mesh->mVertices);
-
     }
 
-    aiVector3D ReadVertex()
-    {
+    aiVector3D ReadVertex() {
         aiVector3D vertex;
 
         vertex.x = ai_strtof(xmlReader->getAttributeValue(D3MF::XmlTag::x.c_str()), nullptr);
@@ -196,16 +233,18 @@ private:
         return vertex;
     }
 
-    void ImportTriangles(aiMesh* mesh)
-    {
+    void ImportTriangles(aiMesh* mesh) {
          std::vector<aiFace> faces;
 
-
-         while(ReadToEndElement(D3MF::XmlTag::triangles))
-         {
-             if(xmlReader->getNodeName() == D3MF::XmlTag::triangle)
-             {
+         while(ReadToEndElement(D3MF::XmlTag::triangles)) {
+             const std::string nodeName( xmlReader->getNodeName() );
+             if(xmlReader->getNodeName() == D3MF::XmlTag::triangle) {
                  faces.push_back(ReadTriangle());
+                 const char *pidToken( xmlReader->getAttributeValue( D3MF::XmlTag::p1.c_str() ) );
+                 if ( nullptr != pidToken ) {
+                     int matIdx( std::atoi( pidToken ) );
+                     mesh->mMaterialIndex = matIdx;
+                 }
              }
          }
 
@@ -216,8 +255,7 @@ private:
         std::copy(faces.begin(), faces.end(), mesh->mFaces);
     }
 
-    aiFace ReadTriangle()
-    {
+    aiFace ReadTriangle() {
         aiFace face;
 
         face.mNumIndices = 3;
@@ -229,45 +267,150 @@ private:
         return face;
     }
 
+    void ReadBaseMaterials() {
+        std::vector<unsigned int> MatIdArray;
+        const char *baseMaterialId( xmlReader->getAttributeValue( D3MF::XmlTag::basematerials_id.c_str() ) );
+        if ( nullptr != baseMaterialId ) {
+            unsigned int id = std::atoi( baseMaterialId );
+            const size_t newMatIdx( mMatArray.size() );
+            if ( id != mActiveMatGroup ) {
+                mActiveMatGroup = id;
+                MatId2MatArray::const_iterator it( mMatId2MatArray.find( id ) );
+                if ( mMatId2MatArray.end() == it ) {
+                    MatIdArray.clear();
+                    mMatId2MatArray[ id ] = MatIdArray;
+                } else {
+                    MatIdArray = it->second;
+                }
+            }
+            MatIdArray.push_back( static_cast<unsigned int>( newMatIdx ) );
+            mMatId2MatArray[ mActiveMatGroup ] = MatIdArray;
+        }
+
+        while ( ReadToEndElement( D3MF::XmlTag::basematerials ) ) {
+            mMatArray.push_back( readMaterialDef() );
+        }
+    }
+
+    bool parseColor( const char *color, aiColor4D &diffuse ) {
+        if ( nullptr == color ) {
+            return false;
+        }
+
+        const size_t len( strlen( color ) );
+        if ( 9 != len ) {
+            return false;
+        }
+
+        const char *buf( color );
+        if ( '#' != *buf ) {
+            return false;
+        }
+        ++buf;
+        char comp[ 3 ] = { 0,0,'\0' };
+
+        comp[ 0 ] = *buf;
+        ++buf;
+        comp[ 1 ] = *buf;
+        ++buf;
+        diffuse.r = static_cast<ai_real>( strtol( comp, NULL, 16 ) );
+
+
+        comp[ 0 ] = *buf;
+        ++buf;
+        comp[ 1 ] = *buf;
+        ++buf;
+        diffuse.g = static_cast< ai_real >( strtol( comp, NULL, 16 ) );
+
+        comp[ 0 ] = *buf;
+        ++buf;
+        comp[ 1 ] = *buf;
+        ++buf;
+        diffuse.b = static_cast< ai_real >( strtol( comp, NULL, 16 ) );
+
+        comp[ 0 ] = *buf;
+        ++buf;
+        comp[ 1 ] = *buf;
+        ++buf;
+        diffuse.a = static_cast< ai_real >( strtol( comp, NULL, 16 ) );
+
+        return true;
+    }
+
+    void assignDiffuseColor( aiMaterial *mat ) {
+        const char *color = xmlReader->getAttributeValue( D3MF::XmlTag::basematerials_displaycolor.c_str() );
+        aiColor4D diffuse;
+        if ( parseColor( color, diffuse ) ) {
+            mat->AddProperty<aiColor4D>( &diffuse, 1, AI_MATKEY_COLOR_DIFFUSE );
+        }
+
+    }
+    aiMaterial *readMaterialDef() {
+        aiMaterial *mat( nullptr );
+        const char *name( nullptr );
+        const std::string nodeName( xmlReader->getNodeName() );
+        if ( nodeName == D3MF::XmlTag::basematerials_base ) {
+            name = xmlReader->getAttributeValue( D3MF::XmlTag::basematerials_name.c_str() );
+            std::string stdMatName;
+            aiString matName;
+            std::string strId( to_string( mActiveMatGroup ) );
+            stdMatName += "id";
+            stdMatName += strId;
+            stdMatName += "_";
+            if ( nullptr != name ) {
+                stdMatName += std::string( name );
+            } else {
+                stdMatName += "basemat";
+            }
+            matName.Set( stdMatName );
+
+            mat = new aiMaterial;
+            mat->AddProperty( &matName, AI_MATKEY_NAME );
+
+            assignDiffuseColor( mat );
+        }
+
+        return mat;
+    }
+
 private:
-    bool ReadToStartElement(const std::string& startTag)
-    {
-        while(xmlReader->read())
-        {
-            if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT && xmlReader->getNodeName() == startTag)
-            {
+    bool ReadToStartElement(const std::string& startTag) {
+        while(xmlReader->read()) {
+            const std::string &nodeName( xmlReader->getNodeName() );
+            if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT && nodeName == startTag) {
                 return true;
-            }
-            else if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT_END &&
-                     xmlReader->getNodeName() == startTag)
-            {
+            } else if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT_END && nodeName == startTag) {
                 return false;
             }
         }
-        //DefaultLogger::get()->error("unexpected EOF, expected closing <" + closeTag + "> tag");
+
         return false;
     }
 
-    bool ReadToEndElement(const std::string& closeTag)
-    {
-        while(xmlReader->read())
-        {
+    bool ReadToEndElement(const std::string& closeTag) {
+        while(xmlReader->read()) {
+            const std::string &nodeName( xmlReader->getNodeName() );
             if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT) {
                 return true;
-            }
-            else if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT_END
-                     && xmlReader->getNodeName() == closeTag)
-            {
+            } else if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT_END && nodeName == closeTag) {
                 return false;
             }
         }
         DefaultLogger::get()->error("unexpected EOF, expected closing <" + closeTag + "> tag");
+
         return false;
     }
 
-
 private:
-    std::vector<aiMesh*> meshes;
+    struct MetaEntry {
+        std::string name;
+        std::string value;
+    };
+    std::vector<MetaEntry> mMetaData;
+    std::vector<aiMesh*> mMeshes;
+    MatArray mMatArray;
+    unsigned int mActiveMatGroup;
+    MatId2MatArray mMatId2MatArray;
     XmlReader* xmlReader;
 };
 
@@ -288,7 +431,6 @@ static const aiImporterDesc desc = {
     Extension.c_str()
 };
 
-
 D3MFImporter::D3MFImporter()
 : BaseImporter() {
     // empty
@@ -298,14 +440,19 @@ D3MFImporter::~D3MFImporter() {
     // empty
 }
 
-bool D3MFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string extension = GetExtension(pFile);
+bool D3MFImporter::CanRead(const std::string &filename, IOSystem *pIOHandler, bool checkSig) const {
+    const std::string extension( GetExtension( filename ) );
     if(extension == Extension ) {
         return true;
     } else if ( !extension.length() || checkSig ) {
-        if (nullptr == pIOHandler ) {
-            return true;
+        if ( nullptr == pIOHandler ) {
+            return false;
+        }
+        if ( !D3MF::D3MFOpcPackage::isZipArchive( pIOHandler, filename ) ) {
+            return false;
         }
+        D3MF::D3MFOpcPackage opcPackage( pIOHandler, filename );
+        return opcPackage.validate();
     }
 
     return false;
@@ -319,8 +466,8 @@ const aiImporterDesc *D3MFImporter::GetInfo() const {
     return &desc;
 }
 
-void D3MFImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
-    D3MF::D3MFOpcPackage opcPackage(pIOHandler, pFile);
+void D3MFImporter::InternReadFile( const std::string &filename, aiScene *pScene, IOSystem *pIOHandler ) {
+    D3MF::D3MFOpcPackage opcPackage(pIOHandler, filename);
 
     std::unique_ptr<CIrrXML_IOStreamReader> xmlStream(new CIrrXML_IOStreamReader(opcPackage.RootStream()));
     std::unique_ptr<D3MF::XmlReader> xmlReader(irr::io::createIrrXMLReader(xmlStream.get()));

+ 39 - 29
code/D3MFOpcPackage.cpp

@@ -247,13 +247,13 @@ private:
 // ------------------------------------------------------------------------------------------------
 //  Constructor.
 D3MFZipArchive::D3MFZipArchive(IOSystem* pIOHandler, const std::string& rFile)
-: m_ZipFileHandle(NULL)
+: 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 != NULL) {            
+        if(m_ZipFileHandle != nullptr ) {
             mapArchive();
         }
     }
@@ -267,32 +267,32 @@ D3MFZipArchive::~D3MFZipArchive() {
     }
     m_ArchiveMap.clear();
 
-    if(m_ZipFileHandle != NULL) {
+    if(m_ZipFileHandle != nullptr) {
         unzClose(m_ZipFileHandle);
-        m_ZipFileHandle = NULL;
+        m_ZipFileHandle = nullptr;
     }
 }
 
 // ------------------------------------------------------------------------------------------------
 //  Returns true, if the archive is already open.
 bool D3MFZipArchive::isOpen() const {
-    return (m_ZipFileHandle != NULL);
+    return (m_ZipFileHandle != nullptr );
 }
 
 // ------------------------------------------------------------------------------------------------
 //  Returns true, if the filename is part of the archive.
 bool D3MFZipArchive::Exists(const char* pFile) const {
-    ai_assert(pFile != NULL);
-
-    bool exist = false;
+    ai_assert(pFile != nullptr );
 
-    if (pFile != NULL) {
-        std::string rFile(pFile);
-        std::map<std::string, ZipFile*>::const_iterator it = m_ArchiveMap.find(rFile);
+    if ( pFile == nullptr ) {
+        return false;
+    }
 
-        if(it != m_ArchiveMap.end()) {
-            exist = true;
-        }
+    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;
@@ -434,8 +434,8 @@ public:
 
     std::vector<OpcPackageRelationshipPtr> m_relationShips;
 };
-// ------------------------------------------------------------------------------------------------
 
+// ------------------------------------------------------------------------------------------------
 D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
 : mRootStream(nullptr)
 , mZipArchive() {    
@@ -460,7 +460,7 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
             if ( rootFile.size() > 0 && rootFile[ 0 ] == '/' ) {
                 rootFile = rootFile.substr( 1 );
                 if ( rootFile[ 0 ] == '/' ) {
-                    // deal with zipbug
+                    // deal with zip-bug
                     rootFile = rootFile.substr( 1 );
                 }
             }
@@ -470,18 +470,9 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile)
             mRootStream = mZipArchive->Open(rootFile.c_str());
             ai_assert( mRootStream != nullptr );
             if ( nullptr == mRootStream ) {
-                throw DeadlyExportError( "Cannot open rootfile in archive : " + rootFile );
+                throw DeadlyExportError( "Cannot open root-file in archive : " + rootFile );
             }
 
-        //    const size_t size = zipArchive->FileSize();
-        //    m_Data.resize( size );
-
-        //    const size_t readSize = pMapFile->Read( &m_Data[0], sizeof( char ), size );
-        //    if ( readSize != size )
-        //    {
-        //        m_Data.clear();
-        //        return false;
-        //    }
             mZipArchive->Close( fileStream );
 
         } else if( file == D3MF::XmlTag::CONTENT_TYPES_ARCHIVE) {
@@ -498,6 +489,25 @@ IOStream* D3MFOpcPackage::RootStream() const {
     return mRootStream;
 }
 
+static const std::string ModelRef = "3D/3dmodel.model";
+
+bool D3MFOpcPackage::validate() {
+    if ( nullptr == mRootStream || nullptr == mZipArchive ) {
+        return false;
+    }
+
+    return mZipArchive->Exists( ModelRef.c_str() );
+}
+
+bool D3MFOpcPackage::isZipArchive( IOSystem* pIOHandler, const std::string& rFile ) {
+    D3MF::D3MFZipArchive ar( pIOHandler, rFile );
+    if ( !ar.isOpen() ) {
+        return false;
+    }
+
+    return true;
+}
+
 std::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream* stream) {
     std::unique_ptr<CIrrXML_IOStreamReader> xmlStream(new CIrrXML_IOStreamReader(stream));
     std::unique_ptr<XmlReader> xml(irr::io::createIrrXMLReader(xmlStream.get()));
@@ -508,14 +518,14 @@ std::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream* stream) {
         return rel->type == XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE;
     });
 
-    if(itr == reader.m_relationShips.end())
-        throw DeadlyImportError("Cannot find " + XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE);
+    if ( itr == reader.m_relationShips.end() ) {
+        throw DeadlyImportError( "Cannot find " + XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE );
+    }
 
     return (*itr)->target;
 }
 
 } // Namespace D3MF
-
 } // Namespace Assimp
 
 #endif //ASSIMP_BUILD_NO_3MF_IMPORTER

+ 7 - 5
code/D3MFOpcPackage.h

@@ -51,8 +51,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp {
 namespace D3MF {
 
-typedef irr::io::IrrXMLReader XmlReader;
-typedef std::shared_ptr<XmlReader> XmlReaderPtr;
+using XmlReader = irr::io::IrrXMLReader ;
+using XmlReaderPtr = std::shared_ptr<XmlReader> ;
 
 struct OpcPackageRelationship {
     std::string id;
@@ -64,9 +64,11 @@ class D3MFZipArchive;
 
 class D3MFOpcPackage {
 public:
-    D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile);
+    D3MFOpcPackage( IOSystem* pIOHandler, const std::string& rFile );
     ~D3MFOpcPackage();
     IOStream* RootStream() const;
+    bool validate();
+    static bool isZipArchive( IOSystem* pIOHandler, const std::string& rFile );
 
 protected:
     std::string ReadPackageRootRelationship(IOStream* stream);
@@ -76,7 +78,7 @@ private:
     std::unique_ptr<D3MFZipArchive> mZipArchive;
 };
 
-}
-}
+} // Namespace D3MF
+} // Namespace Assimp
 
 #endif // D3MFOPCPACKAGE_H

+ 0 - 1
code/DXFHelper.h

@@ -169,7 +169,6 @@ public:
     }
 
 private:
-
     LineSplitter splitter;
     int groupcode;
     std::string value;

+ 3 - 3
code/DefaultIOSystem.cpp

@@ -76,7 +76,7 @@ bool DefaultIOSystem::Exists( const char* pFile) const
 #ifdef _WIN32
     wchar_t fileName16[PATHLIMIT];
 
-    bool isUnicode = IsTextUnicode(pFile, strlen(pFile), NULL);
+    bool isUnicode = IsTextUnicode(pFile, static_cast<int>(strlen(pFile)), NULL);
     if (isUnicode) {
 
         MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, pFile, -1, fileName16, PATHLIMIT);
@@ -110,7 +110,7 @@ IOStream* DefaultIOSystem::Open( const char* strFile, const char* strMode)
     FILE* file;
 #ifdef _WIN32
     wchar_t fileName16[PATHLIMIT];
-    bool isUnicode = IsTextUnicode(strFile, strlen(strFile), NULL );
+    bool isUnicode = IsTextUnicode(strFile, static_cast<int>(strlen(strFile)), NULL );
     if (isUnicode) {
         MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, strFile, -1, fileName16, PATHLIMIT);
         std::string mode8(strMode);
@@ -158,7 +158,7 @@ inline static void MakeAbsolutePath (const char* in, char* _out)
 {
     ai_assert(in && _out);
 #if defined( _MSC_VER ) || defined( __MINGW32__ )
-    bool isUnicode = IsTextUnicode(in, strlen(in), NULL);
+    bool isUnicode = IsTextUnicode(in, static_cast<int>(strlen(in)), NULL);
     if (isUnicode) {
         wchar_t out16[PATHLIMIT];
         wchar_t in16[PATHLIMIT];

+ 56 - 68
code/DefaultLogger.cpp

@@ -45,7 +45,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *  @brief Implementation of DefaultLogger (and Logger)
  */
 
-
 // Default log streams
 #include "Win32DebugLogStream.h"
 #include "StdOStreamLogStream.h"
@@ -62,8 +61,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #ifndef ASSIMP_BUILD_SINGLETHREADED
 #   include <thread>
 #   include <mutex>
-
-std::mutex loggerMutex;
+    std::mutex loggerMutex;
 #endif
 
 namespace Assimp    {
@@ -76,22 +74,19 @@ static const unsigned int SeverityAll = Logger::Info | Logger::Err | Logger::War
 
 // ----------------------------------------------------------------------------------
 // Represents a log-stream + its error severity
-struct LogStreamInfo
-{
-    unsigned int m_uiErrorSeverity;
-    LogStream *m_pStream;
+struct LogStreamInfo {
+    unsigned int  m_uiErrorSeverity;
+    LogStream    *m_pStream;
 
     // Constructor
     LogStreamInfo( unsigned int uiErrorSev, LogStream *pStream ) :
         m_uiErrorSeverity( uiErrorSev ),
-        m_pStream( pStream )
-    {
+        m_pStream( pStream ) {
         // empty
     }
 
     // Destructor
-    ~LogStreamInfo()
-    {
+    ~LogStreamInfo() {
         delete m_pStream;
     }
 };
@@ -109,7 +104,7 @@ LogStream* LogStream::createDefaultStream(aiDefaultLogStream    streams,
 #ifdef WIN32
         return new Win32DebugLogStream();
 #else
-        return NULL;
+        return nullptr;
 #endif
 
         // Platform-independent default streams
@@ -118,7 +113,7 @@ LogStream* LogStream::createDefaultStream(aiDefaultLogStream    streams,
     case aiDefaultLogStream_STDOUT:
         return new StdOStreamLogStream(std::cout);
     case aiDefaultLogStream_FILE:
-        return (name && *name ? new FileLogStream(name,io) : NULL);
+        return (name && *name ? new FileLogStream(name,io) : nullptr );
     default:
         // We don't know this default log stream, so raise an assertion
         ai_assert(false);
@@ -134,34 +129,38 @@ LogStream* LogStream::createDefaultStream(aiDefaultLogStream    streams,
 Logger *DefaultLogger::create(const char* name /*= "AssimpLog.txt"*/,
     LogSeverity severity                       /*= NORMAL*/,
     unsigned int defStreams                    /*= aiDefaultLogStream_DEBUGGER | aiDefaultLogStream_FILE*/,
-    IOSystem* io                               /*= NULL*/)
-{
+    IOSystem* io                               /*= NULL*/) {
     // enter the mutex here to avoid concurrency problems
 #ifndef ASSIMP_BUILD_SINGLETHREADED
     std::lock_guard<std::mutex> lock(loggerMutex);
 #endif
 
-    if (m_pLogger && !isNullLogger() )
+    if ( m_pLogger && !isNullLogger() ) {
         delete m_pLogger;
+    }
 
     m_pLogger = new DefaultLogger( severity );
 
     // Attach default log streams
     // Stream the log to the MSVC debugger?
-    if (defStreams & aiDefaultLogStream_DEBUGGER)
-        m_pLogger->attachStream( LogStream::createDefaultStream(aiDefaultLogStream_DEBUGGER));
+    if ( defStreams & aiDefaultLogStream_DEBUGGER ) {
+        m_pLogger->attachStream( LogStream::createDefaultStream( aiDefaultLogStream_DEBUGGER ) );
+    }
 
     // Stream the log to COUT?
-    if (defStreams & aiDefaultLogStream_STDOUT)
-        m_pLogger->attachStream( LogStream::createDefaultStream(aiDefaultLogStream_STDOUT));
+    if ( defStreams & aiDefaultLogStream_STDOUT ) {
+        m_pLogger->attachStream( LogStream::createDefaultStream( aiDefaultLogStream_STDOUT ) );
+    }
 
     // Stream the log to CERR?
-    if (defStreams & aiDefaultLogStream_STDERR)
-         m_pLogger->attachStream( LogStream::createDefaultStream(aiDefaultLogStream_STDERR));
+    if ( defStreams & aiDefaultLogStream_STDERR ) {
+        m_pLogger->attachStream( LogStream::createDefaultStream( aiDefaultLogStream_STDERR ) );
+    }
 
     // Stream the log to a file
-    if (defStreams & aiDefaultLogStream_FILE && name && *name)
-        m_pLogger->attachStream( LogStream::createDefaultStream(aiDefaultLogStream_FILE,name,io));
+    if ( defStreams & aiDefaultLogStream_FILE && name && *name ) {
+        m_pLogger->attachStream( LogStream::createDefaultStream( aiDefaultLogStream_FILE, name, io ) );
+    }
 
     return m_pLogger;
 }
@@ -200,7 +199,6 @@ void Logger::warn(const char* message)  {
 
 // ----------------------------------------------------------------------------------
 void Logger::error(const char* message) {
-
     // SECURITY FIX: see above
     if (strlen(message)>MAX_LOG_MESSAGE_LENGTH) {
         return;
@@ -209,23 +207,24 @@ void Logger::error(const char* message) {
 }
 
 // ----------------------------------------------------------------------------------
-void DefaultLogger::set( Logger *logger )
-{
+void DefaultLogger::set( Logger *logger ) {
     // enter the mutex here to avoid concurrency problems
 #ifndef ASSIMP_BUILD_SINGLETHREADED
     std::lock_guard<std::mutex> lock(loggerMutex);
 #endif
 
-    if (!logger)logger = &s_pNullLogger;
-    if (m_pLogger && !isNullLogger() )
+    if ( nullptr == logger ) {
+        logger = &s_pNullLogger;
+    }
+    if ( nullptr != m_pLogger && !isNullLogger() ) {
         delete m_pLogger;
+    }
 
     DefaultLogger::m_pLogger = logger;
 }
 
 // ----------------------------------------------------------------------------------
-bool DefaultLogger::isNullLogger()
-{
+bool DefaultLogger::isNullLogger() {
     return m_pLogger == &s_pNullLogger;
 }
 
@@ -236,8 +235,7 @@ Logger *DefaultLogger::get() {
 
 // ----------------------------------------------------------------------------------
 //  Kills the only instance
-void DefaultLogger::kill()
-{
+void DefaultLogger::kill() {
     // enter the mutex here to avoid concurrency problems
 #ifndef ASSIMP_BUILD_SINGLETHREADED
     std::lock_guard<std::mutex> lock(loggerMutex);
@@ -252,10 +250,10 @@ void DefaultLogger::kill()
 
 // ----------------------------------------------------------------------------------
 //  Debug message
-void DefaultLogger::OnDebug( const char* message )
-{
-	if ( m_Severity == Logger::NORMAL )
-		return;
+void DefaultLogger::OnDebug( const char* message ) {
+    if ( m_Severity == Logger::NORMAL ) {
+        return;
+    }
 
 	static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16;
 	char msg[Size];
@@ -266,8 +264,7 @@ void DefaultLogger::OnDebug( const char* message )
 
 // ----------------------------------------------------------------------------------
 //  Logs an info
-void DefaultLogger::OnInfo( const char* message )
-{
+void DefaultLogger::OnInfo( const char* message ){
 	static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16;
 	char msg[Size];
     ai_snprintf(msg, Size, "Info,  T%u: %s", GetThreadID(), message );
@@ -277,8 +274,7 @@ void DefaultLogger::OnInfo( const char* message )
 
 // ----------------------------------------------------------------------------------
 //  Logs a warning
-void DefaultLogger::OnWarn( const char* message )
-{
+void DefaultLogger::OnWarn( const char* message ) {
 	static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16;
 	char msg[Size];
 	ai_snprintf(msg, Size, "Warn,  T%u: %s", GetThreadID(), message );
@@ -288,8 +284,7 @@ void DefaultLogger::OnWarn( const char* message )
 
 // ----------------------------------------------------------------------------------
 //  Logs an error
-void DefaultLogger::OnError( const char* message )
-{
+void DefaultLogger::OnError( const char* message ) {
 	static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16;
 	char msg[ Size ];
     ai_snprintf(msg, Size, "Error, T%u: %s", GetThreadID(), message );
@@ -299,10 +294,10 @@ void DefaultLogger::OnError( const char* message )
 
 // ----------------------------------------------------------------------------------
 //  Will attach a new stream
-bool DefaultLogger::attachStream( LogStream *pStream, unsigned int severity )
-{
-    if (!pStream)
+bool DefaultLogger::attachStream( LogStream *pStream, unsigned int severity ) {
+    if ( nullptr == pStream ) {
         return false;
+    }
 
     if (0 == severity)  {
         severity = Logger::Info | Logger::Err | Logger::Warn | Logger::Debugging;
@@ -312,8 +307,7 @@ bool DefaultLogger::attachStream( LogStream *pStream, unsigned int severity )
         it != m_StreamArray.end();
         ++it )
     {
-        if ( (*it)->m_pStream == pStream )
-        {
+        if ( (*it)->m_pStream == pStream ) {
             (*it)->m_uiErrorSeverity |= severity;
             return true;
         }
@@ -326,34 +320,31 @@ bool DefaultLogger::attachStream( LogStream *pStream, unsigned int severity )
 
 // ----------------------------------------------------------------------------------
 //  Detach a stream
-bool DefaultLogger::detatchStream( LogStream *pStream, unsigned int severity )
-{
-    if (!pStream)
+bool DefaultLogger::detatchStream( LogStream *pStream, unsigned int severity ) {
+    if ( nullptr == pStream ) {
         return false;
+    }
 
     if (0 == severity)  {
         severity = SeverityAll;
     }
 
-    for ( StreamIt it = m_StreamArray.begin();
-        it != m_StreamArray.end();
-        ++it )
-    {
-        if ( (*it)->m_pStream == pStream )
-        {
+    bool res( false );
+    for ( StreamIt it = m_StreamArray.begin(); it != m_StreamArray.end(); ++it ) {
+        if ( (*it)->m_pStream == pStream ) {
             (*it)->m_uiErrorSeverity &= ~severity;
-            if ( (*it)->m_uiErrorSeverity == 0 )
-            {
+            if ( (*it)->m_uiErrorSeverity == 0 ) {
                 // don't delete the underlying stream 'cause the caller gains ownership again
-                (**it).m_pStream = NULL;
+                (**it).m_pStream = nullptr;
                 delete *it;
                 m_StreamArray.erase( it );
+                res = true;
                 break;
             }
             return true;
         }
     }
-    return false;
+    return res;
 }
 
 // ----------------------------------------------------------------------------------
@@ -361,15 +352,13 @@ bool DefaultLogger::detatchStream( LogStream *pStream, unsigned int severity )
 DefaultLogger::DefaultLogger(LogSeverity severity)
     :   Logger  ( severity )
     ,   noRepeatMsg (false)
-    ,   lastLen( 0 )
-{
+    ,   lastLen( 0 ) {
     lastMsg[0] = '\0';
 }
 
 // ----------------------------------------------------------------------------------
 //  Destructor
-DefaultLogger::~DefaultLogger()
-{
+DefaultLogger::~DefaultLogger() {
     for ( StreamIt it = m_StreamArray.begin(); it != m_StreamArray.end(); ++it ) {
         // also frees the underlying stream, we are its owner.
         delete *it;
@@ -378,9 +367,8 @@ DefaultLogger::~DefaultLogger()
 
 // ----------------------------------------------------------------------------------
 //  Writes message to stream
-void DefaultLogger::WriteToStreams(const char *message, ErrorSeverity ErrorSev )
-{
-    ai_assert(NULL != message);
+void DefaultLogger::WriteToStreams(const char *message, ErrorSeverity ErrorSev ) {
+    ai_assert(nullptr != message);
 
     // Check whether this is a repeated message
     if (! ::strncmp( message,lastMsg, lastLen-1))

+ 6 - 6
code/EmbedTexturesProcess.cpp

@@ -99,22 +99,22 @@ void EmbedTexturesProcess::Execute(aiScene* pScene) {
 }
 
 bool EmbedTexturesProcess::addTexture(aiScene* pScene, std::string path) const {
-    uint32_t imageSize = 0;
-    std::string imagePath = path;
+    std::streampos imageSize = 0;
+    std::string    imagePath = path;
 
     // Test path directly
     std::ifstream file(imagePath, std::ios::binary | std::ios::ate);
-    if ((imageSize = file.tellg()) == -1u) {
+    if ((imageSize = file.tellg()) == std::streampos(-1)) {
         DefaultLogger::get()->warn("EmbedTexturesProcess: Cannot find image: " + imagePath + ". Will try to find it in root folder.");
 
         // Test path in root path
         imagePath = mRootPath + path;
         file.open(imagePath, std::ios::binary | std::ios::ate);
-        if ((imageSize = file.tellg()) == -1u) {
+        if ((imageSize = file.tellg()) == std::streampos(-1)) {
             // Test path basename in root path
             imagePath = mRootPath + path.substr(path.find_last_of("\\/") + 1u);
             file.open(imagePath, std::ios::binary | std::ios::ate);
-            if ((imageSize = file.tellg()) == -1u) {
+            if ((imageSize = file.tellg()) == std::streampos(-1)) {
                 DefaultLogger::get()->error("EmbedTexturesProcess: Unable to embed texture: " + path + ".");
                 return false;
             }
@@ -134,7 +134,7 @@ bool EmbedTexturesProcess::addTexture(aiScene* pScene, std::string path) const {
     // Add the new texture
     auto pTexture = new aiTexture();
     pTexture->mHeight = 0; // Means that this is still compressed
-    pTexture->mWidth = imageSize;
+    pTexture->mWidth = static_cast<uint32_t>(imageSize);
     pTexture->pcData = imageContent;
 
     auto extension = path.substr(path.find_last_of('.') + 1u);

+ 9 - 1
code/Exporter.cpp

@@ -97,6 +97,8 @@ void ExportSceneGLB2(const char*, IOSystem*, const aiScene*, const ExportPropert
 void ExportSceneAssbin(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneAssxml(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneX3D(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 ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* );
 
 // ------------------------------------------------------------------------------------------------
@@ -169,6 +171,11 @@ Exporter::ExportFormatEntry gExporters[] =
     Exporter::ExportFormatEntry( "x3d", "Extensible 3D", "x3d" , &ExportSceneX3D, 0 ),
 #endif
 
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+    Exporter::ExportFormatEntry( "fbx", "Autodesk FBX (binary)", "fbx", &ExportSceneFBX, 0 ),
+    Exporter::ExportFormatEntry( "fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0 ),
+#endif
+
 #ifndef ASSIMP_BUILD_NO_3MF_EXPORTER
     Exporter::ExportFormatEntry( "3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0 )
 #endif
@@ -301,7 +308,8 @@ bool IsVerboseFormat(const aiScene* pScene) {
 }
 
 // ------------------------------------------------------------------------------------------------
-aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const char* pPath, unsigned int pPreprocessing, const ExportProperties* pProperties) {
+aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const char* pPath,
+        unsigned int pPreprocessing, const ExportProperties* pProperties) {
     ASSIMP_BEGIN_EXCEPTION_REGION();
 
     // when they create scenes from scratch, users will likely create them not in verbose

+ 86 - 0
code/FBXCommon.h

@@ -0,0 +1,86 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2018, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file FBXCommon.h
+* Some useful constants and enums for dealing with FBX files.
+*/
+#ifndef AI_FBXCOMMON_H_INC
+#define AI_FBXCOMMON_H_INC
+
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+
+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 SEPARATOR = {'\x00', '\x01'}; // for use inside strings
+    const std::string MAGIC_NODE_TAG = "_$AssimpFbx$"; // from import
+    const int64_t SECOND = 46186158000; // FBX's kTime unit
+
+    // rotation order. We'll probably use EulerXYZ for everything
+    enum RotOrder {
+        RotOrder_EulerXYZ = 0,
+        RotOrder_EulerXZY,
+        RotOrder_EulerYZX,
+        RotOrder_EulerYXZ,
+        RotOrder_EulerZXY,
+        RotOrder_EulerZYX,
+
+        RotOrder_SphericXYZ,
+
+        RotOrder_MAX // end-of-enum sentinel
+    };
+
+    // transformation inheritance method. Most of the time RSrs
+    enum TransformInheritance {
+        TransformInheritance_RrSs = 0,
+        TransformInheritance_RSrs,
+        TransformInheritance_Rrs,
+
+        TransformInheritance_MAX // end-of-enum sentinel
+    };
+}
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#endif // AI_FBXCOMMON_H_INC

+ 158 - 109
code/FBXConverter.cpp

@@ -61,6 +61,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <memory>
 #include <iterator>
 #include <vector>
+#include <sstream>
+#include <iomanip>
 
 namespace Assimp {
 namespace FBX {
@@ -133,15 +135,14 @@ void Converter::ConvertRootNode() {
     ConvertNodes( 0L, *out->mRootNode );
 }
 
-
-void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform )
-{
+void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform ) {
     const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced( id, "Model" );
 
     std::vector<aiNode*> nodes;
     nodes.reserve( conns.size() );
 
     std::vector<aiNode*> nodes_chain;
+    std::vector<aiNode*> post_nodes_chain;
 
     try {
         for( const Connection* con : conns ) {
@@ -152,15 +153,16 @@ void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& pa
             }
 
             const Object* const object = con->SourceObject();
-            if ( !object ) {
+            if ( nullptr == object ) {
                 FBXImporter::LogWarn( "failed to convert source object for Model link" );
                 continue;
             }
 
             const Model* const model = dynamic_cast<const Model*>( object );
 
-            if ( model ) {
+            if ( nullptr != model ) {
                 nodes_chain.clear();
+                post_nodes_chain.clear();
 
                 aiMatrix4x4 new_abs_transform = parent_transform;
 
@@ -168,11 +170,11 @@ void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& pa
                 // assimp (or rather: the complicated transformation chain that
                 // is employed by fbx) means that we may need multiple aiNode's
                 // to represent a fbx node's transformation.
-                GenerateTransformationNodeChain( *model, nodes_chain );
+                GenerateTransformationNodeChain( *model, nodes_chain, post_nodes_chain );
 
                 ai_assert( nodes_chain.size() );
 
-                const std::string& original_name = FixNodeName( model->Name() );
+                std::string original_name = FixNodeName( model->Name() );
 
                 // check if any of the nodes in the chain has the name the fbx node
                 // is supposed to have. If there is none, add another node to
@@ -187,7 +189,15 @@ void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& pa
                 }
 
                 if ( !name_carrier ) {
+                    NodeNameCache::const_iterator it( std::find( mNodeNames.begin(), mNodeNames.end(), original_name ) );
+                    if ( it != mNodeNames.end() ) {
+                        original_name = original_name + std::string( "001" );
+                    }
+
+                    mNodeNames.push_back( original_name );
                     nodes_chain.push_back( new aiNode( original_name ) );
+                } else {
+                    original_name = nodes_chain.back()->mName.C_Str();
                 }
 
                 //setup metadata on newest node
@@ -213,15 +223,46 @@ void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& pa
                 // attach geometry
                 ConvertModel( *model, *nodes_chain.back(), new_abs_transform );
 
-                // attach sub-nodes
-                ConvertNodes( model->ID(), *nodes_chain.back(), new_abs_transform );
+                // check if there will be any child nodes
+                const std::vector<const Connection*>& child_conns
+                    = doc.GetConnectionsByDestinationSequenced( model->ID(), "Model" );
+
+                // if so, link the geometric transform inverse nodes
+                // before we attach any child nodes
+                if (child_conns.size()) {
+                    for( aiNode* postnode : post_nodes_chain ) {
+                        ai_assert( postnode );
+
+                        if ( last_parent != &parent ) {
+                            last_parent->mNumChildren = 1;
+                            last_parent->mChildren = new aiNode*[ 1 ];
+                            last_parent->mChildren[ 0 ] = postnode;
+                        }
+
+                        postnode->mParent = last_parent;
+                        last_parent = postnode;
+
+                        new_abs_transform *= postnode->mTransformation;
+                    }
+                } else {
+                    // free the nodes we allocated as we don't need them
+                    Util::delete_fun<aiNode> deleter;
+                    std::for_each(
+                        post_nodes_chain.begin(),
+                        post_nodes_chain.end(),
+                        deleter
+                    );
+                }
+
+                // attach sub-nodes (if any)
+                ConvertNodes( model->ID(), *last_parent, new_abs_transform );
 
                 if ( doc.Settings().readLights ) {
-                    ConvertLights( *model );
+                    ConvertLights( *model, original_name );
                 }
 
                 if ( doc.Settings().readCameras ) {
-                    ConvertCameras( *model );
+                    ConvertCameras( *model, original_name );
                 }
 
                 nodes.push_back( nodes_chain.front() );
@@ -240,38 +281,36 @@ void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& pa
         Util::delete_fun<aiNode> deleter;
         std::for_each( nodes.begin(), nodes.end(), deleter );
         std::for_each( nodes_chain.begin(), nodes_chain.end(), deleter );
+        std::for_each( post_nodes_chain.begin(), post_nodes_chain.end(), deleter );
     }
 }
 
 
-void Converter::ConvertLights( const Model& model )
-{
+void Converter::ConvertLights( const Model& model, const std::string &orig_name ) {
     const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
     for( const NodeAttribute* attr : node_attrs ) {
         const Light* const light = dynamic_cast<const Light*>( attr );
         if ( light ) {
-            ConvertLight( model, *light );
+            ConvertLight( *light, orig_name );
         }
     }
 }
 
-void Converter::ConvertCameras( const Model& model )
-{
+void Converter::ConvertCameras( const Model& model, const std::string &orig_name ) {
     const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
     for( const NodeAttribute* attr : node_attrs ) {
         const Camera* const cam = dynamic_cast<const Camera*>( attr );
         if ( cam ) {
-            ConvertCamera( model, *cam );
+            ConvertCamera( *cam, orig_name );
         }
     }
 }
 
-void Converter::ConvertLight( const Model& model, const Light& light )
-{
+void Converter::ConvertLight( const Light& light, const std::string &orig_name ) {
     lights.push_back( new aiLight() );
     aiLight* const out_light = lights.back();
 
-    out_light->mName.Set( FixNodeName( model.Name() ) );
+    out_light->mName.Set( orig_name );
 
     const float intensity = light.Intensity() / 100.0f;
     const aiVector3D& col = light.Color();
@@ -344,12 +383,12 @@ void Converter::ConvertLight( const Model& model, const Light& light )
     }
 }
 
-void Converter::ConvertCamera( const Model& model, const Camera& cam )
+void Converter::ConvertCamera( const Camera& cam, const std::string &orig_name )
 {
     cameras.push_back( new aiCamera() );
     aiCamera* const out_camera = cameras.back();
 
-    out_camera->mName.Set( FixNodeName( model.Name() ) );
+    out_camera->mName.Set( orig_name );
 
     out_camera->mAspect = cam.AspectWidth() / cam.AspectHeight();
 
@@ -363,6 +402,31 @@ void Converter::ConvertCamera( const Model& model, const Camera& cam )
     out_camera->mClipPlaneFar = cam.FarPlane();
 }
 
+static bool HasName( NodeNameCache &cache, const std::string &name ) {
+    NodeNameCache::const_iterator it( std::find( cache.begin(), cache.end(), name ) );
+    return it != cache.end();
+
+}
+void Converter::GetUniqueName( const std::string &name, std::string uniqueName ) {
+    if ( !HasName( mNodeNames, name ) ) {
+        uniqueName = name;
+        return;
+    }
+
+    int i( 0 );
+    std::string newName;
+    while ( HasName( mNodeNames, newName ) ) {
+        ++i;
+        newName.clear();
+        newName += name;
+        std::stringstream ext;
+        ext << std::setfill( '0' ) << std::setw( 3 ) << i;
+        newName += ext.str();
+    }
+    uniqueName = newName;
+    mNodeNames.push_back( uniqueName );
+}
+
 
 const char* Converter::NameTransformationComp( TransformationComp comp )
 {
@@ -396,6 +460,12 @@ const char* Converter::NameTransformationComp( TransformationComp comp )
         return "GeometricRotation";
     case TransformationComp_GeometricTranslation:
         return "GeometricTranslation";
+    case TransformationComp_GeometricScalingInverse:
+        return "GeometricScalingInverse";
+    case TransformationComp_GeometricRotationInverse:
+        return "GeometricRotationInverse";
+    case TransformationComp_GeometricTranslationInverse:
+        return "GeometricTranslationInverse";
     case TransformationComp_MAXIMUM: // this is to silence compiler warnings
     default:
         break;
@@ -437,6 +507,12 @@ const char* Converter::NameTransformationCompProperty( TransformationComp comp )
         return "GeometricRotation";
     case TransformationComp_GeometricTranslation:
         return "GeometricTranslation";
+    case TransformationComp_GeometricScalingInverse:
+        return "GeometricScalingInverse";
+    case TransformationComp_GeometricRotationInverse:
+        return "GeometricRotationInverse";
+    case TransformationComp_GeometricTranslationInverse:
+        return "GeometricTranslationInverse";
     case TransformationComp_MAXIMUM: // this is to silence compiler warnings
         break;
     }
@@ -548,17 +624,25 @@ bool Converter::NeedsComplexTransformationChain( const Model& model )
     bool ok;
 
     const float zero_epsilon = 1e-6f;
+    const aiVector3D all_ones(1.0f, 1.0f, 1.0f);
     for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i ) {
         const TransformationComp comp = static_cast< TransformationComp >( i );
 
-        if ( comp == TransformationComp_Rotation || comp == TransformationComp_Scaling || comp == TransformationComp_Translation ||
-                comp == TransformationComp_GeometricScaling || comp == TransformationComp_GeometricRotation || comp == TransformationComp_GeometricTranslation ) {
+        if ( comp == TransformationComp_Rotation || comp == TransformationComp_Scaling || comp == TransformationComp_Translation ) {
             continue;
         }
 
+        bool scale_compare = ( comp == TransformationComp_GeometricScaling || comp == TransformationComp_Scaling );
+
         const aiVector3D& v = PropertyGet<aiVector3D>( props, NameTransformationCompProperty( comp ), ok );
-        if ( ok && v.SquareLength() > zero_epsilon ) {
-            return true;
+        if ( ok && scale_compare ) {
+            if ( (v - all_ones).SquareLength() > zero_epsilon ) {
+                return true;
+            }
+        } else if ( ok ) {
+            if ( v.SquareLength() > zero_epsilon ) {
+                return true;
+            }
         }
     }
 
@@ -570,7 +654,7 @@ std::string Converter::NameTransformationChainNode( const std::string& name, Tra
     return name + std::string( MAGIC_NODE_TAG ) + "_" + NameTransformationComp( comp );
 }
 
-void Converter::GenerateTransformationNodeChain( const Model& model, std::vector<aiNode*>& output_nodes )
+void Converter::GenerateTransformationNodeChain( const Model& model, std::vector<aiNode*>& output_nodes, std::vector<aiNode*>& post_output_nodes )
 {
     const PropertyTable& props = model.Props();
     const Model::RotOrder rot = model.RotationOrder();
@@ -582,20 +666,21 @@ void Converter::GenerateTransformationNodeChain( const Model& model, std::vector
 
     // generate transformation matrices for all the different transformation components
     const float zero_epsilon = 1e-6f;
+    const aiVector3D all_ones(1.0f, 1.0f, 1.0f);
     bool is_complex = false;
 
     const aiVector3D& PreRotation = PropertyGet<aiVector3D>( props, "PreRotation", ok );
     if ( ok && PreRotation.SquareLength() > zero_epsilon ) {
         is_complex = true;
 
-        GetRotationMatrix( rot, PreRotation, chain[ TransformationComp_PreRotation ] );
+        GetRotationMatrix( Model::RotOrder::RotOrder_EulerXYZ, PreRotation, chain[ TransformationComp_PreRotation ] );
     }
 
     const aiVector3D& PostRotation = PropertyGet<aiVector3D>( props, "PostRotation", ok );
     if ( ok && PostRotation.SquareLength() > zero_epsilon ) {
         is_complex = true;
 
-        GetRotationMatrix( rot, PostRotation, chain[ TransformationComp_PostRotation ] );
+        GetRotationMatrix( Model::RotOrder::RotOrder_EulerXYZ, PostRotation, chain[ TransformationComp_PostRotation ] );
     }
 
     const aiVector3D& RotationPivot = PropertyGet<aiVector3D>( props, "RotationPivot", ok );
@@ -634,7 +719,7 @@ void Converter::GenerateTransformationNodeChain( const Model& model, std::vector
     }
 
     const aiVector3D& Scaling = PropertyGet<aiVector3D>( props, "Lcl Scaling", ok );
-    if ( ok && std::fabs( Scaling.SquareLength() - 1.0f ) > zero_epsilon ) {
+    if ( ok && (Scaling - all_ones).SquareLength() > zero_epsilon ) {
         aiMatrix4x4::Scaling( Scaling, chain[ TransformationComp_Scaling ] );
     }
 
@@ -644,18 +729,38 @@ void Converter::GenerateTransformationNodeChain( const Model& model, std::vector
     }
 
     const aiVector3D& GeometricScaling = PropertyGet<aiVector3D>( props, "GeometricScaling", ok );
-    if ( ok && std::fabs( GeometricScaling.SquareLength() - 1.0f ) > zero_epsilon ) {
+    if ( ok && (GeometricScaling - all_ones).SquareLength() > zero_epsilon ) {
+        is_complex = true;
         aiMatrix4x4::Scaling( GeometricScaling, chain[ TransformationComp_GeometricScaling ] );
+        aiVector3D GeometricScalingInverse = GeometricScaling;
+        bool canscale = true;
+        for (unsigned int i = 0; i < 3; ++i) {
+            if ( std::fabs( GeometricScalingInverse[i] ) > zero_epsilon ) {
+                GeometricScalingInverse[i] = 1.0f / GeometricScaling[i];
+            } else {
+                FBXImporter::LogError( "cannot invert geometric scaling matrix with a 0.0 scale component" );
+                canscale = false;
+                break;
+            }
+        }
+        if (canscale) {
+            aiMatrix4x4::Scaling( GeometricScalingInverse, chain[ TransformationComp_GeometricScalingInverse ] );
+        }
     }
 
     const aiVector3D& GeometricRotation = PropertyGet<aiVector3D>( props, "GeometricRotation", ok );
     if ( ok && GeometricRotation.SquareLength() > zero_epsilon ) {
+        is_complex = true;
         GetRotationMatrix( rot, GeometricRotation, chain[ TransformationComp_GeometricRotation ] );
+        GetRotationMatrix( rot, GeometricRotation, chain[ TransformationComp_GeometricRotationInverse ] );
+        chain[ TransformationComp_GeometricRotationInverse ].Inverse();
     }
 
     const aiVector3D& GeometricTranslation = PropertyGet<aiVector3D>( props, "GeometricTranslation", ok );
     if ( ok && GeometricTranslation.SquareLength() > zero_epsilon ) {
+        is_complex = true;
         aiMatrix4x4::Translation( GeometricTranslation, chain[ TransformationComp_GeometricTranslation ] );
+        aiMatrix4x4::Translation( -GeometricTranslation, chain[ TransformationComp_GeometricTranslationInverse ] );
     }
 
     // is_complex needs to be consistent with NeedsComplexTransformationChain()
@@ -663,7 +768,7 @@ void Converter::GenerateTransformationNodeChain( const Model& model, std::vector
     // not be guaranteed.
     ai_assert( NeedsComplexTransformationChain( model ) == is_complex );
 
-    const std::string& name = FixNodeName( model.Name() );
+    std::string name = FixNodeName( model.Name() );
 
     // now, if we have more than just Translation, Scaling and Rotation,
     // we need to generate a full node chain to accommodate for assimp's
@@ -690,10 +795,18 @@ void Converter::GenerateTransformationNodeChain( const Model& model, std::vector
             }
 
             aiNode* nd = new aiNode();
-            output_nodes.push_back( nd );
-
             nd->mName.Set( NameTransformationChainNode( name, comp ) );
             nd->mTransformation = chain[ i ];
+
+            // geometric inverses go in a post-node chain
+            if ( comp == TransformationComp_GeometricScalingInverse ||
+                 comp == TransformationComp_GeometricRotationInverse ||
+                 comp == TransformationComp_GeometricTranslationInverse
+            ) {
+                post_output_nodes.push_back( nd );
+            } else {
+                output_nodes.push_back( nd );
+            }
         }
 
         ai_assert( output_nodes.size() );
@@ -703,8 +816,10 @@ void Converter::GenerateTransformationNodeChain( const Model& model, std::vector
     // else, we can just multiply the matrices together
     aiNode* nd = new aiNode();
     output_nodes.push_back( nd );
+    std::string uniqueName;
+    GetUniqueName( name, uniqueName );
 
-    nd->mName.Set( name );
+    nd->mName.Set( uniqueName );
 
     for (const auto &transform : chain) {
         nd->mTransformation = nd->mTransformation * transform;
@@ -1559,9 +1674,8 @@ void Converter::TrySetTextureProperties( aiMaterial* out_mat, const TextureMap&
 }
 
 void Converter::TrySetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures,
-    const std::string& propName,
-    aiTextureType target, const MeshGeometry* const mesh )
-{
+        const std::string& propName,
+        aiTextureType target, const MeshGeometry* const mesh ) {
     LayeredTextureMap::const_iterator it = layeredTextures.find( propName );
     if ( it == layeredTextures.end() ) {
         return;
@@ -1805,11 +1919,11 @@ void Converter::SetShadingPropertiesCommon( aiMaterial* out_mat, const PropertyT
 
     // TransparentColor / TransparencyFactor... gee thanks FBX :rolleyes:
     const aiColor3D& Transparent = GetColorPropertyFactored( props, "TransparentColor", "TransparencyFactor", ok );
-    float CalculatedOpacity = 1.0;
+    float CalculatedOpacity = 1.0f;
     if ( ok ) {
         out_mat->AddProperty( &Transparent, 1, AI_MATKEY_COLOR_TRANSPARENT );
         // as calculated by FBX SDK 2017:
-        CalculatedOpacity = 1.0 - ((Transparent.r + Transparent.g + Transparent.b) / 3.0);
+        CalculatedOpacity = 1.0f - ((Transparent.r + Transparent.g + Transparent.b) / 3.0f);
     }
 
     // use of TransparencyFactor is inconsistent.
@@ -1923,81 +2037,17 @@ void Converter::ConvertAnimations()
     }
 }
 
-void Converter::RenameNode( const std::string& fixed_name, const std::string& new_name ) {
-    if ( node_names.find( fixed_name ) == node_names.end() ) {
-        FBXImporter::LogError( "Cannot rename node " + fixed_name + ", not existing.");
-        return;
-    }
-
-    if ( node_names.find( new_name ) != node_names.end() ) {
-        FBXImporter::LogError( "Cannot rename node " + fixed_name + " to " + new_name +", name already existing." );
-        return;
-    }
-
-    ai_assert( node_names.find( fixed_name ) != node_names.end() );
-    ai_assert( node_names.find( new_name ) == node_names.end() );
-
-    renamed_nodes[ fixed_name ] = new_name;
-
-    const aiString fn( fixed_name );
-
-    for( aiCamera* cam : cameras ) {
-        if ( cam->mName == fn ) {
-            cam->mName.Set( new_name );
-            break;
-        }
-    }
-
-    for( aiLight* light : lights ) {
-        if ( light->mName == fn ) {
-            light->mName.Set( new_name );
-            break;
-        }
-    }
-
-    for( aiAnimation* anim : animations ) {
-        for ( unsigned int i = 0; i < anim->mNumChannels; ++i ) {
-            aiNodeAnim* const na = anim->mChannels[ i ];
-            if ( na->mNodeName == fn ) {
-                na->mNodeName.Set( new_name );
-                break;
-            }
-        }
-    }
-}
-
-
-std::string Converter::FixNodeName( const std::string& name )
-{
+std::string Converter::FixNodeName( const std::string& name ) {
     // strip Model:: prefix, avoiding ambiguities (i.e. don't strip if
     // this causes ambiguities, well possible between empty identifiers,
     // such as "Model::" and ""). Make sure the behaviour is consistent
     // across multiple calls to FixNodeName().
     if ( name.substr( 0, 7 ) == "Model::" ) {
         std::string temp = name.substr( 7 );
-
-        const NodeNameMap::const_iterator it = node_names.find( temp );
-        if ( it != node_names.end() ) {
-            if ( !( *it ).second ) {
-                return FixNodeName( name + "_" );
-            }
-        }
-        node_names[ temp ] = true;
-
-        const NameNameMap::const_iterator rit = renamed_nodes.find( temp );
-        return rit == renamed_nodes.end() ? temp : ( *rit ).second;
-    }
-
-    const NodeNameMap::const_iterator it = node_names.find( name );
-    if ( it != node_names.end() ) {
-        if ( ( *it ).second ) {
-            return FixNodeName( name + "_" );
-        }
+        return temp;
     }
-    node_names[ name ] = false;
 
-    const NameNameMap::const_iterator rit = renamed_nodes.find( name );
-    return rit == renamed_nodes.end() ? name : ( *rit ).second;
+    return name;
 }
 
 void Converter::ConvertAnimationStack( const AnimationStack& st )
@@ -2209,8 +2259,7 @@ void Converter::GenerateNodeAnimations( std::vector<aiNodeAnim*>& node_anims,
 
             has_any = true;
 
-            if ( comp != TransformationComp_Rotation && comp != TransformationComp_Scaling && comp != TransformationComp_Translation &&
-                comp != TransformationComp_GeometricScaling && comp != TransformationComp_GeometricRotation && comp != TransformationComp_GeometricTranslation )
+            if ( comp != TransformationComp_Rotation && comp != TransformationComp_Scaling && comp != TransformationComp_Translation )
             {
                 has_complex = true;
             }

+ 15 - 26
code/FBXConverter.h

@@ -68,6 +68,8 @@ namespace FBX {
 
 class Document;
 
+using NodeNameCache = std::vector<std::string>;
+
 /** 
  *  Convert a FBX #Document to #aiScene
  *  @param out Empty scene to be populated
@@ -82,7 +84,10 @@ public:
     *  The different parts that make up the final local transformation of a fbx-node
     */
     enum TransformationComp {
-        TransformationComp_Translation = 0,
+        TransformationComp_GeometricScalingInverse = 0,
+        TransformationComp_GeometricRotationInverse,
+        TransformationComp_GeometricTranslationInverse,
+        TransformationComp_Translation,
         TransformationComp_RotationOffset,
         TransformationComp_RotationPivot,
         TransformationComp_PreRotation,
@@ -114,16 +119,19 @@ private:
     void ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4());
 
     // ------------------------------------------------------------------------------------------------
-    void ConvertLights(const Model& model);
+    void ConvertLights(const Model& model, const std::string &orig_name );
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertCameras(const Model& model, const std::string &orig_name );
 
     // ------------------------------------------------------------------------------------------------
-    void ConvertCameras(const Model& model);
+    void ConvertLight( const Light& light, const std::string &orig_name );
 
     // ------------------------------------------------------------------------------------------------
-    void ConvertLight(const Model& model, const Light& light);
+    void ConvertCamera( const Camera& cam, const std::string &orig_name );
 
     // ------------------------------------------------------------------------------------------------
-    void ConvertCamera(const Model& model, const Camera& cam);
+    void GetUniqueName( const std::string &name, std::string uniqueName );
 
     // ------------------------------------------------------------------------------------------------
     // this returns unified names usable within assimp identifiers (i.e. no space characters -
@@ -153,7 +161,7 @@ private:
     /**
     *  note: memory for output_nodes will be managed by the caller
     */
-    void GenerateTransformationNodeChain(const Model& model, std::vector<aiNode*>& output_nodes);
+    void GenerateTransformationNodeChain(const Model& model, std::vector<aiNode*>& output_nodes, std::vector<aiNode*>& post_output_nodes);
 
     // ------------------------------------------------------------------------------------------------
     void SetupNodeMetadata(const Model& model, aiNode& nd);
@@ -255,18 +263,6 @@ private:
     // convert animation data to aiAnimation et al
     void ConvertAnimations();
 
-    // ------------------------------------------------------------------------------------------------
-    // rename a node already partially converted. fixed_name is a string previously returned by
-    // FixNodeName, new_name specifies the string FixNodeName should return on all further invocations
-    // which would previously have returned the old value.
-    //
-    // this also updates names in node animations, cameras and light sources and is thus slow.
-    //
-    // NOTE: the caller is responsible for ensuring that the new name is unique and does
-    // not collide with any other identifiers. The best way to ensure this is to only
-    // append to the old name, which is guaranteed to match these requirements.
-    void RenameNode(const std::string& fixed_name, const std::string& new_name);
-
     // ------------------------------------------------------------------------------------------------
     // takes a fbx node name and returns the identifier to be used in the assimp output scene.
     // the function is guaranteed to provide consistent results over multiple invocations
@@ -278,7 +274,6 @@ private:
     // XXX: better use multi_map ..
     typedef std::map<std::string, std::vector<const AnimationCurveNode*> > NodeMap;
 
-
     // ------------------------------------------------------------------------------------------------
     void ConvertAnimationStack(const AnimationStack& st);
 
@@ -429,13 +424,7 @@ private:
     typedef std::map<std::string, unsigned int> NodeAnimBitMap;
     NodeAnimBitMap node_anim_chain_bits;
 
-    // name -> has had its prefix_stripped?
-    typedef std::map<std::string, bool> NodeNameMap;
-    NodeNameMap node_names;
-
-    typedef std::map<std::string, std::string> NameNameMap;
-    NameNameMap renamed_nodes;
-
+    NodeNameCache mNodeNames;
     double anim_fps;
 
     aiScene* const out;

+ 3 - 1
code/FBXDocument.cpp

@@ -351,7 +351,9 @@ void Document::ReadGlobalSettings()
         return;
     }
 
-    std::shared_ptr<const PropertyTable> props = GetPropertyTable(*this, "", *ehead, *ehead->Compound(), true);
+    std::shared_ptr<const PropertyTable> props = GetPropertyTable( *this, "", *ehead, *ehead->Compound(), true );
+
+    //double v = PropertyGet<float>( *props.get(), std::string("UnitScaleFactor"), 1.0 );
 
     if(!props) {
         DOMError("GlobalSettings dictionary contains no property table");

+ 2 - 2
code/FBXDocument.h

@@ -1022,8 +1022,8 @@ public:
     fbx_simple_property(CoordAxisSign, int, 1)
     fbx_simple_property(OriginalUpAxis, int, 0)
     fbx_simple_property(OriginalUpAxisSign, int, 1)
-    fbx_simple_property(UnitScaleFactor, double, 1)
-    fbx_simple_property(OriginalUnitScaleFactor, double, 1)
+    fbx_simple_property(UnitScaleFactor, float, 1)
+    fbx_simple_property(OriginalUnitScaleFactor, float, 1)
     fbx_simple_property(AmbientColor, aiVector3D, aiVector3D(0,0,0))
     fbx_simple_property(DefaultCamera, std::string, "")
 

+ 568 - 0
code/FBXExportNode.cpp

@@ -0,0 +1,568 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2018, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+#ifndef ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#include "FBXExportNode.h"
+#include "FBXCommon.h"
+
+#include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/Exceptional.h> // DeadlyExportError
+#include <assimp/ai_assert.h>
+#include <assimp/StringUtils.h> // ai_snprintf
+
+#include <string>
+#include <ostream>
+#include <sstream> // ostringstream
+#include <memory> // shared_ptr
+
+// AddP70<type> helpers... there's no usable pattern here,
+// so all are defined as separate functions.
+// Even "animatable" properties are often completely different
+// from the standard (nonanimated) property definition,
+// so they are specified with an 'A' suffix.
+
+void FBX::Node::AddP70int(
+    const std::string& name, int32_t value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "int", "Integer", "", value);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70bool(
+    const std::string& name, bool value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "bool", "", "", int32_t(value));
+    AddChild(n);
+}
+
+void FBX::Node::AddP70double(
+    const std::string& name, double value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "double", "Number", "", value);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70numberA(
+    const std::string& name, double value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "Number", "", "A", value);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70color(
+    const std::string& name, double r, double g, double b
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "ColorRGB", "Color", "", r, g, b);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70colorA(
+    const std::string& name, double r, double g, double b
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "Color", "", "A", r, g, b);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70vector(
+    const std::string& name, double x, double y, double z
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "Vector3D", "Vector", "", x, y, z);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70vectorA(
+    const std::string& name, double x, double y, double z
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "Vector", "", "A", x, y, z);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70string(
+    const std::string& name, const std::string& value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "KString", "", "", value);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70enum(
+    const std::string& name, int32_t value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "enum", "", "", value);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70time(
+    const std::string& name, int64_t value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "KTime", "Time", "", value);
+    AddChild(n);
+}
+
+
+// public member functions for writing nodes to stream
+
+void FBX::Node::Dump(
+    std::shared_ptr<Assimp::IOStream> outfile,
+    bool binary, int indent
+) {
+    if (binary) {
+        Assimp::StreamWriterLE outstream(outfile);
+        DumpBinary(outstream);
+    } else {
+        std::ostringstream ss;
+        DumpAscii(ss, indent);
+        std::string s = ss.str();
+        outfile->Write(s.c_str(), s.size(), 1);
+    }
+}
+
+void FBX::Node::Dump(
+    Assimp::StreamWriterLE &outstream,
+    bool binary, int indent
+) {
+    if (binary) {
+        DumpBinary(outstream);
+    } else {
+        std::ostringstream ss;
+        DumpAscii(ss, indent);
+        outstream.PutString(ss.str());
+    }
+}
+
+
+// public member functions for low-level writing
+
+void FBX::Node::Begin(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent
+) {
+    if (binary) {
+        BeginBinary(s);
+    } else {
+        // assume we're at the correct place to start already
+        (void)indent;
+        std::ostringstream ss;
+        BeginAscii(ss, indent);
+        s.PutString(ss.str());
+    }
+}
+
+void FBX::Node::DumpProperties(
+    Assimp::StreamWriterLE& s,
+    bool binary, int indent
+) {
+    if (binary) {
+        DumpPropertiesBinary(s);
+    } else {
+        std::ostringstream ss;
+        DumpPropertiesAscii(ss, indent);
+        s.PutString(ss.str());
+    }
+}
+
+void FBX::Node::EndProperties(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent
+) {
+    EndProperties(s, binary, indent, properties.size());
+}
+
+void FBX::Node::EndProperties(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent,
+    size_t num_properties
+) {
+    if (binary) {
+        EndPropertiesBinary(s, num_properties);
+    } else {
+        // nothing to do
+        (void)indent;
+    }
+}
+
+void FBX::Node::BeginChildren(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent
+) {
+    if (binary) {
+        // nothing to do
+    } else {
+        std::ostringstream ss;
+        BeginChildrenAscii(ss, indent);
+        s.PutString(ss.str());
+    }
+}
+
+void FBX::Node::DumpChildren(
+    Assimp::StreamWriterLE& s,
+    bool binary, int indent
+) {
+    if (binary) {
+        DumpChildrenBinary(s);
+    } else {
+        std::ostringstream ss;
+        DumpChildrenAscii(ss, indent);
+        s.PutString(ss.str());
+    }
+}
+
+void FBX::Node::End(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent,
+    bool has_children
+) {
+    if (binary) {
+        EndBinary(s, has_children);
+    } else {
+        std::ostringstream ss;
+        EndAscii(ss, indent, has_children);
+        s.PutString(ss.str());
+    }
+}
+
+
+// public member functions for writing to binary fbx
+
+void FBX::Node::DumpBinary(Assimp::StreamWriterLE &s)
+{
+    // write header section (with placeholders for some things)
+    BeginBinary(s);
+
+    // write properties
+    DumpPropertiesBinary(s);
+
+    // go back and fill in property related placeholders
+    EndPropertiesBinary(s, properties.size());
+
+    // write children
+    DumpChildrenBinary(s);
+
+    // finish, filling in end offset placeholder
+    EndBinary(s, force_has_children || !children.empty());
+}
+
+
+// public member functions for writing to ascii fbx
+
+void FBX::Node::DumpAscii(std::ostream &s, int indent)
+{
+    // write name
+    BeginAscii(s, indent);
+
+    // write properties
+    DumpPropertiesAscii(s, indent);
+
+    if (force_has_children || !children.empty()) {
+        // begin children (with a '{')
+        BeginChildrenAscii(s, indent + 1);
+        // write children
+        DumpChildrenAscii(s, indent + 1);
+    }
+
+    // finish (also closing the children bracket '}')
+    EndAscii(s, indent, force_has_children || !children.empty());
+}
+
+
+// private member functions for low-level writing to fbx
+
+void FBX::Node::BeginBinary(Assimp::StreamWriterLE &s)
+{
+    // remember start pos so we can come back and write the end pos
+    this->start_pos = s.Tell();
+
+    // placeholders for end pos and property section info
+    s.PutU4(0); // end pos
+    s.PutU4(0); // number of properties
+    s.PutU4(0); // total property section length
+
+    // node name
+    s.PutU1(uint8_t(name.size())); // length of node name
+    s.PutString(name); // node name as raw bytes
+
+    // property data comes after here
+    this->property_start = s.Tell();
+}
+
+void FBX::Node::DumpPropertiesBinary(Assimp::StreamWriterLE& s)
+{
+    for (auto &p : properties) {
+        p.DumpBinary(s);
+    }
+}
+
+void FBX::Node::EndPropertiesBinary(
+    Assimp::StreamWriterLE &s,
+    size_t num_properties
+) {
+    if (num_properties == 0) { return; }
+    size_t pos = s.Tell();
+    ai_assert(pos > property_start);
+    size_t property_section_size = pos - property_start;
+    s.Seek(start_pos + 4);
+    s.PutU4(uint32_t(num_properties));
+    s.PutU4(uint32_t(property_section_size));
+    s.Seek(pos);
+}
+
+void FBX::Node::DumpChildrenBinary(Assimp::StreamWriterLE& s)
+{
+    for (FBX::Node& child : children) {
+        child.DumpBinary(s);
+    }
+}
+
+void FBX::Node::EndBinary(
+    Assimp::StreamWriterLE &s,
+    bool has_children
+) {
+    // if there were children, add a null record
+    if (has_children) { s.PutString(FBX::NULL_RECORD); }
+
+    // now go back and write initial pos
+    this->end_pos = s.Tell();
+    s.Seek(start_pos);
+    s.PutU4(uint32_t(end_pos));
+    s.Seek(end_pos);
+}
+
+
+void FBX::Node::BeginAscii(std::ostream& s, int indent)
+{
+    s << '\n';
+    for (int i = 0; i < indent; ++i) { s << '\t'; }
+    s << name << ": ";
+}
+
+void FBX::Node::DumpPropertiesAscii(std::ostream &s, int indent)
+{
+    for (size_t i = 0; i < properties.size(); ++i) {
+        if (i > 0) { s << ", "; }
+        properties[i].DumpAscii(s, indent);
+    }
+}
+
+void FBX::Node::BeginChildrenAscii(std::ostream& s, int indent)
+{
+    // only call this if there are actually children
+    s << " {";
+    (void)indent;
+}
+
+void FBX::Node::DumpChildrenAscii(std::ostream& s, int indent)
+{
+    // children will need a lot of padding and corralling
+    if (children.size() || force_has_children) {
+        for (size_t i = 0; i < children.size(); ++i) {
+            // no compression in ascii files, so skip this node if it exists
+            if (children[i].name == "EncryptionType") { continue; }
+            // the child can dump itself
+            children[i].DumpAscii(s, indent);
+        }
+    }
+}
+
+void FBX::Node::EndAscii(std::ostream& s, int indent, bool has_children)
+{
+    if (!has_children) { return; } // nothing to do
+    s << '\n';
+    for (int i = 0; i < indent; ++i) { s << '\t'; }
+    s << "}";
+}
+
+// private helpers for static member functions
+
+// ascii property node from vector of doubles
+void FBX::Node::WritePropertyNodeAscii(
+    const std::string& name,
+    const std::vector<double>& v,
+    Assimp::StreamWriterLE& s,
+    int indent
+){
+    char buffer[32];
+    FBX::Node node(name);
+    node.Begin(s, false, indent);
+    std::string vsize = std::to_string(v.size());
+    // *<size> {
+    s.PutChar('*'); s.PutString(vsize); s.PutString(" {\n");
+    // indent + 1
+    for (int i = 0; i < indent + 1; ++i) { s.PutChar('\t'); }
+    // a: value,value,value,...
+    s.PutString("a: ");
+    int count = 0;
+    for (size_t i = 0; i < v.size(); ++i) {
+        if (i > 0) { s.PutChar(','); }
+        int len = ai_snprintf(buffer, sizeof(buffer), "%f", v[i]);
+        count += len;
+        if (count > 2048) { s.PutChar('\n'); count = 0; }
+        if (len < 0 || len > 31) {
+            // this should never happen
+            throw DeadlyExportError("failed to convert double to string");
+        }
+        for (int j = 0; j < len; ++j) { s.PutChar(buffer[j]); }
+    }
+    // }
+    s.PutChar('\n');
+    for (int i = 0; i < indent; ++i) { s.PutChar('\t'); }
+    s.PutChar('}'); s.PutChar(' ');
+    node.End(s, false, indent, false);
+}
+
+// ascii property node from vector of int32_t
+void FBX::Node::WritePropertyNodeAscii(
+    const std::string& name,
+    const std::vector<int32_t>& v,
+    Assimp::StreamWriterLE& s,
+    int indent
+){
+    char buffer[32];
+    FBX::Node node(name);
+    node.Begin(s, false, indent);
+    std::string vsize = std::to_string(v.size());
+    // *<size> {
+    s.PutChar('*'); s.PutString(vsize); s.PutString(" {\n");
+    // indent + 1
+    for (int i = 0; i < indent + 1; ++i) { s.PutChar('\t'); }
+    // a: value,value,value,...
+    s.PutString("a: ");
+    int count = 0;
+    for (size_t i = 0; i < v.size(); ++i) {
+        if (i > 0) { s.PutChar(','); }
+        int len = ai_snprintf(buffer, sizeof(buffer), "%d", v[i]);
+        count += len;
+        if (count > 2048) { s.PutChar('\n'); count = 0; }
+        if (len < 0 || len > 31) {
+            // this should never happen
+            throw DeadlyExportError("failed to convert double to string");
+        }
+        for (int j = 0; j < len; ++j) { s.PutChar(buffer[j]); }
+    }
+    // }
+    s.PutChar('\n');
+    for (int i = 0; i < indent; ++i) { s.PutChar('\t'); }
+    s.PutChar('}'); s.PutChar(' ');
+    node.End(s, false, indent, false);
+}
+
+// binary property node from vector of doubles
+// TODO: optional zip compression!
+void FBX::Node::WritePropertyNodeBinary(
+    const std::string& name,
+    const std::vector<double>& v,
+    Assimp::StreamWriterLE& s
+){
+    FBX::Node node(name);
+    node.BeginBinary(s);
+    s.PutU1('d');
+    s.PutU4(uint32_t(v.size())); // number of elements
+    s.PutU4(0); // no encoding (1 would be zip-compressed)
+    s.PutU4(uint32_t(v.size()) * 8); // data size
+    for (auto it = v.begin(); it != v.end(); ++it) { s.PutF8(*it); }
+    node.EndPropertiesBinary(s, 1);
+    node.EndBinary(s, false);
+}
+
+// binary property node from vector of int32_t
+// TODO: optional zip compression!
+void FBX::Node::WritePropertyNodeBinary(
+    const std::string& name,
+    const std::vector<int32_t>& v,
+    Assimp::StreamWriterLE& s
+){
+    FBX::Node node(name);
+    node.BeginBinary(s);
+    s.PutU1('i');
+    s.PutU4(uint32_t(v.size())); // number of elements
+    s.PutU4(0); // no encoding (1 would be zip-compressed)
+    s.PutU4(uint32_t(v.size()) * 4); // data size
+    for (auto it = v.begin(); it != v.end(); ++it) { s.PutI4(*it); }
+    node.EndPropertiesBinary(s, 1);
+    node.EndBinary(s, false);
+}
+
+// public static member functions
+
+// convenience function to create and write a property node,
+// holding a single property which is an array of values.
+// does not copy the data, so is efficient for large arrays.
+void FBX::Node::WritePropertyNode(
+    const std::string& name,
+    const std::vector<double>& v,
+    Assimp::StreamWriterLE& s,
+    bool binary, int indent
+){
+    if (binary) {
+        FBX::Node::WritePropertyNodeBinary(name, v, s);
+    } else {
+        FBX::Node::WritePropertyNodeAscii(name, v, s, indent);
+    }
+}
+
+// convenience function to create and write a property node,
+// holding a single property which is an array of values.
+// does not copy the data, so is efficient for large arrays.
+void FBX::Node::WritePropertyNode(
+    const std::string& name,
+    const std::vector<int32_t>& v,
+    Assimp::StreamWriterLE& s,
+    bool binary, int indent
+){
+    if (binary) {
+        FBX::Node::WritePropertyNodeBinary(name, v, s);
+    } else {
+        FBX::Node::WritePropertyNodeAscii(name, v, s, indent);
+    }
+}
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 258 - 0
code/FBXExportNode.h

@@ -0,0 +1,258 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2018, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file FBXExportNode.h
+* Declares the FBX::Node helper class for fbx export.
+*/
+#ifndef AI_FBXEXPORTNODE_H_INC
+#define AI_FBXEXPORTNODE_H_INC
+
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#include "FBXExportProperty.h"
+
+#include <assimp/StreamWriter.h> // StreamWriterLE
+
+#include <string>
+#include <vector>
+
+namespace FBX {
+    class Node;
+}
+
+class FBX::Node
+{
+public: // public data members
+    // TODO: accessors
+    std::string name; // node name
+    std::vector<FBX::Property> properties; // node properties
+    std::vector<FBX::Node> children; // child nodes
+
+    // some nodes always pretend they have children...
+    bool force_has_children = false;
+
+public: // constructors
+    Node() = default;
+    Node(const std::string& n) : name(n) {}
+
+    // convenience template to construct with properties directly
+    template <typename... More>
+    Node(const std::string& n, const More... more)
+        : name(n)
+        { AddProperties(more...); }
+
+public: // functions to add properties or children
+    // add a single property to the node
+    template <typename T>
+    void AddProperty(T value) {
+        properties.emplace_back(value);
+    }
+
+    // convenience function to add multiple properties at once
+    template <typename T, typename... More>
+    void AddProperties(T value, More... more) {
+        properties.emplace_back(value);
+        AddProperties(more...);
+    }
+    void AddProperties() {}
+
+    // add a child node directly
+    void AddChild(const Node& node) { children.push_back(node); }
+
+    // convenience function to add a child node with a single property
+    template <typename... More>
+    void AddChild(
+        const std::string& name,
+        More... more
+    ) {
+        FBX::Node c(name);
+        c.AddProperties(more...);
+        children.push_back(c);
+    }
+
+public: // support specifically for dealing with Properties70 nodes
+
+    // it really is simpler to make these all separate functions.
+    // the versions with 'A' suffixes are for animatable properties.
+    // those often follow a completely different format internally in FBX.
+    void AddP70int(const std::string& name, int32_t value);
+    void AddP70bool(const std::string& name, bool value);
+    void AddP70double(const std::string& name, double value);
+    void AddP70numberA(const std::string& name, double value);
+    void AddP70color(const std::string& name, double r, double g, double b);
+    void AddP70colorA(const std::string& name, double r, double g, double b);
+    void AddP70vector(const std::string& name, double x, double y, double z);
+    void AddP70vectorA(const std::string& name, double x, double y, double z);
+    void AddP70string(const std::string& name, const std::string& value);
+    void AddP70enum(const std::string& name, int32_t value);
+    void AddP70time(const std::string& name, int64_t value);
+
+    // template for custom P70 nodes.
+    // anything that doesn't fit in the above can be created manually.
+    template <typename... More>
+    void AddP70(
+        const std::string& name,
+        const std::string& type,
+        const std::string& type2,
+        const std::string& flags,
+        More... more
+    ) {
+        Node n("P");
+        n.AddProperties(name, type, type2, flags, more...);
+        AddChild(n);
+    }
+
+public: // member functions for writing data to a file or stream
+
+    // write the full node to the given file or stream
+    void Dump(
+        std::shared_ptr<Assimp::IOStream> outfile,
+        bool binary, int indent
+    );
+    void Dump(Assimp::StreamWriterLE &s, bool binary, int indent);
+
+    // these other functions are for writing data piece by piece.
+    // they must be used carefully.
+    // for usage examples see FBXExporter.cpp.
+    void Begin(Assimp::StreamWriterLE &s, bool binary, int indent);
+    void DumpProperties(Assimp::StreamWriterLE& s, bool binary, int indent);
+    void EndProperties(Assimp::StreamWriterLE &s, bool binary, int indent);
+    void EndProperties(
+        Assimp::StreamWriterLE &s, bool binary, int indent,
+        size_t num_properties
+    );
+    void BeginChildren(Assimp::StreamWriterLE &s, bool binary, int indent);
+    void DumpChildren(Assimp::StreamWriterLE& s, bool binary, int indent);
+    void End(
+        Assimp::StreamWriterLE &s, bool binary, int indent,
+        bool has_children
+    );
+
+private: // internal functions used for writing
+
+    void DumpBinary(Assimp::StreamWriterLE &s);
+    void DumpAscii(Assimp::StreamWriterLE &s, int indent);
+    void DumpAscii(std::ostream &s, int indent);
+
+    void BeginBinary(Assimp::StreamWriterLE &s);
+    void DumpPropertiesBinary(Assimp::StreamWriterLE& s);
+    void EndPropertiesBinary(Assimp::StreamWriterLE &s);
+    void EndPropertiesBinary(Assimp::StreamWriterLE &s, size_t num_properties);
+    void DumpChildrenBinary(Assimp::StreamWriterLE& s);
+    void EndBinary(Assimp::StreamWriterLE &s, bool has_children);
+
+    void BeginAscii(std::ostream &s, int indent);
+    void DumpPropertiesAscii(std::ostream &s, int indent);
+    void BeginChildrenAscii(std::ostream &s, int indent);
+    void DumpChildrenAscii(std::ostream &s, int indent);
+    void EndAscii(std::ostream &s, int indent, bool has_children);
+
+private: // data used for binary dumps
+    size_t start_pos; // starting position in stream
+    size_t end_pos; // ending position in stream
+    size_t property_start; // starting position of property section
+
+public: // static member functions
+
+    // convenience function to create a node with a single property,
+    // and write it to the stream.
+    template <typename T>
+    static void WritePropertyNode(
+        const std::string& name,
+        const T value,
+        Assimp::StreamWriterLE& s,
+        bool binary, int indent
+    ) {
+        FBX::Property p(value);
+        FBX::Node node(name, p);
+        node.Dump(s, binary, indent);
+    }
+
+    // convenience function to create and write a property node,
+    // holding a single property which is an array of values.
+    // does not copy the data, so is efficient for large arrays.
+    static void WritePropertyNode(
+        const std::string& name,
+        const std::vector<double>& v,
+        Assimp::StreamWriterLE& s,
+        bool binary, int indent
+    );
+
+    // convenience function to create and write a property node,
+    // holding a single property which is an array of values.
+    // does not copy the data, so is efficient for large arrays.
+    static void WritePropertyNode(
+        const std::string& name,
+        const std::vector<int32_t>& v,
+        Assimp::StreamWriterLE& s,
+        bool binary, int indent
+    );
+
+private: // static helper functions
+    static void WritePropertyNodeAscii(
+        const std::string& name,
+        const std::vector<double>& v,
+        Assimp::StreamWriterLE& s,
+        int indent
+    );
+    static void WritePropertyNodeAscii(
+        const std::string& name,
+        const std::vector<int32_t>& v,
+        Assimp::StreamWriterLE& s,
+        int indent
+    );
+    static void WritePropertyNodeBinary(
+        const std::string& name,
+        const std::vector<double>& v,
+        Assimp::StreamWriterLE& s
+    );
+    static void WritePropertyNodeBinary(
+        const std::string& name,
+        const std::vector<int32_t>& v,
+        Assimp::StreamWriterLE& s
+    );
+
+};
+
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#endif // AI_FBXEXPORTNODE_H_INC

+ 364 - 0
code/FBXExportProperty.cpp

@@ -0,0 +1,364 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2018, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+#ifndef ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#include "FBXExportProperty.h"
+
+#include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/Exceptional.h> // DeadlyExportError
+
+#include <string>
+#include <vector>
+#include <ostream>
+#include <locale>
+#include <sstream> // ostringstream
+
+
+// constructors for single element properties
+
+FBX::Property::Property(bool v)
+    : type('C'), data(1)
+{
+    data = {uint8_t(v)};
+}
+
+FBX::Property::Property(int16_t v) : type('Y'), data(2)
+{
+    uint8_t* d = data.data();
+    (reinterpret_cast<int16_t*>(d))[0] = v;
+}
+
+FBX::Property::Property(int32_t v) : type('I'), data(4)
+{
+    uint8_t* d = data.data();
+    (reinterpret_cast<int32_t*>(d))[0] = v;
+}
+
+FBX::Property::Property(float v) : type('F'), data(4)
+{
+    uint8_t* d = data.data();
+    (reinterpret_cast<float*>(d))[0] = v;
+}
+
+FBX::Property::Property(double v) : type('D'), data(8)
+{
+    uint8_t* d = data.data();
+    (reinterpret_cast<double*>(d))[0] = v;
+}
+
+FBX::Property::Property(int64_t v) : type('L'), data(8)
+{
+    uint8_t* d = data.data();
+    (reinterpret_cast<int64_t*>(d))[0] = v;
+}
+
+
+// constructors for array-type properties
+
+FBX::Property::Property(const char* c, bool raw)
+    : Property(std::string(c), raw)
+{}
+
+// strings can either be saved as "raw" (R) data, or "string" (S) data
+FBX::Property::Property(const std::string& s, bool raw)
+    : type(raw ? 'R' : 'S'), data(s.size())
+{
+    for (size_t i = 0; i < s.size(); ++i) {
+        data[i] = uint8_t(s[i]);
+    }
+}
+
+FBX::Property::Property(const std::vector<uint8_t>& r)
+    : type('R'), data(r)
+{}
+
+FBX::Property::Property(const std::vector<int32_t>& va)
+    : type('i'), data(4*va.size())
+{
+    int32_t* d = reinterpret_cast<int32_t*>(data.data());
+    for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; }
+}
+
+FBX::Property::Property(const std::vector<int64_t>& va)
+    : type('l'), data(8*va.size())
+{
+    int64_t* d = reinterpret_cast<int64_t*>(data.data());
+    for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; }
+}
+
+FBX::Property::Property(const std::vector<float>& va)
+    : type('f'), data(4*va.size())
+{
+    float* d = reinterpret_cast<float*>(data.data());
+    for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; }
+}
+
+FBX::Property::Property(const std::vector<double>& va)
+    : type('d'), data(8*va.size())
+{
+    double* d = reinterpret_cast<double*>(data.data());
+    for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; }
+}
+
+FBX::Property::Property(const aiMatrix4x4& vm)
+    : type('d'), data(8*16)
+{
+    double* d = reinterpret_cast<double*>(data.data());
+    for (unsigned int c = 0; c < 4; ++c) {
+        for (unsigned int r = 0; r < 4; ++r) {
+            d[4*c+r] = vm[r][c];
+        }
+    }
+}
+
+// public member functions
+
+size_t FBX::Property::size()
+{
+    switch (type) {
+    case 'C': case 'Y': case 'I': case 'F': case 'D': case 'L':
+        return data.size() + 1;
+    case 'S': case 'R':
+        return data.size() + 5;
+    case 'i': case 'd':
+        return data.size() + 13;
+    default:
+        throw DeadlyExportError("Requested size on property of unknown type");
+    }
+}
+
+void FBX::Property::DumpBinary(Assimp::StreamWriterLE &s)
+{
+    s.PutU1(type);
+    uint8_t* d = data.data();
+    size_t N;
+    switch (type) {
+    case 'C': s.PutU1(*(reinterpret_cast<uint8_t*>(d))); return;
+    case 'Y': s.PutI2(*(reinterpret_cast<int16_t*>(d))); return;
+    case 'I': s.PutI4(*(reinterpret_cast<int32_t*>(d))); return;
+    case 'F': s.PutF4(*(reinterpret_cast<float*>(d))); return;
+    case 'D': s.PutF8(*(reinterpret_cast<double*>(d))); return;
+    case 'L': s.PutI8(*(reinterpret_cast<int64_t*>(d))); return;
+    case 'S':
+    case 'R':
+        s.PutU4(uint32_t(data.size()));
+        for (size_t i = 0; i < data.size(); ++i) { s.PutU1(data[i]); }
+        return;
+    case 'i':
+        N = data.size() / 4;
+        s.PutU4(uint32_t(N)); // number of elements
+        s.PutU4(0); // no encoding (1 would be zip-compressed)
+        // TODO: compress if large?
+        s.PutU4(uint32_t(data.size())); // data size
+        for (size_t i = 0; i < N; ++i) {
+            s.PutI4((reinterpret_cast<int32_t*>(d))[i]);
+        }
+        return;
+    case 'l':
+        N = data.size() / 8;
+        s.PutU4(uint32_t(N)); // number of elements
+        s.PutU4(0); // no encoding (1 would be zip-compressed)
+        // TODO: compress if large?
+        s.PutU4(uint32_t(data.size())); // data size
+        for (size_t i = 0; i < N; ++i) {
+            s.PutI8((reinterpret_cast<int64_t*>(d))[i]);
+        }
+        return;
+    case 'f':
+        N = data.size() / 4;
+        s.PutU4(uint32_t(N)); // number of elements
+        s.PutU4(0); // no encoding (1 would be zip-compressed)
+        // TODO: compress if large?
+        s.PutU4(uint32_t(data.size())); // data size
+        for (size_t i = 0; i < N; ++i) {
+            s.PutF4((reinterpret_cast<float*>(d))[i]);
+        }
+        return;
+    case 'd':
+        N = data.size() / 8;
+        s.PutU4(uint32_t(N)); // number of elements
+        s.PutU4(0); // no encoding (1 would be zip-compressed)
+        // TODO: compress if large?
+        s.PutU4(uint32_t(data.size())); // data size
+        for (size_t i = 0; i < N; ++i) {
+            s.PutF8((reinterpret_cast<double*>(d))[i]);
+        }
+        return;
+    default:
+        std::ostringstream err;
+        err << "Tried to dump property with invalid type '";
+        err << type << "'!";
+        throw DeadlyExportError(err.str());
+    }
+}
+
+void FBX::Property::DumpAscii(Assimp::StreamWriterLE &outstream, int indent)
+{
+    std::ostringstream ss;
+    ss.imbue(std::locale::classic());
+    ss.precision(15); // this seems to match official FBX SDK exports
+    DumpAscii(ss, indent);
+    outstream.PutString(ss.str());
+}
+
+void FBX::Property::DumpAscii(std::ostream& s, int indent)
+{
+    // no writing type... or anything. just shove it into the stream.
+    uint8_t* d = data.data();
+    size_t N;
+    size_t swap = data.size();
+    size_t count = 0;
+    switch (type) {
+    case 'C':
+        if (*(reinterpret_cast<uint8_t*>(d))) { s << 'T'; }
+        else { s << 'F'; }
+        return;
+    case 'Y': s << *(reinterpret_cast<int16_t*>(d)); return;
+    case 'I': s << *(reinterpret_cast<int32_t*>(d)); return;
+    case 'F': s << *(reinterpret_cast<float*>(d)); return;
+    case 'D': s << *(reinterpret_cast<double*>(d)); return;
+    case 'L': s << *(reinterpret_cast<int64_t*>(d)); return;
+    case 'S':
+        // first search to see if it has "\x00\x01" in it -
+        // which separates fields which are reversed in the ascii version.
+        // yeah.
+        // FBX, yeah.
+        for (size_t i = 0; i < data.size(); ++i) {
+            if (data[i] == '\0') {
+                swap = i;
+                break;
+            }
+        }
+    case 'R':
+        s << '"';
+        // we might as well check this now,
+        // probably it will never happen
+        for (size_t i = 0; i < data.size(); ++i) {
+            char c = data[i];
+            if (c == '"') {
+                throw runtime_error("can't handle quotes in property string");
+            }
+        }
+        // first write the SWAPPED member (if any)
+        for (size_t i = swap + 2; i < data.size(); ++i) {
+            char c = data[i];
+            s << c;
+        }
+        // then a separator
+        if (swap != data.size()) {
+            s << "::";
+        }
+        // then the initial member
+        for (size_t i = 0; i < swap; ++i) {
+            char c = data[i];
+            s << c;
+        }
+        s << '"';
+        return;
+    case 'i':
+        N = data.size() / 4; // number of elements
+        s << '*' << N << " {\n";
+        for (int i = 0; i < indent + 1; ++i) { s << '\t'; }
+        s << "a: ";
+        for (size_t i = 0; i < N; ++i) {
+            if (i > 0) { s << ','; }
+            if (count++ > 120) { s << '\n'; count = 0; }
+            s << (reinterpret_cast<int32_t*>(d))[i];
+        }
+        s << '\n';
+        for (int i = 0; i < indent; ++i) { s << '\t'; }
+        s << "} ";
+        return;
+    case 'l':
+        N = data.size() / 8;
+        s << '*' << N << " {\n";
+        for (int i = 0; i < indent + 1; ++i) { s << '\t'; }
+        s << "a: ";
+        for (size_t i = 0; i < N; ++i) {
+            if (i > 0) { s << ','; }
+            if (count++ > 120) { s << '\n'; count = 0; }
+            s << (reinterpret_cast<int64_t*>(d))[i];
+        }
+        s << '\n';
+        for (int i = 0; i < indent; ++i) { s << '\t'; }
+        s << "} ";
+        return;
+    case 'f':
+        N = data.size() / 4;
+        s << '*' << N << " {\n";
+        for (int i = 0; i < indent + 1; ++i) { s << '\t'; }
+        s << "a: ";
+        for (size_t i = 0; i < N; ++i) {
+            if (i > 0) { s << ','; }
+            if (count++ > 120) { s << '\n'; count = 0; }
+            s << (reinterpret_cast<float*>(d))[i];
+        }
+        s << '\n';
+        for (int i = 0; i < indent; ++i) { s << '\t'; }
+        s << "} ";
+        return;
+    case 'd':
+        N = data.size() / 8;
+        s << '*' << N << " {\n";
+        for (int i = 0; i < indent + 1; ++i) { s << '\t'; }
+        s << "a: ";
+        // set precision to something that can handle doubles
+        s.precision(15);
+        for (size_t i = 0; i < N; ++i) {
+            if (i > 0) { s << ','; }
+            if (count++ > 120) { s << '\n'; count = 0; }
+            s << (reinterpret_cast<double*>(d))[i];
+        }
+        s << '\n';
+        for (int i = 0; i < indent; ++i) { s << '\t'; }
+        s << "} ";
+        return;
+    default:
+        std::ostringstream err;
+        err << "Tried to dump property with invalid type '";
+        err << type << "'!";
+        throw runtime_error(err.str());
+    }
+}
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 129 - 0
code/FBXExportProperty.h

@@ -0,0 +1,129 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2018, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file FBXExportProperty.h
+* Declares the FBX::Property helper class for fbx export.
+*/
+#ifndef AI_FBXEXPORTPROPERTY_H_INC
+#define AI_FBXEXPORTPROPERTY_H_INC
+
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+
+#include <assimp/types.h> // aiMatrix4x4
+#include <assimp/StreamWriter.h> // StreamWriterLE
+
+#include <string>
+#include <vector>
+#include <ostream>
+#include <type_traits> // is_void
+
+namespace FBX {
+    class Property;
+}
+
+/** FBX::Property
+ *
+ *  Holds a value of any of FBX's recognized types,
+ *  each represented by a particular one-character code.
+ *  C : 1-byte uint8, usually 0x00 or 0x01 to represent boolean false and true
+ *  Y : 2-byte int16
+ *  I : 4-byte int32
+ *  F : 4-byte float
+ *  D : 8-byte double
+ *  L : 8-byte int64
+ *  i : array of int32
+ *  f : array of float
+ *  d : array of double
+ *  l : array of int64
+ *  b : array of 1-byte booleans (0x00 or 0x01)
+ *  S : string (array of 1-byte char)
+ *  R : raw data (array of bytes)
+ */
+class FBX::Property
+{
+public:
+    // constructors for basic types.
+    // all explicit to avoid accidental typecasting
+    explicit Property(bool v);
+    // TODO: determine if there is actually a byte type,
+    // or if this always means <bool>. 'C' seems to imply <char>,
+    // so possibly the above was intended to represent both.
+    explicit Property(int16_t v);
+    explicit Property(int32_t v);
+    explicit Property(float v);
+    explicit Property(double v);
+    explicit Property(int64_t v);
+    // strings can either be stored as 'R' (raw) or 'S' (string) type
+    explicit Property(const char* c, bool raw=false);
+    explicit Property(const std::string& s, bool raw=false);
+    explicit Property(const std::vector<uint8_t>& r);
+    explicit Property(const std::vector<int32_t>& va);
+    explicit Property(const std::vector<int64_t>& va);
+    explicit Property(const std::vector<double>& va);
+    explicit Property(const std::vector<float>& va);
+    explicit Property(const aiMatrix4x4& vm);
+
+    // this will catch any type not defined above,
+    // so that we don't accidentally convert something we don't want.
+    // for example (const char*) --> (bool)... seriously wtf C++
+    template <class T>
+    explicit Property(T v) : type('X') {
+        static_assert(std::is_void<T>::value, "TRIED TO CREATE FBX PROPERTY WITH UNSUPPORTED TYPE, CHECK YOUR PROPERTY INSTANTIATION");
+    } // note: no line wrap so it appears verbatim on the compiler error
+
+    // the size of this property node in a binary file, in bytes
+    size_t size();
+
+    // write this property node as binary data to the given stream
+    void DumpBinary(Assimp::StreamWriterLE &s);
+    void DumpAscii(Assimp::StreamWriterLE &s, int indent=0);
+    void DumpAscii(std::ostream &s, int indent=0);
+    // note: make sure the ostream is in classic "C" locale
+
+private:
+    char type;
+    std::vector<uint8_t> data;
+};
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#endif // AI_FBXEXPORTPROPERTY_H_INC

+ 2475 - 0
code/FBXExporter.cpp

@@ -0,0 +1,2475 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2018, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+#ifndef ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#include "FBXExporter.h"
+#include "FBXExportNode.h"
+#include "FBXExportProperty.h"
+#include "FBXCommon.h"
+
+#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>
+
+// Header files, standard library.
+#include <memory> // shared_ptr
+#include <string>
+#include <sstream> // stringstream
+#include <ctime> // localtime, tm_*
+#include <map>
+#include <set>
+#include <vector>
+#include <array>
+#include <unordered_set>
+
+// RESOURCES:
+// https://code.blender.org/2013/08/fbx-binary-file-format-specification/
+// https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure
+
+const double DEG = 57.29577951308232087679815481; // degrees per radian
+
+// some constants that we'll use for writing metadata
+namespace FBX {
+    const std::string EXPORT_VERSION_STR = "7.4.0";
+    const uint32_t EXPORT_VERSION_INT = 7400; // 7.4 == 2014/2015
+    // FBX files have some hashed values that depend on the creation time field,
+    // but for now we don't actually know how to generate these.
+    // what we can do is set them to a known-working version.
+    // this is the data that Blender uses in their FBX export process.
+    const std::string GENERIC_CTIME = "1970-01-01 10:00:00:000";
+    const std::string GENERIC_FILEID =
+        "\x28\xb3\x2a\xeb\xb6\x24\xcc\xc2\xbf\xc8\xb0\x2a\xa9\x2b\xfc\xf1";
+    const std::string GENERIC_FOOTID =
+        "\xfa\xbc\xab\x09\xd0\xc8\xd4\x66\xb1\x76\xfb\x83\x1c\xf7\x26\x7e";
+    const std::string FOOT_MAGIC =
+        "\xf8\x5a\x8c\x6a\xde\xf5\xd9\x7e\xec\xe9\x0c\xe3\x75\x8f\x29\x0b";
+    const std::string COMMENT_UNDERLINE =
+        ";------------------------------------------------------------------";
+}
+
+using namespace Assimp;
+using namespace FBX;
+
+namespace Assimp {
+
+    // ---------------------------------------------------------------------
+    // Worker function for exporting a scene to binary FBX.
+    // Prototyped and registered in Exporter.cpp
+    void ExportSceneFBX (
+        const char* pFile,
+        IOSystem* pIOSystem,
+        const aiScene* pScene,
+        const ExportProperties* pProperties
+    ){
+        // initialize the exporter
+        FBXExporter exporter(pScene, pProperties);
+
+        // perform binary export
+        exporter.ExportBinary(pFile, pIOSystem);
+    }
+
+    // ---------------------------------------------------------------------
+    // Worker function for exporting a scene to ASCII FBX.
+    // Prototyped and registered in Exporter.cpp
+    void ExportSceneFBXA (
+        const char* pFile,
+        IOSystem* pIOSystem,
+        const aiScene* pScene,
+        const ExportProperties* pProperties
+    ){
+        // initialize the exporter
+        FBXExporter exporter(pScene, pProperties);
+
+        // perform ascii export
+        exporter.ExportAscii(pFile, pIOSystem);
+    }
+
+} // end of namespace Assimp
+
+FBXExporter::FBXExporter (
+    const aiScene* pScene,
+    const ExportProperties* pProperties
+)
+    : mScene(pScene)
+    , mProperties(pProperties)
+{
+    // will probably need to determine UIDs, connections, etc here.
+    // basically anything that needs to be known
+    // before we start writing sections to the stream.
+}
+
+void FBXExporter::ExportBinary (
+    const char* pFile,
+    IOSystem* pIOSystem
+){
+    // remember that we're exporting in binary mode
+    binary = true;
+
+    // we're not currently using these preferences,
+    // but clang will cry about it if we never touch it.
+    // TODO: some of these might be relevant to export
+    (void)mProperties;
+
+    // open the indicated file for writing (in binary mode)
+    outfile.reset(pIOSystem->Open(pFile,"wb"));
+    if (!outfile) {
+        throw DeadlyExportError(
+            "could not open output .fbx file: " + std::string(pFile)
+        );
+    }
+
+    // first a binary-specific file header
+    WriteBinaryHeader();
+
+    // the rest of the file is in node entries.
+    // we have to serialize each entry before we write to the output,
+    // as the first thing we write is the byte offset of the _next_ entry.
+    // Either that or we can skip back to write the offset when we finish.
+    WriteAllNodes();
+
+    // finally we have a binary footer to the file
+    WriteBinaryFooter();
+
+    // explicitly release file pointer,
+    // so we don't have to rely on class destruction.
+    outfile.reset();
+}
+
+void FBXExporter::ExportAscii (
+    const char* pFile,
+    IOSystem* pIOSystem
+){
+    // remember that we're exporting in ascii mode
+    binary = false;
+
+    // open the indicated file for writing in text mode
+    outfile.reset(pIOSystem->Open(pFile,"wt"));
+    if (!outfile) {
+        throw DeadlyExportError(
+            "could not open output .fbx file: " + std::string(pFile)
+        );
+    }
+
+    // write the ascii header
+    WriteAsciiHeader();
+
+    // write all the sections
+    WriteAllNodes();
+
+    // make sure the file ends with a newline.
+    // note: if the file is opened in text mode,
+    // this should do the right cross-platform thing.
+    outfile->Write("\n", 1, 1);
+
+    // explicitly release file pointer,
+    // so we don't have to rely on class destruction.
+    outfile.reset();
+}
+
+void FBXExporter::WriteAsciiHeader()
+{
+    // basically just a comment at the top of the file
+    std::stringstream head;
+    head << "; FBX " << EXPORT_VERSION_STR << " project file\n";
+    head << "; Created by the Open Asset Import Library (Assimp)\n";
+    head << "; http://assimp.org\n";
+    head << "; -------------------------------------------------\n";
+    const std::string ascii_header = head.str();
+    outfile->Write(ascii_header.c_str(), ascii_header.size(), 1);
+}
+
+void FBXExporter::WriteAsciiSectionHeader(const std::string& title)
+{
+    StreamWriterLE outstream(outfile);
+    std::stringstream s;
+    s << "\n\n; " << title << '\n';
+    s << FBX::COMMENT_UNDERLINE << "\n";
+    outstream.PutString(s.str());
+}
+
+void FBXExporter::WriteBinaryHeader()
+{
+    // first a specific sequence of 23 bytes, always the same
+    const char binary_header[24] = "Kaydara FBX Binary\x20\x20\x00\x1a\x00";
+    outfile->Write(binary_header, 1, 23);
+
+    // then FBX version number, "multiplied" by 1000, as little-endian uint32.
+    // so 7.3 becomes 7300 == 0x841C0000, 7.4 becomes 7400 == 0xE81C0000, etc
+    {
+        StreamWriterLE outstream(outfile);
+        outstream.PutU4(EXPORT_VERSION_INT);
+    } // StreamWriter destructor writes the data to the file
+
+    // after this the node data starts immediately
+    // (probably with the FBXHEaderExtension node)
+}
+
+void FBXExporter::WriteBinaryFooter()
+{
+    outfile->Write(NULL_RECORD.c_str(), NULL_RECORD.size(), 1);
+
+    outfile->Write(GENERIC_FOOTID.c_str(), GENERIC_FOOTID.size(), 1);
+
+    // here some padding is added for alignment to 16 bytes.
+    // if already aligned, the full 16 bytes is added.
+    size_t pos = outfile->Tell();
+    size_t pad = 16 - (pos % 16);
+    for (size_t i = 0; i < pad; ++i) {
+        outfile->Write("\x00", 1, 1);
+    }
+
+    // not sure what this is, but it seems to always be 0 in modern files
+    for (size_t i = 0; i < 4; ++i) {
+        outfile->Write("\x00", 1, 1);
+    }
+
+    // now the file version again
+    {
+        StreamWriterLE outstream(outfile);
+        outstream.PutU4(EXPORT_VERSION_INT);
+    } // StreamWriter destructor writes the data to the file
+
+    // and finally some binary footer added to all files
+    for (size_t i = 0; i < 120; ++i) {
+        outfile->Write("\x00", 1, 1);
+    }
+    outfile->Write(FOOT_MAGIC.c_str(), FOOT_MAGIC.size(), 1);
+}
+
+void FBXExporter::WriteAllNodes ()
+{
+    // header
+    // (and fileid, creation time, creator, if binary)
+    WriteHeaderExtension();
+
+    // global settings
+    WriteGlobalSettings();
+
+    // documents
+    WriteDocuments();
+
+    // references
+    WriteReferences();
+
+    // definitions
+    WriteDefinitions();
+
+    // objects
+    WriteObjects();
+
+    // connections
+    WriteConnections();
+
+    // WriteTakes? (deprecated since at least 2015 (fbx 7.4))
+}
+
+//FBXHeaderExtension top-level node
+void FBXExporter::WriteHeaderExtension ()
+{
+    if (!binary) {
+        // no title, follows directly from the top comment
+    }
+    FBX::Node n("FBXHeaderExtension");
+    StreamWriterLE outstream(outfile);
+    int indent = 0;
+
+    // begin node
+    n.Begin(outstream, binary, indent);
+
+    // write properties
+    // (none)
+
+    // finish properties
+    n.EndProperties(outstream, binary, indent, 0);
+
+    // begin children
+    n.BeginChildren(outstream, binary, indent);
+
+    indent = 1;
+
+    // write child nodes
+    FBX::Node::WritePropertyNode(
+        "FBXHeaderVersion", int32_t(1003), outstream, binary, indent
+    );
+    FBX::Node::WritePropertyNode(
+        "FBXVersion", int32_t(EXPORT_VERSION_INT), outstream, binary, indent
+    );
+    if (binary) {
+        FBX::Node::WritePropertyNode(
+            "EncryptionType", int32_t(0), outstream, binary, indent
+        );
+    }
+
+    FBX::Node CreationTimeStamp("CreationTimeStamp");
+    time_t rawtime;
+    time(&rawtime);
+    struct tm * now = localtime(&rawtime);
+    CreationTimeStamp.AddChild("Version", int32_t(1000));
+    CreationTimeStamp.AddChild("Year", int32_t(now->tm_year + 1900));
+    CreationTimeStamp.AddChild("Month", int32_t(now->tm_mon + 1));
+    CreationTimeStamp.AddChild("Day", int32_t(now->tm_mday));
+    CreationTimeStamp.AddChild("Hour", int32_t(now->tm_hour));
+    CreationTimeStamp.AddChild("Minute", int32_t(now->tm_min));
+    CreationTimeStamp.AddChild("Second", int32_t(now->tm_sec));
+    CreationTimeStamp.AddChild("Millisecond", int32_t(0));
+    CreationTimeStamp.Dump(outstream, binary, indent);
+
+    std::stringstream creator;
+    creator << "Open Asset Import Library (Assimp) " << aiGetVersionMajor()
+            << "." << aiGetVersionMinor() << "." << aiGetVersionRevision();
+    FBX::Node::WritePropertyNode(
+        "Creator", creator.str(), outstream, binary, indent
+    );
+
+    //FBX::Node sceneinfo("SceneInfo");
+    //sceneinfo.AddProperty("GlobalInfo" + FBX::SEPARATOR + "SceneInfo");
+    // not sure if any of this is actually needed,
+    // so just write an empty node for now.
+    //sceneinfo.Dump(outstream, binary, indent);
+
+    indent = 0;
+
+    // finish node
+    n.End(outstream, binary, indent, true);
+
+    // that's it for FBXHeaderExtension...
+    if (!binary) { return; }
+
+    // but binary files also need top-level FileID, CreationTime, Creator:
+    std::vector<uint8_t> raw(GENERIC_FILEID.size());
+    for (size_t i = 0; i < GENERIC_FILEID.size(); ++i) {
+        raw[i] = uint8_t(GENERIC_FILEID[i]);
+    }
+    FBX::Node::WritePropertyNode(
+        "FileId", raw, outstream, binary, indent
+    );
+    FBX::Node::WritePropertyNode(
+        "CreationTime", GENERIC_CTIME, outstream, binary, indent
+    );
+    FBX::Node::WritePropertyNode(
+        "Creator", creator.str(), outstream, binary, indent
+    );
+}
+
+void FBXExporter::WriteGlobalSettings ()
+{
+    if (!binary) {
+        // no title, follows directly from the header extension
+    }
+    FBX::Node gs("GlobalSettings");
+    gs.AddChild("Version", int32_t(1000));
+
+    FBX::Node p("Properties70");
+    p.AddP70int("UpAxis", 1);
+    p.AddP70int("UpAxisSign", 1);
+    p.AddP70int("FrontAxis", 2);
+    p.AddP70int("FrontAxisSign", 1);
+    p.AddP70int("CoordAxis", 0);
+    p.AddP70int("CoordAxisSign", 1);
+    p.AddP70int("OriginalUpAxis", 1);
+    p.AddP70int("OriginalUpAxisSign", 1);
+    p.AddP70double("UnitScaleFactor", 1.0);
+    p.AddP70double("OriginalUnitScaleFactor", 1.0);
+    p.AddP70color("AmbientColor", 0.0, 0.0, 0.0);
+    p.AddP70string("DefaultCamera", "Producer Perspective");
+    p.AddP70enum("TimeMode", 11);
+    p.AddP70enum("TimeProtocol", 2);
+    p.AddP70enum("SnapOnFrameMode", 0);
+    p.AddP70time("TimeSpanStart", 0); // TODO: animation support
+    p.AddP70time("TimeSpanStop", FBX::SECOND); // TODO: animation support
+    p.AddP70double("CustomFrameRate", -1.0);
+    p.AddP70("TimeMarker", "Compound", "", ""); // not sure what this is
+    p.AddP70int("CurrentTimeMarker", -1);
+    gs.AddChild(p);
+
+    gs.Dump(outfile, binary, 0);
+}
+
+void FBXExporter::WriteDocuments ()
+{
+    if (!binary) {
+        WriteAsciiSectionHeader("Documents Description");
+    }
+    
+    // not sure what the use of multiple documents would be,
+    // or whether any end-application supports it
+    FBX::Node docs("Documents");
+    docs.AddChild("Count", int32_t(1));
+    FBX::Node doc("Document");
+
+    // generate uid
+    int64_t uid = generate_uid();
+    doc.AddProperties(uid, "", "Scene");
+    FBX::Node p("Properties70");
+    p.AddP70("SourceObject", "object", "", ""); // what is this even for?
+    p.AddP70string("ActiveAnimStackName", ""); // should do this properly?
+    doc.AddChild(p);
+
+    // UID for root node in scene heirarchy.
+    // always set to 0 in the case of a single document.
+    // not sure what happens if more than one document exists,
+    // but that won't matter to us as we're exporting a single scene.
+    doc.AddChild("RootNode", int64_t(0));
+
+    docs.AddChild(doc);
+    docs.Dump(outfile, binary, 0);
+}
+
+void FBXExporter::WriteReferences ()
+{
+    if (!binary) {
+        WriteAsciiSectionHeader("Document References");
+    }
+    // always empty for now.
+    // not really sure what this is for.
+    FBX::Node n("References");
+    n.force_has_children = true;
+    n.Dump(outfile, binary, 0);
+}
+
+
+// ---------------------------------------------------------------
+// some internal helper functions used for writing the definitions
+// (before any actual data is written)
+// ---------------------------------------------------------------
+
+size_t count_nodes(const aiNode* n) {
+    size_t count = 1;
+    for (size_t i = 0; i < n->mNumChildren; ++i) {
+        count += count_nodes(n->mChildren[i]);
+    }
+    return count;
+}
+
+bool has_phong_mat(const aiScene* scene)
+{
+    // just search for any material with a shininess exponent
+    for (size_t i = 0; i < scene->mNumMaterials; ++i) {
+        aiMaterial* mat = scene->mMaterials[i];
+        float shininess = 0;
+        mat->Get(AI_MATKEY_SHININESS, shininess);
+        if (shininess > 0) {
+            return true;
+        }
+    }
+    return false;
+}
+
+size_t count_images(const aiScene* scene) {
+    std::unordered_set<std::string> images;
+    aiString texpath;
+    for (size_t i = 0; i < scene->mNumMaterials; ++i) {
+        aiMaterial* mat = scene->mMaterials[i];
+        for (
+            size_t tt = aiTextureType_DIFFUSE;
+            tt < aiTextureType_UNKNOWN;
+            ++tt
+        ){
+            const aiTextureType textype = static_cast<aiTextureType>(tt);
+            const size_t texcount = mat->GetTextureCount(textype);
+            for (unsigned int j = 0; j < texcount; ++j) {
+                mat->GetTexture(textype, j, &texpath);
+                images.insert(std::string(texpath.C_Str()));
+            }
+        }
+    }
+    return images.size();
+}
+
+size_t count_textures(const aiScene* scene) {
+    size_t count = 0;
+    for (size_t i = 0; i < scene->mNumMaterials; ++i) {
+        aiMaterial* mat = scene->mMaterials[i];
+        for (
+            size_t tt = aiTextureType_DIFFUSE;
+            tt < aiTextureType_UNKNOWN;
+            ++tt
+        ){
+            // TODO: handle layered textures
+            if (mat->GetTextureCount(static_cast<aiTextureType>(tt)) > 0) {
+                count += 1;
+            }
+        }
+    }
+    return count;
+}
+
+size_t count_deformers(const aiScene* scene) {
+    size_t count = 0;
+    for (size_t i = 0; i < scene->mNumMeshes; ++i) {
+        const size_t n = scene->mMeshes[i]->mNumBones;
+        if (n) {
+            // 1 main deformer, 1 subdeformer per bone
+            count += n + 1;
+        }
+    }
+    return count;
+}
+
+void FBXExporter::WriteDefinitions ()
+{
+    // basically this is just bookkeeping:
+    // determining how many of each type of object there are
+    // and specifying the base properties to use when otherwise unspecified.
+
+    // ascii section header
+    if (!binary) {
+        WriteAsciiSectionHeader("Object definitions");
+    }
+
+    // we need to count the objects
+    int32_t count;
+    int32_t total_count = 0;
+
+    // and store them
+    std::vector<FBX::Node> object_nodes;
+    FBX::Node n, pt, p;
+
+    // GlobalSettings
+    // this seems to always be here in Maya exports
+    n = FBX::Node("ObjectType", "GlobalSettings");
+    count = 1;
+    n.AddChild("Count", count);
+    object_nodes.push_back(n);
+    total_count += count;
+
+    // AnimationStack / FbxAnimStack
+    // this seems to always be here in Maya exports,
+    // but no harm seems to come of leaving it out.
+    count = mScene->mNumAnimations;
+    if (count) {
+        n = FBX::Node("ObjectType", "AnimationStack");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxAnimStack");
+        p = FBX::Node("Properties70");
+        p.AddP70string("Description", "");
+        p.AddP70time("LocalStart", 0);
+        p.AddP70time("LocalStop", 0);
+        p.AddP70time("ReferenceStart", 0);
+        p.AddP70time("ReferenceStop", 0);
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // AnimationLayer / FbxAnimLayer
+    // this seems to always be here in Maya exports,
+    // but no harm seems to come of leaving it out.
+    // Assimp doesn't support animation layers,
+    // so there will be one per aiAnimation
+    count = mScene->mNumAnimations;
+    if (count) {
+        n = FBX::Node("ObjectType", "AnimationLayer");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FBXAnimLayer");
+        p = FBX::Node("Properties70");
+        p.AddP70("Weight", "Number", "", "A", double(100));
+        p.AddP70bool("Mute", 0);
+        p.AddP70bool("Solo", 0);
+        p.AddP70bool("Lock", 0);
+        p.AddP70color("Color", 0.8, 0.8, 0.8);
+        p.AddP70("BlendMode", "enum", "", "", int32_t(0));
+        p.AddP70("RotationAccumulationMode", "enum", "", "", int32_t(0));
+        p.AddP70("ScaleAccumulationMode", "enum", "", "", int32_t(0));
+        p.AddP70("BlendModeBypass", "ULongLong", "", "", int64_t(0));
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // NodeAttribute
+    // this is completely absurd.
+    // there can only be one "NodeAttribute" template,
+    // but FbxSkeleton, FbxCamera, FbxLight all are "NodeAttributes".
+    // so if only one exists we should set the template for that,
+    // otherwise... we just pick one :/.
+    // the others have to set all their properties every instance,
+    // because there's no template.
+    count = 1; // TODO: select properly
+    if (count) {
+        // FbxSkeleton
+        n = FBX::Node("ObjectType", "NodeAttribute");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxSkeleton");
+        p = FBX::Node("Properties70");
+        p.AddP70color("Color", 0.8, 0.8, 0.8);
+        p.AddP70double("Size", 33.333333333333);
+        p.AddP70("LimbLength", "double", "Number", "H", double(1));
+        // note: not sure what the "H" flag is for - hidden?
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Model / FbxNode
+    // <~~ node heirarchy
+    count = int32_t(count_nodes(mScene->mRootNode)) - 1; // (not counting root node)
+    if (count) {
+        n = FBX::Node("ObjectType", "Model");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxNode");
+        p = FBX::Node("Properties70");
+        p.AddP70enum("QuaternionInterpolate", 0);
+        p.AddP70vector("RotationOffset", 0.0, 0.0, 0.0);
+        p.AddP70vector("RotationPivot", 0.0, 0.0, 0.0);
+        p.AddP70vector("ScalingOffset", 0.0, 0.0, 0.0);
+        p.AddP70vector("ScalingPivot", 0.0, 0.0, 0.0);
+        p.AddP70bool("TranslationActive", 0);
+        p.AddP70vector("TranslationMin", 0.0, 0.0, 0.0);
+        p.AddP70vector("TranslationMax", 0.0, 0.0, 0.0);
+        p.AddP70bool("TranslationMinX", 0);
+        p.AddP70bool("TranslationMinY", 0);
+        p.AddP70bool("TranslationMinZ", 0);
+        p.AddP70bool("TranslationMaxX", 0);
+        p.AddP70bool("TranslationMaxY", 0);
+        p.AddP70bool("TranslationMaxZ", 0);
+        p.AddP70enum("RotationOrder", 0);
+        p.AddP70bool("RotationSpaceForLimitOnly", 0);
+        p.AddP70double("RotationStiffnessX", 0.0);
+        p.AddP70double("RotationStiffnessY", 0.0);
+        p.AddP70double("RotationStiffnessZ", 0.0);
+        p.AddP70double("AxisLen", 10.0);
+        p.AddP70vector("PreRotation", 0.0, 0.0, 0.0);
+        p.AddP70vector("PostRotation", 0.0, 0.0, 0.0);
+        p.AddP70bool("RotationActive", 0);
+        p.AddP70vector("RotationMin", 0.0, 0.0, 0.0);
+        p.AddP70vector("RotationMax", 0.0, 0.0, 0.0);
+        p.AddP70bool("RotationMinX", 0);
+        p.AddP70bool("RotationMinY", 0);
+        p.AddP70bool("RotationMinZ", 0);
+        p.AddP70bool("RotationMaxX", 0);
+        p.AddP70bool("RotationMaxY", 0);
+        p.AddP70bool("RotationMaxZ", 0);
+        p.AddP70enum("InheritType", 0);
+        p.AddP70bool("ScalingActive", 0);
+        p.AddP70vector("ScalingMin", 0.0, 0.0, 0.0);
+        p.AddP70vector("ScalingMax", 1.0, 1.0, 1.0);
+        p.AddP70bool("ScalingMinX", 0);
+        p.AddP70bool("ScalingMinY", 0);
+        p.AddP70bool("ScalingMinZ", 0);
+        p.AddP70bool("ScalingMaxX", 0);
+        p.AddP70bool("ScalingMaxY", 0);
+        p.AddP70bool("ScalingMaxZ", 0);
+        p.AddP70vector("GeometricTranslation", 0.0, 0.0, 0.0);
+        p.AddP70vector("GeometricRotation", 0.0, 0.0, 0.0);
+        p.AddP70vector("GeometricScaling", 1.0, 1.0, 1.0);
+        p.AddP70double("MinDampRangeX", 0.0);
+        p.AddP70double("MinDampRangeY", 0.0);
+        p.AddP70double("MinDampRangeZ", 0.0);
+        p.AddP70double("MaxDampRangeX", 0.0);
+        p.AddP70double("MaxDampRangeY", 0.0);
+        p.AddP70double("MaxDampRangeZ", 0.0);
+        p.AddP70double("MinDampStrengthX", 0.0);
+        p.AddP70double("MinDampStrengthY", 0.0);
+        p.AddP70double("MinDampStrengthZ", 0.0);
+        p.AddP70double("MaxDampStrengthX", 0.0);
+        p.AddP70double("MaxDampStrengthY", 0.0);
+        p.AddP70double("MaxDampStrengthZ", 0.0);
+        p.AddP70double("PreferedAngleX", 0.0);
+        p.AddP70double("PreferedAngleY", 0.0);
+        p.AddP70double("PreferedAngleZ", 0.0);
+        p.AddP70("LookAtProperty", "object", "", "");
+        p.AddP70("UpVectorProperty", "object", "", "");
+        p.AddP70bool("Show", 1);
+        p.AddP70bool("NegativePercentShapeSupport", 1);
+        p.AddP70int("DefaultAttributeIndex", -1);
+        p.AddP70bool("Freeze", 0);
+        p.AddP70bool("LODBox", 0);
+        p.AddP70(
+            "Lcl Translation", "Lcl Translation", "", "A",
+            double(0), double(0), double(0)
+        );
+        p.AddP70(
+            "Lcl Rotation", "Lcl Rotation", "", "A",
+            double(0), double(0), double(0)
+        );
+        p.AddP70(
+            "Lcl Scaling", "Lcl Scaling", "", "A",
+            double(1), double(1), double(1)
+        );
+        p.AddP70("Visibility", "Visibility", "", "A", double(1));
+        p.AddP70(
+            "Visibility Inheritance", "Visibility Inheritance", "", "",
+            int32_t(1)
+        );
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Geometry / FbxMesh
+    // <~~ aiMesh
+    count = mScene->mNumMeshes;
+    if (count) {
+        n = FBX::Node("ObjectType", "Geometry");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxMesh");
+        p = FBX::Node("Properties70");
+        p.AddP70color("Color", 0, 0, 0);
+        p.AddP70vector("BBoxMin", 0, 0, 0);
+        p.AddP70vector("BBoxMax", 0, 0, 0);
+        p.AddP70bool("Primary Visibility", 1);
+        p.AddP70bool("Casts Shadows", 1);
+        p.AddP70bool("Receive Shadows", 1);
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Material / FbxSurfacePhong, FbxSurfaceLambert, FbxSurfaceMaterial
+    // <~~ aiMaterial
+    // basically if there's any phong material this is defined as phong,
+    // and otherwise lambert.
+    // More complex materials cause a bare-bones FbxSurfaceMaterial definition
+    // and are treated specially, as they're not really supported by FBX.
+    // TODO: support Maya's Stingray PBS material
+    count = mScene->mNumMaterials;
+    if (count) {
+        bool has_phong = has_phong_mat(mScene);
+        n = FBX::Node("ObjectType", "Material");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate");
+        if (has_phong) {
+            pt.AddProperty("FbxSurfacePhong");
+        } else {
+            pt.AddProperty("FbxSurfaceLambert");
+        }
+        p = FBX::Node("Properties70");
+        if (has_phong) {
+            p.AddP70string("ShadingModel", "Phong");
+        } else {
+            p.AddP70string("ShadingModel", "Lambert");
+        }
+        p.AddP70bool("MultiLayer", 0);
+        p.AddP70colorA("EmissiveColor", 0.0, 0.0, 0.0);
+        p.AddP70numberA("EmissiveFactor", 1.0);
+        p.AddP70colorA("AmbientColor", 0.2, 0.2, 0.2);
+        p.AddP70numberA("AmbientFactor", 1.0);
+        p.AddP70colorA("DiffuseColor", 0.8, 0.8, 0.8);
+        p.AddP70numberA("DiffuseFactor", 1.0);
+        p.AddP70vector("Bump", 0.0, 0.0, 0.0);
+        p.AddP70vector("NormalMap", 0.0, 0.0, 0.0);
+        p.AddP70double("BumpFactor", 1.0);
+        p.AddP70colorA("TransparentColor", 0.0, 0.0, 0.0);
+        p.AddP70numberA("TransparencyFactor", 0.0);
+        p.AddP70color("DisplacementColor", 0.0, 0.0, 0.0);
+        p.AddP70double("DisplacementFactor", 1.0);
+        p.AddP70color("VectorDisplacementColor", 0.0, 0.0, 0.0);
+        p.AddP70double("VectorDisplacementFactor", 1.0);
+        if (has_phong) {
+            p.AddP70colorA("SpecularColor", 0.2, 0.2, 0.2);
+            p.AddP70numberA("SpecularFactor", 1.0);
+            p.AddP70numberA("ShininessExponent", 20.0);
+            p.AddP70colorA("ReflectionColor", 0.0, 0.0, 0.0);
+            p.AddP70numberA("ReflectionFactor", 1.0);
+        }
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Video / FbxVideo
+    // one for each image file.
+    count = int32_t(count_images(mScene));
+    if (count) {
+        n = FBX::Node("ObjectType", "Video");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxVideo");
+        p = FBX::Node("Properties70");
+        p.AddP70bool("ImageSequence", 0);
+        p.AddP70int("ImageSequenceOffset", 0);
+        p.AddP70double("FrameRate", 0.0);
+        p.AddP70int("LastFrame", 0);
+        p.AddP70int("Width", 0);
+        p.AddP70int("Height", 0);
+        p.AddP70("Path", "KString", "XRefUrl", "", "");
+        p.AddP70int("StartFrame", 0);
+        p.AddP70int("StopFrame", 0);
+        p.AddP70double("PlaySpeed", 0.0);
+        p.AddP70time("Offset", 0);
+        p.AddP70enum("InterlaceMode", 0);
+        p.AddP70bool("FreeRunning", 0);
+        p.AddP70bool("Loop", 0);
+        p.AddP70enum("AccessMode", 0);
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Texture / FbxFileTexture
+    // <~~ aiTexture
+    count = int32_t(count_textures(mScene));
+    if (count) {
+        n = FBX::Node("ObjectType", "Texture");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxFileTexture");
+        p = FBX::Node("Properties70");
+        p.AddP70enum("TextureTypeUse", 0);
+        p.AddP70numberA("Texture alpha", 1.0);
+        p.AddP70enum("CurrentMappingType", 0);
+        p.AddP70enum("WrapModeU", 0);
+        p.AddP70enum("WrapModeV", 0);
+        p.AddP70bool("UVSwap", 0);
+        p.AddP70bool("PremultiplyAlpha", 1);
+        p.AddP70vectorA("Translation", 0.0, 0.0, 0.0);
+        p.AddP70vectorA("Rotation", 0.0, 0.0, 0.0);
+        p.AddP70vectorA("Scaling", 1.0, 1.0, 1.0);
+        p.AddP70vector("TextureRotationPivot", 0.0, 0.0, 0.0);
+        p.AddP70vector("TextureScalingPivot", 0.0, 0.0, 0.0);
+        p.AddP70enum("CurrentTextureBlendMode", 1);
+        p.AddP70string("UVSet", "default");
+        p.AddP70bool("UseMaterial", 0);
+        p.AddP70bool("UseMipMap", 0);
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // AnimationCurveNode / FbxAnimCurveNode
+    count = mScene->mNumAnimations * 3;
+    if (count) {
+        n = FBX::Node("ObjectType", "AnimationCurveNode");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxAnimCurveNode");
+        p = FBX::Node("Properties70");
+        p.AddP70("d", "Compound", "", "");
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // AnimationCurve / FbxAnimCurve
+    count = mScene->mNumAnimations * 9;
+    if (count) {
+        n = FBX::Node("ObjectType", "AnimationCurve");
+        n.AddChild("Count", count);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Pose
+    count = 0;
+    for (size_t i = 0; i < mScene->mNumMeshes; ++i) {
+        aiMesh* mesh = mScene->mMeshes[i];
+        if (mesh->HasBones()) { ++count; }
+    }
+    if (count) {
+        n = FBX::Node("ObjectType", "Pose");
+        n.AddChild("Count", count);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Deformer
+    count = int32_t(count_deformers(mScene));
+    if (count) {
+        n = FBX::Node("ObjectType", "Deformer");
+        n.AddChild("Count", count);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // (template)
+    count = 0;
+    if (count) {
+        n = FBX::Node("ObjectType", "");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "");
+        p = FBX::Node("Properties70");
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // now write it all
+    FBX::Node defs("Definitions");
+    defs.AddChild("Version", int32_t(100));
+    defs.AddChild("Count", int32_t(total_count));
+    for (auto &n : object_nodes) { defs.AddChild(n); }
+    defs.Dump(outfile, binary, 0);
+}
+
+
+// -------------------------------------------------------------------
+// some internal helper functions used for writing the objects section
+// (which holds the actual data)
+// -------------------------------------------------------------------
+
+aiNode* get_node_for_mesh(unsigned int meshIndex, aiNode* node)
+{
+    for (size_t i = 0; i < node->mNumMeshes; ++i) {
+        if (node->mMeshes[i] == meshIndex) {
+            return node;
+        }
+    }
+    for (size_t i = 0; i < node->mNumChildren; ++i) {
+        aiNode* ret = get_node_for_mesh(meshIndex, node->mChildren[i]);
+        if (ret) { return ret; }
+    }
+    return nullptr;
+}
+
+aiMatrix4x4 get_world_transform(const aiNode* node, const aiScene* scene)
+{
+    std::vector<const aiNode*> node_chain;
+    while (node != scene->mRootNode) {
+        node_chain.push_back(node);
+        node = node->mParent;
+    }
+    aiMatrix4x4 transform;
+    for (auto n = node_chain.rbegin(); n != node_chain.rend(); ++n) {
+        transform *= (*n)->mTransformation;
+    }
+    return transform;
+}
+
+int64_t to_ktime(double ticks, const aiAnimation* anim) {
+    if (anim->mTicksPerSecond <= 0) {
+        return ticks * FBX::SECOND;
+    }
+    return (ticks / anim->mTicksPerSecond) * FBX::SECOND;
+}
+
+void FBXExporter::WriteObjects ()
+{
+    if (!binary) {
+        WriteAsciiSectionHeader("Object properties");
+    }
+    // numbers should match those given in definitions! make sure to check
+    StreamWriterLE outstream(outfile);
+    FBX::Node object_node("Objects");
+    int indent = 0;
+    object_node.Begin(outstream, binary, indent);
+    object_node.EndProperties(outstream, binary, indent);
+    object_node.BeginChildren(outstream, binary, indent);
+
+    // geometry (aiMesh)
+    mesh_uids.clear();
+    indent = 1;
+    for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
+        // it's all about this mesh
+        aiMesh* m = mScene->mMeshes[mi];
+
+        // start the node record
+        FBX::Node n("Geometry");
+        int64_t uid = generate_uid();
+        mesh_uids.push_back(uid);
+        n.AddProperty(uid);
+        n.AddProperty(FBX::SEPARATOR + "Geometry");
+        n.AddProperty("Mesh");
+        n.Begin(outstream, binary, indent);
+        n.DumpProperties(outstream, binary, indent);
+        n.EndProperties(outstream, binary, indent);
+        n.BeginChildren(outstream, binary, indent);
+        indent = 2;
+
+        // output vertex data - each vertex should be unique (probably)
+        std::vector<double> flattened_vertices;
+        // 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;
+                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));
+            }
+        }
+        FBX::Node::WritePropertyNode(
+            "Vertices", flattened_vertices, outstream, binary, indent
+        );
+
+        // output polygon data as a flattened array of vertex indices.
+        // the last vertex index of each polygon is negated and - 1
+        std::vector<int32_t> polygon_data;
+        for (size_t fi = 0; fi < m->mNumFaces; ++fi) {
+            const aiFace &f = m->mFaces[fi];
+            for (size_t pvi = 0; pvi < f.mNumIndices - 1; ++pvi) {
+                polygon_data.push_back(vertex_indices[f.mIndices[pvi]]);
+            }
+            polygon_data.push_back(
+                -1 - vertex_indices[f.mIndices[f.mNumIndices-1]]
+            );
+        }
+        FBX::Node::WritePropertyNode(
+            "PolygonVertexIndex", polygon_data, outstream, binary, indent
+        );
+
+        // here could be edges but they're insane.
+        // it's optional anyway, so let's ignore it.
+
+        FBX::Node::WritePropertyNode(
+            "GeometryVersion", int32_t(124), outstream, binary, indent
+        );
+
+        // normals, if any
+        if (m->HasNormals()) {
+            FBX::Node normals("LayerElementNormal", int32_t(0));
+            normals.Begin(outstream, binary, indent);
+            normals.DumpProperties(outstream, binary, indent);
+            normals.EndProperties(outstream, binary, indent);
+            normals.BeginChildren(outstream, binary, indent);
+            indent = 3;
+            FBX::Node::WritePropertyNode(
+                "Version", int32_t(101), outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "Name", "", outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "MappingInformationType", "ByPolygonVertex",
+                outstream, binary, indent
+            );
+            // TODO: vertex-normals or indexed normals when appropriate
+            FBX::Node::WritePropertyNode(
+                "ReferenceInformationType", "Direct",
+                outstream, binary, indent
+            );
+            std::vector<double> normal_data;
+            normal_data.reserve(3 * 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 aiVector3D &n = m->mNormals[f.mIndices[pvi]];
+                    normal_data.push_back(n.x);
+                    normal_data.push_back(n.y);
+                    normal_data.push_back(n.z);
+                }
+            }
+            FBX::Node::WritePropertyNode(
+                "Normals", normal_data, outstream, binary, indent
+            );
+            // note: version 102 has a NormalsW also... not sure what it is,
+            // so we can stick with version 101 for now.
+            indent = 2;
+            normals.End(outstream, binary, indent, true);
+        }
+
+        // uvs, if any
+        for (size_t uvi = 0; uvi < m->GetNumUVChannels(); ++uvi) {
+            if (m->mNumUVComponents[uvi] > 2) {
+                // FBX only supports 2-channel UV maps...
+                // or at least i'm not sure how to indicate a different number
+                std::stringstream err;
+                err << "Only 2-channel UV maps supported by FBX,";
+                err << " but mesh " << mi;
+                if (m->mName.length) {
+                    err << " (" << m->mName.C_Str() << ")";
+                }
+                err << " UV map " << uvi;
+                err << " has " << m->mNumUVComponents[uvi];
+                err << " components! Data will be preserved,";
+                err << " but may be incorrectly interpreted on load.";
+                DefaultLogger::get()->warn(err.str());
+            }
+            FBX::Node uv("LayerElementUV", int32_t(uvi));
+            uv.Begin(outstream, binary, indent);
+            uv.DumpProperties(outstream, binary, indent);
+            uv.EndProperties(outstream, binary, indent);
+            uv.BeginChildren(outstream, binary, indent);
+            indent = 3;
+            FBX::Node::WritePropertyNode(
+                "Version", int32_t(101), outstream, binary, indent
+            );
+            // it doesn't seem like assimp keeps the uv map name,
+            // so just leave it blank.
+            FBX::Node::WritePropertyNode(
+                "Name", "", outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "MappingInformationType", "ByPolygonVertex",
+                outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "ReferenceInformationType", "IndexToDirect",
+                outstream, binary, indent
+            );
+
+            std::vector<double> uv_data;
+            std::vector<int32_t> uv_indices;
+            std::map<aiVector3D,int32_t> index_by_uv;
+            int32_t index = 0;
+            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 aiVector3D &uv =
+                        m->mTextureCoords[uvi][f.mIndices[pvi]];
+                    auto elem = index_by_uv.find(uv);
+                    if (elem == index_by_uv.end()) {
+                        index_by_uv[uv] = index;
+                        uv_indices.push_back(index);
+                        for (unsigned int x = 0; x < m->mNumUVComponents[uvi]; ++x) {
+                            uv_data.push_back(uv[x]);
+                        }
+                        ++index;
+                    } else {
+                        uv_indices.push_back(elem->second);
+                    }
+                }
+            }
+            FBX::Node::WritePropertyNode(
+                "UV", uv_data, outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "UVIndex", uv_indices, outstream, binary, indent
+            );
+            indent = 2;
+            uv.End(outstream, binary, indent, true);
+        }
+
+        // i'm not really sure why this material section exists,
+        // as the material is linked via "Connections".
+        // it seems to always have the same "0" value.
+        FBX::Node mat("LayerElementMaterial", int32_t(0));
+        mat.AddChild("Version", int32_t(101));
+        mat.AddChild("Name", "");
+        mat.AddChild("MappingInformationType", "AllSame");
+        mat.AddChild("ReferenceInformationType", "IndexToDirect");
+        std::vector<int32_t> mat_indices = {0};
+        mat.AddChild("Materials", mat_indices);
+        mat.Dump(outstream, binary, indent);
+
+        // finally we have the layer specifications,
+        // which select the normals / UV set / etc to use.
+        // TODO: handle multiple uv sets correctly?
+        FBX::Node layer("Layer", int32_t(0));
+        layer.AddChild("Version", int32_t(100));
+        FBX::Node le("LayerElement");
+        le.AddChild("Type", "LayerElementNormal");
+        le.AddChild("TypedIndex", int32_t(0));
+        layer.AddChild(le);
+        le = FBX::Node("LayerElement");
+        le.AddChild("Type", "LayerElementMaterial");
+        le.AddChild("TypedIndex", int32_t(0));
+        layer.AddChild(le);
+        le = FBX::Node("LayerElement");
+        le.AddChild("Type", "LayerElementUV");
+        le.AddChild("TypedIndex", int32_t(0));
+        layer.AddChild(le);
+        layer.Dump(outstream, binary, indent);
+
+        // finish the node record
+        indent = 1;
+        n.End(outstream, binary, indent, true);
+    }
+
+    // aiMaterial
+    material_uids.clear();
+    for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
+        // it's all about this material
+        aiMaterial* m = mScene->mMaterials[i];
+
+        // these are used to recieve material data
+        float f; aiColor3D c;
+
+        // start the node record
+        FBX::Node n("Material");
+
+        int64_t uid = generate_uid();
+        material_uids.push_back(uid);
+        n.AddProperty(uid);
+
+        aiString name;
+        m->Get(AI_MATKEY_NAME, name);
+        n.AddProperty(name.C_Str() + FBX::SEPARATOR + "Material");
+
+        n.AddProperty("");
+
+        n.AddChild("Version", int32_t(102));
+        f = 0;
+        m->Get(AI_MATKEY_SHININESS, f);
+        bool phong = (f > 0);
+        if (phong) {
+            n.AddChild("ShadingModel", "phong");
+        } else {
+            n.AddChild("ShadingModel", "lambert");
+        }
+        n.AddChild("MultiLayer", int32_t(0));
+
+        FBX::Node p("Properties70");
+
+        // materials exported using the FBX SDK have two sets of fields.
+        // there are the properties specified in the PropertyTemplate,
+        // which are those supported by the modernFBX SDK,
+        // and an extra set of properties with simpler names.
+        // The extra properties are a legacy material system from pre-2009.
+        //
+        // In the modern system, each property has "color" and "factor".
+        // Generally the interpretation of these seems to be
+        // that the colour is multiplied by the factor before use,
+        // but this is not always clear-cut.
+        //
+        // Usually assimp only stores the colour,
+        // so we can just leave the factors at the default "1.0".
+
+        // first we can export the "standard" properties
+        if (m->Get(AI_MATKEY_COLOR_AMBIENT, c) == aiReturn_SUCCESS) {
+            p.AddP70colorA("AmbientColor", c.r, c.g, c.b);
+            //p.AddP70numberA("AmbientFactor", 1.0);
+        }
+        if (m->Get(AI_MATKEY_COLOR_DIFFUSE, c) == aiReturn_SUCCESS) {
+            p.AddP70colorA("DiffuseColor", c.r, c.g, c.b);
+            //p.AddP70numberA("DiffuseFactor", 1.0);
+        }
+        if (m->Get(AI_MATKEY_COLOR_TRANSPARENT, c) == aiReturn_SUCCESS) {
+            // "TransparentColor" / "TransparencyFactor"...
+            // thanks FBX, for your insightful interpretation of consistency
+            p.AddP70colorA("TransparentColor", c.r, c.g, c.b);
+            // TransparencyFactor defaults to 0.0, so set it to 1.0.
+            // note: Maya always sets this to 1.0,
+            // so we can't use it sensibly as "Opacity".
+            // In stead we rely on the legacy "Opacity" value, below.
+            // Blender also relies on "Opacity" not "TransparencyFactor",
+            // probably for a similar reason.
+            p.AddP70numberA("TransparencyFactor", 1.0);
+        }
+        if (m->Get(AI_MATKEY_COLOR_REFLECTIVE, c) == aiReturn_SUCCESS) {
+            p.AddP70colorA("ReflectionColor", c.r, c.g, c.b);
+        }
+        if (m->Get(AI_MATKEY_REFLECTIVITY, f) == aiReturn_SUCCESS) {
+            p.AddP70numberA("ReflectionFactor", f);
+        }
+        if (phong) {
+            if (m->Get(AI_MATKEY_COLOR_SPECULAR, c) == aiReturn_SUCCESS) {
+                p.AddP70colorA("SpecularColor", c.r, c.g, c.b);
+            }
+            if (m->Get(AI_MATKEY_SHININESS_STRENGTH, f) == aiReturn_SUCCESS) {
+                p.AddP70numberA("ShininessFactor", f);
+            }
+            if (m->Get(AI_MATKEY_SHININESS, f) == aiReturn_SUCCESS) {
+                p.AddP70numberA("ShininessExponent", f);
+            }
+            if (m->Get(AI_MATKEY_REFLECTIVITY, f) == aiReturn_SUCCESS) {
+                p.AddP70numberA("ReflectionFactor", f);
+            }
+        }
+
+        // Now the legacy system.
+        // For safety let's include it.
+        // thrse values don't exist in the property template,
+        // and usualy are completely ignored when loading.
+        // One notable exception is the "Opacity" property,
+        // which Blender uses as (1.0 - alpha).
+        c.r = 0.0f; c.g = 0.0f; c.b = 0.0f;
+        m->Get(AI_MATKEY_COLOR_EMISSIVE, c);
+        p.AddP70vector("Emissive", c.r, c.g, c.b);
+        c.r = 0.2f; c.g = 0.2f; c.b = 0.2f;
+        m->Get(AI_MATKEY_COLOR_AMBIENT, c);
+        p.AddP70vector("Ambient", c.r, c.g, c.b);
+        c.r = 0.8f; c.g = 0.8f; c.b = 0.8f;
+        m->Get(AI_MATKEY_COLOR_DIFFUSE, c);
+        p.AddP70vector("Diffuse", c.r, c.g, c.b);
+        // The FBX SDK determines "Opacity" from transparency colour (RGB)
+        // and factor (F) as: O = (1.0 - F * ((R + G + B) / 3)).
+        // However we actually have an opacity value,
+        // so we should take it from AI_MATKEY_OPACITY if possible.
+        // It might make more sense to use TransparencyFactor,
+        // but Blender actually loads "Opacity" correctly, so let's use it.
+        f = 1.0f;
+        if (m->Get(AI_MATKEY_COLOR_TRANSPARENT, c) == aiReturn_SUCCESS) {
+            f = 1.0f - ((c.r + c.g + c.b) / 3.0f);
+        }
+        m->Get(AI_MATKEY_OPACITY, f);
+        p.AddP70double("Opacity", f);
+        if (phong) {
+            // specular color is multiplied by shininess_strength
+            c.r = 0.2f; c.g = 0.2f; c.b = 0.2f;
+            m->Get(AI_MATKEY_COLOR_SPECULAR, c);
+            f = 1.0f;
+            m->Get(AI_MATKEY_SHININESS_STRENGTH, f);
+            p.AddP70vector("Specular", f*c.r, f*c.g, f*c.b);
+            f = 20.0f;
+            m->Get(AI_MATKEY_SHININESS, f);
+            p.AddP70double("Shininess", f);
+            // Legacy "Reflectivity" is F*F*((R+G+B)/3),
+            // where F is the proportion of light reflected (AKA reflectivity),
+            // and RGB is the reflective colour of the material.
+            // No idea why, but we might as well set it the same way.
+            f = 0.0f;
+            m->Get(AI_MATKEY_REFLECTIVITY, f);
+            c.r = 1.0f, c.g = 1.0f, c.b = 1.0f;
+            m->Get(AI_MATKEY_COLOR_REFLECTIVE, c);
+            p.AddP70double("Reflectivity", f*f*((c.r+c.g+c.b)/3.0));
+        }
+
+        n.AddChild(p);
+
+        n.Dump(outstream, binary, indent);
+    }
+
+    // we need to look up all the images we're using,
+    // so we can generate uids, and eliminate duplicates.
+    std::map<std::string, int64_t> uid_by_image;
+    for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
+        aiString texpath;
+        aiMaterial* mat = mScene->mMaterials[i];
+        for (
+            size_t tt = aiTextureType_DIFFUSE;
+            tt < aiTextureType_UNKNOWN;
+            ++tt
+        ){
+            const aiTextureType textype = static_cast<aiTextureType>(tt);
+            const size_t texcount = mat->GetTextureCount(textype);
+            for (size_t j = 0; j < texcount; ++j) {
+                mat->GetTexture(textype, (unsigned int)j, &texpath);
+                const std::string texstring = texpath.C_Str();
+                auto elem = uid_by_image.find(texstring);
+                if (elem == uid_by_image.end()) {
+                    uid_by_image[texstring] = generate_uid();
+                }
+            }
+        }
+    }
+
+    // FbxVideo - stores images used by textures.
+    for (const auto &it : uid_by_image) {
+        if (it.first.compare(0, 1, "*") == 0) {
+            // TODO: embedded textures
+            continue;
+        }
+        FBX::Node n("Video");
+        const int64_t& uid = it.second;
+        const std::string name = ""; // TODO: ... name???
+        n.AddProperties(uid, name + FBX::SEPARATOR + "Video", "Clip");
+        n.AddChild("Type", "Clip");
+        FBX::Node p("Properties70");
+        // TODO: get full path... relative path... etc... ugh...
+        // for now just use the same path for everything,
+        // and hopefully one of them will work out.
+        const std::string& path = it.first;
+        p.AddP70("Path", "KString", "XRefUrl", "", path);
+        n.AddChild(p);
+        n.AddChild("UseMipMap", int32_t(0));
+        n.AddChild("Filename", path);
+        n.AddChild("RelativeFilename", path);
+        n.Dump(outstream, binary, indent);
+    }
+
+    // Textures
+    // referenced by material_index/texture_type pairs.
+    std::map<std::pair<size_t,size_t>,int64_t> texture_uids;
+    const std::map<aiTextureType,std::string> prop_name_by_tt = {
+        {aiTextureType_DIFFUSE, "DiffuseColor"},
+        {aiTextureType_SPECULAR, "SpecularColor"},
+        {aiTextureType_AMBIENT, "AmbientColor"},
+        {aiTextureType_EMISSIVE, "EmissiveColor"},
+        {aiTextureType_HEIGHT, "Bump"},
+        {aiTextureType_NORMALS, "NormalMap"},
+        {aiTextureType_SHININESS, "ShininessExponent"},
+        {aiTextureType_OPACITY, "TransparentColor"},
+        {aiTextureType_DISPLACEMENT, "DisplacementColor"},
+        //{aiTextureType_LIGHTMAP, "???"},
+        {aiTextureType_REFLECTION, "ReflectionColor"}
+        //{aiTextureType_UNKNOWN, ""}
+    };
+    for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
+        // textures are attached to materials
+        aiMaterial* mat = mScene->mMaterials[i];
+        int64_t material_uid = material_uids[i];
+
+        for (
+            size_t j = aiTextureType_DIFFUSE;
+            j < aiTextureType_UNKNOWN;
+            ++j
+        ) {
+            const aiTextureType tt = static_cast<aiTextureType>(j);
+            size_t n = mat->GetTextureCount(tt);
+
+            if (n < 1) { // no texture of this type
+                continue;
+            }
+
+            if (n > 1) {
+                // TODO: multilayer textures
+                std::stringstream err;
+                err << "Multilayer textures not supported (for now),";
+                err << " skipping texture type " << j;
+                err << " of material " << i;
+                DefaultLogger::get()->warn(err.str());
+            }
+
+            // get image path for this (single-image) texture
+            aiString tpath;
+            if (mat->GetTexture(tt, 0, &tpath) != aiReturn_SUCCESS) {
+                std::stringstream err;
+                err << "Failed to get texture 0 for texture of type " << tt;
+                err << " on material " << i;
+                err << ", however GetTextureCount returned 1.";
+                throw DeadlyExportError(err.str());
+            }
+            const std::string texture_path(tpath.C_Str());
+
+            // get connected image uid
+            auto elem = uid_by_image.find(texture_path);
+            if (elem == uid_by_image.end()) {
+                // this should never happen
+                std::stringstream err;
+                err << "Failed to find video element for texture with path";
+                err << " \"" << texture_path << "\"";
+                err << ", type " << j << ", material " << i;
+                throw DeadlyExportError(err.str());
+            }
+            const int64_t image_uid = elem->second;
+
+            // get the name of the material property to connect to
+            auto elem2 = prop_name_by_tt.find(tt);
+            if (elem2 == prop_name_by_tt.end()) {
+                // don't know how to handle this type of texture,
+                // so skip it.
+                std::stringstream err;
+                err << "Not sure how to handle texture of type " << j;
+                err << " on material " << i;
+                err << ", skipping...";
+                DefaultLogger::get()->warn(err.str());
+                continue;
+            }
+            const std::string& prop_name = elem2->second;
+
+            // generate a uid for this texture
+            const int64_t texture_uid = generate_uid();
+
+            // link the texture to the material
+            connections.emplace_back(
+                "C", "OP", texture_uid, material_uid, prop_name
+            );
+
+            // link the image data to the texture
+            connections.emplace_back("C", "OO", image_uid, texture_uid);
+
+            // now write the actual texture node
+            FBX::Node tnode("Texture");
+            // TODO: some way to determine texture name?
+            const std::string texture_name = "" + FBX::SEPARATOR + "Texture";
+            tnode.AddProperties(texture_uid, texture_name, "");
+            // there really doesn't seem to be a better type than this:
+            tnode.AddChild("Type", "TextureVideoClip");
+            tnode.AddChild("Version", int32_t(202));
+            tnode.AddChild("TextureName", texture_name);
+            FBX::Node p("Properties70");
+            p.AddP70enum("CurrentTextureBlendMode", 0); // TODO: verify
+            //p.AddP70string("UVSet", ""); // TODO: how should this work?
+            p.AddP70bool("UseMaterial", 1);
+            tnode.AddChild(p);
+            // can't easily detrmine which texture path will be correct,
+            // so just store what we have in every field.
+            // these being incorrect is a common problem with FBX anyway.
+            tnode.AddChild("FileName", texture_path);
+            tnode.AddChild("RelativeFilename", texture_path);
+            tnode.AddChild("ModelUVTranslation", double(0.0), double(0.0));
+            tnode.AddChild("ModelUVScaling", double(1.0), double(1.0));
+            tnode.AddChild("Texture_Alpha_Source", "None");
+            tnode.AddChild(
+                "Cropping", int32_t(0), int32_t(0), int32_t(0), int32_t(0)
+            );
+            tnode.Dump(outstream, binary, indent);
+        }
+    }
+
+    // bones.
+    //
+    // output structure:
+    // subset of node heirarchy that are "skeleton",
+    // i.e. do not have meshes but only bones.
+    // but.. i'm not sure how anyone could guarantee that...
+    //
+    // input...
+    // well, for each mesh it has "bones",
+    // and the bone names correspond to nodes.
+    // of course we also need the parent nodes,
+    // as they give some of the transform........
+    //
+    // well. we can assume a sane input, i suppose.
+    //
+    // so input is the bone node heirarchy,
+    // with an extra thing for the transformation of the MESH in BONE space.
+    //
+    // output is a set of bone nodes,
+    // a "bindpose" which indicates the default local transform of all bones,
+    // and a set of "deformers".
+    // each deformer is parented to a mesh geometry,
+    // and has one or more "subdeformer"s as children.
+    // each subdeformer has one bone node as a child,
+    // and represents the influence of that bone on the grandparent mesh.
+    // the subdeformer has a list of indices, and weights,
+    // with indices specifying vertex indices,
+    // and weights specifying the correspoding influence of this bone.
+    // it also has Transform and TransformLink elements,
+    // specifying the transform of the MESH in BONE space,
+    // and the transformation of the BONE in WORLD space,
+    // likely in the bindpose.
+    //
+    // the input bone structure is different but similar,
+    // storing the number of weights for this bone,
+    // and an array of (vertex index, weight) pairs.
+    //
+    // one sticky point is that the number of vertices may not match,
+    // because assimp splits vertices by normal, uv, etc.
+
+    // first we should mark the skeleton for each mesh.
+    // the skeleton must include not only the aiBones,
+    // but also all their parent nodes.
+    // anything that affects the position of any bone node must be included.
+    std::vector<std::set<const aiNode*>> skeleton_by_mesh(mScene->mNumMeshes);
+    // at the same time we can build a list of all the skeleton nodes,
+    // which will be used later to mark them as type "limbNode".
+    std::unordered_set<const aiNode*> limbnodes;
+    // and a map of nodes by bone name, as finding them is annoying.
+    std::map<std::string,aiNode*> node_by_bone;
+    for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
+        const aiMesh* m = mScene->mMeshes[mi];
+        std::set<const aiNode*> skeleton;
+        for (size_t bi =0; bi < m->mNumBones; ++bi) {
+            const aiBone* b = m->mBones[bi];
+            const std::string name(b->mName.C_Str());
+            auto elem = node_by_bone.find(name);
+            aiNode* n;
+            if (elem != node_by_bone.end()) {
+                n = elem->second;
+            } else {
+                n = mScene->mRootNode->FindNode(b->mName);
+                if (!n) {
+                    // this should never happen
+                    std::stringstream err;
+                    err << "Failed to find node for bone: \"" << name << "\"";
+                    throw DeadlyExportError(err.str());
+                }
+                node_by_bone[name] = n;
+                limbnodes.insert(n);
+            }
+            skeleton.insert(n);
+            // mark all parent nodes as skeleton as well,
+            // up until we find the root node,
+            // or else the node containing the mesh,
+            // or else the parent of a node containig the mesh.
+            for (
+                const aiNode* parent = n->mParent;
+                parent && parent != mScene->mRootNode;
+                parent = parent->mParent
+            ) {
+                // if we've already done this node we can skip it all
+                if (skeleton.count(parent)) {
+                    break;
+                }
+                // ignore fbx transform nodes as these will be collapsed later
+                // TODO: cache this by aiNode*
+                const std::string node_name(parent->mName.C_Str());
+                if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) {
+                    continue;
+                }
+                // otherwise check if this is the root of the skeleton
+                bool end = false;
+                // is the mesh part of this node?
+                for (size_t i = 0; i < parent->mNumMeshes; ++i) {
+                    if (parent->mMeshes[i] == mi) {
+                        end = true;
+                        break;
+                    }
+                }
+                // is the mesh in one of the children of this node?
+                for (size_t j = 0; j < parent->mNumChildren; ++j) {
+                    aiNode* child = parent->mChildren[j];
+                    for (size_t i = 0; i < child->mNumMeshes; ++i) {
+                        if (child->mMeshes[i] == mi) {
+                            end = true;
+                            break;
+                        }
+                    }
+                    if (end) { break; }
+                }
+                limbnodes.insert(parent);
+                skeleton.insert(parent);
+                // if it was the skeleton root we can finish here
+                if (end) { break; }
+            }
+        }
+        skeleton_by_mesh[mi] = skeleton;
+    }
+
+    // we'll need the uids for the bone nodes, so generate them now
+    for (size_t i = 0; i < mScene->mNumMeshes; ++i) {
+        auto &s = skeleton_by_mesh[i];
+        for (const aiNode* n : s) {
+            auto elem = node_uids.find(n);
+            if (elem == node_uids.end()) {
+                node_uids[n] = generate_uid();
+            }
+        }
+    }
+
+    // now, for each aiMesh, we need to export a deformer,
+    // and for each aiBone a subdeformer,
+    // which should have all the skinning info.
+    // these will need to be connected properly to the mesh,
+    // and we can do that all now.
+    for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
+        const aiMesh* m = mScene->mMeshes[mi];
+        if (!m->HasBones()) {
+            continue;
+        }
+        // make a deformer for this mesh
+        int64_t deformer_uid = generate_uid();
+        FBX::Node dnode("Deformer");
+        dnode.AddProperties(deformer_uid, FBX::SEPARATOR + "Deformer", "Skin");
+        dnode.AddChild("Version", int32_t(101));
+        // "acuracy"... this is not a typo....
+        dnode.AddChild("Link_DeformAcuracy", double(50));
+        dnode.AddChild("SkinningType", "Linear"); // TODO: other modes?
+        dnode.Dump(outstream, binary, indent);
+
+        // connect it
+        connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]);
+
+        // we will be indexing by vertex...
+        // but there might be a different number of "vertices"
+        // between assimp and our output FBX.
+        // this code is cut-and-pasted from the geometry section above...
+        // ideally this should not be so.
+        // ---
+        // index of original vertex in vertex data vector
+        std::vector<int32_t> vertex_indices;
+        // map of vertex value to its index in the data vector
+        std::map<aiVector3D,size_t> index_by_vertex_value;
+        int32_t index = 0;
+        for (size_t vi = 0; vi < m->mNumVertices; ++vi) {
+            aiVector3D vtx = m->mVertices[vi];
+            auto elem = index_by_vertex_value.find(vtx);
+            if (elem == index_by_vertex_value.end()) {
+                vertex_indices.push_back(index);
+                index_by_vertex_value[vtx] = index;
+                ++index;
+            } else {
+                vertex_indices.push_back(int32_t(elem->second));
+            }
+        }
+
+        // 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.
+        std::set<const aiBone*> not_in_bind_pose;
+        std::set<const aiNode*> no_offset_matrix;
+
+        // first get this mesh's position in world space,
+        // as we'll need it for each subdeformer.
+        //
+        // ...of course taking the position of the MESH doesn't make sense,
+        // as it can be instanced to many nodes.
+        // All we can do is assume no instancing,
+        // and take the first node we find that contains the mesh.
+        aiNode* mesh_node = get_node_for_mesh((unsigned int)mi, mScene->mRootNode);
+        aiMatrix4x4 mesh_xform = get_world_transform(mesh_node, mScene);
+
+        // now make a subdeformer for each bone in the skeleton
+        const std::set<const aiNode*> &skeleton = skeleton_by_mesh[mi];
+        for (const aiNode* bone_node : skeleton) {
+            // if there's a bone for this node, find it
+            const aiBone* b = nullptr;
+            for (size_t bi = 0; bi < m->mNumBones; ++bi) {
+                // TODO: this probably should index by something else
+                const std::string name(m->mBones[bi]->mName.C_Str());
+                if (node_by_bone[name] == bone_node) {
+                    b = m->mBones[bi];
+                    break;
+                }
+            }
+            if (!b) {
+                no_offset_matrix.insert(bone_node);
+            }
+
+            // start the subdeformer node
+            const int64_t subdeformer_uid = generate_uid();
+            FBX::Node sdnode("Deformer");
+            sdnode.AddProperties(
+                subdeformer_uid, FBX::SEPARATOR + "SubDeformer", "Cluster"
+            );
+            sdnode.AddChild("Version", int32_t(100));
+            sdnode.AddChild("UserData", "", "");
+
+            // add indices and weights, if any
+            if (b) {
+                std::vector<int32_t> subdef_indices;
+                std::vector<double> subdef_weights;
+                int32_t last_index = -1;
+                for (size_t wi = 0; wi < b->mNumWeights; ++wi) {
+                    int32_t vi = vertex_indices[b->mWeights[wi].mVertexId];
+                    if (vi == last_index) {
+                        // only for vertices we exported to fbx
+                        // TODO, FIXME: this assumes identically-located vertices
+                        // will always deform in the same way.
+                        // as assimp doesn't store a separate list of "positions",
+                        // there's not much that can be done about this
+                        // other than assuming that identical position means
+                        // identical vertex.
+                        continue;
+                    }
+                    subdef_indices.push_back(vi);
+                    subdef_weights.push_back(b->mWeights[wi].mWeight);
+                    last_index = vi;
+                }
+                // yes, "indexes"
+                sdnode.AddChild("Indexes", subdef_indices);
+                sdnode.AddChild("Weights", subdef_weights);
+            }
+
+            // transform is the transform of the mesh, but in bone space.
+            // if the skeleton is in the bind pose,
+            // we can take the inverse of the world-space bone transform
+            // and multiply by the world-space transform of the mesh.
+            aiMatrix4x4 bone_xform = get_world_transform(bone_node, mScene);
+            aiMatrix4x4 inverse_bone_xform = bone_xform;
+            inverse_bone_xform.Inverse();
+            aiMatrix4x4 tr = inverse_bone_xform * mesh_xform;
+
+            // this should be the same as the bone's mOffsetMatrix.
+            // if it's not the same, the skeleton isn't in the bind pose.
+            const float epsilon = 1e-4f; // some error is to be expected
+            bool bone_xform_okay = true;
+            if (b && ! tr.Equal(b->mOffsetMatrix, epsilon)) {
+                not_in_bind_pose.insert(b);
+                bone_xform_okay = false;
+            }
+
+            // 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 nonexistant),
+            // 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);
+            }
+            // note: this means we ALWAYS rely on the mesh node transform
+            // being unchanged from the time the skeleton was bound.
+            // there's not really any way around this at the moment.
+
+            // done
+            sdnode.Dump(outstream, binary, indent);
+
+            // lastly, connect to the parent deformer
+            connections.emplace_back(
+                "C", "OO", subdeformer_uid, deformer_uid
+            );
+
+            // we also need to connect the limb node to the subdeformer.
+            connections.emplace_back(
+                "C", "OO", node_uids[bone_node], subdeformer_uid
+            );
+        }
+
+        // if we cannot create a valid FBX file, simply die.
+        // this will both prevent unnecessary bug reports,
+        // and tell the user what they can do to fix the situation
+        // (i.e. export their model in the bind pose).
+        if (no_offset_matrix.size() && not_in_bind_pose.size()) {
+            std::stringstream err;
+            err << "Not enough information to construct bind pose";
+            err << " for mesh " << mi << "!";
+            err << " Transform matrix for bone \"";
+            err << (*not_in_bind_pose.begin())->mName.C_Str() << "\"";
+            if (not_in_bind_pose.size() > 1) {
+                err << " (and " << not_in_bind_pose.size() - 1 << " more)";
+            }
+            err << " does not match mOffsetMatrix,";
+            err << " and node \"";
+            err << (*no_offset_matrix.begin())->mName.C_Str() << "\"";
+            if (no_offset_matrix.size() > 1) {
+                err << " (and " << no_offset_matrix.size() - 1 << " more)";
+            }
+            err << " has no offset matrix to rely on.";
+            err << " Please ensure bones are in the bind pose to export.";
+            throw DeadlyExportError(err.str());
+        }
+
+    }
+
+    // BindPose
+    //
+    // This is a legacy system, which should be unnecessary.
+    //
+    // Somehow including it slows file loading by the official FBX SDK,
+    // and as it can reconstruct it from the deformers anyway,
+    // this is not currently included.
+    //
+    // The code is kept here in case it's useful in the future,
+    // but it's pretty much a hack anyway,
+    // as assimp doesn't store bindpose information for full skeletons.
+    //
+    /*for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
+        aiMesh* mesh = mScene->mMeshes[mi];
+        if (! mesh->HasBones()) { continue; }
+        int64_t bindpose_uid = generate_uid();
+        FBX::Node bpnode("Pose");
+        bpnode.AddProperty(bindpose_uid);
+        // note: this uid is never linked or connected to anything.
+        bpnode.AddProperty(FBX::SEPARATOR + "Pose"); // blank name
+        bpnode.AddProperty("BindPose");
+
+        bpnode.AddChild("Type", "BindPose");
+        bpnode.AddChild("Version", int32_t(100));
+
+        aiNode* mesh_node = get_node_for_mesh(mi, mScene->mRootNode);
+
+        // next get the whole skeleton for this mesh.
+        // we need it all to define the bindpose section.
+        // the FBX SDK will complain if it's missing,
+        // and also if parents of used bones don't have a subdeformer.
+        // order shouldn't matter.
+        std::set<aiNode*> skeleton;
+        for (size_t bi = 0; bi < mesh->mNumBones; ++bi) {
+            // bone node should have already been indexed
+            const aiBone* b = mesh->mBones[bi];
+            const std::string bone_name(b->mName.C_Str());
+            aiNode* parent = node_by_bone[bone_name];
+            // insert all nodes down to the root or mesh node
+            while (
+                parent
+                && parent != mScene->mRootNode
+                && parent != mesh_node
+            ) {
+                skeleton.insert(parent);
+                parent = parent->mParent;
+            }
+        }
+
+        // number of pose nodes. includes one for the mesh itself.
+        bpnode.AddChild("NbPoseNodes", int32_t(1 + skeleton.size()));
+
+        // the first pose node is always the mesh itself
+        FBX::Node pose("PoseNode");
+        pose.AddChild("Node", mesh_uids[mi]);
+        aiMatrix4x4 mesh_node_xform = get_world_transform(mesh_node, mScene);
+        pose.AddChild("Matrix", mesh_node_xform);
+        bpnode.AddChild(pose);
+
+        for (aiNode* bonenode : skeleton) {
+            // does this node have a uid yet?
+            int64_t node_uid;
+            auto node_uid_iter = node_uids.find(bonenode);
+            if (node_uid_iter != node_uids.end()) {
+                node_uid = node_uid_iter->second;
+            } else {
+                node_uid = generate_uid();
+                node_uids[bonenode] = node_uid;
+            }
+
+            // make a pose thingy
+            pose = FBX::Node("PoseNode");
+            pose.AddChild("Node", node_uid);
+            aiMatrix4x4 node_xform = get_world_transform(bonenode, mScene);
+            pose.AddChild("Matrix", node_xform);
+            bpnode.AddChild(pose);
+        }
+
+        // now write it
+        bpnode.Dump(outstream, binary, indent);
+    }*/
+
+    // TODO: cameras, lights
+
+    // write nodes (i.e. model heirarchy)
+    // start at root node
+    WriteModelNodes(
+        outstream, mScene->mRootNode, 0, limbnodes
+    );
+
+    // animations
+    //
+    // in FBX there are:
+    // * AnimationStack - corresponds to an aiAnimation
+    // * AnimationLayer - a combinable animation component
+    // * AnimationCurveNode - links the property to be animated
+    // * AnimationCurve - defines animation data for a single property value
+    //
+    // the CurveNode also provides the default value for a property,
+    // such as the X, Y, Z coordinates for animatable translation.
+    //
+    // the Curve only specifies values for one component of the property,
+    // so there will be a separate AnimationCurve for X, Y, and Z.
+    //
+    // Assimp has:
+    // * aiAnimation - basically corresponds to an AnimationStack
+    // * aiNodeAnim - defines all animation for one aiNode
+    // * aiVectorKey/aiQuatKey - define the keyframe data for T/R/S
+    //
+    // assimp has no equivalent for AnimationLayer,
+    // and these are flattened on FBX import.
+    // we can assume there will be one per AnimationStack.
+    //
+    // the aiNodeAnim contains all animation data for a single aiNode,
+    // which will correspond to three AnimationCurveNode's:
+    // one each for translation, rotation and scale.
+    // The data for each of these will be put in 9 AnimationCurve's,
+    // T.X, T.Y, T.Z, R.X, R.Y, R.Z, etc.
+
+    // AnimationStack / aiAnimation
+    std::vector<int64_t> animation_stack_uids(mScene->mNumAnimations);
+    for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
+        int64_t animstack_uid = generate_uid();
+        animation_stack_uids[ai] = animstack_uid;
+        const aiAnimation* anim = mScene->mAnimations[ai];
+
+        FBX::Node asnode("AnimationStack");
+        std::string name = anim->mName.C_Str() + FBX::SEPARATOR + "AnimStack";
+        asnode.AddProperties(animstack_uid, name, "");
+        FBX::Node p("Properties70");
+        p.AddP70time("LocalStart", 0); // assimp doesn't store this
+        p.AddP70time("LocalStop", to_ktime(anim->mDuration, anim));
+        p.AddP70time("ReferenceStart", 0);
+        p.AddP70time("ReferenceStop", to_ktime(anim->mDuration, anim));
+        asnode.AddChild(p);
+
+        // this node absurdly always pretends it has children
+        // (in this case it does, but just in case...)
+        asnode.force_has_children = true;
+        asnode.Dump(outstream, binary, indent);
+
+        // note: animation stacks are not connected to anything
+    }
+
+    // AnimationLayer - one per aiAnimation
+    std::vector<int64_t> animation_layer_uids(mScene->mNumAnimations);
+    for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
+        int64_t animlayer_uid = generate_uid();
+        animation_layer_uids[ai] = animlayer_uid;
+        FBX::Node alnode("AnimationLayer");
+        alnode.AddProperties(animlayer_uid, FBX::SEPARATOR + "AnimLayer", "");
+
+        // this node absurdly always pretends it has children
+        alnode.force_has_children = true;
+        alnode.Dump(outstream, binary, indent);
+
+        // connect to the relevant animstack
+        connections.emplace_back(
+            "C", "OO", animlayer_uid, animation_stack_uids[ai]
+        );
+    }
+
+    // AnimCurveNode - three per aiNodeAnim
+    std::vector<std::vector<std::array<int64_t,3>>> curve_node_uids;
+    for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
+        const aiAnimation* anim = mScene->mAnimations[ai];
+        const int64_t layer_uid = animation_layer_uids[ai];
+        std::vector<std::array<int64_t,3>> nodeanim_uids;
+        for (size_t nai = 0; nai < anim->mNumChannels; ++nai) {
+            const aiNodeAnim* na = anim->mChannels[nai];
+            // get the corresponding aiNode
+            const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName);
+            // and its transform
+            const aiMatrix4x4 node_xfm = get_world_transform(node, mScene);
+            aiVector3D T, R, S;
+            node_xfm.Decompose(S, R, T);
+
+            // AnimationCurveNode uids
+            std::array<int64_t,3> ids;
+            ids[0] = generate_uid(); // T
+            ids[1] = generate_uid(); // R
+            ids[2] = generate_uid(); // S
+
+            // translation
+            WriteAnimationCurveNode(outstream,
+                ids[0], "T", T, "Lcl Translation",
+                layer_uid, node_uids[node]
+            );
+
+            // rotation
+            WriteAnimationCurveNode(outstream,
+                ids[1], "R", R, "Lcl Rotation",
+                layer_uid, node_uids[node]
+            );
+
+            // scale
+            WriteAnimationCurveNode(outstream,
+                ids[2], "S", S, "Lcl Scale",
+                layer_uid, node_uids[node]
+            );
+
+            // store the uids for later use
+            nodeanim_uids.push_back(ids);
+        }
+        curve_node_uids.push_back(nodeanim_uids);
+    }
+
+    // AnimCurve - defines actual keyframe data.
+    // there's a separate curve for every component of every vector,
+    // for example a transform curvenode will have separate X/Y/Z AnimCurve's
+    for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
+        const aiAnimation* anim = mScene->mAnimations[ai];
+        for (size_t nai = 0; nai < anim->mNumChannels; ++nai) {
+            const aiNodeAnim* na = anim->mChannels[nai];
+            // get the corresponding aiNode
+            const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName);
+            // and its transform
+            const aiMatrix4x4 node_xfm = get_world_transform(node, mScene);
+            aiVector3D T, R, S;
+            node_xfm.Decompose(S, R, T);
+            const std::array<int64_t,3>& ids = curve_node_uids[ai][nai];
+
+            std::vector<int64_t> times;
+            std::vector<float> xval, yval, zval;
+
+            // position/translation
+            for (size_t ki = 0; ki < na->mNumPositionKeys; ++ki) {
+                const aiVectorKey& k = na->mPositionKeys[ki];
+                times.push_back(to_ktime(k.mTime, anim));
+                xval.push_back(k.mValue.x);
+                yval.push_back(k.mValue.y);
+                zval.push_back(k.mValue.z);
+            }
+            // one curve each for X, Y, Z
+            WriteAnimationCurve(outstream, T.x, times, xval, ids[0], "d|X");
+            WriteAnimationCurve(outstream, T.y, times, yval, ids[0], "d|Y");
+            WriteAnimationCurve(outstream, T.z, times, zval, ids[0], "d|Z");
+
+            // rotation
+            times.clear(); xval.clear(); yval.clear(); zval.clear();
+            for (size_t ki = 0; ki < na->mNumRotationKeys; ++ki) {
+                const aiQuatKey& k = na->mRotationKeys[ki];
+                times.push_back(to_ktime(k.mTime, anim));
+                // TODO: aiQuaternion method to convert to Euler...
+                aiMatrix4x4 m(k.mValue.GetMatrix());
+                aiVector3D qs, qr, qt;
+                m.Decompose(qs, qr, qt);
+                qr *= DEG;
+                xval.push_back(qr.x);
+                yval.push_back(qr.y);
+                zval.push_back(qr.z);
+            }
+            WriteAnimationCurve(outstream, R.x, times, xval, ids[1], "d|X");
+            WriteAnimationCurve(outstream, R.y, times, yval, ids[1], "d|Y");
+            WriteAnimationCurve(outstream, R.z, times, zval, ids[1], "d|Z");
+
+            // scaling/scale
+            times.clear(); xval.clear(); yval.clear(); zval.clear();
+            for (size_t ki = 0; ki < na->mNumScalingKeys; ++ki) {
+                const aiVectorKey& k = na->mScalingKeys[ki];
+                times.push_back(to_ktime(k.mTime, anim));
+                xval.push_back(k.mValue.x);
+                yval.push_back(k.mValue.y);
+                zval.push_back(k.mValue.z);
+            }
+            WriteAnimationCurve(outstream, S.x, times, xval, ids[2], "d|X");
+            WriteAnimationCurve(outstream, S.y, times, yval, ids[2], "d|Y");
+            WriteAnimationCurve(outstream, S.z, times, zval, ids[2], "d|Z");
+        }
+    }
+
+    indent = 0;
+    object_node.End(outstream, binary, indent, true);
+}
+
+// convenience map of magic node name strings to FBX properties,
+// including the expected type of transform.
+const std::map<std::string,std::pair<std::string,char>> transform_types = {
+    {"Translation", {"Lcl Translation", 't'}},
+    {"RotationOffset", {"RotationOffset", 't'}},
+    {"RotationPivot", {"RotationPivot", 't'}},
+    {"PreRotation", {"PreRotation", 'r'}},
+    {"Rotation", {"Lcl Rotation", 'r'}},
+    {"PostRotation", {"PostRotation", 'r'}},
+    {"RotationPivotInverse", {"RotationPivotInverse", 'i'}},
+    {"ScalingOffset", {"ScalingOffset", 't'}},
+    {"ScalingPivot", {"ScalingPivot", 't'}},
+    {"Scaling", {"Lcl Scaling", 's'}},
+    {"ScalingPivotInverse", {"ScalingPivotInverse", 'i'}},
+    {"GeometricScaling", {"GeometricScaling", 's'}},
+    {"GeometricRotation", {"GeometricRotation", 'r'}},
+    {"GeometricTranslation", {"GeometricTranslation", 't'}},
+    {"GeometricTranslationInverse", {"GeometricTranslationInverse", 'i'}},
+    {"GeometricRotationInverse", {"GeometricRotationInverse", 'i'}},
+    {"GeometricScalingInverse", {"GeometricScalingInverse", 'i'}}
+};
+
+// write a single model node to the stream
+void FBXExporter::WriteModelNode(
+    StreamWriterLE& outstream,
+    bool binary,
+    const aiNode* node,
+    int64_t node_uid,
+    const std::string& type,
+    const std::vector<std::pair<std::string,aiVector3D>>& transform_chain,
+    TransformInheritance inherit_type
+){
+    const aiVector3D zero = {0, 0, 0};
+    const aiVector3D one = {1, 1, 1};
+    FBX::Node m("Model");
+    std::string name = node->mName.C_Str() + FBX::SEPARATOR + "Model";
+    m.AddProperties(node_uid, name, type);
+    m.AddChild("Version", int32_t(232));
+    FBX::Node p("Properties70");
+    p.AddP70bool("RotationActive", 1);
+    p.AddP70int("DefaultAttributeIndex", 0);
+    p.AddP70enum("InheritType", inherit_type);
+    if (transform_chain.empty()) {
+        // decompose 4x4 transform matrix into TRS
+        aiVector3D t, r, s;
+        node->mTransformation.Decompose(s, r, t);
+        if (t != zero) {
+            p.AddP70(
+                "Lcl Translation", "Lcl Translation", "", "A",
+                double(t.x), double(t.y), double(t.z)
+            );
+        }
+        if (r != zero) {
+            p.AddP70(
+                "Lcl Rotation", "Lcl Rotation", "", "A",
+                double(DEG*r.x), double(DEG*r.y), double(DEG*r.z)
+            );
+        }
+        if (s != one) {
+            p.AddP70(
+                "Lcl Scaling", "Lcl Scaling", "", "A",
+                double(s.x), double(s.y), double(s.z)
+            );
+        }
+    } else {
+        // apply the transformation chain.
+        // these transformation elements are created when importing FBX,
+        // which has a complex transformation heirarchy for each node.
+        // as such we can bake the heirarchy back into the node on export.
+        for (auto &item : transform_chain) {
+            auto elem = transform_types.find(item.first);
+            if (elem == transform_types.end()) {
+                // then this is a bug
+                std::stringstream err;
+                err << "unrecognized FBX transformation type: ";
+                err << item.first;
+                throw DeadlyExportError(err.str());
+            }
+            const std::string &name = elem->second.first;
+            const aiVector3D &v = item.second;
+            if (name.compare(0, 4, "Lcl ") == 0) {
+                // special handling for animatable properties
+                p.AddP70(
+                    name, name, "", "A",
+                    double(v.x), double(v.y), double(v.z)
+                );
+            } else {
+                p.AddP70vector(name, v.x, v.y, v.z);
+            }
+        }
+    }
+    m.AddChild(p);
+
+    // not sure what these are for,
+    // but they seem to be omnipresent
+    m.AddChild("Shading", Property(true));
+    m.AddChild("Culling", Property("CullingOff"));
+
+    m.Dump(outstream, binary, 1);
+}
+
+// wrapper for WriteModelNodes to create and pass a blank transform chain
+void FBXExporter::WriteModelNodes(
+    StreamWriterLE& s,
+    const aiNode* node,
+    int64_t parent_uid,
+    const std::unordered_set<const aiNode*>& limbnodes
+) {
+    std::vector<std::pair<std::string,aiVector3D>> chain;
+    WriteModelNodes(s, node, parent_uid, limbnodes, chain);
+}
+
+void FBXExporter::WriteModelNodes(
+    StreamWriterLE& outstream,
+    const aiNode* node,
+    int64_t parent_uid,
+    const std::unordered_set<const aiNode*>& limbnodes,
+    std::vector<std::pair<std::string,aiVector3D>>& transform_chain
+) {
+    // first collapse any expanded transformation chains created by FBX import.
+    std::string node_name(node->mName.C_Str());
+    if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) {
+        auto pos = node_name.find(MAGIC_NODE_TAG) + MAGIC_NODE_TAG.size() + 1;
+        std::string type_name = node_name.substr(pos);
+        auto elem = transform_types.find(type_name);
+        if (elem == transform_types.end()) {
+            // then this is a bug and should be fixed
+            std::stringstream err;
+            err << "unrecognized FBX transformation node";
+            err << " of type " << type_name << " in node " << node_name;
+            throw DeadlyExportError(err.str());
+        }
+        aiVector3D t, r, s;
+        node->mTransformation.Decompose(s, r, t);
+        switch (elem->second.second) {
+        case 'i': // inverse
+            // we don't need to worry about the inverse matrices
+            break;
+        case 't': // translation
+            transform_chain.emplace_back(elem->first, t);
+            break;
+        case 'r': // rotation
+            r *= float(DEG);
+            transform_chain.emplace_back(elem->first, r);
+            break;
+        case 's': // scale
+            transform_chain.emplace_back(elem->first, s);
+            break;
+        default:
+            // this should never happen
+            std::stringstream err;
+            err << "unrecognized FBX transformation type code: ";
+            err << elem->second.second;
+            throw DeadlyExportError(err.str());
+        }
+        // now continue on to any child nodes
+        for (unsigned i = 0; i < node->mNumChildren; ++i) {
+            WriteModelNodes(
+                outstream,
+                node->mChildren[i],
+                parent_uid,
+                limbnodes,
+                transform_chain
+            );
+        }
+        return;
+    }
+
+    int64_t node_uid = 0;
+    // generate uid and connect to parent, if not the root node,
+    if (node != mScene->mRootNode) {
+        auto elem = node_uids.find(node);
+        if (elem != node_uids.end()) {
+            node_uid = elem->second;
+        } else {
+            node_uid = generate_uid();
+            node_uids[node] = node_uid;
+        }
+        connections.emplace_back("C", "OO", node_uid, parent_uid);
+    }
+
+    // what type of node is this?
+    if (node == mScene->mRootNode) {
+        // handled later
+    } else if (node->mNumMeshes == 1) {
+        // connect to child mesh, which should have been written previously
+        connections.emplace_back(
+            "C", "OO", mesh_uids[node->mMeshes[0]], node_uid
+        );
+        // also connect to the material for the child mesh
+        connections.emplace_back(
+            "C", "OO",
+            material_uids[mScene->mMeshes[node->mMeshes[0]]->mMaterialIndex],
+            node_uid
+        );
+        // write model node
+        WriteModelNode(
+            outstream, binary, node, node_uid, "Mesh", transform_chain
+        );
+    } else if (limbnodes.count(node)) {
+        WriteModelNode(
+            outstream, binary, node, node_uid, "LimbNode", transform_chain
+        );
+        // we also need to write a nodeattribute to mark it as a skeleton
+        int64_t node_attribute_uid = generate_uid();
+        FBX::Node na("NodeAttribute");
+        na.AddProperties(
+            node_attribute_uid, FBX::SEPARATOR + "NodeAttribute", "LimbNode"
+        );
+        na.AddChild("TypeFlags", Property("Skeleton"));
+        na.Dump(outstream, binary, 1);
+        // and connect them
+        connections.emplace_back("C", "OO", node_attribute_uid, node_uid);
+    } else {
+        // generate a null node so we can add children to it
+        WriteModelNode(
+            outstream, binary, node, node_uid, "Null", transform_chain
+        );
+    }
+
+    // if more than one child mesh, make nodes for each mesh
+    if (node->mNumMeshes > 1 || node == mScene->mRootNode) {
+        for (size_t i = 0; i < node->mNumMeshes; ++i) {
+            // make a new model node
+            int64_t new_node_uid = generate_uid();
+            // connect to parent node
+            connections.emplace_back("C", "OO", new_node_uid, node_uid);
+            // connect to child mesh, which should have been written previously
+            connections.emplace_back(
+                "C", "OO", mesh_uids[node->mMeshes[i]], new_node_uid
+            );
+            // also connect to the material for the child mesh
+            connections.emplace_back(
+                "C", "OO",
+                material_uids[
+                    mScene->mMeshes[node->mMeshes[i]]->mMaterialIndex
+                ],
+                new_node_uid
+            );
+            // write model node
+            FBX::Node m("Model");
+            // take name from mesh name, if it exists
+            std::string name = mScene->mMeshes[node->mMeshes[i]]->mName.C_Str();
+            name += FBX::SEPARATOR + "Model";
+            m.AddProperties(new_node_uid, name, "Mesh");
+            m.AddChild("Version", int32_t(232));
+            FBX::Node p("Properties70");
+            p.AddP70enum("InheritType", 1);
+            m.AddChild(p);
+            m.Dump(outstream, binary, 1);
+        }
+    }
+
+    // now recurse into children
+    for (size_t i = 0; i < node->mNumChildren; ++i) {
+        WriteModelNodes(
+            outstream, node->mChildren[i], node_uid, limbnodes
+        );
+    }
+}
+
+
+void FBXExporter::WriteAnimationCurveNode(
+    StreamWriterLE& outstream,
+    int64_t uid,
+    std::string name, // "T", "R", or "S"
+    aiVector3D default_value,
+    std::string property_name, // "Lcl Translation" etc
+    int64_t layer_uid,
+    int64_t node_uid
+) {
+    FBX::Node n("AnimationCurveNode");
+    n.AddProperties(uid, name + FBX::SEPARATOR + "AnimCurveNode", "");
+    FBX::Node p("Properties70");
+    p.AddP70numberA("d|X", default_value.x);
+    p.AddP70numberA("d|Y", default_value.y);
+    p.AddP70numberA("d|Z", default_value.z);
+    n.AddChild(p);
+    n.Dump(outstream, binary, 1);
+    // connect to layer
+    this->connections.emplace_back("C", "OO", uid, layer_uid);
+    // connect to bone
+    this->connections.emplace_back("C", "OP", uid, node_uid, property_name);
+}
+
+
+void FBXExporter::WriteAnimationCurve(
+    StreamWriterLE& outstream,
+    double default_value,
+    const std::vector<int64_t>& times,
+    const std::vector<float>& values,
+    int64_t curvenode_uid,
+    const std::string& property_link // "d|X", "d|Y", etc
+) {
+    FBX::Node n("AnimationCurve");
+    int64_t curve_uid = generate_uid();
+    n.AddProperties(curve_uid, FBX::SEPARATOR + "AnimCurve", "");
+    n.AddChild("Default", default_value);
+    n.AddChild("KeyVer", int32_t(4009));
+    n.AddChild("KeyTime", times);
+    n.AddChild("KeyValueFloat", values);
+    // TODO: keyattr flags and data (STUB for now)
+    n.AddChild("KeyAttrFlags", std::vector<int32_t>{0});
+    n.AddChild("KeyAttrDataFloat", std::vector<float>{0,0,0,0});
+    ai_assert(times.size() <= std::numeric_limits<int32_t>::max());
+    n.AddChild(
+        "KeyAttrRefCount",
+        std::vector<int32_t>{static_cast<int32_t>(times.size())}
+    );
+    n.Dump(outstream, binary, 1);
+    this->connections.emplace_back(
+        "C", "OP", curve_uid, curvenode_uid, property_link
+    );
+}
+
+
+void FBXExporter::WriteConnections ()
+{
+    // we should have completed the connection graph already,
+    // so basically just dump it here
+    if (!binary) {
+        WriteAsciiSectionHeader("Object connections");
+    }
+    // TODO: comments with names in the ascii version
+    FBX::Node conn("Connections");
+    StreamWriterLE outstream(outfile);
+    conn.Begin(outstream, binary, 0);
+    conn.BeginChildren(outstream, binary, 0);
+    for (auto &n : connections) {
+        n.Dump(outstream, binary, 1);
+    }
+    conn.End(outstream, binary, 0, !connections.empty());
+    connections.clear();
+}
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 178 - 0
code/FBXExporter.h

@@ -0,0 +1,178 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2018, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file FBXExporter.h
+* Declares the exporter class to write a scene to an fbx file
+*/
+#ifndef AI_FBXEXPORTER_H_INC
+#define AI_FBXEXPORTER_H_INC
+
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#include "FBXExportNode.h" // FBX::Node
+#include "FBXCommon.h" // FBX::TransformInheritance
+
+#include <assimp/types.h>
+//#include <assimp/material.h>
+#include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/Exceptional.h> // DeadlyExportError
+
+#include <vector>
+#include <map>
+#include <unordered_set>
+#include <memory> // shared_ptr
+#include <sstream> // stringstream
+
+struct aiScene;
+struct aiNode;
+//struct aiMaterial;
+
+namespace Assimp
+{
+    class IOSystem;
+    class IOStream;
+    class ExportProperties;
+
+    // ---------------------------------------------------------------------
+    /** Helper class to export a given scene to an FBX file. */
+    // ---------------------------------------------------------------------
+    class FBXExporter
+    {
+    public:
+        /// Constructor for a specific scene to export
+        FBXExporter(const aiScene* pScene, const ExportProperties* pProperties);
+
+        // call one of these methods to export
+        void ExportBinary(const char* pFile, IOSystem* pIOSystem);
+        void ExportAscii(const char* pFile, IOSystem* pIOSystem);
+
+    private:
+        bool binary; // whether current export is in binary or ascii format
+        const aiScene* mScene; // the scene to export
+        const ExportProperties* mProperties; // currently unused
+        std::shared_ptr<IOStream> outfile; // file to write to
+
+        std::vector<FBX::Node> connections; // conection storage
+
+        std::vector<int64_t> mesh_uids;
+        std::vector<int64_t> material_uids;
+        std::map<const aiNode*,int64_t> node_uids;
+
+        // this crude unique-ID system is actually fine
+        int64_t last_uid = 999999;
+        int64_t generate_uid() { return ++last_uid; }
+
+        // binary files have a specific header and footer,
+        // in addition to the actual data
+        void WriteBinaryHeader();
+        void WriteBinaryFooter();
+
+        // ascii files have a comment at the top
+        void WriteAsciiHeader();
+
+        // WriteAllNodes does the actual export.
+        // It just calls all the Write<Section> methods below in order.
+        void WriteAllNodes();
+
+        // Methods to write individual sections.
+        // The order here matches the order inside an FBX file.
+        // Each method corresponds to a top-level FBX section,
+        // except WriteHeader which also includes some binary-only sections
+        // and WriteFooter which is binary data only.
+        void WriteHeaderExtension();
+        // WriteFileId(); // binary-only, included in WriteHeader
+        // WriteCreationTime(); // binary-only, included in WriteHeader
+        // WriteCreator(); // binary-only, included in WriteHeader
+        void WriteGlobalSettings();
+        void WriteDocuments();
+        void WriteReferences();
+        void WriteDefinitions();
+        void WriteObjects();
+        void WriteConnections();
+        // WriteTakes(); // deprecated since at least 2015 (fbx 7.4)
+
+        // helpers
+        void WriteAsciiSectionHeader(const std::string& title);
+        void WriteModelNodes(
+            Assimp::StreamWriterLE& s,
+            const aiNode* node,
+            int64_t parent_uid,
+            const std::unordered_set<const aiNode*>& limbnodes
+        );
+        void WriteModelNodes( // usually don't call this directly
+            StreamWriterLE& s,
+            const aiNode* node,
+            int64_t parent_uid,
+            const std::unordered_set<const aiNode*>& limbnodes,
+            std::vector<std::pair<std::string,aiVector3D>>& transform_chain
+        );
+        void WriteModelNode( // nor this
+            StreamWriterLE& s,
+            bool binary,
+            const aiNode* node,
+            int64_t node_uid,
+            const std::string& type,
+            const std::vector<std::pair<std::string,aiVector3D>>& xfm_chain,
+            FBX::TransformInheritance ti_type=FBX::TransformInheritance_RSrs
+        );
+        void WriteAnimationCurveNode(
+            StreamWriterLE& outstream,
+            int64_t uid,
+            std::string name, // "T", "R", or "S"
+            aiVector3D default_value,
+            std::string property_name, // "Lcl Translation" etc
+            int64_t animation_layer_uid,
+            int64_t node_uid
+        );
+        void WriteAnimationCurve(
+            StreamWriterLE& outstream,
+            double default_value,
+            const std::vector<int64_t>& times,
+            const std::vector<float>& values,
+            int64_t curvenode_id,
+            const std::string& property_link // "d|X", "d|Y", etc
+        );
+    };
+}
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#endif // AI_FBXEXPORTER_H_INC

+ 6 - 3
code/FBXMaterial.cpp

@@ -54,6 +54,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "FBXProperties.h"
 #include <assimp/ByteSwapper.h>
 
+#include <algorithm> // std::transform
+
 namespace Assimp {
 namespace FBX {
 
@@ -82,11 +84,12 @@ Material::Material(uint64_t id, const Element& element, const Document& doc, con
 
     std::string templateName;
 
-    const char* const sh = shading.c_str();
-    if(!strcmp(sh,"phong")) {
+    // lower-case shading because Blender (for example) writes "Phong"
+    std::transform(shading.begin(), shading.end(), shading.begin(), ::tolower);
+    if(shading == "phong") {
         templateName = "Material.FbxSurfacePhong";
     }
-    else if(!strcmp(sh,"lambert")) {
+    else if(shading == "lambert") {
         templateName = "Material.FbxSurfaceLambert";
     }
     else {

+ 20 - 21
code/FBXMeshGeometry.cpp

@@ -79,14 +79,13 @@ Geometry::Geometry(uint64_t id, const Element& element, const std::string& name,
 // ------------------------------------------------------------------------------------------------
 Geometry::~Geometry()
 {
-
+    // empty
 }
 
 const Skin* Geometry::DeformerSkin() const {
     return skin;
 }
 
-
 // ------------------------------------------------------------------------------------------------
 MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc)
 : Geometry(id, element,name, doc)
@@ -186,9 +185,8 @@ MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::strin
 }
 
 // ------------------------------------------------------------------------------------------------
-MeshGeometry::~MeshGeometry()
-{
-
+MeshGeometry::~MeshGeometry() {
+    // empty
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -308,7 +306,6 @@ void MeshGeometry::ReadLayerElement(const Scope& layerElement)
         << type << ", index: " << typedIndex);
 }
 
-
 // ------------------------------------------------------------------------------------------------
 void MeshGeometry::ReadVertexData(const std::string& type, int index, const Scope& source)
 {
@@ -412,7 +409,6 @@ void MeshGeometry::ReadVertexData(const std::string& type, int index, const Scop
     }
 }
 
-
 // ------------------------------------------------------------------------------------------------
 // Lengthy utility function to read and resolve a FBX vertex data array - that is, the
 // output is in polygon vertex order. This logic is used for reading normals, UVs, colors,
@@ -428,16 +424,19 @@ void ResolveVertexDataArray(std::vector<T>& data_out, const Scope& source,
     const std::vector<unsigned int>& mapping_offsets,
     const std::vector<unsigned int>& mappings)
 {
+    bool isDirect = ReferenceInformationType == "Direct";
+    bool isIndexToDirect = ReferenceInformationType == "IndexToDirect";
 
+    // fall-back to direct data if there is no index data element
+    if ( isIndexToDirect && !HasElement( source, indexDataElementName ) ) {
+        isDirect = true;
+        isIndexToDirect = false;
+    }
 
     // handle permutations of Mapping and Reference type - it would be nice to
     // deal with this more elegantly and with less redundancy, but right
     // now it seems unavoidable.
-    if (MappingInformationType == "ByVertice" && ReferenceInformationType == "Direct") {
-        if ( !HasElement( source, indexDataElementName ) ) {
-            return;
-        }
-
+    if (MappingInformationType == "ByVertice" && isDirect) {
         std::vector<T> tempData;
 		ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName));
 
@@ -450,14 +449,11 @@ void ResolveVertexDataArray(std::vector<T>& data_out, const Scope& source,
             }
         }
     }
-    else if (MappingInformationType == "ByVertice" && ReferenceInformationType == "IndexToDirect") {
+    else if (MappingInformationType == "ByVertice" && isIndexToDirect) {
 		std::vector<T> tempData;
 		ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName));
 
         data_out.resize(vertex_count);
-        if ( !HasElement( source, indexDataElementName ) ) {
-            return;
-        }
 
         std::vector<int> uvIndices;
         ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName));
@@ -472,7 +468,7 @@ void ResolveVertexDataArray(std::vector<T>& data_out, const Scope& source,
             }
         }
     }
-    else if (MappingInformationType == "ByPolygonVertex" && ReferenceInformationType == "Direct") {
+    else if (MappingInformationType == "ByPolygonVertex" && isDirect) {
 		std::vector<T> tempData;
 		ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName));
 
@@ -485,7 +481,7 @@ void ResolveVertexDataArray(std::vector<T>& data_out, const Scope& source,
 
 		data_out.swap(tempData);
     }
-    else if (MappingInformationType == "ByPolygonVertex" && ReferenceInformationType == "IndexToDirect") {
+    else if (MappingInformationType == "ByPolygonVertex" && isIndexToDirect) {
 		std::vector<T> tempData;
 		ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName));
 
@@ -499,9 +495,14 @@ void ResolveVertexDataArray(std::vector<T>& data_out, const Scope& source,
             return;
         }
 
+        const T empty;
         unsigned int next = 0;
         for(int i : uvIndices) {
-			if (static_cast<size_t>(i) >= tempData.size()) {
+            if ( -1 == i ) {
+                data_out[ next++ ] = empty;
+                continue;
+            }
+            if (static_cast<size_t>(i) >= tempData.size()) {
                 DOMError("index out of range",&GetRequiredElement(source,indexDataElementName));
             }
 
@@ -528,7 +529,6 @@ void MeshGeometry::ReadVertexDataNormals(std::vector<aiVector3D>& normals_out, c
         m_mappings);
 }
 
-
 // ------------------------------------------------------------------------------------------------
 void MeshGeometry::ReadVertexDataUV(std::vector<aiVector2D>& uv_out, const Scope& source,
     const std::string& MappingInformationType,
@@ -543,7 +543,6 @@ void MeshGeometry::ReadVertexDataUV(std::vector<aiVector2D>& uv_out, const Scope
         m_mappings);
 }
 
-
 // ------------------------------------------------------------------------------------------------
 void MeshGeometry::ReadVertexDataColors(std::vector<aiColor4D>& colors_out, const Scope& source,
     const std::string& MappingInformationType,

+ 2 - 3
code/FBXMeshGeometry.h

@@ -68,7 +68,6 @@ private:
     const Skin* skin;
 };
 
-
 typedef std::vector<int> MatIndexArray;
 
 
@@ -95,8 +94,8 @@ public:
     *  if no tangents are specified */
     const std::vector<aiVector3D>& GetTangents() const;
 
-    /** Get a list of all vertex binormals or an empty array
-    *  if no binormals are specified */
+    /** Get a list of all vertex bi-normals or an empty array
+    *  if no bi-normals are specified */
     const std::vector<aiVector3D>& GetBinormals() const;
 
     /** Return list of faces - each entry denotes a face and specifies

+ 108 - 63
code/FileSystemFilter.h

@@ -42,13 +42,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *  Implements a filter system to filter calls to Exists() and Open()
  *  in order to improve the success rate of file opening ...
  */
+#pragma once
 #ifndef AI_FILESYSTEMFILTER_H_INC
 #define AI_FILESYSTEMFILTER_H_INC
 
-#include "../include/assimp/IOSystem.hpp"
-#include "../include/assimp/DefaultLogger.hpp"
-#include "../include/assimp/fast_atof.h"
-#include "../include/assimp/ParsingUtils.h"
+#include <assimp/IOSystem.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/fast_atof.h>
+#include <assimp/ParsingUtils.h>
 
 namespace Assimp    {
 
@@ -64,90 +65,89 @@ class FileSystemFilter : public IOSystem
 public:
     /** Constructor. */
     FileSystemFilter(const std::string& file, IOSystem* old)
-        : wrapped  (old)
-        , src_file (file)
-        , sep(wrapped->getOsSeparator())
-    {
-        ai_assert(NULL != wrapped);
+    : mWrapped  (old)
+    , mSrc_file(file)
+    , sep(mWrapped->getOsSeparator()) {
+        ai_assert(nullptr != mWrapped);
 
         // Determine base directory
-        base = src_file;
+        mBase = mSrc_file;
         std::string::size_type ss2;
-        if (std::string::npos != (ss2 = base.find_last_of("\\/")))  {
-            base.erase(ss2,base.length()-ss2);
-        }
-        else {
-            base = "";
-        //  return;
+        if (std::string::npos != (ss2 = mBase.find_last_of("\\/")))  {
+            mBase.erase(ss2,mBase.length()-ss2);
+        } else {
+            mBase = "";
         }
 
         // make sure the directory is terminated properly
         char s;
 
-        if (base.length() == 0) {
-            base = ".";
-            base += getOsSeparator();
-        }
-        else if ((s = *(base.end()-1)) != '\\' && s != '/') {
-            base += getOsSeparator();
+        if ( mBase.empty() ) {
+            mBase = ".";
+            mBase += getOsSeparator();
+        } else if ((s = *(mBase.end()-1)) != '\\' && s != '/') {
+            mBase += getOsSeparator();
         }
 
-        DefaultLogger::get()->info("Import root directory is \'" + base + "\'");
+        DefaultLogger::get()->info("Import root directory is \'" + mBase + "\'");
     }
 
     /** Destructor. */
-    ~FileSystemFilter()
-    {
-        // haha
+    ~FileSystemFilter() {
+        // empty
     }
 
     // -------------------------------------------------------------------
     /** Tests for the existence of a file at the given path. */
-    bool Exists( const char* pFile) const
-    {
+    bool Exists( const char* pFile) const {
+        ai_assert( nullptr != mWrapped );
+        
         std::string tmp = pFile;
 
         // Currently this IOSystem is also used to open THE ONE FILE.
-        if (tmp != src_file)    {
+        if (tmp != mSrc_file)    {
             BuildPath(tmp);
             Cleanup(tmp);
         }
 
-        return wrapped->Exists(tmp);
+        return mWrapped->Exists(tmp);
     }
 
     // -------------------------------------------------------------------
     /** Returns the directory separator. */
-    char getOsSeparator() const
-    {
+    char getOsSeparator() const {
         return sep;
     }
 
     // -------------------------------------------------------------------
     /** Open a new file with a given path. */
-    IOStream* Open( const char* pFile, const char* pMode = "rb")
-    {
-        ai_assert(pFile);
-        ai_assert(pMode);
+    IOStream* Open( const char* pFile, const char* pMode = "rb") {
+        ai_assert( nullptr != mWrapped );
+        if ( nullptr == pFile || nullptr == pMode ) {
+            return nullptr;
+        }
+        
+        ai_assert( nullptr != pFile );
+        ai_assert( nullptr != pMode );
 
         // First try the unchanged path
-        IOStream* s = wrapped->Open(pFile,pMode);
+        IOStream* s = mWrapped->Open(pFile,pMode);
 
-        if (!s) {
+        if (nullptr == s) {
             std::string tmp = pFile;
 
             // Try to convert between absolute and relative paths
             BuildPath(tmp);
-            s = wrapped->Open(tmp,pMode);
+            s = mWrapped->Open(tmp,pMode);
 
-            if (!s) {
+            if (nullptr == s) {
                 // Finally, look for typical issues with paths
                 // and try to correct them. This is our last
                 // resort.
                 tmp = pFile;
                 Cleanup(tmp);
                 BuildPath(tmp);
-                s = wrapped->Open(tmp,pMode);
+                s = mWrapped->Open(tmp,pMode);
             }
         }
 
@@ -156,27 +156,75 @@ public:
 
     // -------------------------------------------------------------------
     /** Closes the given file and releases all resources associated with it. */
-    void Close( IOStream* pFile)
-    {
-        return wrapped->Close(pFile);
+    void Close( IOStream* pFile) {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->Close(pFile);
     }
 
     // -------------------------------------------------------------------
     /** Compare two paths */
-    bool ComparePaths (const char* one, const char* second) const
-    {
-        return wrapped->ComparePaths (one,second);
+    bool ComparePaths (const char* one, const char* second) const {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->ComparePaths (one,second);
     }
 
-private:
+    // -------------------------------------------------------------------
+    /** Pushes a new directory onto the directory stack. */
+    bool PushDirectory(const std::string &path ) {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->PushDirectory(path);
+    }
+
+    // -------------------------------------------------------------------
+    /** Returns the top directory from the stack. */
+    const std::string &CurrentDirectory() const {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->CurrentDirectory();
+    }
+
+    // -------------------------------------------------------------------
+    /** Returns the number of directories stored on the stack. */
+    size_t StackSize() const {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->StackSize();
+    }
+
+    // -------------------------------------------------------------------
+    /** Pops the top directory from the stack. */
+    bool PopDirectory() {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->PopDirectory();
+    }
 
+    // -------------------------------------------------------------------
+    /** Creates an new directory at the given path. */
+    bool CreateDirectory(const std::string &path) {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->CreateDirectory(path);
+    }
+
+    // -------------------------------------------------------------------
+    /** Will change the current directory to the given path. */
+    bool ChangeDirectory(const std::string &path) {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->ChangeDirectory(path);
+    }
+
+    // -------------------------------------------------------------------
+    /** Delete file. */
+    bool DeleteFile(const std::string &file) {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->DeleteFile(file);
+    }
+
+private:
     // -------------------------------------------------------------------
     /** Build a valid path from a given relative or absolute path.
      */
-    void BuildPath (std::string& in) const
-    {
+    void BuildPath (std::string& in) const {
+        ai_assert( nullptr != mWrapped );
         // if we can already access the file, great.
-        if (in.length() < 3 || wrapped->Exists(in)) {
+        if (in.length() < 3 || mWrapped->Exists(in)) {
             return;
         }
 
@@ -184,8 +232,8 @@ private:
         if (in[1] != ':') {
 
             // append base path and try
-            const std::string tmp = base + in;
-            if (wrapped->Exists(tmp)) {
+            const std::string tmp = mBase + in;
+            if (mWrapped->Exists(tmp)) {
                 in = tmp;
                 return;
             }
@@ -207,7 +255,7 @@ private:
             std::string::size_type last_dirsep = std::string::npos;
 
             while(true) {
-                tmp = base;
+                tmp = mBase;
                 tmp += sep;
 
                 std::string::size_type dirsep = in.rfind('/', last_dirsep);
@@ -223,7 +271,7 @@ private:
                 last_dirsep = dirsep-1;
 
                 tmp += in.substr(dirsep+1, in.length()-pos);
-                if (wrapped->Exists(tmp)) {
+                if (mWrapped->Exists(tmp)) {
                     in = tmp;
                     return;
                 }
@@ -236,15 +284,14 @@ private:
     // -------------------------------------------------------------------
     /** Cleanup the given path
      */
-    void Cleanup (std::string& in) const
-    {
-        char last = 0;
+    void Cleanup (std::string& in) const {
         if(in.empty()) {
             return;
         }
 
         // Remove a very common issue when we're parsing file names: spaces at the
         // beginning of the path.
+        char last = 0;
         std::string::iterator it = in.begin();
         while (IsSpaceOrNewLine( *it ))++it;
         if (it != in.begin()) {
@@ -274,9 +321,7 @@ private:
                     it = in.erase(it);
                     --it;
                 }
-            }
-            else if (*it == '%' && in.end() - it > 2) {
-
+            } else if (*it == '%' && in.end() - it > 2) {
                 // Hex sequence in URIs
                 if( IsHex((&*it)[0]) && IsHex((&*it)[1]) ) {
                     *it = HexOctetToDecimal(&*it);
@@ -290,8 +335,8 @@ private:
     }
 
 private:
-    IOSystem* wrapped;
-    std::string src_file, base;
+    IOSystem *mWrapped;
+    std::string mSrc_file, mBase;
     char sep;
 };
 

+ 7 - 7
code/FindInstancesProcess.h

@@ -60,9 +60,9 @@ namespace Assimp    {
  *  @param in Input mesh
  *  @return Hash.
  */
-inline uint64_t GetMeshHash(aiMesh* in)
-{
-    ai_assert(NULL != in);
+inline
+uint64_t GetMeshHash(aiMesh* in) {
+    ai_assert(nullptr != in);
 
     // ... get an unique value representing the vertex format of the mesh
     const unsigned int fhash = GetMeshVFormatUnique(in);
@@ -78,14 +78,14 @@ inline uint64_t GetMeshHash(aiMesh* in)
 /** @brief Perform a component-wise comparison of two arrays
  *
  *  @param first First array
- *  @param second Second aray
+ *  @param second Second array
  *  @param size Size of both arrays
  *  @param e Epsilon
  *  @return true if the arrays are identical
  */
-inline bool CompareArrays(const aiVector3D* first, const aiVector3D* second,
-    unsigned int size, float e)
-{
+inline
+bool CompareArrays(const aiVector3D* first, const aiVector3D* second,
+        unsigned int size, float e) {
     for (const aiVector3D* end = first+size; first != end; ++first,++second) {
         if ( (*first - *second).SquareLength() >= e)
             return false;

+ 8 - 12
code/IRRLoader.cpp

@@ -100,26 +100,22 @@ IRRImporter::~IRRImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool IRRImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
-{
-    /* NOTE: A simple check for the file extension is not enough
-     * here. Irrmesh and irr are easy, but xml is too generic
-     * and could be collada, too. So we need to open the file and
-     * search for typical tokens.
-     */
+bool IRRImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
     const std::string extension = GetExtension(pFile);
-
-    if (extension == "irr")return true;
-    else if (extension == "xml" || checkSig)
-    {
+    if ( extension == "irr" ) {
+        return true;
+    } else if (extension == "xml" || checkSig) {
         /*  If CanRead() is called in order to check whether we
          *  support a specific file extension in general pIOHandler
          *  might be NULL and it's our duty to return true here.
          */
-        if (!pIOHandler)return true;
+        if ( nullptr == pIOHandler ) {
+            return true;
+        }
         const char* tokens[] = {"irr_scene"};
         return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
     }
+
     return false;
 }
 

+ 2 - 14
code/Importer.cpp

@@ -190,7 +190,7 @@ Importer::~Importer()
     delete pimpl->mIOHandler;
     delete pimpl->mProgressHandler;
 
-    // Kill imported scene. Destructors should do that recursivly
+    // Kill imported scene. Destructor's should do that recursively
     delete pimpl->mScene;
 
     // Delete shared post-processing data
@@ -200,18 +200,6 @@ Importer::~Importer()
     delete pimpl;
 }
 
-// ------------------------------------------------------------------------------------------------
-// Copy constructor - copies the config of another Importer, not the scene
-Importer::Importer(const Importer &other)
-	: pimpl(NULL) {
-    new(this) Importer();
-
-    pimpl->mIntProperties    = other.pimpl->mIntProperties;
-    pimpl->mFloatProperties  = other.pimpl->mFloatProperties;
-    pimpl->mStringProperties = other.pimpl->mStringProperties;
-    pimpl->mMatrixProperties = other.pimpl->mMatrixProperties;
-}
-
 // ------------------------------------------------------------------------------------------------
 // Register a custom post-processing step
 aiReturn Importer::RegisterPPStep(BaseProcess* pImp)
@@ -634,7 +622,6 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
             if (s != std::string::npos) {
                 DefaultLogger::get()->info("File extension not known, trying signature-based detection");
                 for( unsigned int a = 0; a < pimpl->mImporter.size(); a++)  {
-
                     if( pimpl->mImporter[a]->CanRead( pFile, pimpl->mIOHandler, true)) {
                         imp = pimpl->mImporter[a];
                         break;
@@ -959,6 +946,7 @@ BaseImporter* Importer::GetImporter (const char* szExtension) const
 size_t Importer::GetImporterIndex (const char* szExtension) const
 {
     ai_assert(szExtension);
+
     ASSIMP_BEGIN_EXCEPTION_REGION();
 
     // skip over wildcard and dot characters at string head --

+ 4 - 4
code/Importer/IFC/IFCLoader.cpp

@@ -141,7 +141,8 @@ bool IFCImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool
         // it is only unambiguous as long as we don't support any further
         // file formats with STEP as their encoding.
         const char* tokens[] = {"ISO-10303-21"};
-        return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
+        const bool found( SearchFileHeaderForToken( pIOHandler, pFile, tokens, 1 ) );
+        return found;
     }
     return false;
 }
@@ -582,9 +583,8 @@ typedef std::map<std::string, std::string> Metadata;
 
 // ------------------------------------------------------------------------------------------------
 void ProcessMetadata(const Schema_2x3::ListOf< Schema_2x3::Lazy< Schema_2x3::IfcProperty >, 1, 0 >& set, ConversionData& conv, Metadata& properties,
-    const std::string& prefix = "",
-    unsigned int nest = 0)
-{
+        const std::string& prefix = "",
+        unsigned int nest = 0) {
     for(const Schema_2x3::IfcProperty& property : set) {
         const std::string& key = prefix.length() > 0 ? (prefix + "." + property.Name) : property.Name;
         if (const Schema_2x3::IfcPropertySingleValue* const singleValue = property.ToPtr<Schema_2x3::IfcPropertySingleValue>()) {

+ 3 - 2
code/Importer/IFC/IFCReaderGen_4.cpp

@@ -40,13 +40,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 /** MACHINE-GENERATED by scripts/ICFImporter/CppGenerator.py */
 
-#include "AssimpPCH.h"
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 
-#include "IFCReaderGen.h"
+#include "AssimpPCH.h"
+#include "IFCReaderGen4.h"
 
 namespace Assimp {
 using namespace IFC;
+using namespace ::Assimp::IFC::Schema_4;
 
 namespace {
 

+ 575 - 572
code/Importer/IFC/IFCReaderGen_4.h

@@ -47,6 +47,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 namespace Assimp {
 namespace IFC {
+namespace Schema_4 {
+
 	using namespace STEP;
 	using namespace STEP::EXPRESS;
 	
@@ -4866,583 +4868,584 @@ namespace IFC {
 } //! IFC
 namespace STEP {
 
-	// ******************************************************************************
-	// Converter stubs
-	// ******************************************************************************
-	
+    // ******************************************************************************
+    // Converter stubs
+    // ******************************************************************************
+
 #define DECL_CONV_STUB(type) template <> size_t GenericFill<IFC::type>(const STEP::DB& db, const EXPRESS::LIST& params, IFC::type* in)
-	
-	DECL_CONV_STUB(IfcRoot);
-	DECL_CONV_STUB(IfcObjectDefinition);
-	DECL_CONV_STUB(IfcObject);
-	DECL_CONV_STUB(IfcControl);
-	DECL_CONV_STUB(IfcActionRequest);
-	DECL_CONV_STUB(IfcActor);
-	DECL_CONV_STUB(IfcProduct);
-	DECL_CONV_STUB(IfcElement);
-	DECL_CONV_STUB(IfcDistributionElement);
-	DECL_CONV_STUB(IfcDistributionControlElement);
-	DECL_CONV_STUB(IfcActuator);
-	DECL_CONV_STUB(IfcTypeObject);
-	DECL_CONV_STUB(IfcTypeProduct);
-	DECL_CONV_STUB(IfcElementType);
-	DECL_CONV_STUB(IfcDistributionElementType);
-	DECL_CONV_STUB(IfcDistributionControlElementType);
-	DECL_CONV_STUB(IfcActuatorType);
-	DECL_CONV_STUB(IfcRepresentationItem);
-	DECL_CONV_STUB(IfcGeometricRepresentationItem);
-	DECL_CONV_STUB(IfcSolidModel);
-	DECL_CONV_STUB(IfcManifoldSolidBrep);
-	DECL_CONV_STUB(IfcAdvancedBrep);
-	DECL_CONV_STUB(IfcAdvancedBrepWithVoids);
-	DECL_CONV_STUB(IfcTopologicalRepresentationItem);
-	DECL_CONV_STUB(IfcFace);
-	DECL_CONV_STUB(IfcFaceSurface);
-	DECL_CONV_STUB(IfcAdvancedFace);
-	DECL_CONV_STUB(IfcDistributionFlowElement);
-	DECL_CONV_STUB(IfcFlowTerminal);
-	DECL_CONV_STUB(IfcAirTerminal);
-	DECL_CONV_STUB(IfcFlowController);
-	DECL_CONV_STUB(IfcAirTerminalBox);
-	DECL_CONV_STUB(IfcDistributionFlowElementType);
-	DECL_CONV_STUB(IfcFlowControllerType);
-	DECL_CONV_STUB(IfcAirTerminalBoxType);
-	DECL_CONV_STUB(IfcFlowTerminalType);
-	DECL_CONV_STUB(IfcAirTerminalType);
-	DECL_CONV_STUB(IfcEnergyConversionDevice);
-	DECL_CONV_STUB(IfcAirToAirHeatRecovery);
-	DECL_CONV_STUB(IfcEnergyConversionDeviceType);
-	DECL_CONV_STUB(IfcAirToAirHeatRecoveryType);
-	DECL_CONV_STUB(IfcAlarm);
-	DECL_CONV_STUB(IfcAlarmType);
-	DECL_CONV_STUB(IfcAnnotation);
-	DECL_CONV_STUB(IfcAnnotationFillArea);
-	DECL_CONV_STUB(IfcProfileDef);
-	DECL_CONV_STUB(IfcArbitraryClosedProfileDef);
-	DECL_CONV_STUB(IfcArbitraryOpenProfileDef);
-	DECL_CONV_STUB(IfcArbitraryProfileDefWithVoids);
-	DECL_CONV_STUB(IfcGroup);
-	DECL_CONV_STUB(IfcAsset);
-	DECL_CONV_STUB(IfcParameterizedProfileDef);
-	DECL_CONV_STUB(IfcAsymmetricIShapeProfileDef);
-	DECL_CONV_STUB(IfcAudioVisualAppliance);
-	DECL_CONV_STUB(IfcAudioVisualApplianceType);
-	DECL_CONV_STUB(IfcPlacement);
-	DECL_CONV_STUB(IfcAxis1Placement);
-	DECL_CONV_STUB(IfcAxis2Placement2D);
-	DECL_CONV_STUB(IfcAxis2Placement3D);
-	DECL_CONV_STUB(IfcCurve);
-	DECL_CONV_STUB(IfcBoundedCurve);
-	DECL_CONV_STUB(IfcBSplineCurve);
-	DECL_CONV_STUB(IfcBSplineCurveWithKnots);
-	DECL_CONV_STUB(IfcSurface);
-	DECL_CONV_STUB(IfcBoundedSurface);
-	DECL_CONV_STUB(IfcBSplineSurface);
-	DECL_CONV_STUB(IfcBSplineSurfaceWithKnots);
-	DECL_CONV_STUB(IfcBuildingElement);
-	DECL_CONV_STUB(IfcBeam);
-	DECL_CONV_STUB(IfcBeamStandardCase);
-	DECL_CONV_STUB(IfcBuildingElementType);
-	DECL_CONV_STUB(IfcBeamType);
-	DECL_CONV_STUB(IfcPresentationItem);
-	DECL_CONV_STUB(IfcCsgPrimitive3D);
-	DECL_CONV_STUB(IfcBlock);
-	DECL_CONV_STUB(IfcBoiler);
-	DECL_CONV_STUB(IfcBoilerType);
-	DECL_CONV_STUB(IfcBooleanResult);
-	DECL_CONV_STUB(IfcBooleanClippingResult);
-	DECL_CONV_STUB(IfcCompositeCurve);
-	DECL_CONV_STUB(IfcCompositeCurveOnSurface);
-	DECL_CONV_STUB(IfcBoundaryCurve);
-	DECL_CONV_STUB(IfcBoundingBox);
-	DECL_CONV_STUB(IfcHalfSpaceSolid);
-	DECL_CONV_STUB(IfcBoxedHalfSpace);
-	DECL_CONV_STUB(IfcSpatialElement);
-	DECL_CONV_STUB(IfcSpatialStructureElement);
-	DECL_CONV_STUB(IfcBuilding);
-	DECL_CONV_STUB(IfcElementComponent);
-	DECL_CONV_STUB(IfcBuildingElementPart);
-	DECL_CONV_STUB(IfcElementComponentType);
-	DECL_CONV_STUB(IfcBuildingElementPartType);
-	DECL_CONV_STUB(IfcBuildingElementProxy);
-	DECL_CONV_STUB(IfcBuildingElementProxyType);
-	DECL_CONV_STUB(IfcBuildingStorey);
-	DECL_CONV_STUB(IfcSystem);
-	DECL_CONV_STUB(IfcBuildingSystem);
-	DECL_CONV_STUB(IfcBurner);
-	DECL_CONV_STUB(IfcBurnerType);
-	DECL_CONV_STUB(IfcCShapeProfileDef);
-	DECL_CONV_STUB(IfcFlowFitting);
-	DECL_CONV_STUB(IfcCableCarrierFitting);
-	DECL_CONV_STUB(IfcFlowFittingType);
-	DECL_CONV_STUB(IfcCableCarrierFittingType);
-	DECL_CONV_STUB(IfcFlowSegment);
-	DECL_CONV_STUB(IfcCableCarrierSegment);
-	DECL_CONV_STUB(IfcFlowSegmentType);
-	DECL_CONV_STUB(IfcCableCarrierSegmentType);
-	DECL_CONV_STUB(IfcCableFitting);
-	DECL_CONV_STUB(IfcCableFittingType);
-	DECL_CONV_STUB(IfcCableSegment);
-	DECL_CONV_STUB(IfcCableSegmentType);
-	DECL_CONV_STUB(IfcPoint);
-	DECL_CONV_STUB(IfcCartesianPoint);
-	DECL_CONV_STUB(IfcCartesianPointList);
-	DECL_CONV_STUB(IfcCartesianPointList2D);
-	DECL_CONV_STUB(IfcCartesianPointList3D);
-	DECL_CONV_STUB(IfcCartesianTransformationOperator);
-	DECL_CONV_STUB(IfcCartesianTransformationOperator2D);
-	DECL_CONV_STUB(IfcCartesianTransformationOperator2DnonUniform);
-	DECL_CONV_STUB(IfcCartesianTransformationOperator3D);
-	DECL_CONV_STUB(IfcCartesianTransformationOperator3DnonUniform);
-	DECL_CONV_STUB(IfcCenterLineProfileDef);
-	DECL_CONV_STUB(IfcChiller);
-	DECL_CONV_STUB(IfcChillerType);
-	DECL_CONV_STUB(IfcChimney);
-	DECL_CONV_STUB(IfcChimneyType);
-	DECL_CONV_STUB(IfcConic);
-	DECL_CONV_STUB(IfcCircle);
-	DECL_CONV_STUB(IfcCircleProfileDef);
-	DECL_CONV_STUB(IfcCircleHollowProfileDef);
-	DECL_CONV_STUB(IfcCivilElement);
-	DECL_CONV_STUB(IfcCivilElementType);
-	DECL_CONV_STUB(IfcConnectedFaceSet);
-	DECL_CONV_STUB(IfcClosedShell);
-	DECL_CONV_STUB(IfcCoil);
-	DECL_CONV_STUB(IfcCoilType);
-	DECL_CONV_STUB(IfcColourSpecification);
-	DECL_CONV_STUB(IfcColourRgb);
-	DECL_CONV_STUB(IfcColumn);
-	DECL_CONV_STUB(IfcColumnStandardCase);
-	DECL_CONV_STUB(IfcColumnType);
-	DECL_CONV_STUB(IfcCommunicationsAppliance);
-	DECL_CONV_STUB(IfcCommunicationsApplianceType);
-	DECL_CONV_STUB(IfcPropertyAbstraction);
-	DECL_CONV_STUB(IfcProperty);
-	DECL_CONV_STUB(IfcComplexProperty);
-	DECL_CONV_STUB(IfcPropertyDefinition);
-	DECL_CONV_STUB(IfcCompositeCurveSegment);
-	DECL_CONV_STUB(IfcCompositeProfileDef);
-	DECL_CONV_STUB(IfcFlowMovingDevice);
-	DECL_CONV_STUB(IfcCompressor);
-	DECL_CONV_STUB(IfcFlowMovingDeviceType);
-	DECL_CONV_STUB(IfcCompressorType);
-	DECL_CONV_STUB(IfcCondenser);
-	DECL_CONV_STUB(IfcCondenserType);
-	DECL_CONV_STUB(IfcResource);
-	DECL_CONV_STUB(IfcConstructionResource);
-	DECL_CONV_STUB(IfcConstructionEquipmentResource);
-	DECL_CONV_STUB(IfcTypeResource);
-	DECL_CONV_STUB(IfcConstructionResourceType);
-	DECL_CONV_STUB(IfcConstructionEquipmentResourceType);
-	DECL_CONV_STUB(IfcConstructionMaterialResource);
-	DECL_CONV_STUB(IfcConstructionMaterialResourceType);
-	DECL_CONV_STUB(IfcConstructionProductResource);
-	DECL_CONV_STUB(IfcConstructionProductResourceType);
-	DECL_CONV_STUB(IfcContext);
-	DECL_CONV_STUB(IfcNamedUnit);
-	DECL_CONV_STUB(IfcContextDependentUnit);
-	DECL_CONV_STUB(IfcController);
-	DECL_CONV_STUB(IfcControllerType);
-	DECL_CONV_STUB(IfcConversionBasedUnit);
-	DECL_CONV_STUB(IfcConversionBasedUnitWithOffset);
-	DECL_CONV_STUB(IfcCooledBeam);
-	DECL_CONV_STUB(IfcCooledBeamType);
-	DECL_CONV_STUB(IfcCoolingTower);
-	DECL_CONV_STUB(IfcCoolingTowerType);
-	DECL_CONV_STUB(IfcCostItem);
-	DECL_CONV_STUB(IfcCostSchedule);
-	DECL_CONV_STUB(IfcCovering);
-	DECL_CONV_STUB(IfcCoveringType);
-	DECL_CONV_STUB(IfcCrewResource);
-	DECL_CONV_STUB(IfcCrewResourceType);
-	DECL_CONV_STUB(IfcCsgSolid);
-	DECL_CONV_STUB(IfcCurtainWall);
-	DECL_CONV_STUB(IfcCurtainWallType);
-	DECL_CONV_STUB(IfcCurveBoundedPlane);
-	DECL_CONV_STUB(IfcCurveBoundedSurface);
-	DECL_CONV_STUB(IfcPresentationStyle);
-	DECL_CONV_STUB(IfcElementarySurface);
-	DECL_CONV_STUB(IfcCylindricalSurface);
-	DECL_CONV_STUB(IfcDamper);
-	DECL_CONV_STUB(IfcDamperType);
-	DECL_CONV_STUB(IfcDerivedProfileDef);
-	DECL_CONV_STUB(IfcDirection);
-	DECL_CONV_STUB(IfcDiscreteAccessory);
-	DECL_CONV_STUB(IfcDiscreteAccessoryType);
-	DECL_CONV_STUB(IfcDistributionChamberElement);
-	DECL_CONV_STUB(IfcDistributionChamberElementType);
-	DECL_CONV_STUB(IfcDistributionSystem);
-	DECL_CONV_STUB(IfcDistributionCircuit);
-	DECL_CONV_STUB(IfcPort);
-	DECL_CONV_STUB(IfcDistributionPort);
-	DECL_CONV_STUB(IfcDoor);
-	DECL_CONV_STUB(IfcPropertySetDefinition);
-	DECL_CONV_STUB(IfcDoorStandardCase);
-	DECL_CONV_STUB(IfcDoorStyle);
-	DECL_CONV_STUB(IfcDoorType);
-	DECL_CONV_STUB(IfcDuctFitting);
-	DECL_CONV_STUB(IfcDuctFittingType);
-	DECL_CONV_STUB(IfcDuctSegment);
-	DECL_CONV_STUB(IfcDuctSegmentType);
-	DECL_CONV_STUB(IfcFlowTreatmentDevice);
-	DECL_CONV_STUB(IfcDuctSilencer);
-	DECL_CONV_STUB(IfcFlowTreatmentDeviceType);
-	DECL_CONV_STUB(IfcDuctSilencerType);
-	DECL_CONV_STUB(IfcEdge);
-	DECL_CONV_STUB(IfcEdgeCurve);
-	DECL_CONV_STUB(IfcLoop);
-	DECL_CONV_STUB(IfcEdgeLoop);
-	DECL_CONV_STUB(IfcElectricAppliance);
-	DECL_CONV_STUB(IfcElectricApplianceType);
-	DECL_CONV_STUB(IfcElectricDistributionBoard);
-	DECL_CONV_STUB(IfcElectricDistributionBoardType);
-	DECL_CONV_STUB(IfcFlowStorageDevice);
-	DECL_CONV_STUB(IfcElectricFlowStorageDevice);
-	DECL_CONV_STUB(IfcFlowStorageDeviceType);
-	DECL_CONV_STUB(IfcElectricFlowStorageDeviceType);
-	DECL_CONV_STUB(IfcElectricGenerator);
-	DECL_CONV_STUB(IfcElectricGeneratorType);
-	DECL_CONV_STUB(IfcElectricMotor);
-	DECL_CONV_STUB(IfcElectricMotorType);
-	DECL_CONV_STUB(IfcElectricTimeControl);
-	DECL_CONV_STUB(IfcElectricTimeControlType);
-	DECL_CONV_STUB(IfcElementAssembly);
-	DECL_CONV_STUB(IfcElementAssemblyType);
-	DECL_CONV_STUB(IfcQuantitySet);
-	DECL_CONV_STUB(IfcElementQuantity);
-	DECL_CONV_STUB(IfcEllipse);
-	DECL_CONV_STUB(IfcEllipseProfileDef);
-	DECL_CONV_STUB(IfcEngine);
-	DECL_CONV_STUB(IfcEngineType);
-	DECL_CONV_STUB(IfcEvaporativeCooler);
-	DECL_CONV_STUB(IfcEvaporativeCoolerType);
-	DECL_CONV_STUB(IfcEvaporator);
-	DECL_CONV_STUB(IfcEvaporatorType);
-	DECL_CONV_STUB(IfcProcess);
-	DECL_CONV_STUB(IfcEvent);
-	DECL_CONV_STUB(IfcTypeProcess);
-	DECL_CONV_STUB(IfcEventType);
-	DECL_CONV_STUB(IfcExternalSpatialStructureElement);
-	DECL_CONV_STUB(IfcExternalSpatialElement);
-	DECL_CONV_STUB(IfcSweptAreaSolid);
-	DECL_CONV_STUB(IfcExtrudedAreaSolid);
-	DECL_CONV_STUB(IfcExtrudedAreaSolidTapered);
-	DECL_CONV_STUB(IfcFaceBasedSurfaceModel);
-	DECL_CONV_STUB(IfcFaceBound);
-	DECL_CONV_STUB(IfcFaceOuterBound);
-	DECL_CONV_STUB(IfcFacetedBrep);
-	DECL_CONV_STUB(IfcFacetedBrepWithVoids);
-	DECL_CONV_STUB(IfcFan);
-	DECL_CONV_STUB(IfcFanType);
-	DECL_CONV_STUB(IfcFastener);
-	DECL_CONV_STUB(IfcFastenerType);
-	DECL_CONV_STUB(IfcFeatureElement);
-	DECL_CONV_STUB(IfcFeatureElementAddition);
-	DECL_CONV_STUB(IfcFeatureElementSubtraction);
-	DECL_CONV_STUB(IfcFillAreaStyleHatching);
-	DECL_CONV_STUB(IfcFillAreaStyleTiles);
-	DECL_CONV_STUB(IfcFilter);
-	DECL_CONV_STUB(IfcFilterType);
-	DECL_CONV_STUB(IfcFireSuppressionTerminal);
-	DECL_CONV_STUB(IfcFireSuppressionTerminalType);
-	DECL_CONV_STUB(IfcFixedReferenceSweptAreaSolid);
-	DECL_CONV_STUB(IfcFlowInstrument);
-	DECL_CONV_STUB(IfcFlowInstrumentType);
-	DECL_CONV_STUB(IfcFlowMeter);
-	DECL_CONV_STUB(IfcFlowMeterType);
-	DECL_CONV_STUB(IfcFooting);
-	DECL_CONV_STUB(IfcFootingType);
-	DECL_CONV_STUB(IfcFurnishingElement);
-	DECL_CONV_STUB(IfcFurnishingElementType);
-	DECL_CONV_STUB(IfcFurniture);
-	DECL_CONV_STUB(IfcFurnitureType);
-	DECL_CONV_STUB(IfcGeographicElement);
-	DECL_CONV_STUB(IfcGeographicElementType);
-	DECL_CONV_STUB(IfcGeometricSet);
-	DECL_CONV_STUB(IfcGeometricCurveSet);
-	DECL_CONV_STUB(IfcRepresentationContext);
-	DECL_CONV_STUB(IfcGeometricRepresentationContext);
-	DECL_CONV_STUB(IfcGeometricRepresentationSubContext);
-	DECL_CONV_STUB(IfcGrid);
-	DECL_CONV_STUB(IfcObjectPlacement);
-	DECL_CONV_STUB(IfcGridPlacement);
-	DECL_CONV_STUB(IfcHeatExchanger);
-	DECL_CONV_STUB(IfcHeatExchangerType);
-	DECL_CONV_STUB(IfcHumidifier);
-	DECL_CONV_STUB(IfcHumidifierType);
-	DECL_CONV_STUB(IfcIShapeProfileDef);
-	DECL_CONV_STUB(IfcIndexedPolyCurve);
-	DECL_CONV_STUB(IfcTessellatedItem);
-	DECL_CONV_STUB(IfcIndexedPolygonalFace);
-	DECL_CONV_STUB(IfcIndexedPolygonalFaceWithVoids);
-	DECL_CONV_STUB(IfcInterceptor);
-	DECL_CONV_STUB(IfcInterceptorType);
-	DECL_CONV_STUB(IfcSurfaceCurve);
-	DECL_CONV_STUB(IfcIntersectionCurve);
-	DECL_CONV_STUB(IfcInventory);
-	DECL_CONV_STUB(IfcJunctionBox);
-	DECL_CONV_STUB(IfcJunctionBoxType);
-	DECL_CONV_STUB(IfcLShapeProfileDef);
-	DECL_CONV_STUB(IfcLaborResource);
-	DECL_CONV_STUB(IfcLaborResourceType);
-	DECL_CONV_STUB(IfcLamp);
-	DECL_CONV_STUB(IfcLampType);
-	DECL_CONV_STUB(IfcLightFixture);
-	DECL_CONV_STUB(IfcLightFixtureType);
-	DECL_CONV_STUB(IfcLightSource);
-	DECL_CONV_STUB(IfcLightSourceAmbient);
-	DECL_CONV_STUB(IfcLightSourceDirectional);
-	DECL_CONV_STUB(IfcLightSourceGoniometric);
-	DECL_CONV_STUB(IfcLightSourcePositional);
-	DECL_CONV_STUB(IfcLightSourceSpot);
-	DECL_CONV_STUB(IfcLine);
-	DECL_CONV_STUB(IfcLocalPlacement);
-	DECL_CONV_STUB(IfcMappedItem);
-	DECL_CONV_STUB(IfcProductRepresentation);
-	DECL_CONV_STUB(IfcMaterialDefinitionRepresentation);
-	DECL_CONV_STUB(IfcMeasureWithUnit);
-	DECL_CONV_STUB(IfcMechanicalFastener);
-	DECL_CONV_STUB(IfcMechanicalFastenerType);
-	DECL_CONV_STUB(IfcMedicalDevice);
-	DECL_CONV_STUB(IfcMedicalDeviceType);
-	DECL_CONV_STUB(IfcMember);
-	DECL_CONV_STUB(IfcMemberStandardCase);
-	DECL_CONV_STUB(IfcMemberType);
-	DECL_CONV_STUB(IfcMirroredProfileDef);
-	DECL_CONV_STUB(IfcMotorConnection);
-	DECL_CONV_STUB(IfcMotorConnectionType);
-	DECL_CONV_STUB(IfcOccupant);
-	DECL_CONV_STUB(IfcOffsetCurve2D);
-	DECL_CONV_STUB(IfcOffsetCurve3D);
-	DECL_CONV_STUB(IfcOpenShell);
-	DECL_CONV_STUB(IfcOpeningElement);
-	DECL_CONV_STUB(IfcOpeningStandardCase);
-	DECL_CONV_STUB(IfcOrientedEdge);
-	DECL_CONV_STUB(IfcOuterBoundaryCurve);
-	DECL_CONV_STUB(IfcOutlet);
-	DECL_CONV_STUB(IfcOutletType);
-	DECL_CONV_STUB(IfcPath);
-	DECL_CONV_STUB(IfcPcurve);
-	DECL_CONV_STUB(IfcPerformanceHistory);
-	DECL_CONV_STUB(IfcPermit);
-	DECL_CONV_STUB(IfcPile);
-	DECL_CONV_STUB(IfcPileType);
-	DECL_CONV_STUB(IfcPipeFitting);
-	DECL_CONV_STUB(IfcPipeFittingType);
-	DECL_CONV_STUB(IfcPipeSegment);
-	DECL_CONV_STUB(IfcPipeSegmentType);
-	DECL_CONV_STUB(IfcPlanarExtent);
-	DECL_CONV_STUB(IfcPlanarBox);
-	DECL_CONV_STUB(IfcPlane);
-	DECL_CONV_STUB(IfcPlate);
-	DECL_CONV_STUB(IfcPlateStandardCase);
-	DECL_CONV_STUB(IfcPlateType);
-	DECL_CONV_STUB(IfcPointOnCurve);
-	DECL_CONV_STUB(IfcPointOnSurface);
-	DECL_CONV_STUB(IfcPolyLoop);
-	DECL_CONV_STUB(IfcPolygonalBoundedHalfSpace);
-	DECL_CONV_STUB(IfcTessellatedFaceSet);
-	DECL_CONV_STUB(IfcPolygonalFaceSet);
-	DECL_CONV_STUB(IfcPolyline);
-	DECL_CONV_STUB(IfcPresentationStyleAssignment);
-	DECL_CONV_STUB(IfcProcedure);
-	DECL_CONV_STUB(IfcProcedureType);
-	DECL_CONV_STUB(IfcProductDefinitionShape);
-	DECL_CONV_STUB(IfcProject);
-	DECL_CONV_STUB(IfcProjectLibrary);
-	DECL_CONV_STUB(IfcProjectOrder);
-	DECL_CONV_STUB(IfcProjectionElement);
-	DECL_CONV_STUB(IfcSimpleProperty);
-	DECL_CONV_STUB(IfcPropertyBoundedValue);
-	DECL_CONV_STUB(IfcPropertyEnumeratedValue);
-	DECL_CONV_STUB(IfcPropertyListValue);
-	DECL_CONV_STUB(IfcPropertyReferenceValue);
-	DECL_CONV_STUB(IfcPropertySet);
-	DECL_CONV_STUB(IfcPropertySingleValue);
-	DECL_CONV_STUB(IfcPropertyTableValue);
-	DECL_CONV_STUB(IfcProtectiveDevice);
-	DECL_CONV_STUB(IfcProtectiveDeviceTrippingUnit);
-	DECL_CONV_STUB(IfcProtectiveDeviceTrippingUnitType);
-	DECL_CONV_STUB(IfcProtectiveDeviceType);
-	DECL_CONV_STUB(IfcProxy);
-	DECL_CONV_STUB(IfcPump);
-	DECL_CONV_STUB(IfcPumpType);
-	DECL_CONV_STUB(IfcRailing);
-	DECL_CONV_STUB(IfcRailingType);
-	DECL_CONV_STUB(IfcRamp);
-	DECL_CONV_STUB(IfcRampFlight);
-	DECL_CONV_STUB(IfcRampFlightType);
-	DECL_CONV_STUB(IfcRampType);
-	DECL_CONV_STUB(IfcRationalBSplineCurveWithKnots);
-	DECL_CONV_STUB(IfcRationalBSplineSurfaceWithKnots);
-	DECL_CONV_STUB(IfcRectangleProfileDef);
-	DECL_CONV_STUB(IfcRectangleHollowProfileDef);
-	DECL_CONV_STUB(IfcRectangularPyramid);
-	DECL_CONV_STUB(IfcRectangularTrimmedSurface);
-	DECL_CONV_STUB(IfcReinforcingElement);
-	DECL_CONV_STUB(IfcReinforcingBar);
-	DECL_CONV_STUB(IfcReinforcingElementType);
-	DECL_CONV_STUB(IfcReinforcingBarType);
-	DECL_CONV_STUB(IfcReinforcingMesh);
-	DECL_CONV_STUB(IfcReinforcingMeshType);
-	DECL_CONV_STUB(IfcRelationship);
-	DECL_CONV_STUB(IfcRelDecomposes);
-	DECL_CONV_STUB(IfcRelAggregates);
-	DECL_CONV_STUB(IfcRelConnects);
-	DECL_CONV_STUB(IfcRelContainedInSpatialStructure);
-	DECL_CONV_STUB(IfcRelDefines);
-	DECL_CONV_STUB(IfcRelDefinesByProperties);
-	DECL_CONV_STUB(IfcRelFillsElement);
-	DECL_CONV_STUB(IfcRelVoidsElement);
-	DECL_CONV_STUB(IfcReparametrisedCompositeCurveSegment);
-	DECL_CONV_STUB(IfcRepresentation);
-	DECL_CONV_STUB(IfcRepresentationMap);
-	DECL_CONV_STUB(IfcRevolvedAreaSolid);
-	DECL_CONV_STUB(IfcRevolvedAreaSolidTapered);
-	DECL_CONV_STUB(IfcRightCircularCone);
-	DECL_CONV_STUB(IfcRightCircularCylinder);
-	DECL_CONV_STUB(IfcRoof);
-	DECL_CONV_STUB(IfcRoofType);
-	DECL_CONV_STUB(IfcRoundedRectangleProfileDef);
-	DECL_CONV_STUB(IfcSIUnit);
-	DECL_CONV_STUB(IfcSanitaryTerminal);
-	DECL_CONV_STUB(IfcSanitaryTerminalType);
-	DECL_CONV_STUB(IfcSeamCurve);
-	DECL_CONV_STUB(IfcSectionedSpine);
-	DECL_CONV_STUB(IfcSensor);
-	DECL_CONV_STUB(IfcSensorType);
-	DECL_CONV_STUB(IfcShadingDevice);
-	DECL_CONV_STUB(IfcShadingDeviceType);
-	DECL_CONV_STUB(IfcShapeModel);
-	DECL_CONV_STUB(IfcShapeRepresentation);
-	DECL_CONV_STUB(IfcShellBasedSurfaceModel);
-	DECL_CONV_STUB(IfcSite);
-	DECL_CONV_STUB(IfcSlab);
-	DECL_CONV_STUB(IfcSlabElementedCase);
-	DECL_CONV_STUB(IfcSlabStandardCase);
-	DECL_CONV_STUB(IfcSlabType);
-	DECL_CONV_STUB(IfcSolarDevice);
-	DECL_CONV_STUB(IfcSolarDeviceType);
-	DECL_CONV_STUB(IfcSpace);
-	DECL_CONV_STUB(IfcSpaceHeater);
-	DECL_CONV_STUB(IfcSpaceHeaterType);
-	DECL_CONV_STUB(IfcSpatialElementType);
-	DECL_CONV_STUB(IfcSpatialStructureElementType);
-	DECL_CONV_STUB(IfcSpaceType);
-	DECL_CONV_STUB(IfcSpatialZone);
-	DECL_CONV_STUB(IfcSpatialZoneType);
-	DECL_CONV_STUB(IfcSphere);
-	DECL_CONV_STUB(IfcSphericalSurface);
-	DECL_CONV_STUB(IfcStackTerminal);
-	DECL_CONV_STUB(IfcStackTerminalType);
-	DECL_CONV_STUB(IfcStair);
-	DECL_CONV_STUB(IfcStairFlight);
-	DECL_CONV_STUB(IfcStairFlightType);
-	DECL_CONV_STUB(IfcStairType);
-	DECL_CONV_STUB(IfcStructuralActivity);
-	DECL_CONV_STUB(IfcStructuralAction);
-	DECL_CONV_STUB(IfcStructuralAnalysisModel);
-	DECL_CONV_STUB(IfcStructuralItem);
-	DECL_CONV_STUB(IfcStructuralConnection);
-	DECL_CONV_STUB(IfcStructuralCurveAction);
-	DECL_CONV_STUB(IfcStructuralCurveConnection);
-	DECL_CONV_STUB(IfcStructuralMember);
-	DECL_CONV_STUB(IfcStructuralCurveMember);
-	DECL_CONV_STUB(IfcStructuralCurveMemberVarying);
-	DECL_CONV_STUB(IfcStructuralReaction);
-	DECL_CONV_STUB(IfcStructuralCurveReaction);
-	DECL_CONV_STUB(IfcStructuralLinearAction);
-	DECL_CONV_STUB(IfcStructuralLoadGroup);
-	DECL_CONV_STUB(IfcStructuralLoadCase);
-	DECL_CONV_STUB(IfcStructuralSurfaceAction);
-	DECL_CONV_STUB(IfcStructuralPlanarAction);
-	DECL_CONV_STUB(IfcStructuralPointAction);
-	DECL_CONV_STUB(IfcStructuralPointConnection);
-	DECL_CONV_STUB(IfcStructuralPointReaction);
-	DECL_CONV_STUB(IfcStructuralResultGroup);
-	DECL_CONV_STUB(IfcStructuralSurfaceConnection);
-	DECL_CONV_STUB(IfcStructuralSurfaceMember);
-	DECL_CONV_STUB(IfcStructuralSurfaceMemberVarying);
-	DECL_CONV_STUB(IfcStructuralSurfaceReaction);
-	DECL_CONV_STUB(IfcStyleModel);
-	DECL_CONV_STUB(IfcStyledItem);
-	DECL_CONV_STUB(IfcStyledRepresentation);
-	DECL_CONV_STUB(IfcSubContractResource);
-	DECL_CONV_STUB(IfcSubContractResourceType);
-	DECL_CONV_STUB(IfcSubedge);
-	DECL_CONV_STUB(IfcSurfaceCurveSweptAreaSolid);
-	DECL_CONV_STUB(IfcSurfaceFeature);
-	DECL_CONV_STUB(IfcSweptSurface);
-	DECL_CONV_STUB(IfcSurfaceOfLinearExtrusion);
-	DECL_CONV_STUB(IfcSurfaceOfRevolution);
-	DECL_CONV_STUB(IfcSurfaceStyle);
-	DECL_CONV_STUB(IfcSurfaceStyleShading);
-	DECL_CONV_STUB(IfcSurfaceStyleRendering);
-	DECL_CONV_STUB(IfcSurfaceStyleWithTextures);
-	DECL_CONV_STUB(IfcSweptDiskSolid);
-	DECL_CONV_STUB(IfcSweptDiskSolidPolygonal);
-	DECL_CONV_STUB(IfcSwitchingDevice);
-	DECL_CONV_STUB(IfcSwitchingDeviceType);
-	DECL_CONV_STUB(IfcSystemFurnitureElement);
-	DECL_CONV_STUB(IfcSystemFurnitureElementType);
-	DECL_CONV_STUB(IfcTShapeProfileDef);
-	DECL_CONV_STUB(IfcTank);
-	DECL_CONV_STUB(IfcTankType);
-	DECL_CONV_STUB(IfcTask);
-	DECL_CONV_STUB(IfcTaskType);
-	DECL_CONV_STUB(IfcTendon);
-	DECL_CONV_STUB(IfcTendonAnchor);
-	DECL_CONV_STUB(IfcTendonAnchorType);
-	DECL_CONV_STUB(IfcTendonType);
-	DECL_CONV_STUB(IfcTextLiteral);
-	DECL_CONV_STUB(IfcTextLiteralWithExtent);
-	DECL_CONV_STUB(IfcTopologyRepresentation);
-	DECL_CONV_STUB(IfcToroidalSurface);
-	DECL_CONV_STUB(IfcTransformer);
-	DECL_CONV_STUB(IfcTransformerType);
-	DECL_CONV_STUB(IfcTransportElement);
-	DECL_CONV_STUB(IfcTransportElementType);
-	DECL_CONV_STUB(IfcTrapeziumProfileDef);
-	DECL_CONV_STUB(IfcTriangulatedFaceSet);
-	DECL_CONV_STUB(IfcTrimmedCurve);
-	DECL_CONV_STUB(IfcTubeBundle);
-	DECL_CONV_STUB(IfcTubeBundleType);
-	DECL_CONV_STUB(IfcUShapeProfileDef);
-	DECL_CONV_STUB(IfcUnitAssignment);
-	DECL_CONV_STUB(IfcUnitaryControlElement);
-	DECL_CONV_STUB(IfcUnitaryControlElementType);
-	DECL_CONV_STUB(IfcUnitaryEquipment);
-	DECL_CONV_STUB(IfcUnitaryEquipmentType);
-	DECL_CONV_STUB(IfcValve);
-	DECL_CONV_STUB(IfcValveType);
-	DECL_CONV_STUB(IfcVector);
-	DECL_CONV_STUB(IfcVertex);
-	DECL_CONV_STUB(IfcVertexLoop);
-	DECL_CONV_STUB(IfcVertexPoint);
-	DECL_CONV_STUB(IfcVibrationIsolator);
-	DECL_CONV_STUB(IfcVibrationIsolatorType);
-	DECL_CONV_STUB(IfcVirtualElement);
-	DECL_CONV_STUB(IfcVoidingFeature);
-	DECL_CONV_STUB(IfcWall);
-	DECL_CONV_STUB(IfcWallElementedCase);
-	DECL_CONV_STUB(IfcWallStandardCase);
-	DECL_CONV_STUB(IfcWallType);
-	DECL_CONV_STUB(IfcWasteTerminal);
-	DECL_CONV_STUB(IfcWasteTerminalType);
-	DECL_CONV_STUB(IfcWindow);
-	DECL_CONV_STUB(IfcWindowStandardCase);
-	DECL_CONV_STUB(IfcWindowStyle);
-	DECL_CONV_STUB(IfcWindowType);
-	DECL_CONV_STUB(IfcWorkCalendar);
-	DECL_CONV_STUB(IfcWorkControl);
-	DECL_CONV_STUB(IfcWorkPlan);
-	DECL_CONV_STUB(IfcWorkSchedule);
-	DECL_CONV_STUB(IfcZShapeProfileDef);
-	DECL_CONV_STUB(IfcZone);
+
+    DECL_CONV_STUB( IfcRoot );
+    DECL_CONV_STUB( IfcObjectDefinition );
+    DECL_CONV_STUB( IfcObject );
+    DECL_CONV_STUB( IfcControl );
+    DECL_CONV_STUB( IfcActionRequest );
+    DECL_CONV_STUB( IfcActor );
+    DECL_CONV_STUB( IfcProduct );
+    DECL_CONV_STUB( IfcElement );
+    DECL_CONV_STUB( IfcDistributionElement );
+    DECL_CONV_STUB( IfcDistributionControlElement );
+    DECL_CONV_STUB( IfcActuator );
+    DECL_CONV_STUB( IfcTypeObject );
+    DECL_CONV_STUB( IfcTypeProduct );
+    DECL_CONV_STUB( IfcElementType );
+    DECL_CONV_STUB( IfcDistributionElementType );
+    DECL_CONV_STUB( IfcDistributionControlElementType );
+    DECL_CONV_STUB( IfcActuatorType );
+    DECL_CONV_STUB( IfcRepresentationItem );
+    DECL_CONV_STUB( IfcGeometricRepresentationItem );
+    DECL_CONV_STUB( IfcSolidModel );
+    DECL_CONV_STUB( IfcManifoldSolidBrep );
+    DECL_CONV_STUB( IfcAdvancedBrep );
+    DECL_CONV_STUB( IfcAdvancedBrepWithVoids );
+    DECL_CONV_STUB( IfcTopologicalRepresentationItem );
+    DECL_CONV_STUB( IfcFace );
+    DECL_CONV_STUB( IfcFaceSurface );
+    DECL_CONV_STUB( IfcAdvancedFace );
+    DECL_CONV_STUB( IfcDistributionFlowElement );
+    DECL_CONV_STUB( IfcFlowTerminal );
+    DECL_CONV_STUB( IfcAirTerminal );
+    DECL_CONV_STUB( IfcFlowController );
+    DECL_CONV_STUB( IfcAirTerminalBox );
+    DECL_CONV_STUB( IfcDistributionFlowElementType );
+    DECL_CONV_STUB( IfcFlowControllerType );
+    DECL_CONV_STUB( IfcAirTerminalBoxType );
+    DECL_CONV_STUB( IfcFlowTerminalType );
+    DECL_CONV_STUB( IfcAirTerminalType );
+    DECL_CONV_STUB( IfcEnergyConversionDevice );
+    DECL_CONV_STUB( IfcAirToAirHeatRecovery );
+    DECL_CONV_STUB( IfcEnergyConversionDeviceType );
+    DECL_CONV_STUB( IfcAirToAirHeatRecoveryType );
+    DECL_CONV_STUB( IfcAlarm );
+    DECL_CONV_STUB( IfcAlarmType );
+    DECL_CONV_STUB( IfcAnnotation );
+    DECL_CONV_STUB( IfcAnnotationFillArea );
+    DECL_CONV_STUB( IfcProfileDef );
+    DECL_CONV_STUB( IfcArbitraryClosedProfileDef );
+    DECL_CONV_STUB( IfcArbitraryOpenProfileDef );
+    DECL_CONV_STUB( IfcArbitraryProfileDefWithVoids );
+    DECL_CONV_STUB( IfcGroup );
+    DECL_CONV_STUB( IfcAsset );
+    DECL_CONV_STUB( IfcParameterizedProfileDef );
+    DECL_CONV_STUB( IfcAsymmetricIShapeProfileDef );
+    DECL_CONV_STUB( IfcAudioVisualAppliance );
+    DECL_CONV_STUB( IfcAudioVisualApplianceType );
+    DECL_CONV_STUB( IfcPlacement );
+    DECL_CONV_STUB( IfcAxis1Placement );
+    DECL_CONV_STUB( IfcAxis2Placement2D );
+    DECL_CONV_STUB( IfcAxis2Placement3D );
+    DECL_CONV_STUB( IfcCurve );
+    DECL_CONV_STUB( IfcBoundedCurve );
+    DECL_CONV_STUB( IfcBSplineCurve );
+    DECL_CONV_STUB( IfcBSplineCurveWithKnots );
+    DECL_CONV_STUB( IfcSurface );
+    DECL_CONV_STUB( IfcBoundedSurface );
+    DECL_CONV_STUB( IfcBSplineSurface );
+    DECL_CONV_STUB( IfcBSplineSurfaceWithKnots );
+    DECL_CONV_STUB( IfcBuildingElement );
+    DECL_CONV_STUB( IfcBeam );
+    DECL_CONV_STUB( IfcBeamStandardCase );
+    DECL_CONV_STUB( IfcBuildingElementType );
+    DECL_CONV_STUB( IfcBeamType );
+    DECL_CONV_STUB( IfcPresentationItem );
+    DECL_CONV_STUB( IfcCsgPrimitive3D );
+    DECL_CONV_STUB( IfcBlock );
+    DECL_CONV_STUB( IfcBoiler );
+    DECL_CONV_STUB( IfcBoilerType );
+    DECL_CONV_STUB( IfcBooleanResult );
+    DECL_CONV_STUB( IfcBooleanClippingResult );
+    DECL_CONV_STUB( IfcCompositeCurve );
+    DECL_CONV_STUB( IfcCompositeCurveOnSurface );
+    DECL_CONV_STUB( IfcBoundaryCurve );
+    DECL_CONV_STUB( IfcBoundingBox );
+    DECL_CONV_STUB( IfcHalfSpaceSolid );
+    DECL_CONV_STUB( IfcBoxedHalfSpace );
+    DECL_CONV_STUB( IfcSpatialElement );
+    DECL_CONV_STUB( IfcSpatialStructureElement );
+    DECL_CONV_STUB( IfcBuilding );
+    DECL_CONV_STUB( IfcElementComponent );
+    DECL_CONV_STUB( IfcBuildingElementPart );
+    DECL_CONV_STUB( IfcElementComponentType );
+    DECL_CONV_STUB( IfcBuildingElementPartType );
+    DECL_CONV_STUB( IfcBuildingElementProxy );
+    DECL_CONV_STUB( IfcBuildingElementProxyType );
+    DECL_CONV_STUB( IfcBuildingStorey );
+    DECL_CONV_STUB( IfcSystem );
+    DECL_CONV_STUB( IfcBuildingSystem );
+    DECL_CONV_STUB( IfcBurner );
+    DECL_CONV_STUB( IfcBurnerType );
+    DECL_CONV_STUB( IfcCShapeProfileDef );
+    DECL_CONV_STUB( IfcFlowFitting );
+    DECL_CONV_STUB( IfcCableCarrierFitting );
+    DECL_CONV_STUB( IfcFlowFittingType );
+    DECL_CONV_STUB( IfcCableCarrierFittingType );
+    DECL_CONV_STUB( IfcFlowSegment );
+    DECL_CONV_STUB( IfcCableCarrierSegment );
+    DECL_CONV_STUB( IfcFlowSegmentType );
+    DECL_CONV_STUB( IfcCableCarrierSegmentType );
+    DECL_CONV_STUB( IfcCableFitting );
+    DECL_CONV_STUB( IfcCableFittingType );
+    DECL_CONV_STUB( IfcCableSegment );
+    DECL_CONV_STUB( IfcCableSegmentType );
+    DECL_CONV_STUB( IfcPoint );
+    DECL_CONV_STUB( IfcCartesianPoint );
+    DECL_CONV_STUB( IfcCartesianPointList );
+    DECL_CONV_STUB( IfcCartesianPointList2D );
+    DECL_CONV_STUB( IfcCartesianPointList3D );
+    DECL_CONV_STUB( IfcCartesianTransformationOperator );
+    DECL_CONV_STUB( IfcCartesianTransformationOperator2D );
+    DECL_CONV_STUB( IfcCartesianTransformationOperator2DnonUniform );
+    DECL_CONV_STUB( IfcCartesianTransformationOperator3D );
+    DECL_CONV_STUB( IfcCartesianTransformationOperator3DnonUniform );
+    DECL_CONV_STUB( IfcCenterLineProfileDef );
+    DECL_CONV_STUB( IfcChiller );
+    DECL_CONV_STUB( IfcChillerType );
+    DECL_CONV_STUB( IfcChimney );
+    DECL_CONV_STUB( IfcChimneyType );
+    DECL_CONV_STUB( IfcConic );
+    DECL_CONV_STUB( IfcCircle );
+    DECL_CONV_STUB( IfcCircleProfileDef );
+    DECL_CONV_STUB( IfcCircleHollowProfileDef );
+    DECL_CONV_STUB( IfcCivilElement );
+    DECL_CONV_STUB( IfcCivilElementType );
+    DECL_CONV_STUB( IfcConnectedFaceSet );
+    DECL_CONV_STUB( IfcClosedShell );
+    DECL_CONV_STUB( IfcCoil );
+    DECL_CONV_STUB( IfcCoilType );
+    DECL_CONV_STUB( IfcColourSpecification );
+    DECL_CONV_STUB( IfcColourRgb );
+    DECL_CONV_STUB( IfcColumn );
+    DECL_CONV_STUB( IfcColumnStandardCase );
+    DECL_CONV_STUB( IfcColumnType );
+    DECL_CONV_STUB( IfcCommunicationsAppliance );
+    DECL_CONV_STUB( IfcCommunicationsApplianceType );
+    DECL_CONV_STUB( IfcPropertyAbstraction );
+    DECL_CONV_STUB( IfcProperty );
+    DECL_CONV_STUB( IfcComplexProperty );
+    DECL_CONV_STUB( IfcPropertyDefinition );
+    DECL_CONV_STUB( IfcCompositeCurveSegment );
+    DECL_CONV_STUB( IfcCompositeProfileDef );
+    DECL_CONV_STUB( IfcFlowMovingDevice );
+    DECL_CONV_STUB( IfcCompressor );
+    DECL_CONV_STUB( IfcFlowMovingDeviceType );
+    DECL_CONV_STUB( IfcCompressorType );
+    DECL_CONV_STUB( IfcCondenser );
+    DECL_CONV_STUB( IfcCondenserType );
+    DECL_CONV_STUB( IfcResource );
+    DECL_CONV_STUB( IfcConstructionResource );
+    DECL_CONV_STUB( IfcConstructionEquipmentResource );
+    DECL_CONV_STUB( IfcTypeResource );
+    DECL_CONV_STUB( IfcConstructionResourceType );
+    DECL_CONV_STUB( IfcConstructionEquipmentResourceType );
+    DECL_CONV_STUB( IfcConstructionMaterialResource );
+    DECL_CONV_STUB( IfcConstructionMaterialResourceType );
+    DECL_CONV_STUB( IfcConstructionProductResource );
+    DECL_CONV_STUB( IfcConstructionProductResourceType );
+    DECL_CONV_STUB( IfcContext );
+    DECL_CONV_STUB( IfcNamedUnit );
+    DECL_CONV_STUB( IfcContextDependentUnit );
+    DECL_CONV_STUB( IfcController );
+    DECL_CONV_STUB( IfcControllerType );
+    DECL_CONV_STUB( IfcConversionBasedUnit );
+    DECL_CONV_STUB( IfcConversionBasedUnitWithOffset );
+    DECL_CONV_STUB( IfcCooledBeam );
+    DECL_CONV_STUB( IfcCooledBeamType );
+    DECL_CONV_STUB( IfcCoolingTower );
+    DECL_CONV_STUB( IfcCoolingTowerType );
+    DECL_CONV_STUB( IfcCostItem );
+    DECL_CONV_STUB( IfcCostSchedule );
+    DECL_CONV_STUB( IfcCovering );
+    DECL_CONV_STUB( IfcCoveringType );
+    DECL_CONV_STUB( IfcCrewResource );
+    DECL_CONV_STUB( IfcCrewResourceType );
+    DECL_CONV_STUB( IfcCsgSolid );
+    DECL_CONV_STUB( IfcCurtainWall );
+    DECL_CONV_STUB( IfcCurtainWallType );
+    DECL_CONV_STUB( IfcCurveBoundedPlane );
+    DECL_CONV_STUB( IfcCurveBoundedSurface );
+    DECL_CONV_STUB( IfcPresentationStyle );
+    DECL_CONV_STUB( IfcElementarySurface );
+    DECL_CONV_STUB( IfcCylindricalSurface );
+    DECL_CONV_STUB( IfcDamper );
+    DECL_CONV_STUB( IfcDamperType );
+    DECL_CONV_STUB( IfcDerivedProfileDef );
+    DECL_CONV_STUB( IfcDirection );
+    DECL_CONV_STUB( IfcDiscreteAccessory );
+    DECL_CONV_STUB( IfcDiscreteAccessoryType );
+    DECL_CONV_STUB( IfcDistributionChamberElement );
+    DECL_CONV_STUB( IfcDistributionChamberElementType );
+    DECL_CONV_STUB( IfcDistributionSystem );
+    DECL_CONV_STUB( IfcDistributionCircuit );
+    DECL_CONV_STUB( IfcPort );
+    DECL_CONV_STUB( IfcDistributionPort );
+    DECL_CONV_STUB( IfcDoor );
+    DECL_CONV_STUB( IfcPropertySetDefinition );
+    DECL_CONV_STUB( IfcDoorStandardCase );
+    DECL_CONV_STUB( IfcDoorStyle );
+    DECL_CONV_STUB( IfcDoorType );
+    DECL_CONV_STUB( IfcDuctFitting );
+    DECL_CONV_STUB( IfcDuctFittingType );
+    DECL_CONV_STUB( IfcDuctSegment );
+    DECL_CONV_STUB( IfcDuctSegmentType );
+    DECL_CONV_STUB( IfcFlowTreatmentDevice );
+    DECL_CONV_STUB( IfcDuctSilencer );
+    DECL_CONV_STUB( IfcFlowTreatmentDeviceType );
+    DECL_CONV_STUB( IfcDuctSilencerType );
+    DECL_CONV_STUB( IfcEdge );
+    DECL_CONV_STUB( IfcEdgeCurve );
+    DECL_CONV_STUB( IfcLoop );
+    DECL_CONV_STUB( IfcEdgeLoop );
+    DECL_CONV_STUB( IfcElectricAppliance );
+    DECL_CONV_STUB( IfcElectricApplianceType );
+    DECL_CONV_STUB( IfcElectricDistributionBoard );
+    DECL_CONV_STUB( IfcElectricDistributionBoardType );
+    DECL_CONV_STUB( IfcFlowStorageDevice );
+    DECL_CONV_STUB( IfcElectricFlowStorageDevice );
+    DECL_CONV_STUB( IfcFlowStorageDeviceType );
+    DECL_CONV_STUB( IfcElectricFlowStorageDeviceType );
+    DECL_CONV_STUB( IfcElectricGenerator );
+    DECL_CONV_STUB( IfcElectricGeneratorType );
+    DECL_CONV_STUB( IfcElectricMotor );
+    DECL_CONV_STUB( IfcElectricMotorType );
+    DECL_CONV_STUB( IfcElectricTimeControl );
+    DECL_CONV_STUB( IfcElectricTimeControlType );
+    DECL_CONV_STUB( IfcElementAssembly );
+    DECL_CONV_STUB( IfcElementAssemblyType );
+    DECL_CONV_STUB( IfcQuantitySet );
+    DECL_CONV_STUB( IfcElementQuantity );
+    DECL_CONV_STUB( IfcEllipse );
+    DECL_CONV_STUB( IfcEllipseProfileDef );
+    DECL_CONV_STUB( IfcEngine );
+    DECL_CONV_STUB( IfcEngineType );
+    DECL_CONV_STUB( IfcEvaporativeCooler );
+    DECL_CONV_STUB( IfcEvaporativeCoolerType );
+    DECL_CONV_STUB( IfcEvaporator );
+    DECL_CONV_STUB( IfcEvaporatorType );
+    DECL_CONV_STUB( IfcProcess );
+    DECL_CONV_STUB( IfcEvent );
+    DECL_CONV_STUB( IfcTypeProcess );
+    DECL_CONV_STUB( IfcEventType );
+    DECL_CONV_STUB( IfcExternalSpatialStructureElement );
+    DECL_CONV_STUB( IfcExternalSpatialElement );
+    DECL_CONV_STUB( IfcSweptAreaSolid );
+    DECL_CONV_STUB( IfcExtrudedAreaSolid );
+    DECL_CONV_STUB( IfcExtrudedAreaSolidTapered );
+    DECL_CONV_STUB( IfcFaceBasedSurfaceModel );
+    DECL_CONV_STUB( IfcFaceBound );
+    DECL_CONV_STUB( IfcFaceOuterBound );
+    DECL_CONV_STUB( IfcFacetedBrep );
+    DECL_CONV_STUB( IfcFacetedBrepWithVoids );
+    DECL_CONV_STUB( IfcFan );
+    DECL_CONV_STUB( IfcFanType );
+    DECL_CONV_STUB( IfcFastener );
+    DECL_CONV_STUB( IfcFastenerType );
+    DECL_CONV_STUB( IfcFeatureElement );
+    DECL_CONV_STUB( IfcFeatureElementAddition );
+    DECL_CONV_STUB( IfcFeatureElementSubtraction );
+    DECL_CONV_STUB( IfcFillAreaStyleHatching );
+    DECL_CONV_STUB( IfcFillAreaStyleTiles );
+    DECL_CONV_STUB( IfcFilter );
+    DECL_CONV_STUB( IfcFilterType );
+    DECL_CONV_STUB( IfcFireSuppressionTerminal );
+    DECL_CONV_STUB( IfcFireSuppressionTerminalType );
+    DECL_CONV_STUB( IfcFixedReferenceSweptAreaSolid );
+    DECL_CONV_STUB( IfcFlowInstrument );
+    DECL_CONV_STUB( IfcFlowInstrumentType );
+    DECL_CONV_STUB( IfcFlowMeter );
+    DECL_CONV_STUB( IfcFlowMeterType );
+    DECL_CONV_STUB( IfcFooting );
+    DECL_CONV_STUB( IfcFootingType );
+    DECL_CONV_STUB( IfcFurnishingElement );
+    DECL_CONV_STUB( IfcFurnishingElementType );
+    DECL_CONV_STUB( IfcFurniture );
+    DECL_CONV_STUB( IfcFurnitureType );
+    DECL_CONV_STUB( IfcGeographicElement );
+    DECL_CONV_STUB( IfcGeographicElementType );
+    DECL_CONV_STUB( IfcGeometricSet );
+    DECL_CONV_STUB( IfcGeometricCurveSet );
+    DECL_CONV_STUB( IfcRepresentationContext );
+    DECL_CONV_STUB( IfcGeometricRepresentationContext );
+    DECL_CONV_STUB( IfcGeometricRepresentationSubContext );
+    DECL_CONV_STUB( IfcGrid );
+    DECL_CONV_STUB( IfcObjectPlacement );
+    DECL_CONV_STUB( IfcGridPlacement );
+    DECL_CONV_STUB( IfcHeatExchanger );
+    DECL_CONV_STUB( IfcHeatExchangerType );
+    DECL_CONV_STUB( IfcHumidifier );
+    DECL_CONV_STUB( IfcHumidifierType );
+    DECL_CONV_STUB( IfcIShapeProfileDef );
+    DECL_CONV_STUB( IfcIndexedPolyCurve );
+    DECL_CONV_STUB( IfcTessellatedItem );
+    DECL_CONV_STUB( IfcIndexedPolygonalFace );
+    DECL_CONV_STUB( IfcIndexedPolygonalFaceWithVoids );
+    DECL_CONV_STUB( IfcInterceptor );
+    DECL_CONV_STUB( IfcInterceptorType );
+    DECL_CONV_STUB( IfcSurfaceCurve );
+    DECL_CONV_STUB( IfcIntersectionCurve );
+    DECL_CONV_STUB( IfcInventory );
+    DECL_CONV_STUB( IfcJunctionBox );
+    DECL_CONV_STUB( IfcJunctionBoxType );
+    DECL_CONV_STUB( IfcLShapeProfileDef );
+    DECL_CONV_STUB( IfcLaborResource );
+    DECL_CONV_STUB( IfcLaborResourceType );
+    DECL_CONV_STUB( IfcLamp );
+    DECL_CONV_STUB( IfcLampType );
+    DECL_CONV_STUB( IfcLightFixture );
+    DECL_CONV_STUB( IfcLightFixtureType );
+    DECL_CONV_STUB( IfcLightSource );
+    DECL_CONV_STUB( IfcLightSourceAmbient );
+    DECL_CONV_STUB( IfcLightSourceDirectional );
+    DECL_CONV_STUB( IfcLightSourceGoniometric );
+    DECL_CONV_STUB( IfcLightSourcePositional );
+    DECL_CONV_STUB( IfcLightSourceSpot );
+    DECL_CONV_STUB( IfcLine );
+    DECL_CONV_STUB( IfcLocalPlacement );
+    DECL_CONV_STUB( IfcMappedItem );
+    DECL_CONV_STUB( IfcProductRepresentation );
+    DECL_CONV_STUB( IfcMaterialDefinitionRepresentation );
+    DECL_CONV_STUB( IfcMeasureWithUnit );
+    DECL_CONV_STUB( IfcMechanicalFastener );
+    DECL_CONV_STUB( IfcMechanicalFastenerType );
+    DECL_CONV_STUB( IfcMedicalDevice );
+    DECL_CONV_STUB( IfcMedicalDeviceType );
+    DECL_CONV_STUB( IfcMember );
+    DECL_CONV_STUB( IfcMemberStandardCase );
+    DECL_CONV_STUB( IfcMemberType );
+    DECL_CONV_STUB( IfcMirroredProfileDef );
+    DECL_CONV_STUB( IfcMotorConnection );
+    DECL_CONV_STUB( IfcMotorConnectionType );
+    DECL_CONV_STUB( IfcOccupant );
+    DECL_CONV_STUB( IfcOffsetCurve2D );
+    DECL_CONV_STUB( IfcOffsetCurve3D );
+    DECL_CONV_STUB( IfcOpenShell );
+    DECL_CONV_STUB( IfcOpeningElement );
+    DECL_CONV_STUB( IfcOpeningStandardCase );
+    DECL_CONV_STUB( IfcOrientedEdge );
+    DECL_CONV_STUB( IfcOuterBoundaryCurve );
+    DECL_CONV_STUB( IfcOutlet );
+    DECL_CONV_STUB( IfcOutletType );
+    DECL_CONV_STUB( IfcPath );
+    DECL_CONV_STUB( IfcPcurve );
+    DECL_CONV_STUB( IfcPerformanceHistory );
+    DECL_CONV_STUB( IfcPermit );
+    DECL_CONV_STUB( IfcPile );
+    DECL_CONV_STUB( IfcPileType );
+    DECL_CONV_STUB( IfcPipeFitting );
+    DECL_CONV_STUB( IfcPipeFittingType );
+    DECL_CONV_STUB( IfcPipeSegment );
+    DECL_CONV_STUB( IfcPipeSegmentType );
+    DECL_CONV_STUB( IfcPlanarExtent );
+    DECL_CONV_STUB( IfcPlanarBox );
+    DECL_CONV_STUB( IfcPlane );
+    DECL_CONV_STUB( IfcPlate );
+    DECL_CONV_STUB( IfcPlateStandardCase );
+    DECL_CONV_STUB( IfcPlateType );
+    DECL_CONV_STUB( IfcPointOnCurve );
+    DECL_CONV_STUB( IfcPointOnSurface );
+    DECL_CONV_STUB( IfcPolyLoop );
+    DECL_CONV_STUB( IfcPolygonalBoundedHalfSpace );
+    DECL_CONV_STUB( IfcTessellatedFaceSet );
+    DECL_CONV_STUB( IfcPolygonalFaceSet );
+    DECL_CONV_STUB( IfcPolyline );
+    DECL_CONV_STUB( IfcPresentationStyleAssignment );
+    DECL_CONV_STUB( IfcProcedure );
+    DECL_CONV_STUB( IfcProcedureType );
+    DECL_CONV_STUB( IfcProductDefinitionShape );
+    DECL_CONV_STUB( IfcProject );
+    DECL_CONV_STUB( IfcProjectLibrary );
+    DECL_CONV_STUB( IfcProjectOrder );
+    DECL_CONV_STUB( IfcProjectionElement );
+    DECL_CONV_STUB( IfcSimpleProperty );
+    DECL_CONV_STUB( IfcPropertyBoundedValue );
+    DECL_CONV_STUB( IfcPropertyEnumeratedValue );
+    DECL_CONV_STUB( IfcPropertyListValue );
+    DECL_CONV_STUB( IfcPropertyReferenceValue );
+    DECL_CONV_STUB( IfcPropertySet );
+    DECL_CONV_STUB( IfcPropertySingleValue );
+    DECL_CONV_STUB( IfcPropertyTableValue );
+    DECL_CONV_STUB( IfcProtectiveDevice );
+    DECL_CONV_STUB( IfcProtectiveDeviceTrippingUnit );
+    DECL_CONV_STUB( IfcProtectiveDeviceTrippingUnitType );
+    DECL_CONV_STUB( IfcProtectiveDeviceType );
+    DECL_CONV_STUB( IfcProxy );
+    DECL_CONV_STUB( IfcPump );
+    DECL_CONV_STUB( IfcPumpType );
+    DECL_CONV_STUB( IfcRailing );
+    DECL_CONV_STUB( IfcRailingType );
+    DECL_CONV_STUB( IfcRamp );
+    DECL_CONV_STUB( IfcRampFlight );
+    DECL_CONV_STUB( IfcRampFlightType );
+    DECL_CONV_STUB( IfcRampType );
+    DECL_CONV_STUB( IfcRationalBSplineCurveWithKnots );
+    DECL_CONV_STUB( IfcRationalBSplineSurfaceWithKnots );
+    DECL_CONV_STUB( IfcRectangleProfileDef );
+    DECL_CONV_STUB( IfcRectangleHollowProfileDef );
+    DECL_CONV_STUB( IfcRectangularPyramid );
+    DECL_CONV_STUB( IfcRectangularTrimmedSurface );
+    DECL_CONV_STUB( IfcReinforcingElement );
+    DECL_CONV_STUB( IfcReinforcingBar );
+    DECL_CONV_STUB( IfcReinforcingElementType );
+    DECL_CONV_STUB( IfcReinforcingBarType );
+    DECL_CONV_STUB( IfcReinforcingMesh );
+    DECL_CONV_STUB( IfcReinforcingMeshType );
+    DECL_CONV_STUB( IfcRelationship );
+    DECL_CONV_STUB( IfcRelDecomposes );
+    DECL_CONV_STUB( IfcRelAggregates );
+    DECL_CONV_STUB( IfcRelConnects );
+    DECL_CONV_STUB( IfcRelContainedInSpatialStructure );
+    DECL_CONV_STUB( IfcRelDefines );
+    DECL_CONV_STUB( IfcRelDefinesByProperties );
+    DECL_CONV_STUB( IfcRelFillsElement );
+    DECL_CONV_STUB( IfcRelVoidsElement );
+    DECL_CONV_STUB( IfcReparametrisedCompositeCurveSegment );
+    DECL_CONV_STUB( IfcRepresentation );
+    DECL_CONV_STUB( IfcRepresentationMap );
+    DECL_CONV_STUB( IfcRevolvedAreaSolid );
+    DECL_CONV_STUB( IfcRevolvedAreaSolidTapered );
+    DECL_CONV_STUB( IfcRightCircularCone );
+    DECL_CONV_STUB( IfcRightCircularCylinder );
+    DECL_CONV_STUB( IfcRoof );
+    DECL_CONV_STUB( IfcRoofType );
+    DECL_CONV_STUB( IfcRoundedRectangleProfileDef );
+    DECL_CONV_STUB( IfcSIUnit );
+    DECL_CONV_STUB( IfcSanitaryTerminal );
+    DECL_CONV_STUB( IfcSanitaryTerminalType );
+    DECL_CONV_STUB( IfcSeamCurve );
+    DECL_CONV_STUB( IfcSectionedSpine );
+    DECL_CONV_STUB( IfcSensor );
+    DECL_CONV_STUB( IfcSensorType );
+    DECL_CONV_STUB( IfcShadingDevice );
+    DECL_CONV_STUB( IfcShadingDeviceType );
+    DECL_CONV_STUB( IfcShapeModel );
+    DECL_CONV_STUB( IfcShapeRepresentation );
+    DECL_CONV_STUB( IfcShellBasedSurfaceModel );
+    DECL_CONV_STUB( IfcSite );
+    DECL_CONV_STUB( IfcSlab );
+    DECL_CONV_STUB( IfcSlabElementedCase );
+    DECL_CONV_STUB( IfcSlabStandardCase );
+    DECL_CONV_STUB( IfcSlabType );
+    DECL_CONV_STUB( IfcSolarDevice );
+    DECL_CONV_STUB( IfcSolarDeviceType );
+    DECL_CONV_STUB( IfcSpace );
+    DECL_CONV_STUB( IfcSpaceHeater );
+    DECL_CONV_STUB( IfcSpaceHeaterType );
+    DECL_CONV_STUB( IfcSpatialElementType );
+    DECL_CONV_STUB( IfcSpatialStructureElementType );
+    DECL_CONV_STUB( IfcSpaceType );
+    DECL_CONV_STUB( IfcSpatialZone );
+    DECL_CONV_STUB( IfcSpatialZoneType );
+    DECL_CONV_STUB( IfcSphere );
+    DECL_CONV_STUB( IfcSphericalSurface );
+    DECL_CONV_STUB( IfcStackTerminal );
+    DECL_CONV_STUB( IfcStackTerminalType );
+    DECL_CONV_STUB( IfcStair );
+    DECL_CONV_STUB( IfcStairFlight );
+    DECL_CONV_STUB( IfcStairFlightType );
+    DECL_CONV_STUB( IfcStairType );
+    DECL_CONV_STUB( IfcStructuralActivity );
+    DECL_CONV_STUB( IfcStructuralAction );
+    DECL_CONV_STUB( IfcStructuralAnalysisModel );
+    DECL_CONV_STUB( IfcStructuralItem );
+    DECL_CONV_STUB( IfcStructuralConnection );
+    DECL_CONV_STUB( IfcStructuralCurveAction );
+    DECL_CONV_STUB( IfcStructuralCurveConnection );
+    DECL_CONV_STUB( IfcStructuralMember );
+    DECL_CONV_STUB( IfcStructuralCurveMember );
+    DECL_CONV_STUB( IfcStructuralCurveMemberVarying );
+    DECL_CONV_STUB( IfcStructuralReaction );
+    DECL_CONV_STUB( IfcStructuralCurveReaction );
+    DECL_CONV_STUB( IfcStructuralLinearAction );
+    DECL_CONV_STUB( IfcStructuralLoadGroup );
+    DECL_CONV_STUB( IfcStructuralLoadCase );
+    DECL_CONV_STUB( IfcStructuralSurfaceAction );
+    DECL_CONV_STUB( IfcStructuralPlanarAction );
+    DECL_CONV_STUB( IfcStructuralPointAction );
+    DECL_CONV_STUB( IfcStructuralPointConnection );
+    DECL_CONV_STUB( IfcStructuralPointReaction );
+    DECL_CONV_STUB( IfcStructuralResultGroup );
+    DECL_CONV_STUB( IfcStructuralSurfaceConnection );
+    DECL_CONV_STUB( IfcStructuralSurfaceMember );
+    DECL_CONV_STUB( IfcStructuralSurfaceMemberVarying );
+    DECL_CONV_STUB( IfcStructuralSurfaceReaction );
+    DECL_CONV_STUB( IfcStyleModel );
+    DECL_CONV_STUB( IfcStyledItem );
+    DECL_CONV_STUB( IfcStyledRepresentation );
+    DECL_CONV_STUB( IfcSubContractResource );
+    DECL_CONV_STUB( IfcSubContractResourceType );
+    DECL_CONV_STUB( IfcSubedge );
+    DECL_CONV_STUB( IfcSurfaceCurveSweptAreaSolid );
+    DECL_CONV_STUB( IfcSurfaceFeature );
+    DECL_CONV_STUB( IfcSweptSurface );
+    DECL_CONV_STUB( IfcSurfaceOfLinearExtrusion );
+    DECL_CONV_STUB( IfcSurfaceOfRevolution );
+    DECL_CONV_STUB( IfcSurfaceStyle );
+    DECL_CONV_STUB( IfcSurfaceStyleShading );
+    DECL_CONV_STUB( IfcSurfaceStyleRendering );
+    DECL_CONV_STUB( IfcSurfaceStyleWithTextures );
+    DECL_CONV_STUB( IfcSweptDiskSolid );
+    DECL_CONV_STUB( IfcSweptDiskSolidPolygonal );
+    DECL_CONV_STUB( IfcSwitchingDevice );
+    DECL_CONV_STUB( IfcSwitchingDeviceType );
+    DECL_CONV_STUB( IfcSystemFurnitureElement );
+    DECL_CONV_STUB( IfcSystemFurnitureElementType );
+    DECL_CONV_STUB( IfcTShapeProfileDef );
+    DECL_CONV_STUB( IfcTank );
+    DECL_CONV_STUB( IfcTankType );
+    DECL_CONV_STUB( IfcTask );
+    DECL_CONV_STUB( IfcTaskType );
+    DECL_CONV_STUB( IfcTendon );
+    DECL_CONV_STUB( IfcTendonAnchor );
+    DECL_CONV_STUB( IfcTendonAnchorType );
+    DECL_CONV_STUB( IfcTendonType );
+    DECL_CONV_STUB( IfcTextLiteral );
+    DECL_CONV_STUB( IfcTextLiteralWithExtent );
+    DECL_CONV_STUB( IfcTopologyRepresentation );
+    DECL_CONV_STUB( IfcToroidalSurface );
+    DECL_CONV_STUB( IfcTransformer );
+    DECL_CONV_STUB( IfcTransformerType );
+    DECL_CONV_STUB( IfcTransportElement );
+    DECL_CONV_STUB( IfcTransportElementType );
+    DECL_CONV_STUB( IfcTrapeziumProfileDef );
+    DECL_CONV_STUB( IfcTriangulatedFaceSet );
+    DECL_CONV_STUB( IfcTrimmedCurve );
+    DECL_CONV_STUB( IfcTubeBundle );
+    DECL_CONV_STUB( IfcTubeBundleType );
+    DECL_CONV_STUB( IfcUShapeProfileDef );
+    DECL_CONV_STUB( IfcUnitAssignment );
+    DECL_CONV_STUB( IfcUnitaryControlElement );
+    DECL_CONV_STUB( IfcUnitaryControlElementType );
+    DECL_CONV_STUB( IfcUnitaryEquipment );
+    DECL_CONV_STUB( IfcUnitaryEquipmentType );
+    DECL_CONV_STUB( IfcValve );
+    DECL_CONV_STUB( IfcValveType );
+    DECL_CONV_STUB( IfcVector );
+    DECL_CONV_STUB( IfcVertex );
+    DECL_CONV_STUB( IfcVertexLoop );
+    DECL_CONV_STUB( IfcVertexPoint );
+    DECL_CONV_STUB( IfcVibrationIsolator );
+    DECL_CONV_STUB( IfcVibrationIsolatorType );
+    DECL_CONV_STUB( IfcVirtualElement );
+    DECL_CONV_STUB( IfcVoidingFeature );
+    DECL_CONV_STUB( IfcWall );
+    DECL_CONV_STUB( IfcWallElementedCase );
+    DECL_CONV_STUB( IfcWallStandardCase );
+    DECL_CONV_STUB( IfcWallType );
+    DECL_CONV_STUB( IfcWasteTerminal );
+    DECL_CONV_STUB( IfcWasteTerminalType );
+    DECL_CONV_STUB( IfcWindow );
+    DECL_CONV_STUB( IfcWindowStandardCase );
+    DECL_CONV_STUB( IfcWindowStyle );
+    DECL_CONV_STUB( IfcWindowType );
+    DECL_CONV_STUB( IfcWorkCalendar );
+    DECL_CONV_STUB( IfcWorkControl );
+    DECL_CONV_STUB( IfcWorkPlan );
+    DECL_CONV_STUB( IfcWorkSchedule );
+    DECL_CONV_STUB( IfcZShapeProfileDef );
+    DECL_CONV_STUB( IfcZone );
 
 
 #undef DECL_CONV_STUB
 
+} //! Schema_4
 } //! STEP
 } //! Assimp
 

+ 7 - 7
code/LWOLoader.cpp

@@ -432,7 +432,6 @@ void LWOImporter::InternReadFile( const std::string& pFile,
         unsigned int num = static_cast<unsigned int>(apcMeshes.size() - meshStart);
         if (layer.mName != "<LWODefault>" || num > 0) {
             aiNode* pcNode = new aiNode();
-            apcNodes[layer.mIndex] = pcNode;
             pcNode->mName.Set(layer.mName);
             pcNode->mParent = (aiNode*)&layer;
             pcNode->mNumMeshes = num;
@@ -442,6 +441,7 @@ void LWOImporter::InternReadFile( const std::string& pFile,
                 for (unsigned int p = 0; p < pcNode->mNumMeshes;++p)
                     pcNode->mMeshes[p] = p + meshStart;
             }
+            apcNodes[layer.mIndex] = pcNode;
         }
     }
 
@@ -584,7 +584,7 @@ void LWOImporter::GenerateNodeGraph(std::map<uint16_t,aiNode*>& apcNodes)
     //Set parent of all children, inserting pivots
     //std::cout << "Set parent of all children" << std::endl;
     std::map<uint16_t, aiNode*> mapPivot;
-    for (std::map<uint16_t,aiNode*>::iterator itapcNodes = apcNodes.begin(); itapcNodes != apcNodes.end(); ++itapcNodes) {
+    for (auto itapcNodes = apcNodes.begin(); itapcNodes != apcNodes.end(); ++itapcNodes) {
 
         //Get the parent index
         LWO::Layer* nodeLayer = (LWO::Layer*)(itapcNodes->second->mParent);
@@ -593,7 +593,6 @@ void LWOImporter::GenerateNodeGraph(std::map<uint16_t,aiNode*>& apcNodes)
         //Create pivot node, store it into the pivot map, and set the parent as the pivot
         aiNode* pivotNode = new aiNode();
         pivotNode->mName.Set("Pivot-"+std::string(itapcNodes->second->mName.data));
-        mapPivot[-(itapcNodes->first+2)] = pivotNode;
         itapcNodes->second->mParent = pivotNode;
 
         //Look for the parent node to attach the pivot to
@@ -611,18 +610,19 @@ void LWOImporter::GenerateNodeGraph(std::map<uint16_t,aiNode*>& apcNodes)
         pivotNode->mTransformation.a4 = nodeLayer->mPivot.x;
         pivotNode->mTransformation.b4 = nodeLayer->mPivot.y;
         pivotNode->mTransformation.c4 = nodeLayer->mPivot.z;
+        mapPivot[-(itapcNodes->first+2)] = pivotNode;
     }
 
     //Merge pivot map into node map
     //std::cout << "Merge pivot map into node map" << std::endl;
-    for (std::map<uint16_t, aiNode*>::iterator itMapPivot = mapPivot.begin(); itMapPivot != mapPivot.end(); ++itMapPivot) {
+    for (auto itMapPivot = mapPivot.begin(); itMapPivot != mapPivot.end(); ++itMapPivot) {
         apcNodes[itMapPivot->first] = itMapPivot->second;
     }
 
     //Set children of all parents
     apcNodes[-1] = root;
-    for (std::map<uint16_t,aiNode*>::iterator itMapParentNodes = apcNodes.begin(); itMapParentNodes != apcNodes.end(); ++itMapParentNodes) {
-        for (std::map<uint16_t,aiNode*>::iterator itMapChildNodes = apcNodes.begin(); itMapChildNodes != apcNodes.end(); ++itMapChildNodes) {
+    for (auto itMapParentNodes = apcNodes.begin(); itMapParentNodes != apcNodes.end(); ++itMapParentNodes) {
+        for (auto itMapChildNodes = apcNodes.begin(); itMapChildNodes != apcNodes.end(); ++itMapChildNodes) {
             if ((itMapParentNodes->first != itMapChildNodes->first) && (itMapParentNodes->second == itMapChildNodes->second->mParent)) {
                 ++(itMapParentNodes->second->mNumChildren);
             }
@@ -630,7 +630,7 @@ void LWOImporter::GenerateNodeGraph(std::map<uint16_t,aiNode*>& apcNodes)
         if (itMapParentNodes->second->mNumChildren) {
             itMapParentNodes->second->mChildren = new aiNode* [ itMapParentNodes->second->mNumChildren ];
             uint16_t p = 0;
-            for (std::map<uint16_t,aiNode*>::iterator itMapChildNodes = apcNodes.begin(); itMapChildNodes != apcNodes.end(); ++itMapChildNodes) {
+            for (auto itMapChildNodes = apcNodes.begin(); itMapChildNodes != apcNodes.end(); ++itMapChildNodes) {
                 if ((itMapParentNodes->first != itMapChildNodes->first) && (itMapParentNodes->second == itMapChildNodes->second->mParent)) {
                     itMapParentNodes->second->mChildren[p++] = itMapChildNodes->second;
                 }

+ 5 - 1
code/LimitBoneWeightsProcess.h

@@ -120,7 +120,11 @@ public:
     {
         unsigned int mBone; ///< Index of the bone
         float mWeight;      ///< Weight of that bone on this vertex
-        Weight() { }
+        Weight()
+        : mBone(0)
+        , mWeight(0.0f)
+        { }
+
         Weight( unsigned int pBone, float pWeight)
         {
             mBone = pBone;

+ 2 - 2
code/MDLFileData.h

@@ -844,11 +844,11 @@ struct IntGroupInfo_MDL7
 struct IntGroupData_MDL7
 {
     IntGroupData_MDL7()
-        : pcFaces(NULL), bNeed2UV(false)
+        : bNeed2UV(false)
     {}
 
     //! Array of faces that belong to the group
-    MDL::IntFace_MDL7* pcFaces;
+    std::vector<MDL::IntFace_MDL7> pcFaces;
 
     //! Array of vertex positions
     std::vector<aiVector3D>     vPositions;

+ 1 - 1
code/MDLLoader.cpp

@@ -1502,7 +1502,7 @@ void MDLImporter::InternReadFile_3DGS_MDL7( )
                     groupData.bNeed2UV = true;
                 }
             }
-            groupData.pcFaces = new MDL::IntFace_MDL7[groupInfo.pcGroup->numtris];
+            groupData.pcFaces.resize(groupInfo.pcGroup->numtris);
 
             // read all faces into the preallocated arrays
             ReadFaces_3DGS_MDL7(groupInfo, groupData);

+ 13 - 14
code/ObjExporter.cpp

@@ -132,18 +132,18 @@ ObjExporter::ObjExporter(const char* _filename, const aiScene* pScene, bool noMt
     mOutputMat.precision(16);
 
     WriteGeometryFile(noMtl);
-    if (!noMtl)
+    if ( !noMtl ) {
         WriteMaterialFile();
+    }
 }
 
 // ------------------------------------------------------------------------------------------------
 ObjExporter::~ObjExporter() {
-
+    // empty
 }
 
 // ------------------------------------------------------------------------------------------------
-std::string ObjExporter :: GetMaterialLibName()
-{
+std::string ObjExporter::GetMaterialLibName() {
     // within the Obj file, we use just the relative file name with the path stripped
     const std::string& s = GetMaterialLibFileName();
     std::string::size_type il = s.find_last_of("/\\");
@@ -158,8 +158,9 @@ std::string ObjExporter :: GetMaterialLibName()
 std::string ObjExporter::GetMaterialLibFileName() {
     // Remove existing .obj file extension so that the final material file name will be fileName.mtl and not fileName.obj.mtl
     size_t lastdot = filename.find_last_of('.');
-    if (lastdot != std::string::npos)
-        return filename.substr(0, lastdot) + MaterialExt;
+    if ( lastdot != std::string::npos ) {
+        return filename.substr( 0, lastdot ) + MaterialExt;
+    }
 
     return filename + MaterialExt;
 }
@@ -172,8 +173,7 @@ void ObjExporter::WriteHeader(std::ostringstream& out) {
 }
 
 // ------------------------------------------------------------------------------------------------
-std::string ObjExporter::GetMaterialName(unsigned int index)
-{
+std::string ObjExporter::GetMaterialName(unsigned int index) {
     const aiMaterial* const mat = pScene->mMaterials[index];
     if ( nullptr == mat ) {
         static const std::string EmptyStr;
@@ -191,8 +191,7 @@ std::string ObjExporter::GetMaterialName(unsigned int index)
 }
 
 // ------------------------------------------------------------------------------------------------
-void ObjExporter::WriteMaterialFile()
-{
+void ObjExporter::WriteMaterialFile() {
     WriteHeader(mOutputMat);
 
     for(unsigned int i = 0; i < pScene->mNumMaterials; ++i) {
@@ -310,8 +309,9 @@ void ObjExporter::WriteGeometryFile(bool noMtl) {
         if (!m.name.empty()) {
             mOutput << "g " << m.name << endl;
         }
-        if (!noMtl)
+        if ( !noMtl ) {
             mOutput << "usemtl " << m.matname << endl;
+        }
 
         for(const Face& f : m.faces) {
             mOutput << f.kind << ' ';
@@ -382,7 +382,7 @@ void ObjExporter::colIndexMap::getColors( std::vector<aiColor4D> &colors ) {
 
 // ------------------------------------------------------------------------------------------------
 void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat) {
-    mMeshes.push_back(MeshInstance());
+    mMeshes.push_back(MeshInstance() );
     MeshInstance& mesh = mMeshes.back();
 
     mesh.name = std::string( name.data, name.length );
@@ -436,8 +436,7 @@ void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4
 }
 
 // ------------------------------------------------------------------------------------------------
-void ObjExporter::AddNode(const aiNode* nd, const aiMatrix4x4& mParent)
-{
+void ObjExporter::AddNode(const aiNode* nd, const aiMatrix4x4& mParent) {
     const aiMatrix4x4& mAbs = mParent * nd->mTransformation;
 
     for(unsigned int i = 0; i < nd->mNumMeshes; ++i) {

+ 10 - 20
code/ObjFileImporter.cpp

@@ -207,30 +207,24 @@ void ObjFileImporter::CreateDataFromImport(const ObjFile::Model* pModel, aiScene
 
     // Create the root node of the scene
     pScene->mRootNode = new aiNode;
-    if ( !pModel->m_ModelName.empty() )
-    {
+    if ( !pModel->m_ModelName.empty() ) {
         // Set the name of the scene
         pScene->mRootNode->mName.Set(pModel->m_ModelName);
-    }
-    else
-    {
+    } else {
         // This is a fatal error, so break down the application
         ai_assert(false);
     }
 
     // Create nodes for the whole scene
     std::vector<aiMesh*> MeshArray;
-    for (size_t index = 0; index < pModel->m_Objects.size(); index++)
-    {
+    for (size_t index = 0; index < pModel->m_Objects.size(); ++index ) {
         createNodes(pModel, pModel->m_Objects[ index ], pScene->mRootNode, pScene, MeshArray);
     }
 
     // Create mesh pointer buffer for this scene
-    if (pScene->mNumMeshes > 0)
-    {
+    if (pScene->mNumMeshes > 0) {
         pScene->mMeshes = new aiMesh*[ MeshArray.size() ];
-        for (size_t index =0; index < MeshArray.size(); index++)
-        {
+        for (size_t index =0; index < MeshArray.size(); ++index ) {
             pScene->mMeshes[ index ] = MeshArray[ index ];
         }
     }
@@ -261,8 +255,7 @@ aiNode *ObjFileImporter::createNodes(const ObjFile::Model* pModel, const ObjFile
         appendChildToParentNode( pParent, pNode );
     }
 
-    for ( size_t i=0; i< pObject->m_Meshes.size(); i++ )
-    {
+    for ( size_t i=0; i< pObject->m_Meshes.size(); ++i ) {
         unsigned int meshId = pObject->m_Meshes[ i ];
         aiMesh *pMesh = createTopology( pModel, pObject, meshId );
         if( pMesh ) {
@@ -275,8 +268,7 @@ aiNode *ObjFileImporter::createNodes(const ObjFile::Model* pModel, const ObjFile
     }
 
     // Create all nodes from the sub-objects stored in the current object
-    if ( !pObject->m_SubObjects.empty() )
-    {
+    if ( !pObject->m_SubObjects.empty() ) {
         size_t numChilds = pObject->m_SubObjects.size();
         pNode->mNumChildren = static_cast<unsigned int>( numChilds );
         pNode->mChildren = new aiNode*[ numChilds ];
@@ -286,16 +278,14 @@ aiNode *ObjFileImporter::createNodes(const ObjFile::Model* pModel, const ObjFile
 
     // Set mesh instances into scene- and node-instances
     const size_t meshSizeDiff = MeshArray.size()- oldMeshSize;
-    if ( meshSizeDiff > 0 )
-    {
+    if ( meshSizeDiff > 0 ) {
         pNode->mMeshes = new unsigned int[ meshSizeDiff ];
         pNode->mNumMeshes = static_cast<unsigned int>( meshSizeDiff );
         size_t index = 0;
-        for (size_t i = oldMeshSize; i < MeshArray.size(); i++)
-        {
+        for (size_t i = oldMeshSize; i < MeshArray.size(); ++i ) {
             pNode->mMeshes[ index ] = pScene->mNumMeshes;
             pScene->mNumMeshes++;
-            index++;
+            ++index;
         }
     }
 

+ 3 - 2
code/ObjFileMtlImporter.cpp

@@ -303,11 +303,12 @@ void ObjFileMtlImporter::createMaterial()
         // New Material created
         m_pModel->m_pCurrentMaterial = new ObjFile::Material();
         m_pModel->m_pCurrentMaterial->MaterialName.Set( name );
+        m_pModel->m_MaterialLib.push_back( name );
+        m_pModel->m_MaterialMap[ name ] = m_pModel->m_pCurrentMaterial;
+
         if (m_pModel->m_pCurrentMesh) {
             m_pModel->m_pCurrentMesh->m_uiMaterialIndex = static_cast<unsigned int>(m_pModel->m_MaterialLib.size() - 1);
         }
-        m_pModel->m_MaterialLib.push_back( name );
-        m_pModel->m_MaterialMap[ name ] = m_pModel->m_pCurrentMaterial;
     } else {
         // Use older material
         m_pModel->m_pCurrentMaterial = (*it).second;

+ 4 - 3
code/ObjFileParser.cpp

@@ -612,13 +612,14 @@ void ObjFileParser::getMaterialLib() {
         if ( '/' != *path.rbegin() ) {
           path += '/';
         }
-        absName = path + strMatName;
+        absName += path;
+        absName += strMatName;
     } else {
         absName = strMatName;
     }
-    IOStream *pFile = m_pIO->Open( absName );
 
-    if (!pFile ) {
+    IOStream *pFile = m_pIO->Open( absName );
+    if ( nullptr == pFile ) {
         DefaultLogger::get()->error("OBJ: Unable to locate material file " + strMatName);
         std::string strMatFallbackName = m_originalObjFileName.substr(0, m_originalObjFileName.length() - 3) + "mtl";
         DefaultLogger::get()->info("OBJ: Opening fallback material file " + strMatFallbackName);

+ 23 - 32
code/OgreParsingUtils.h

@@ -52,27 +52,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <sstream>
 #include <cctype>
 
-namespace Assimp
-{
-namespace Ogre
-{
+namespace Assimp {
+namespace Ogre {
 
 /// Returns a lower cased copy of @s.
-static inline std::string ToLower(std::string s)
+static AI_FORCE_INLINE
+std::string ToLower(std::string s)
 {
     std::transform(s.begin(), s.end(), s.begin(), ::tolower);
     return s;
 }
 
 /// Returns if @c s ends with @c suffix. If @c caseSensitive is false, both strings will be lower cased before matching.
-static inline bool EndsWith(const std::string &s, const std::string &suffix, bool caseSensitive = true)
-{
-    if (s.empty() || suffix.empty())
-    {
+static AI_FORCE_INLINE
+bool EndsWith(const std::string &s, const std::string &suffix, bool caseSensitive = true) {
+    if (s.empty() || suffix.empty()) {
         return false;
-    }
-    else if (s.length() < suffix.length())
-    {
+    } else if (s.length() < suffix.length()) {
         return false;
     }
 
@@ -82,48 +78,43 @@ static inline bool EndsWith(const std::string &s, const std::string &suffix, boo
 
     size_t len = suffix.length();
     std::string sSuffix = s.substr(s.length()-len, len);
+
     return (ASSIMP_stricmp(sSuffix, suffix) == 0);
 }
 
 // Below trim functions adapted from http://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring
 
 /// Trim from start
-static inline std::string &TrimLeft(std::string &s, bool newlines = true)
-{
-    if (!newlines)
-    {
+static AI_FORCE_INLINE
+std::string &TrimLeft(std::string &s, bool newlines = true) {
+    if (!newlines) {
         s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](char c) { return !Assimp::IsSpace<char>(c); }));
-    }
-    else
-    {
+    } else {
         s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](char c) { return !Assimp::IsSpaceOrNewLine<char>(c); }));
     }
     return s;
 }
 
 /// Trim from end
-static inline std::string &TrimRight(std::string &s, bool newlines = true)
-{
-    if (!newlines)
-    {
+static AI_FORCE_INLINE
+std::string &TrimRight(std::string &s, bool newlines = true) {
+    if (!newlines) {
         s.erase(std::find_if(s.rbegin(), s.rend(), [](char c) { return !Assimp::IsSpace<char>(c); }).base(),s.end());
-    }
-    else
-    {
+    } else {
         s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](char c) { return !Assimp::IsSpaceOrNewLine<char>(c); }));
     }
     return s;
 }
 
 /// Trim from both ends
-static inline std::string &Trim(std::string &s, bool newlines = true)
-{
+static AI_FORCE_INLINE
+std::string &Trim(std::string &s, bool newlines = true) {
     return TrimLeft(TrimRight(s, newlines), newlines);
 }
 
 /// Skips a line from current @ss position until a newline. Returns the skipped part.
-static inline std::string SkipLine(std::stringstream &ss)
-{
+static AI_FORCE_INLINE
+std::string SkipLine(std::stringstream &ss) {
     std::string skipped;
     getline(ss, skipped);
     return skipped;
@@ -131,8 +122,8 @@ static inline std::string SkipLine(std::stringstream &ss)
 
 /// Skips a line and reads next element from @c ss to @c nextElement.
 /** @return Skipped line content until newline. */
-static inline std::string NextAfterNewLine(std::stringstream &ss, std::string &nextElement)
-{
+static AI_FORCE_INLINE
+std::string NextAfterNewLine(std::stringstream &ss, std::string &nextElement) {
     std::string skipped = SkipLine(ss);
     ss >> nextElement;
     return skipped;

+ 10 - 11
code/OgreXmlSerializer.cpp

@@ -213,18 +213,18 @@ std::string &OgreXmlSerializer::SkipCurrentNode()
     DefaultLogger::get()->debug("Skipping node <" + m_currentNodeName + ">");
 #endif
 
-    for(;;)
-    {
-        if (!m_reader->read())
-        {
+    for(;;) {
+        if (!m_reader->read()) {
             m_currentNodeName = "";
             return m_currentNodeName;
         }
-        if (m_reader->getNodeType() != irr::io::EXN_ELEMENT_END)
+        if ( m_reader->getNodeType() != irr::io::EXN_ELEMENT_END ) {
             continue;
-        else if (std::string(m_reader->getNodeName()) == m_currentNodeName)
+        } else if ( std::string( m_reader->getNodeName() ) == m_currentNodeName ) {
             break;
+        }
     }
+
     return NextNode();
 }
 
@@ -303,17 +303,16 @@ static const char *anZ = "z";
 
 // Mesh
 
-MeshXml *OgreXmlSerializer::ImportMesh(XmlReader *reader)
-{
+MeshXml *OgreXmlSerializer::ImportMesh(XmlReader *reader) {
     OgreXmlSerializer serializer(reader);
 
     MeshXml *mesh = new MeshXml();
     serializer.ReadMesh(mesh);
+
     return mesh;
 }
 
-void OgreXmlSerializer::ReadMesh(MeshXml *mesh)
-{
+void OgreXmlSerializer::ReadMesh(MeshXml *mesh) {
     if (NextNode() != nnMesh) {
         throw DeadlyImportError("Root node is <" + m_currentNodeName + "> expecting <mesh>");
     }
@@ -835,7 +834,7 @@ void OgreXmlSerializer::ReadAnimationTracks(Animation *dest)
 
 void OgreXmlSerializer::ReadAnimationKeyFrames(Animation *anim, VertexAnimationTrack *dest)
 {
-    static const aiVector3D zeroVec(0.f, 0.f, 0.f);
+    const aiVector3D zeroVec(0.f, 0.f, 0.f);
 
     NextNode();
     while(m_currentNodeName == nnKeyFrame)

+ 27 - 29
code/OpenGEXImporter.cpp

@@ -221,12 +221,8 @@ static void propId2StdString( Property *prop, std::string &name, std::string &ke
 
 //------------------------------------------------------------------------------------------------
 OpenGEXImporter::VertexContainer::VertexContainer()
-: m_numVerts( 0 )
-, m_vertices( nullptr )
-, m_numColors( 0 )
+: m_numColors( 0 )
 , m_colors( nullptr )
-, m_numNormals( 0 )
-, m_normals( nullptr )
 , m_numUVComps()
 , m_textureCoords() {
     // empty
@@ -234,9 +230,7 @@ OpenGEXImporter::VertexContainer::VertexContainer()
 
 //------------------------------------------------------------------------------------------------
 OpenGEXImporter::VertexContainer::~VertexContainer() {
-    delete[] m_vertices;
     delete[] m_colors;
-    delete[] m_normals;
 
     for(auto &texcoords : m_textureCoords) {
         delete [] texcoords;
@@ -697,7 +691,8 @@ void OpenGEXImporter::handleTransformNode( ODDLParser::DDLNode *node, aiScene *
 void OpenGEXImporter::handleMeshNode( ODDLParser::DDLNode *node, aiScene *pScene ) {
     m_currentMesh = new aiMesh;
     const size_t meshidx( m_meshCache.size() );
-    m_meshCache.push_back( m_currentMesh );
+    // ownership is transfered but a reference remains in m_currentMesh
+    m_meshCache.emplace_back( m_currentMesh );
 
     Property *prop = node->getProperties();
     if( nullptr != prop ) {
@@ -736,17 +731,22 @@ enum MeshAttribute {
     TexCoord
 };
 
+static const std::string PosToken = "position";
+static const std::string ColToken = "color";
+static const std::string NormalToken = "normal";
+static const std::string TexCoordToken = "texcoord";
+
 //------------------------------------------------------------------------------------------------
 static MeshAttribute getAttributeByName( const char *attribName ) {
     ai_assert( nullptr != attribName  );
 
-    if ( 0 == strncmp( "position", attribName, strlen( "position" ) ) ) {
+    if ( 0 == strncmp( PosToken.c_str(), attribName, PosToken.size() ) ) {
         return Position;
-    } else if ( 0 == strncmp( "color", attribName, strlen( "color" ) ) ) {
+    } else if ( 0 == strncmp( ColToken.c_str(), attribName, ColToken.size() ) ) {
         return Color;
-    } else if( 0 == strncmp( "normal", attribName, strlen( "normal" ) ) ) {
+    } else if( 0 == strncmp( NormalToken.c_str(), attribName, NormalToken.size() ) ) {
         return Normal;
-    } else if( 0 == strncmp( "texcoord", attribName, strlen( "texcoord" ) ) ) {
+    } else if( 0 == strncmp( TexCoordToken.c_str(), attribName, TexCoordToken.size() ) ) {
         return TexCoord;
     }
 
@@ -857,17 +857,15 @@ void OpenGEXImporter::handleVertexArrayNode( ODDLParser::DDLNode *node, aiScene
         const size_t numItems( countDataArrayListItems( vaList ) );
 
         if( Position == attribType ) {
-            m_currentVertices.m_numVerts = numItems;
-            m_currentVertices.m_vertices = new aiVector3D[ numItems ];
-            copyVectorArray( numItems, vaList, m_currentVertices.m_vertices );
+            m_currentVertices.m_vertices.resize( numItems );
+            copyVectorArray( numItems, vaList, m_currentVertices.m_vertices.data() );
         } else if ( Color == attribType ) {
             m_currentVertices.m_numColors = numItems;
             m_currentVertices.m_colors = new aiColor4D[ numItems ];
             copyColor4DArray( numItems, vaList, m_currentVertices.m_colors );
         } else if( Normal == attribType ) {
-            m_currentVertices.m_numNormals = numItems;
-            m_currentVertices.m_normals = new aiVector3D[ numItems ];
-            copyVectorArray( numItems, vaList, m_currentVertices.m_normals );
+            m_currentVertices.m_normals.resize( numItems );
+            copyVectorArray( numItems, vaList, m_currentVertices.m_normals.data() );
         } else if( TexCoord == attribType ) {
             m_currentVertices.m_numUVComps[ 0 ] = numItems;
             m_currentVertices.m_textureCoords[ 0 ] = new aiVector3D[ numItems ];
@@ -904,7 +902,7 @@ void OpenGEXImporter::handleIndexArrayNode( ODDLParser::DDLNode *node, aiScene *
         hasColors = true;
     }
     bool hasNormalCoords( false );
-    if ( m_currentVertices.m_numNormals > 0 ) {
+    if ( !m_currentVertices.m_normals.empty() ) {
         m_currentMesh->mNormals = new aiVector3D[ m_currentMesh->mNumVertices ];
         hasNormalCoords = true;
     }
@@ -922,7 +920,7 @@ void OpenGEXImporter::handleIndexArrayNode( ODDLParser::DDLNode *node, aiScene *
         Value *next( vaList->m_dataList );
         for( size_t indices = 0; indices < current.mNumIndices; indices++ ) {
             const int idx( next->getUnsignedInt32() );
-            ai_assert( static_cast<size_t>( idx ) <= m_currentVertices.m_numVerts );
+            ai_assert( static_cast<size_t>( idx ) <= m_currentVertices.m_vertices.size() );
             ai_assert( index < m_currentMesh->mNumVertices );
             aiVector3D &pos = ( m_currentVertices.m_vertices[ idx ] );
             m_currentMesh->mVertices[ index ].Set( pos.x, pos.y, pos.z );
@@ -1105,14 +1103,12 @@ void OpenGEXImporter::handleParamNode( ODDLParser::DDLNode *node, aiScene * /*pS
             return;
         }
         const float floatVal( val->getFloat() );
-        if ( prop->m_value  != nullptr ) {
-            if ( 0 == ASSIMP_strincmp( "fov", prop->m_value->getString(), 3 ) ) {
-                m_currentCamera->mHorizontalFOV = floatVal;
-            } else if ( 0 == ASSIMP_strincmp( "near", prop->m_value->getString(), 3 ) ) {
-                m_currentCamera->mClipPlaneNear = floatVal;
-            } else if ( 0 == ASSIMP_strincmp( "far", prop->m_value->getString(), 3 ) ) {
-                m_currentCamera->mClipPlaneFar = floatVal;
-            }
+        if ( 0 == ASSIMP_strincmp( "fov", prop->m_value->getString(), 3 ) ) {
+            m_currentCamera->mHorizontalFOV = floatVal;
+        } else if ( 0 == ASSIMP_strincmp( "near", prop->m_value->getString(), 4 ) ) {
+            m_currentCamera->mClipPlaneNear = floatVal;
+        } else if ( 0 == ASSIMP_strincmp( "far", prop->m_value->getString(), 3 ) ) {
+            m_currentCamera->mClipPlaneFar = floatVal;
         }
     }
 }
@@ -1145,7 +1141,9 @@ void OpenGEXImporter::copyMeshes( aiScene *pScene ) {
 
     pScene->mNumMeshes = static_cast<unsigned int>(m_meshCache.size());
     pScene->mMeshes = new aiMesh*[ pScene->mNumMeshes ];
-    std::copy( m_meshCache.begin(), m_meshCache.end(), pScene->mMeshes );
+    for (unsigned int i = 0; i < pScene->mNumMeshes; i++) {
+        pScene->mMeshes[i] = m_meshCache[i].release();
+    }
 }
 
 //------------------------------------------------------------------------------------------------

+ 4 - 6
code/OpenGEXImporter.h

@@ -144,12 +144,10 @@ protected:
 
 private:
     struct VertexContainer {
-        size_t m_numVerts;
-        aiVector3D *m_vertices;
+        std::vector<aiVector3D> m_vertices;
         size_t m_numColors;
         aiColor4D *m_colors;
-        size_t m_numNormals;
-        aiVector3D *m_normals;
+        std::vector<aiVector3D> m_normals;
         size_t m_numUVComps[ AI_MAX_NUMBER_OF_TEXTURECOORDS ];
         aiVector3D *m_textureCoords[ AI_MAX_NUMBER_OF_TEXTURECOORDS ];
 
@@ -185,7 +183,7 @@ private:
     typedef std::map<aiNode*, std::unique_ptr<ChildInfo> > NodeChildMap;
     NodeChildMap m_nodeChildMap;
 
-    std::vector<aiMesh*> m_meshCache;
+    std::vector<std::unique_ptr<aiMesh> > m_meshCache;
     typedef std::map<std::string, size_t> ReferenceMap;
     std::map<std::string, size_t> m_mesh2refMap;
     std::map<std::string, size_t> m_material2refMap;
@@ -194,7 +192,7 @@ private:
     MetricInfo m_metrics[ MetricInfo::Max ];
     aiNode *m_currentNode;
     VertexContainer m_currentVertices;
-    aiMesh *m_currentMesh;
+    aiMesh *m_currentMesh;  // not owned, target is owned by m_meshCache
     aiMaterial *m_currentMaterial;
     aiLight *m_currentLight;
     aiCamera *m_currentCamera;

+ 11 - 0
code/PlyExporter.cpp

@@ -148,6 +148,17 @@ PlyExporter::PlyExporter(const char* _filename, const aiScene* pScene, bool bina
         << aiGetVersionMajor() << '.' << aiGetVersionMinor() << '.'
         << aiGetVersionRevision() << ")" << endl;
 
+    // Look through materials for a diffuse texture, and add it if found
+    for ( unsigned int i = 0; i < pScene->mNumMaterials; ++i )
+    {
+        const aiMaterial* const mat = pScene->mMaterials[i];
+        aiString s;
+        if ( AI_SUCCESS == mat->Get( AI_MATKEY_TEXTURE_DIFFUSE( 0 ), s ) )
+        {
+            mOutput << "comment TextureFile " << s.data << endl;
+        }
+    }
+
     // TODO: probably want to check here rather than just assume something
     //       definitely not good to always write float even if we might have double precision
 

+ 142 - 185
code/PlyLoader.cpp

@@ -58,16 +58,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 using namespace Assimp;
 
 static const aiImporterDesc desc = {
-  "Stanford Polygon Library (PLY) Importer",
-  "",
-  "",
-  "",
-  aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportTextFlavour,
-  0,
-  0,
-  0,
-  0,
-  "ply"
+    "Stanford Polygon Library (PLY) Importer",
+    "",
+    "",
+    "",
+    aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportTextFlavour,
+    0,
+    0,
+    0,
+    0,
+    "ply"
 };
 
 
@@ -92,229 +92,188 @@ namespace
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 PLYImporter::PLYImporter()
-  : mBuffer(nullptr)
-  , pcDOM(nullptr)
-  , mGeneratedMesh(nullptr){
-  // empty
+: mBuffer(nullptr)
+, pcDOM(nullptr)
+, mGeneratedMesh(nullptr) {
+    // empty
 }
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
 PLYImporter::~PLYImporter() {
-  // empty
+    // empty
 }
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool PLYImporter::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
-{
-  const std::string extension = GetExtension(pFile);
+bool PLYImporter::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
+    const std::string extension = GetExtension(pFile);
 
-  if (extension == "ply")
-    return true;
-  else if (!extension.length() || checkSig)
-  {
-    if (!pIOHandler)return true;
-    const char* tokens[] = { "ply" };
-    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
-  }
-  return false;
+    if ( extension == "ply" ) {
+        return true;
+    } else if (!extension.length() || checkSig) {
+        if ( !pIOHandler ) {
+            return true;
+        }
+        static const char* tokens[] = { "ply" };
+        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
+    }
+
+    return false;
 }
 
 // ------------------------------------------------------------------------------------------------
-const aiImporterDesc* PLYImporter::GetInfo() const
-{
-  return &desc;
+const aiImporterDesc* PLYImporter::GetInfo() const {
+    return &desc;
 }
 
 // ------------------------------------------------------------------------------------------------
 static bool isBigEndian(const char* szMe) {
-  ai_assert(NULL != szMe);
+    ai_assert(NULL != szMe);
 
-  // binary_little_endian
-  // binary_big_endian
-  bool isBigEndian(false);
+    // binary_little_endian
+    // binary_big_endian
+    bool isBigEndian(false);
 #if (defined AI_BUILD_BIG_ENDIAN)
-  if ( 'l' == *szMe || 'L' == *szMe ) {
-    isBigEndian = true;
-  }
+    if ( 'l' == *szMe || 'L' == *szMe ) {
+        isBigEndian = true;
+    }
 #else
-  if ('b' == *szMe || 'B' == *szMe) {
-    isBigEndian = true;
-  }
+    if ('b' == *szMe || 'B' == *szMe) {
+        isBigEndian = true;
+    }
 #endif // ! AI_BUILD_BIG_ENDIAN
 
-  return isBigEndian;
+    return isBigEndian;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure.
-void PLYImporter::InternReadFile(const std::string& pFile,
-  aiScene* pScene, IOSystem* pIOHandler)
-{
-  static const std::string mode = "rb";
-  std::unique_ptr<IOStream> fileStream(pIOHandler->Open(pFile, mode));
-  if (!fileStream.get()) {
-    throw DeadlyImportError("Failed to open file " + pFile + ".");
-  }
+void PLYImporter::InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) {
+    static const std::string mode = "rb";
+    std::unique_ptr<IOStream> fileStream(pIOHandler->Open(pFile, mode));
+    if (!fileStream.get()) {
+        throw DeadlyImportError("Failed to open file " + pFile + ".");
+    }
 
-  // Get the file-size
-  size_t fileSize = fileStream->FileSize();
-  if ( 0 == fileSize ) {
-      throw DeadlyImportError("File " + pFile + " is empty.");
-  }
+    // Get the file-size
+    const size_t fileSize( fileStream->FileSize() );
+    if ( 0 == fileSize ) {
+        throw DeadlyImportError("File " + pFile + " is empty.");
+    }
 
-  IOStreamBuffer<char> streamedBuffer(1024 * 1024);
-  streamedBuffer.open(fileStream.get());
+    IOStreamBuffer<char> streamedBuffer(1024 * 1024);
+    streamedBuffer.open(fileStream.get());
 
-  // the beginning of the file must be PLY - magic, magic
-  std::vector<char> headerCheck;
-  streamedBuffer.getNextLine(headerCheck);
+    // the beginning of the file must be PLY - magic, magic
+    std::vector<char> headerCheck;
+    streamedBuffer.getNextLine(headerCheck);
 
-  if ((headerCheck.size() < 3) ||
-      (headerCheck[0] != 'P' && headerCheck[0] != 'p') ||
-      (headerCheck[1] != 'L' && headerCheck[1] != 'l') ||
-      (headerCheck[2] != 'Y' && headerCheck[2] != 'y') )
-  {
-    streamedBuffer.close();
-    throw DeadlyImportError("Invalid .ply file: Magic number \'ply\' is no there");
-  }
+    if ((headerCheck.size() < 3) ||
+            (headerCheck[0] != 'P' && headerCheck[0] != 'p') ||
+            (headerCheck[1] != 'L' && headerCheck[1] != 'l') ||
+            (headerCheck[2] != 'Y' && headerCheck[2] != 'y') ) {
+        streamedBuffer.close();
+        throw DeadlyImportError("Invalid .ply file: Magic number \'ply\' is no there");
+    }
 
-  std::vector<char> mBuffer2;
-  streamedBuffer.getNextLine(mBuffer2);
-  mBuffer = (unsigned char*)&mBuffer2[0];
+    std::vector<char> mBuffer2;
+    streamedBuffer.getNextLine(mBuffer2);
+    mBuffer = (unsigned char*)&mBuffer2[0];
 
-  char* szMe = (char*)&this->mBuffer[0];
-  SkipSpacesAndLineEnd(szMe, (const char**)&szMe);
+    char* szMe = (char*)&this->mBuffer[0];
+    SkipSpacesAndLineEnd(szMe, (const char**)&szMe);
 
-  // determine the format of the file data and construct the aimesh
-  PLY::DOM sPlyDom;
-  this->pcDOM = &sPlyDom;
+    // determine the format of the file data and construct the aimesh
+    PLY::DOM sPlyDom;   
+    this->pcDOM = &sPlyDom;
 
-  if (TokenMatch(szMe, "format", 6)) {
-    if (TokenMatch(szMe, "ascii", 5)) {
-      SkipLine(szMe, (const char**)&szMe);
-      if (!PLY::DOM::ParseInstance(streamedBuffer, &sPlyDom, this))
-      {
-        if (mGeneratedMesh != NULL)
-        {
-          delete(mGeneratedMesh);
-          mGeneratedMesh = nullptr;
-        }
+    if (TokenMatch(szMe, "format", 6)) {
+        if (TokenMatch(szMe, "ascii", 5)) {
+            SkipLine(szMe, (const char**)&szMe);
+            if (!PLY::DOM::ParseInstance(streamedBuffer, &sPlyDom, this)) {
+                if (mGeneratedMesh != NULL) {
+                    delete(mGeneratedMesh);
+                    mGeneratedMesh = nullptr;
+                }
 
-        streamedBuffer.close();
-        throw DeadlyImportError("Invalid .ply file: Unable to build DOM (#1)");
-      }
-    }
-    else if (!::strncmp(szMe, "binary_", 7))
-    {
-      szMe += 7;
-      const bool bIsBE(isBigEndian(szMe));
+                streamedBuffer.close();
+                throw DeadlyImportError("Invalid .ply file: Unable to build DOM (#1)");
+            }
+        } else if (!::strncmp(szMe, "binary_", 7)) {
+            szMe += 7;
+            const bool bIsBE(isBigEndian(szMe));
+
+            // skip the line, parse the rest of the header and build the DOM
+            if (!PLY::DOM::ParseInstanceBinary(streamedBuffer, &sPlyDom, this, bIsBE)) {
+                if (mGeneratedMesh != NULL) {
+                    delete(mGeneratedMesh);
+                    mGeneratedMesh = nullptr;
+                }
+
+                streamedBuffer.close();
+                throw DeadlyImportError("Invalid .ply file: Unable to build DOM (#2)");
+            }
+        } else {
+            if (mGeneratedMesh != NULL) {
+                delete(mGeneratedMesh);
+                mGeneratedMesh = nullptr;
+            }
 
-      // skip the line, parse the rest of the header and build the DOM
-      if (!PLY::DOM::ParseInstanceBinary(streamedBuffer, &sPlyDom, this, bIsBE))
-      {
-        if (mGeneratedMesh != NULL)
-        {
-          delete(mGeneratedMesh);
-          mGeneratedMesh = nullptr;
+            streamedBuffer.close();
+            throw DeadlyImportError("Invalid .ply file: Unknown file format");
+        }
+    } else {
+        AI_DEBUG_INVALIDATE_PTR(this->mBuffer);
+        if (mGeneratedMesh != NULL) {
+            delete(mGeneratedMesh);
+            mGeneratedMesh = nullptr;
         }
 
         streamedBuffer.close();
-        throw DeadlyImportError("Invalid .ply file: Unable to build DOM (#2)");
-      }
-    }
-    else
-    {
-      if (mGeneratedMesh != NULL)
-      {
-        delete(mGeneratedMesh);
-        mGeneratedMesh = nullptr;
-      }
-
-      streamedBuffer.close();
-      throw DeadlyImportError("Invalid .ply file: Unknown file format");
-    }
-  }
-  else
-  {
-    AI_DEBUG_INVALIDATE_PTR(this->mBuffer);
-    if (mGeneratedMesh != NULL)
-    {
-      delete(mGeneratedMesh);
-      mGeneratedMesh = nullptr;
+        throw DeadlyImportError("Invalid .ply file: Missing format specification");
     }
 
+    //free the file buffer
     streamedBuffer.close();
-    throw DeadlyImportError("Invalid .ply file: Missing format specification");
-  }
-
-  //free the file buffer
-  streamedBuffer.close();
-
-  if (mGeneratedMesh == NULL)
-  {
-    throw DeadlyImportError("Invalid .ply file: Unable to extract mesh data ");
-  }
-
-  // if no face list is existing we assume that the vertex
-  // list is containing a list of points
-  bool pointsOnly = mGeneratedMesh->mFaces == NULL ? true : false;
-  if (pointsOnly)
-  {
-    if (mGeneratedMesh->mNumVertices < 3)
-    {
-      if (mGeneratedMesh != NULL)
-      {
-        delete(mGeneratedMesh);
-        mGeneratedMesh = nullptr;
-      }
 
-      streamedBuffer.close();
-      throw DeadlyImportError("Invalid .ply file: Not enough "
-        "vertices to build a proper face list. ");
+    if (mGeneratedMesh == NULL) {
+        throw DeadlyImportError("Invalid .ply file: Unable to extract mesh data ");
     }
 
-    const unsigned int iNum = (unsigned int)mGeneratedMesh->mNumVertices / 3;
-    mGeneratedMesh->mNumFaces = iNum;
-    mGeneratedMesh->mFaces = new aiFace[mGeneratedMesh->mNumFaces];
-
-    for (unsigned int i = 0; i < iNum; ++i)
-    {
-      mGeneratedMesh->mFaces[i].mNumIndices = 3;
-      mGeneratedMesh->mFaces[i].mIndices = new unsigned int[3];
-      mGeneratedMesh->mFaces[i].mIndices[0] = (i * 3);
-      mGeneratedMesh->mFaces[i].mIndices[1] = (i * 3) + 1;
-      mGeneratedMesh->mFaces[i].mIndices[2] = (i * 3) + 2;
+    // if no face list is existing we assume that the vertex
+    // list is containing a list of points
+    bool pointsOnly = mGeneratedMesh->mFaces == NULL ? true : false;
+    if (pointsOnly) {
+      mGeneratedMesh->mPrimitiveTypes = aiPrimitiveType::aiPrimitiveType_POINT;
     }
-  }
 
-  // now load a list of all materials
-  std::vector<aiMaterial*> avMaterials;
-  std::string defaultTexture;
-  LoadMaterial(&avMaterials, defaultTexture, pointsOnly);
+    // now load a list of all materials
+    std::vector<aiMaterial*> avMaterials;
+    std::string defaultTexture;
+    LoadMaterial(&avMaterials, defaultTexture, pointsOnly);
 
-  // now generate the output scene object. Fill the material list
-  pScene->mNumMaterials = (unsigned int)avMaterials.size();
-  pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials];
-  for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) {
-    pScene->mMaterials[i] = avMaterials[i];
-  }
+    // now generate the output scene object. Fill the material list
+    pScene->mNumMaterials = (unsigned int)avMaterials.size();
+    pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials];
+    for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) {
+        pScene->mMaterials[i] = avMaterials[i];
+    }
 
-  // fill the mesh list
-  pScene->mNumMeshes = 1;
-  pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
-  pScene->mMeshes[0] = mGeneratedMesh;
-  mGeneratedMesh = nullptr;
+    // fill the mesh list
+    pScene->mNumMeshes = 1;
+    pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
+    pScene->mMeshes[0] = mGeneratedMesh;
+    mGeneratedMesh = nullptr;
 
-  // generate a simple node structure
-  pScene->mRootNode = new aiNode();
-  pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
-  pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes];
+    // generate a simple node structure
+    pScene->mRootNode = new aiNode();
+    pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
+    pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes];
 
-  for (unsigned int i = 0; i < pScene->mRootNode->mNumMeshes; ++i) {
-    pScene->mRootNode->mMeshes[i] = i;
-  }
+    for (unsigned int i = 0; i < pScene->mRootNode->mNumMeshes; ++i) {
+        pScene->mRootNode->mMeshes[i] = i;
+    }
 }
 
 void PLYImporter::LoadVertex(const PLY::Element* pcElement, const PLY::ElementInstance* instElement, unsigned int pos) {
@@ -521,9 +480,7 @@ void PLYImporter::LoadVertex(const PLY::Element* pcElement, const PLY::ElementIn
 
 // ------------------------------------------------------------------------------------------------
 // Convert a color component to [0...1]
-ai_real PLYImporter::NormalizeColorValue(PLY::PropertyInstance::ValueUnion val,
-  PLY::EDataType eType)
-{
+ai_real PLYImporter::NormalizeColorValue(PLY::PropertyInstance::ValueUnion val, PLY::EDataType eType) {
   switch (eType)
   {
   case EDT_Float:

+ 0 - 1
code/PlyLoader.h

@@ -57,7 +57,6 @@ struct aiMesh;
 
 namespace Assimp {
 
-
 using namespace PLY;
 
 // ---------------------------------------------------------------------------

+ 44 - 24
code/PlyParser.cpp

@@ -1043,71 +1043,91 @@ bool PLY::PropertyInstance::ParseValueBinary(IOStreamBuffer<char> &streamBuffer,
   switch (eType)
   {
   case EDT_UInt:
-    out->iUInt = (uint32_t)*((uint32_t*)pCur);
-    pCur += 4;
+  {
+    uint32_t t;
+    memcpy(&t, pCur, sizeof(uint32_t));
+    pCur += sizeof(uint32_t);
 
     // Swap endianness
-    if (p_bBE)ByteSwap::Swap((int32_t*)&out->iUInt);
+    if (p_bBE)ByteSwap::Swap(&t);
+    out->iUInt = t;
     break;
+  }
 
   case EDT_UShort:
   {
-    uint16_t i = *((uint16_t*)pCur);
+    uint16_t t;
+    memcpy(&t, pCur, sizeof(uint16_t));
+    pCur += sizeof(uint16_t);
 
     // Swap endianness
-    if (p_bBE)ByteSwap::Swap(&i);
-    out->iUInt = (uint32_t)i;
-    pCur += 2;
+    if (p_bBE)ByteSwap::Swap(&t);
+    out->iUInt = t;
     break;
   }
 
   case EDT_UChar:
   {
-    out->iUInt = (uint32_t)(*((uint8_t*)pCur));
-    pCur++;
+    uint8_t t;
+    memcpy(&t, pCur, sizeof(uint8_t));
+    pCur += sizeof(uint8_t);
+    out->iUInt = t;
     break;
   }
 
   case EDT_Int:
-    out->iInt = *((int32_t*)pCur);
-    pCur += 4;
+  {
+    int32_t t;
+    memcpy(&t, pCur, sizeof(int32_t));
+    pCur += sizeof(int32_t);
 
     // Swap endianness
-    if (p_bBE)ByteSwap::Swap(&out->iInt);
+    if (p_bBE)ByteSwap::Swap(&t);
+    out->iInt = t;
     break;
+  }
 
   case EDT_Short:
   {
-    int16_t i = *((int16_t*)pCur);
+    int16_t t;
+    memcpy(&t, pCur, sizeof(int16_t));
+    pCur += sizeof(int16_t);
 
     // Swap endianness
-    if (p_bBE)ByteSwap::Swap(&i);
-    out->iInt = (int32_t)i;
-    pCur += 2;
+    if (p_bBE)ByteSwap::Swap(&t);
+    out->iInt = t;
     break;
   }
 
   case EDT_Char:
-    out->iInt = (int32_t)*((int8_t*)pCur);
-    pCur++;
+  {
+    int8_t t;
+    memcpy(&t, pCur, sizeof(int8_t));
+    pCur += sizeof(int8_t);
+    out->iInt = t;
     break;
+  }
 
   case EDT_Float:
   {
-    out->fFloat = *((float*)pCur);
+    float t;
+    memcpy(&t, pCur, sizeof(float));
+    pCur += sizeof(float);
 
     // Swap endianness
-    if (p_bBE)ByteSwap::Swap((int32_t*)&out->fFloat);
-    pCur += 4;
+    if (p_bBE)ByteSwap::Swap(&t);
+    out->fFloat = t;
     break;
   }
   case EDT_Double:
   {
-    out->fDouble = *((double*)pCur);
+    double t;
+    memcpy(&t, pCur, sizeof(double));
+    pCur += sizeof(double);
 
     // Swap endianness
-    if (p_bBE)ByteSwap::Swap((int64_t*)&out->fDouble);
-    pCur += 8;
+    if (p_bBE)ByteSwap::Swap(&t);
+    out->fDouble = t;
     break;
   }
   default:

+ 18 - 25
code/PlyParser.h

@@ -39,12 +39,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 */
 
-
 /** @file Defines the helper data structures for importing PLY files  */
+#pragma once
 #ifndef AI_PLYFILEHELPER_H_INC
 #define AI_PLYFILEHELPER_H_INC
 
-
 #include <assimp/ParsingUtils.h>
 #include <assimp/IOStreamBuffer.h>
 #include <vector>
@@ -58,8 +57,7 @@ class PLYImporter;
 // http://local.wasp.uwa.edu.au/~pbourke/dataformats/ply/
 // http://w3.impa.br/~lvelho/outgoing/sossai/old/ViHAP_D4.4.2_PLY_format_v1.1.pdf
 // http://www.okino.com/conv/exp_ply.htm
-namespace PLY
-{
+namespace PLY {
 
 // ---------------------------------------------------------------------------------
 /*
@@ -78,8 +76,7 @@ int8
 int16
 uint8 ... forms are also used
 */
-enum EDataType
-{
+enum EDataType {
     EDT_Char = 0x0u,
     EDT_UChar,
     EDT_Short,
@@ -98,8 +95,7 @@ enum EDataType
  *
  * Semantics define the usage of a property, e.g. x coordinate
 */
-enum ESemantic
-{
+enum ESemantic {
     //! vertex position x coordinate
     EST_XCoord = 0x0u,
     //! vertex position x coordinate
@@ -182,15 +178,14 @@ enum ESemantic
  *
  * Semantics define the usage of an element, e.g. vertex or material
 */
-enum EElementSemantic
-{
+enum EElementSemantic {
     //! The element is a vertex
     EEST_Vertex = 0x0u,
 
     //! The element is a face description (index table)
     EEST_Face,
 
-    //! The element is a tristrip description (index table)
+    //! The element is a triangle-strip description (index table)
     EEST_TriStrip,
 
     //! The element is an edge description (ignored)
@@ -211,17 +206,16 @@ enum EElementSemantic
  *
  * This can e.g. be a part of the vertex declaration
  */
-class Property
-{
+class Property {
 public:
-
     //! Default constructor
     Property()
-        : eType (EDT_Int),
-        Semantic(),
-        bIsList(false),
-        eFirstType(EDT_UChar)
-    {}
+    : eType (EDT_Int)
+    , Semantic()
+    , bIsList(false)
+    , eFirstType(EDT_UChar) {
+        // empty
+    }
 
     //! Data type of the property
     EDataType eType;
@@ -260,15 +254,14 @@ public:
  * This can e.g. be the vertex declaration. Elements contain a
  * well-defined number of properties.
  */
-class Element
-{
+class Element {
 public:
-
     //! Default constructor
     Element()
-        :   eSemantic (EEST_INVALID)
-        ,   NumOccur(0)
-    {}
+    : eSemantic (EEST_INVALID)
+    , NumOccur(0) {
+        // empty
+    }
 
     //! List of properties assigned to the element
     //! std::vector to support operator[]

+ 7 - 2
code/PostStepRegistry.cpp

@@ -125,6 +125,9 @@ corresponding preprocessor flag to selectively disable steps.
 #ifndef ASSIMP_BUILD_NO_DEBONE_PROCESS
 #   include "DeboneProcess.h"
 #endif
+#if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS)
+#   include "ScaleProcess.h"
+#endif
 
 namespace Assimp {
 
@@ -136,7 +139,7 @@ void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out)
     // of sequence it is executed. Steps that are added here are not
     // validated - as RegisterPPStep() does - all dependencies must be given.
     // ----------------------------------------------------------------------------
-    out.reserve(30);
+    out.reserve(31);
 #if (!defined ASSIMP_BUILD_NO_MAKELEFTHANDED_PROCESS)
     out.push_back( new MakeLeftHandedProcess());
 #endif
@@ -197,7 +200,9 @@ void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out)
 #if (!defined ASSIMP_BUILD_NO_GENFACENORMALS_PROCESS)
     out.push_back( new GenFaceNormalsProcess());
 #endif
-
+#if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS)
+    out.push_back( new ScaleProcess());
+#endif
     // .........................................................................
     // DON'T change the order of these five ..
     // XXX this is actually a design weakness that dates back to the time

+ 6 - 3
code/PretransformVertices.cpp

@@ -651,7 +651,8 @@ void PretransformVertices::Execute( aiScene* pScene)
             // generate mesh nodes
             for (unsigned int i = 0; i < pScene->mNumMeshes;++i,++nodes)
             {
-                aiNode* pcNode = *nodes = new aiNode();
+                aiNode* pcNode = new aiNode();
+                *nodes = pcNode;
                 pcNode->mParent = pScene->mRootNode;
                 pcNode->mName = pScene->mMeshes[i]->mName;
 
@@ -663,7 +664,8 @@ void PretransformVertices::Execute( aiScene* pScene)
             // generate light nodes
             for (unsigned int i = 0; i < pScene->mNumLights;++i,++nodes)
             {
-                aiNode* pcNode = *nodes = new aiNode();
+                aiNode* pcNode = new aiNode();
+                *nodes = pcNode;
                 pcNode->mParent = pScene->mRootNode;
                 pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "light_%u",i);
                 pScene->mLights[i]->mName = pcNode->mName;
@@ -671,7 +673,8 @@ void PretransformVertices::Execute( aiScene* pScene)
             // generate camera nodes
             for (unsigned int i = 0; i < pScene->mNumCameras;++i,++nodes)
             {
-                aiNode* pcNode = *nodes = new aiNode();
+                aiNode* pcNode = new aiNode();
+                *nodes = pcNode;
                 pcNode->mParent = pScene->mRootNode;
                 pcNode->mName.length = ::ai_snprintf(pcNode->mName.data,MAXLEN,"cam_%u",i);
                 pScene->mCameras[i]->mName = pcNode->mName;

+ 6 - 2
code/STLExporter.cpp

@@ -172,12 +172,16 @@ void STLExporter :: WriteMeshBinary(const aiMesh* m)
             }
             nor.Normalize();
         }
-        ai_real nx = nor.x, ny = nor.y, nz = nor.z;
+        // STL binary files use 4-byte floats. This may possibly cause loss of precision
+        // for clients using 8-byte doubles
+        float nx = (float) nor.x;
+        float ny = (float) nor.y;
+        float nz = (float) nor.z;
         AI_SWAP4(nx); AI_SWAP4(ny); AI_SWAP4(nz);
         mOutput.write((char *)&nx, 4); mOutput.write((char *)&ny, 4); mOutput.write((char *)&nz, 4);
         for(unsigned int a = 0; a < f.mNumIndices; ++a) {
             const aiVector3D& v  = m->mVertices[f.mIndices[a]];
-            ai_real vx = v.x, vy = v.y, vz = v.z;
+            float vx = (float) v.x, vy = (float) v.y, vz = (float) v.z;
             AI_SWAP4(vx); AI_SWAP4(vy); AI_SWAP4(vz);
             mOutput.write((char *)&vx, 4); mOutput.write((char *)&vy, 4); mOutput.write((char *)&vz, 4);
         }

+ 25 - 8
code/STLLoader.cpp

@@ -449,26 +449,43 @@ bool STLImporter::LoadBinaryFile()
     aiVector3D *vp = pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
     aiVector3D *vn = pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
 
+    typedef aiVector3t<float> aiVector3F;
+    aiVector3F* theVec;
+    aiVector3F theVec3F;
+    
     for ( unsigned int i = 0; i < pMesh->mNumFaces; ++i ) {
         // NOTE: Blender sometimes writes empty normals ... this is not
         // our fault ... the RemoveInvalidData helper step should fix that
-        ::memcpy( vn, sz, sizeof( aiVector3D ) );
-        sz += sizeof(aiVector3D);
+
+        // There's one normal for the face in the STL; use it three times
+        // for vertex normals
+        theVec = (aiVector3F*) sz;
+        ::memcpy( &theVec3F, theVec, sizeof(aiVector3F) );
+        vn->x = theVec3F.x; vn->y = theVec3F.y; vn->z = theVec3F.z;
         *(vn+1) = *vn;
         *(vn+2) = *vn;
+        ++theVec;
         vn += 3;
 
-        ::memcpy( vp, sz, sizeof( aiVector3D ) );
+        // vertex 1
+        ::memcpy( &theVec3F, theVec, sizeof(aiVector3F) );
+        vp->x = theVec3F.x; vp->y = theVec3F.y; vp->z = theVec3F.z;
+        ++theVec;
         ++vp;
-        sz += sizeof(aiVector3D);
 
-        ::memcpy( vp, sz, sizeof( aiVector3D ) );
+        // vertex 2
+        ::memcpy( &theVec3F, theVec, sizeof(aiVector3F) );
+        vp->x = theVec3F.x; vp->y = theVec3F.y; vp->z = theVec3F.z;
+        ++theVec;
         ++vp;
-        sz += sizeof(aiVector3D);
 
-        ::memcpy( vp, sz, sizeof( aiVector3D ) );
+        // vertex 3
+        ::memcpy( &theVec3F, theVec, sizeof(aiVector3F) );
+        vp->x = theVec3F.x; vp->y = theVec3F.y; vp->z = theVec3F.z;
+        ++theVec;
         ++vp;
-        sz += sizeof(aiVector3D);
+        
+        sz = (const unsigned char*) theVec;
 
         uint16_t color = *((uint16_t*)sz);
         sz += 2;

+ 4 - 7
code/ScaleProcess.cpp

@@ -39,6 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 ----------------------------------------------------------------------
 */
+#ifndef ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS
+
 #include "ScaleProcess.h"
 
 #include <assimp/scene.h>
@@ -86,13 +88,6 @@ void ScaleProcess::Execute( aiScene* pScene ) {
 
 void ScaleProcess::traverseNodes( aiNode *node ) {
     applyScaling( node );
-
-    /*for ( unsigned int i = 0; i < node->mNumChildren; ++i ) {
-        aiNode *currentNode = currentNode->mChildren[ i ];
-        if ( nullptr != currentNode ) {
-            traverseNodes( currentNode );
-        }
-    }*/
 }
 
 void ScaleProcess::applyScaling( aiNode *currentNode ) {
@@ -104,3 +99,5 @@ void ScaleProcess::applyScaling( aiNode *currentNode ) {
 }
 
 } // Namespace Assimp
+
+#endif // !! ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS

+ 41 - 32
code/SceneCombiner.cpp

@@ -55,6 +55,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/SceneCombiner.h>
 #include <assimp/StringUtils.h>
 #include <assimp/fast_atof.h>
+#include <assimp/metadata.h>
 #include <assimp/Hash.h>
 #include "time.h"
 #include <assimp/DefaultLogger.hpp>
@@ -806,8 +807,9 @@ void SceneCombiner::MergeMeshes(aiMesh** _out, unsigned int /*flags*/,
             for (std::vector<aiMesh*>::const_iterator it = begin; it != end;++it)   {
                 if ((*it)->mNormals)    {
                     ::memcpy(pv2,(*it)->mNormals,(*it)->mNumVertices*sizeof(aiVector3D));
+                } else {
+                    DefaultLogger::get()->warn( "JoinMeshes: Normals expected but input mesh contains no normals" );
                 }
-                else DefaultLogger::get()->warn("JoinMeshes: Normals expected but input mesh contains no normals");
                 pv2 += (*it)->mNumVertices;
             }
         }
@@ -817,28 +819,29 @@ void SceneCombiner::MergeMeshes(aiMesh** _out, unsigned int /*flags*/,
             pv2 = out->mTangents = new aiVector3D[out->mNumVertices];
             aiVector3D* pv2b = out->mBitangents = new aiVector3D[out->mNumVertices];
 
-            for (std::vector<aiMesh*>::const_iterator it = begin; it != end;++it)   {
+            for (std::vector<aiMesh*>::const_iterator it = begin; it != end;++it) {
                 if ((*it)->mTangents)   {
                     ::memcpy(pv2, (*it)->mTangents,  (*it)->mNumVertices*sizeof(aiVector3D));
                     ::memcpy(pv2b,(*it)->mBitangents,(*it)->mNumVertices*sizeof(aiVector3D));
+                } else {
+                    DefaultLogger::get()->warn( "JoinMeshes: Tangents expected but input mesh contains no tangents" );
                 }
-                else DefaultLogger::get()->warn("JoinMeshes: Tangents expected but input mesh contains no tangents");
                 pv2  += (*it)->mNumVertices;
                 pv2b += (*it)->mNumVertices;
             }
         }
         // copy texture coordinates
         unsigned int n = 0;
-        while ((**begin).HasTextureCoords(n))   {
+        while ((**begin).HasTextureCoords(n)) {
             out->mNumUVComponents[n] = (*begin)->mNumUVComponents[n];
 
             pv2 = out->mTextureCoords[n] = new aiVector3D[out->mNumVertices];
             for (std::vector<aiMesh*>::const_iterator it = begin; it != end;++it)   {
-
                 if ((*it)->mTextureCoords[n])   {
                     ::memcpy(pv2,(*it)->mTextureCoords[n],(*it)->mNumVertices*sizeof(aiVector3D));
+                } else {
+                    DefaultLogger::get()->warn( "JoinMeshes: UVs expected but input mesh contains no UVs" );
                 }
-                else DefaultLogger::get()->warn("JoinMeshes: UVs expected but input mesh contains no UVs");
                 pv2 += (*it)->mNumVertices;
             }
             ++n;
@@ -848,11 +851,11 @@ void SceneCombiner::MergeMeshes(aiMesh** _out, unsigned int /*flags*/,
         while ((**begin).HasVertexColors(n))    {
             aiColor4D* pv2 = out->mColors[n] = new aiColor4D[out->mNumVertices];
             for (std::vector<aiMesh*>::const_iterator it = begin; it != end;++it)   {
-
                 if ((*it)->mColors[n])  {
                     ::memcpy(pv2,(*it)->mColors[n],(*it)->mNumVertices*sizeof(aiColor4D));
+                } else {
+                    DefaultLogger::get()->warn( "JoinMeshes: VCs expected but input mesh contains no VCs" );
                 }
-                else DefaultLogger::get()->warn("JoinMeshes: VCs expected but input mesh contains no VCs");
                 pv2 += (*it)->mNumVertices;
             }
             ++n;
@@ -1001,7 +1004,12 @@ void SceneCombiner::CopyScene(aiScene** _dest,const aiScene* src,bool allocate)
         *_dest = new aiScene();
     }
     aiScene* dest = *_dest;
-    ai_assert(dest);
+    ai_assert(nullptr != dest);
+
+    // copy metadata
+    if ( nullptr != src->mMetaData ) {
+        dest->mMetaData = new aiMetadata( *src->mMetaData );
+    }
 
     // copy animations
     dest->mNumAnimations = src->mNumAnimations;
@@ -1256,29 +1264,30 @@ void SceneCombiner::Copy(aiMetadata** _dest, const aiMetadata* src) {
         aiMetadataEntry& out = dest->mValues[i];
         out.mType = in.mType;
         switch (dest->mValues[i].mType) {
-        case AI_BOOL:
-            out.mData = new bool(*static_cast<bool*>(in.mData));
-            break;
-        case AI_INT32:
-            out.mData = new int32_t(*static_cast<int32_t*>(in.mData));
-            break;
-        case AI_UINT64:
-            out.mData = new uint64_t(*static_cast<uint64_t*>(in.mData));
-            break;
-        case AI_FLOAT:
-            out.mData = new float(*static_cast<float*>(in.mData));
-            break;
-        case AI_DOUBLE:
-            out.mData = new double(*static_cast<double*>(in.mData));
-            break;
-        case AI_AISTRING:
-            out.mData = new aiString(*static_cast<aiString*>(in.mData));
-            break;
-        case AI_AIVECTOR3D:
-            out.mData = new aiVector3D(*static_cast<aiVector3D*>(in.mData));
-            break;
-        default:
-            ai_assert(false);
+            case AI_BOOL:
+                out.mData = new bool(*static_cast<bool*>(in.mData));
+                break;
+            case AI_INT32:
+                out.mData = new int32_t(*static_cast<int32_t*>(in.mData));
+                break;
+            case AI_UINT64:
+                out.mData = new uint64_t(*static_cast<uint64_t*>(in.mData));
+                break;
+            case AI_FLOAT:
+                out.mData = new float(*static_cast<float*>(in.mData));
+                break;
+            case AI_DOUBLE:
+                out.mData = new double(*static_cast<double*>(in.mData));
+                break;
+            case AI_AISTRING:
+                out.mData = new aiString(*static_cast<aiString*>(in.mData));
+                break;
+            case AI_AIVECTOR3D:
+                out.mData = new aiVector3D(*static_cast<aiVector3D*>(in.mData));
+                break;
+            default:
+                ai_assert(false);
+                break;
         }
     }
 }

+ 23 - 10
code/ScenePrivate.h

@@ -42,22 +42,25 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 /** @file Stuff to deal with aiScene::mPrivate
  */
+#pragma once
 #ifndef AI_SCENEPRIVATE_H_INCLUDED
 #define AI_SCENEPRIVATE_H_INCLUDED
 
+#include <assimp/ai_assert.h>
 #include <assimp/scene.h>
 
-namespace Assimp    {
+namespace Assimp {
 
+// Forward declarations
 class Importer;
 
 struct ScenePrivateData {
-
     ScenePrivateData()
-        : mOrigImporter()
-        , mPPStepsApplied()
-        , mIsCopy()
-    {}
+    : mOrigImporter( nullptr )
+    , mPPStepsApplied( 0 )
+    , mIsCopy( false ) {
+        // empty
+    }
 
     // Importer that originally loaded the scene though the C-API
     // If set, this object is owned by this private data instance.
@@ -75,14 +78,24 @@ struct ScenePrivateData {
 };
 
 // Access private data stored in the scene
-inline ScenePrivateData* ScenePriv(aiScene* in) {
+inline
+ScenePrivateData* ScenePriv(aiScene* in) {
+    ai_assert( nullptr != in );
+    if ( nullptr == in ) {
+        return nullptr;
+    }
     return static_cast<ScenePrivateData*>(in->mPrivate);
 }
 
-inline const ScenePrivateData* ScenePriv(const aiScene* in) {
+inline
+const ScenePrivateData* ScenePriv(const aiScene* in) {
+    ai_assert( nullptr != in );
+    if ( nullptr == in ) {
+        return nullptr;
+    }
     return static_cast<const ScenePrivateData*>(in->mPrivate);
 }
 
-}
+} // Namespace Assimp
 
-#endif
+#endif // AI_SCENEPRIVATE_H_INCLUDED

+ 1 - 1
code/SpatialSort.cpp

@@ -294,7 +294,7 @@ void SpatialSort::FindIdenticalPositions( const aiVector3D& pPosition,
         index++;
 
     // Now start iterating from there until the first position lays outside of the distance range.
-    // Add all positions inside the distance range within the tolerance to the result aray
+    // Add all positions inside the distance range within the tolerance to the result array
     std::vector<Entry>::const_iterator it = mPositions.begin() + index;
     while( ToBinary(it->mDistance) < maxDistBinary)
     {

+ 6 - 9
code/VertexTriangleAdjacency.cpp

@@ -48,7 +48,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "VertexTriangleAdjacency.h"
 #include <assimp/mesh.h>
 
-
 using namespace Assimp;
 
 // ------------------------------------------------------------------------------------------------
@@ -60,8 +59,8 @@ VertexTriangleAdjacency::VertexTriangleAdjacency(aiFace *pcFaces,
     // compute the number of referenced vertices if it wasn't specified by the caller
     const aiFace* const pcFaceEnd = pcFaces + iNumFaces;
     if (!iNumVertices)  {
-
         for (aiFace* pcFace = pcFaces; pcFace != pcFaceEnd; ++pcFace)   {
+            ai_assert( nullptr != pcFace );
             ai_assert(3 == pcFace->mNumIndices);
             iNumVertices = std::max(iNumVertices,pcFace->mIndices[0]);
             iNumVertices = std::max(iNumVertices,pcFace->mIndices[1]);
@@ -69,19 +68,18 @@ VertexTriangleAdjacency::VertexTriangleAdjacency(aiFace *pcFaces,
         }
     }
 
-    this->iNumVertices = iNumVertices;
+    mNumVertices = iNumVertices;
 
     unsigned int* pi;
 
     // allocate storage
     if (bComputeNumTriangles)   {
         pi = mLiveTriangles = new unsigned int[iNumVertices+1];
-        memset(mLiveTriangles,0,sizeof(unsigned int)*(iNumVertices+1));
+        ::memset(mLiveTriangles,0,sizeof(unsigned int)*(iNumVertices+1));
         mOffsetTable = new unsigned int[iNumVertices+2]+1;
-    }
-    else {
+    } else {
         pi = mOffsetTable = new unsigned int[iNumVertices+2]+1;
-        memset(mOffsetTable,0,sizeof(unsigned int)*(iNumVertices+1));
+        ::memset(mOffsetTable,0,sizeof(unsigned int)*(iNumVertices+1));
         mLiveTriangles = NULL; // important, otherwise the d'tor would crash
     }
 
@@ -90,8 +88,7 @@ VertexTriangleAdjacency::VertexTriangleAdjacency(aiFace *pcFaces,
     *piEnd++ = 0u;
 
     // first pass: compute the number of faces referencing each vertex
-    for (aiFace* pcFace = pcFaces; pcFace != pcFaceEnd; ++pcFace)
-    {
+    for (aiFace* pcFace = pcFaces; pcFace != pcFaceEnd; ++pcFace) {
         pi[pcFace->mIndices[0]]++;
         pi[pcFace->mIndices[1]]++;
         pi[pcFace->mIndices[2]]++;

+ 9 - 20
code/VertexTriangleAdjacency.h

@@ -60,10 +60,8 @@ namespace Assimp    {
  *  @note Although it is called #VertexTriangleAdjacency, the current version does also
  *    support arbitrary polygons. */
 // --------------------------------------------------------------------------------------------
-class ASSIMP_API VertexTriangleAdjacency
-{
+class ASSIMP_API VertexTriangleAdjacency {
 public:
-
     // ----------------------------------------------------------------------------
     /** @brief Construction from an existing index buffer
      *  @param pcFaces Index buffer
@@ -77,39 +75,30 @@ public:
         unsigned int iNumVertices = 0,
         bool bComputeNumTriangles = true);
 
-
     // ----------------------------------------------------------------------------
     /** @brief Destructor */
     ~VertexTriangleAdjacency();
 
-
-public:
-
     // ----------------------------------------------------------------------------
     /** @brief Get all triangles adjacent to a vertex
      *  @param iVertIndex Index of the vertex
      *  @return A pointer to the adjacency list. */
-    unsigned int* GetAdjacentTriangles(unsigned int iVertIndex) const
-    {
-        ai_assert(iVertIndex < iNumVertices);
+    unsigned int* GetAdjacentTriangles(unsigned int iVertIndex) const {
+        ai_assert(iVertIndex < mNumVertices);
         return &mAdjacencyTable[ mOffsetTable[iVertIndex]];
     }
 
-
     // ----------------------------------------------------------------------------
     /** @brief Get the number of triangles that are referenced by
      *    a vertex. This function returns a reference that can be modified
      *  @param iVertIndex Index of the vertex
      *  @return Number of referenced triangles */
-    unsigned int& GetNumTrianglesPtr(unsigned int iVertIndex)
-    {
-        ai_assert(iVertIndex < iNumVertices && NULL != mLiveTriangles);
+    unsigned int& GetNumTrianglesPtr(unsigned int iVertIndex) {
+        ai_assert( iVertIndex < mNumVertices );
+        ai_assert( nullptr != mLiveTriangles );
         return mLiveTriangles[iVertIndex];
     }
 
-
-public:
-
     //! Offset table
     unsigned int* mOffsetTable;
 
@@ -120,9 +109,9 @@ public:
     unsigned int* mLiveTriangles;
 
     //! Debug: Number of referenced vertices
-    unsigned int iNumVertices;
-
+    unsigned int mNumVertices;
 };
-}
+
+} //! ns Assimp
 
 #endif // !! AI_VTADJACENCY_H_INC

+ 152 - 187
code/XFileImporter.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2018, assimp team
 
-
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -44,7 +42,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *  @brief Implementation of the XFile importer class
  */
 
-
 #ifndef ASSIMP_BUILD_NO_X_IMPORTER
 
 #include "XFileImporter.h"
@@ -79,17 +76,19 @@ static const aiImporterDesc desc = {
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 XFileImporter::XFileImporter()
-{}
+: mBuffer() {
+    // empty
+}
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
-XFileImporter::~XFileImporter()
-{}
+XFileImporter::~XFileImporter() {
+    // empty
+}
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool XFileImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
-{
+bool XFileImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
     std::string extension = GetExtension(pFile);
     if(extension == "x") {
         return true;
@@ -104,23 +103,24 @@ bool XFileImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, boo
 
 // ------------------------------------------------------------------------------------------------
 // Get file extension list
-const aiImporterDesc* XFileImporter::GetInfo () const
-{
+const aiImporterDesc* XFileImporter::GetInfo () const {
     return &desc;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure.
-void XFileImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
-{
+void XFileImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) {
     // read file into memory
     std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
-    if( file.get() == NULL)
-        throw DeadlyImportError( "Failed to open file " + pFile + ".");
+    if ( file.get() == NULL ) {
+        throw DeadlyImportError( "Failed to open file " + pFile + "." );
+    }
 
+    static const size_t MinSize = 16;
     size_t fileSize = file->FileSize();
-    if( fileSize < 16)
-        throw DeadlyImportError( "XFile is too small.");
+    if ( fileSize < MinSize ) {
+        throw DeadlyImportError( "XFile is too small." );
+    }
 
     // in the hope that binary files will never start with a BOM ...
     mBuffer.resize( fileSize + 1);
@@ -134,8 +134,9 @@ void XFileImporter::InternReadFile( const std::string& pFile, aiScene* pScene, I
     CreateDataRepresentationFromImport( pScene, parser.GetImportedData());
 
     // if nothing came from it, report it as error
-    if( !pScene->mRootNode)
-        throw DeadlyImportError( "XFile is ill-formatted - no content imported.");
+    if ( !pScene->mRootNode ) {
+        throw DeadlyImportError( "XFile is ill-formatted - no content imported." );
+    }
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -146,17 +147,15 @@ void XFileImporter::CreateDataRepresentationFromImport( aiScene* pScene, XFile::
     ConvertMaterials( pScene, pData->mGlobalMaterials);
 
     // copy nodes, extracting meshes and materials on the way
-    pScene->mRootNode = CreateNodes( pScene, NULL, pData->mRootNode);
+    pScene->mRootNode = CreateNodes( pScene, nullptr, pData->mRootNode);
 
     // extract animations
     CreateAnimations( pScene, pData);
 
     // read the global meshes that were stored outside of any node
-    if( pData->mGlobalMeshes.size() > 0)
-    {
+    if( !pData->mGlobalMeshes.empty() )  {
         // create a root node to hold them if there isn't any, yet
-        if( pScene->mRootNode == NULL)
-        {
+        if( pScene->mRootNode == nullptr ) {
             pScene->mRootNode = new aiNode;
             pScene->mRootNode->mName.Set( "$dummy_node");
         }
@@ -180,8 +179,7 @@ void XFileImporter::CreateDataRepresentationFromImport( aiScene* pScene, XFile::
     flipper.Execute(pScene);
 
     // finally: create a dummy material if not material was imported
-    if( pScene->mNumMaterials == 0)
-    {
+    if( pScene->mNumMaterials == 0) {
         pScene->mNumMaterials = 1;
         // create the Material
         aiMaterial* mat = new aiMaterial;
@@ -205,10 +203,10 @@ void XFileImporter::CreateDataRepresentationFromImport( aiScene* pScene, XFile::
 
 // ------------------------------------------------------------------------------------------------
 // Recursively creates scene nodes from the imported hierarchy.
-aiNode* XFileImporter::CreateNodes( aiScene* pScene, aiNode* pParent, const XFile::Node* pNode)
-{
-    if( !pNode)
-        return NULL;
+aiNode* XFileImporter::CreateNodes( aiScene* pScene, aiNode* pParent, const XFile::Node* pNode) {
+    if ( !pNode ) {
+        return nullptr;
+    }
 
     // create node
     aiNode* node = new aiNode;
@@ -222,13 +220,13 @@ aiNode* XFileImporter::CreateNodes( aiScene* pScene, aiNode* pParent, const XFil
     CreateMeshes( pScene, node, pNode->mMeshes);
 
     // handle childs
-    if( pNode->mChildren.size() > 0)
-    {
+    if( !pNode->mChildren.empty() ) {
         node->mNumChildren = (unsigned int)pNode->mChildren.size();
         node->mChildren = new aiNode* [node->mNumChildren];
 
-        for( unsigned int a = 0; a < pNode->mChildren.size(); a++)
-            node->mChildren[a] = CreateNodes( pScene, node, pNode->mChildren[a]);
+        for ( unsigned int a = 0; a < pNode->mChildren.size(); ++a ) {
+            node->mChildren[ a ] = CreateNodes( pScene, node, pNode->mChildren[ a ] );
+        }
     }
 
     return node;
@@ -236,16 +234,14 @@ aiNode* XFileImporter::CreateNodes( aiScene* pScene, aiNode* pParent, const XFil
 
 // ------------------------------------------------------------------------------------------------
 // Creates the meshes for the given node.
-void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vector<XFile::Mesh*>& pMeshes)
-{
+void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vector<XFile::Mesh*>& pMeshes) {
     if (pMeshes.empty()) {
         return;
     }
 
     // create a mesh for each mesh-material combination in the source node
     std::vector<aiMesh*> meshes;
-    for( unsigned int a = 0; a < pMeshes.size(); a++)
-    {
+    for( unsigned int a = 0; a < pMeshes.size(); ++a ) {
         XFile::Mesh* sourceMesh = pMeshes[a];
         if ( nullptr == sourceMesh ) {
             continue;
@@ -255,35 +251,30 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec
         ConvertMaterials( pScene, sourceMesh->mMaterials);
 
         unsigned int numMaterials = std::max( (unsigned int)sourceMesh->mMaterials.size(), 1u);
-        for( unsigned int b = 0; b < numMaterials; b++)
-        {
+        for( unsigned int b = 0; b < numMaterials; ++b ) {
             // collect the faces belonging to this material
             std::vector<unsigned int> faces;
             unsigned int numVertices = 0;
-            if( sourceMesh->mFaceMaterials.size() > 0)
-            {
+            if( !sourceMesh->mFaceMaterials.empty() ) {
                 // if there is a per-face material defined, select the faces with the corresponding material
-                for( unsigned int c = 0; c < sourceMesh->mFaceMaterials.size(); c++)
-                {
-                    if( sourceMesh->mFaceMaterials[c] == b)
-                    {
+                for( unsigned int c = 0; c < sourceMesh->mFaceMaterials.size(); ++c ) {
+                    if( sourceMesh->mFaceMaterials[c] == b) {
                         faces.push_back( c);
                         numVertices += (unsigned int)sourceMesh->mPosFaces[c].mIndices.size();
                     }
                 }
-            } else
-            {
+            } else {
                 // if there is no per-face material, place everything into one mesh
-                for( unsigned int c = 0; c < sourceMesh->mPosFaces.size(); c++)
-                {
+                for( unsigned int c = 0; c < sourceMesh->mPosFaces.size(); ++c ) {
                     faces.push_back( c);
                     numVertices += (unsigned int)sourceMesh->mPosFaces[c].mIndices.size();
                 }
             }
 
             // no faces/vertices using this material? strange...
-            if( numVertices == 0)
+            if ( numVertices == 0 ) {
                 continue;
+            }
 
             // create a submesh using this material
             aiMesh* mesh = new aiMesh;
@@ -291,11 +282,9 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec
 
             // find the material in the scene's material list. Either own material
             // or referenced material, it should already have a valid index
-            if( sourceMesh->mFaceMaterials.size() > 0)
-            {
+            if( !sourceMesh->mFaceMaterials.empty() ) {
                 mesh->mMaterialIndex = static_cast<unsigned int>(sourceMesh->mMaterials[b].sceneIndex);
-            } else
-            {
+            } else {
                 mesh->mMaterialIndex = 0;
             }
 
@@ -310,28 +299,28 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec
             mesh->mName.Set(sourceMesh->mName);
 
             // normals?
-            if( sourceMesh->mNormals.size() > 0)
-                mesh->mNormals = new aiVector3D[numVertices];
+            if ( sourceMesh->mNormals.size() > 0 ) {
+                mesh->mNormals = new aiVector3D[ numVertices ];
+            }
             // texture coords
-            for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; c++)
-            {
-                if( sourceMesh->mTexCoords[c].size() > 0)
-                    mesh->mTextureCoords[c] = new aiVector3D[numVertices];
+            for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c ) {
+                if ( !sourceMesh->mTexCoords[ c ].empty() ) {
+                    mesh->mTextureCoords[ c ] = new aiVector3D[ numVertices ];
+                }
             }
             // vertex colors
-            for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; c++)
-            {
-                if( sourceMesh->mColors[c].size() > 0)
-                    mesh->mColors[c] = new aiColor4D[numVertices];
+            for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c ) {
+                if ( !sourceMesh->mColors[ c ].empty() ) {
+                    mesh->mColors[ c ] = new aiColor4D[ numVertices ];
+                }
             }
 
             // now collect the vertex data of all data streams present in the imported mesh
-            unsigned int newIndex = 0;
+            unsigned int newIndex( 0 );
             std::vector<unsigned int> orgPoints; // from which original point each new vertex stems
             orgPoints.resize( numVertices, 0);
 
-            for( unsigned int c = 0; c < faces.size(); c++)
-            {
+            for( unsigned int c = 0; c < faces.size(); ++c ) {
                 unsigned int f = faces[c]; // index of the source face
                 const XFile::Face& pf = sourceMesh->mPosFaces[f]; // position source face
 
@@ -341,30 +330,30 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec
                 df.mIndices = new unsigned int[ df.mNumIndices];
 
                 // collect vertex data for indices of this face
-                for( unsigned int d = 0; d < df.mNumIndices; d++)
-                {
+                for( unsigned int d = 0; d < df.mNumIndices; ++d ) {
                     df.mIndices[d] = newIndex;
                     orgPoints[newIndex] = pf.mIndices[d];
 
                     // Position
                     mesh->mVertices[newIndex] = sourceMesh->mPositions[pf.mIndices[d]];
                     // Normal, if present
-                    if( mesh->HasNormals())
-                        mesh->mNormals[newIndex] = sourceMesh->mNormals[sourceMesh->mNormFaces[f].mIndices[d]];
+                    if ( mesh->HasNormals() ) {
+                        mesh->mNormals[ newIndex ] = sourceMesh->mNormals[ sourceMesh->mNormFaces[ f ].mIndices[ d ] ];
+                    }
 
                     // texture coord sets
-                    for( unsigned int e = 0; e < AI_MAX_NUMBER_OF_TEXTURECOORDS; e++)
-                    {
-                        if( mesh->HasTextureCoords( e))
-                        {
+                    for( unsigned int e = 0; e < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++e ) {
+                        if( mesh->HasTextureCoords( e)) {
                             aiVector2D tex = sourceMesh->mTexCoords[e][pf.mIndices[d]];
                             mesh->mTextureCoords[e][newIndex] = aiVector3D( tex.x, 1.0f - tex.y, 0.0f);
                         }
                     }
                     // vertex color sets
-                    for( unsigned int e = 0; e < AI_MAX_NUMBER_OF_COLOR_SETS; e++)
-                        if( mesh->HasVertexColors( e))
-                            mesh->mColors[e][newIndex] = sourceMesh->mColors[e][pf.mIndices[d]];
+                    for ( unsigned int e = 0; e < AI_MAX_NUMBER_OF_COLOR_SETS; ++e ) {
+                        if ( mesh->HasVertexColors( e ) ) {
+                            mesh->mColors[ e ][ newIndex ] = sourceMesh->mColors[ e ][ pf.mIndices[ d ] ];
+                        }
+                    }
 
                     newIndex++;
                 }
@@ -376,28 +365,29 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec
             // convert all bones of the source mesh which influence vertices in this newly created mesh
             const std::vector<XFile::Bone>& bones = sourceMesh->mBones;
             std::vector<aiBone*> newBones;
-            for( unsigned int c = 0; c < bones.size(); c++)
-            {
+            for( unsigned int c = 0; c < bones.size(); ++c ) {
                 const XFile::Bone& obone = bones[c];
                 // set up a vertex-linear array of the weights for quick searching if a bone influences a vertex
                 std::vector<ai_real> oldWeights( sourceMesh->mPositions.size(), 0.0);
-                for( unsigned int d = 0; d < obone.mWeights.size(); d++)
-                    oldWeights[obone.mWeights[d].mVertex] = obone.mWeights[d].mWeight;
+                for ( unsigned int d = 0; d < obone.mWeights.size(); ++d ) {
+                    oldWeights[ obone.mWeights[ d ].mVertex ] = obone.mWeights[ d ].mWeight;
+                }
 
                 // collect all vertex weights that influence a vertex in the new mesh
                 std::vector<aiVertexWeight> newWeights;
                 newWeights.reserve( numVertices);
-                for( unsigned int d = 0; d < orgPoints.size(); d++)
-                {
+                for( unsigned int d = 0; d < orgPoints.size(); ++d ) {
                     // does the new vertex stem from an old vertex which was influenced by this bone?
                     ai_real w = oldWeights[orgPoints[d]];
-                    if( w > 0.0)
-                        newWeights.push_back( aiVertexWeight( d, w));
+                    if ( w > 0.0 ) {
+                        newWeights.push_back( aiVertexWeight( d, w ) );
+                    }
                 }
 
                 // if the bone has no weights in the newly created mesh, ignore it
-                if( newWeights.size() == 0)
+                if ( newWeights.empty() ) {
                     continue;
+                }
 
                 // create
                 aiBone* nbone = new aiBone;
@@ -407,14 +397,14 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec
                 nbone->mOffsetMatrix = obone.mOffsetMatrix;
                 nbone->mNumWeights = (unsigned int)newWeights.size();
                 nbone->mWeights = new aiVertexWeight[nbone->mNumWeights];
-                for( unsigned int d = 0; d < newWeights.size(); d++)
-                    nbone->mWeights[d] = newWeights[d];
+                for ( unsigned int d = 0; d < newWeights.size(); ++d ) {
+                    nbone->mWeights[ d ] = newWeights[ d ];
+                }
             }
 
             // store the bones in the mesh
             mesh->mNumBones = (unsigned int)newBones.size();
-            if( newBones.size() > 0)
-            {
+            if( !newBones.empty()) {
                 mesh->mBones = new aiBone*[mesh->mNumBones];
                 std::copy( newBones.begin(), newBones.end(), mesh->mBones);
             }
@@ -424,8 +414,7 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec
     // reallocate scene mesh array to be large enough
     aiMesh** prevArray = pScene->mMeshes;
     pScene->mMeshes = new aiMesh*[pScene->mNumMeshes + meshes.size()];
-    if( prevArray)
-    {
+    if( prevArray) {
         memcpy( pScene->mMeshes, prevArray, pScene->mNumMeshes * sizeof( aiMesh*));
         delete [] prevArray;
     }
@@ -435,8 +424,7 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec
     pNode->mMeshes = new unsigned int[pNode->mNumMeshes];
 
     // store all meshes in the mesh library of the scene and store their indices in the node
-    for( unsigned int a = 0; a < meshes.size(); a++)
-    {
+    for( unsigned int a = 0; a < meshes.size(); a++) {
         pScene->mMeshes[pScene->mNumMeshes] = meshes[a];
         pNode->mMeshes[a] = pScene->mNumMeshes;
         pScene->mNumMeshes++;
@@ -445,16 +433,15 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec
 
 // ------------------------------------------------------------------------------------------------
 // Converts the animations from the given imported data and creates them in the scene.
-void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData)
-{
+void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData) {
     std::vector<aiAnimation*> newAnims;
 
-    for( unsigned int a = 0; a < pData->mAnims.size(); a++)
-    {
+    for( unsigned int a = 0; a < pData->mAnims.size(); ++a ) {
         const XFile::Animation* anim = pData->mAnims[a];
         // some exporters mock me with empty animation tags.
-        if( anim->mAnims.size() == 0)
+        if ( anim->mAnims.empty() ) {
             continue;
+        }
 
         // create a new animation to hold the data
         aiAnimation* nanim = new aiAnimation;
@@ -466,15 +453,14 @@ void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData
         nanim->mNumChannels = (unsigned int)anim->mAnims.size();
         nanim->mChannels = new aiNodeAnim*[nanim->mNumChannels];
 
-        for( unsigned int b = 0; b < anim->mAnims.size(); b++)
-        {
+        for( unsigned int b = 0; b < anim->mAnims.size(); ++b ) {
             const XFile::AnimBone* bone = anim->mAnims[b];
             aiNodeAnim* nbone = new aiNodeAnim;
             nbone->mNodeName.Set( bone->mBoneName);
             nanim->mChannels[b] = nbone;
 
             // keyframes are given as combined transformation matrix keys
-            if( bone->mTrafoKeys.size() > 0)
+            if( !bone->mTrafoKeys.empty() )
             {
                 nbone->mNumPositionKeys = (unsigned int)bone->mTrafoKeys.size();
                 nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys];
@@ -483,8 +469,7 @@ void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData
                 nbone->mNumScalingKeys = (unsigned int)bone->mTrafoKeys.size();
                 nbone->mScalingKeys = new aiVectorKey[nbone->mNumScalingKeys];
 
-                for( unsigned int c = 0; c < bone->mTrafoKeys.size(); c++)
-                {
+                for( unsigned int c = 0; c < bone->mTrafoKeys.size(); ++c)  {
                     // deconstruct each matrix into separate position, rotation and scaling
                     double time = bone->mTrafoKeys[c].mTime;
                     aiMatrix4x4 trafo = bone->mTrafoKeys[c].mMatrix;
@@ -516,13 +501,11 @@ void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData
 
                 // longest lasting key sequence determines duration
                 nanim->mDuration = std::max( nanim->mDuration, bone->mTrafoKeys.back().mTime);
-            } else
-            {
+            } else {
                 // separate key sequences for position, rotation, scaling
                 nbone->mNumPositionKeys = (unsigned int)bone->mPosKeys.size();
                 nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys];
-                for( unsigned int c = 0; c < nbone->mNumPositionKeys; c++)
-                {
+                for( unsigned int c = 0; c < nbone->mNumPositionKeys; ++c ) {
                     aiVector3D pos = bone->mPosKeys[c].mValue;
 
                     nbone->mPositionKeys[c].mTime = bone->mPosKeys[c].mTime;
@@ -532,8 +515,7 @@ void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData
                 // rotation
                 nbone->mNumRotationKeys = (unsigned int)bone->mRotKeys.size();
                 nbone->mRotationKeys = new aiQuatKey[nbone->mNumRotationKeys];
-                for( unsigned int c = 0; c < nbone->mNumRotationKeys; c++)
-                {
+                for( unsigned int c = 0; c < nbone->mNumRotationKeys; ++c ) {
                     aiMatrix3x3 rotmat = bone->mRotKeys[c].mValue.GetMatrix();
 
                     nbone->mRotationKeys[c].mTime = bone->mRotKeys[c].mTime;
@@ -573,56 +555,51 @@ void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData
 void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector<XFile::Material>& pMaterials)
 {
     // count the non-referrer materials in the array
-    unsigned int numNewMaterials = 0;
-    for( unsigned int a = 0; a < pMaterials.size(); a++)
-        if( !pMaterials[a].mIsReference)
-            numNewMaterials++;
+    unsigned int numNewMaterials( 0 );
+    for ( unsigned int a = 0; a < pMaterials.size(); ++a ) {
+        if ( !pMaterials[ a ].mIsReference ) {
+            ++numNewMaterials;
+        }
+    }
 
     // resize the scene's material list to offer enough space for the new materials
-  if( numNewMaterials > 0 )
-  {
-      aiMaterial** prevMats = pScene->mMaterials;
-      pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials + numNewMaterials];
-      if( prevMats)
-      {
-          memcpy( pScene->mMaterials, prevMats, pScene->mNumMaterials * sizeof( aiMaterial*));
-          delete [] prevMats;
-      }
-  }
+    if( numNewMaterials > 0 ) {
+        aiMaterial** prevMats = pScene->mMaterials;
+        pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials + numNewMaterials];
+        if( nullptr != prevMats)  {
+            ::memcpy( pScene->mMaterials, prevMats, pScene->mNumMaterials * sizeof( aiMaterial*));
+            delete [] prevMats;
+        }
+    }
 
     // convert all the materials given in the array
-    for( unsigned int a = 0; a < pMaterials.size(); a++)
-    {
+    for( unsigned int a = 0; a < pMaterials.size(); ++a ) {
         XFile::Material& oldMat = pMaterials[a];
-        if( oldMat.mIsReference)
-    {
-      // find the material it refers to by name, and store its index
-      for( size_t a = 0; a < pScene->mNumMaterials; ++a )
-      {
-        aiString name;
-        pScene->mMaterials[a]->Get( AI_MATKEY_NAME, name);
-        if( strcmp( name.C_Str(), oldMat.mName.data()) == 0 )
-        {
-          oldMat.sceneIndex = a;
-          break;
-        }
-      }
+        if( oldMat.mIsReference) {
+            // find the material it refers to by name, and store its index
+            for( size_t a = 0; a < pScene->mNumMaterials; ++a ) {
+                aiString name;
+                pScene->mMaterials[a]->Get( AI_MATKEY_NAME, name);
+                if( strcmp( name.C_Str(), oldMat.mName.data()) == 0 ) {
+                    oldMat.sceneIndex = a;
+                    break;
+                }
+            }
 
-      if( oldMat.sceneIndex == SIZE_MAX )
-      {
-        DefaultLogger::get()->warn( format() << "Could not resolve global material reference \"" << oldMat.mName << "\"" );
-        oldMat.sceneIndex = 0;
-      }
+            if( oldMat.sceneIndex == SIZE_MAX ) {
+                DefaultLogger::get()->warn( format() << "Could not resolve global material reference \"" << oldMat.mName << "\"" );
+                oldMat.sceneIndex = 0;
+            }
 
-      continue;
-    }
+            continue;
+        }
 
         aiMaterial* mat = new aiMaterial;
         aiString name;
         name.Set( oldMat.mName);
         mat->AddProperty( &name, AI_MATKEY_NAME);
 
-        // Shading model: hardcoded to PHONG, there is no such information in an XFile
+        // Shading model: hard-coded to PHONG, there is no such information in an XFile
         // FIX (aramis): If the specular exponent is 0, use gouraud shading. This is a bugfix
         // for some models in the SDK (e.g. good old tiny.x)
         int shadeMode = (int)oldMat.mSpecularExponent == 0.0f
@@ -630,8 +607,8 @@ void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector<XFile::Materi
 
         mat->AddProperty<int>( &shadeMode, 1, AI_MATKEY_SHADING_MODEL);
         // material colours
-    // Unclear: there's no ambient colour, but emissive. What to put for ambient?
-    // Probably nothing at all, let the user select a suitable default.
+        // Unclear: there's no ambient colour, but emissive. What to put for ambient?
+        // Probably nothing at all, let the user select a suitable default.
         mat->AddProperty( &oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE);
         mat->AddProperty( &oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
         mat->AddProperty( &oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR);
@@ -639,36 +616,33 @@ void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector<XFile::Materi
 
 
         // texture, if there is one
-        if (1 == oldMat.mTextures.size())
-        {
+        if (1 == oldMat.mTextures.size() ) {
             const XFile::TexEntry& otex = oldMat.mTextures.back();
-            if (otex.mName.length())
-            {
+            if (otex.mName.length()) {
                 // if there is only one texture assume it contains the diffuse color
                 aiString tex( otex.mName);
-                if( otex.mIsNormalMap)
-                    mat->AddProperty( &tex, AI_MATKEY_TEXTURE_NORMALS(0));
-                else
-                    mat->AddProperty( &tex, AI_MATKEY_TEXTURE_DIFFUSE(0));
+                if ( otex.mIsNormalMap ) {
+                    mat->AddProperty( &tex, AI_MATKEY_TEXTURE_NORMALS( 0 ) );
+                } else {
+                    mat->AddProperty( &tex, AI_MATKEY_TEXTURE_DIFFUSE( 0 ) );
+                }
             }
-        }
-        else
-        {
+        } else {
             // Otherwise ... try to search for typical strings in the
             // texture's file name like 'bump' or 'diffuse'
             unsigned int iHM = 0,iNM = 0,iDM = 0,iSM = 0,iAM = 0,iEM = 0;
-            for( unsigned int b = 0; b < oldMat.mTextures.size(); b++)
-            {
+            for( unsigned int b = 0; b < oldMat.mTextures.size(); ++b ) {
                 const XFile::TexEntry& otex = oldMat.mTextures[b];
                 std::string sz = otex.mName;
-                if (!sz.length())continue;
-
+                if ( !sz.length() ) {
+                    continue;
+                }
 
                 // find the file name
-                //const size_t iLen = sz.length();
                 std::string::size_type s = sz.find_last_of("\\/");
-                if (std::string::npos == s)
+                if ( std::string::npos == s ) {
                     s = 0;
+                }
 
                 // cut off the file extension
                 std::string::size_type sExt = sz.find_last_of('.');
@@ -677,36 +651,27 @@ void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector<XFile::Materi
                 }
 
                 // convert to lower case for easier comparison
-                for( unsigned int c = 0; c < sz.length(); c++)
-                    if( isalpha( sz[c]))
-                        sz[c] = tolower( sz[c]);
-
+                for ( unsigned int c = 0; c < sz.length(); ++c ) {
+                    if ( isalpha( sz[ c ] ) ) {
+                        sz[ c ] = tolower( sz[ c ] );
+                    }
+                }
 
                 // Place texture filename property under the corresponding name
                 aiString tex( oldMat.mTextures[b].mName);
 
                 // bump map
-                if (std::string::npos != sz.find("bump", s) || std::string::npos != sz.find("height", s))
-                {
+                if (std::string::npos != sz.find("bump", s) || std::string::npos != sz.find("height", s)) {
                     mat->AddProperty( &tex, AI_MATKEY_TEXTURE_HEIGHT(iHM++));
-                } else
-                if (otex.mIsNormalMap || std::string::npos != sz.find( "normal", s) || std::string::npos != sz.find("nm", s))
-                {
+                } else if (otex.mIsNormalMap || std::string::npos != sz.find( "normal", s) || std::string::npos != sz.find("nm", s)) {
                     mat->AddProperty( &tex, AI_MATKEY_TEXTURE_NORMALS(iNM++));
-                } else
-                if (std::string::npos != sz.find( "spec", s) || std::string::npos != sz.find( "glanz", s))
-                {
+                } else if (std::string::npos != sz.find( "spec", s) || std::string::npos != sz.find( "glanz", s)) {
                     mat->AddProperty( &tex, AI_MATKEY_TEXTURE_SPECULAR(iSM++));
-                } else
-                if (std::string::npos != sz.find( "ambi", s) || std::string::npos != sz.find( "env", s))
-                {
+                } else if (std::string::npos != sz.find( "ambi", s) || std::string::npos != sz.find( "env", s)) {
                     mat->AddProperty( &tex, AI_MATKEY_TEXTURE_AMBIENT(iAM++));
-                } else
-                if (std::string::npos != sz.find( "emissive", s) || std::string::npos != sz.find( "self", s))
-                {
+                } else if (std::string::npos != sz.find( "emissive", s) || std::string::npos != sz.find( "self", s)) {
                     mat->AddProperty( &tex, AI_MATKEY_TEXTURE_EMISSIVE(iEM++));
-                } else
-                {
+                } else {
                     // Assume it is a diffuse texture
                     mat->AddProperty( &tex, AI_MATKEY_TEXTURE_DIFFUSE(iDM++));
                 }

+ 166 - 146
code/XFileParser.cpp

@@ -87,59 +87,60 @@ static void  dummy_free  (void* /*opaque*/, void* address)  {
 // ------------------------------------------------------------------------------------------------
 // Constructor. Creates a data structure out of the XFile given in the memory block.
 XFileParser::XFileParser( const std::vector<char>& pBuffer)
-{
-    mMajorVersion = mMinorVersion = 0;
-    mIsBinaryFormat = false;
-    mBinaryNumCount = 0;
-    P = End = NULL;
-    mLineNumber = 0;
-    mScene = NULL;
-
+: mMajorVersion( 0 )
+, mMinorVersion( 0 )
+, mIsBinaryFormat( false )
+, mBinaryNumCount( 0 )
+, mP( nullptr )
+, mEnd( nullptr )
+, mLineNumber( 0 )
+, mScene( nullptr ) {
     // vector to store uncompressed file for INFLATE'd X files
     std::vector<char> uncompressed;
 
     // set up memory pointers
-    P = &pBuffer.front();
-    End = P + pBuffer.size() - 1;
+    mP = &pBuffer.front();
+    mEnd = mP + pBuffer.size() - 1;
 
     // check header
-    if( strncmp( P, "xof ", 4) != 0)
-        throw DeadlyImportError( "Header mismatch, file is not an XFile.");
+    if ( 0 != strncmp( mP, "xof ", 4 ) ) {
+        throw DeadlyImportError( "Header mismatch, file is not an XFile." );
+    }
 
     // read version. It comes in a four byte format such as "0302"
-    mMajorVersion = (unsigned int)(P[4] - 48) * 10 + (unsigned int)(P[5] - 48);
-    mMinorVersion = (unsigned int)(P[6] - 48) * 10 + (unsigned int)(P[7] - 48);
+    mMajorVersion = (unsigned int)(mP[4] - 48) * 10 + (unsigned int)(mP[5] - 48);
+    mMinorVersion = (unsigned int)(mP[6] - 48) * 10 + (unsigned int)(mP[7] - 48);
 
     bool compressed = false;
 
     // txt - pure ASCII text format
-    if( strncmp( P + 8, "txt ", 4) == 0)
+    if( strncmp( mP + 8, "txt ", 4) == 0)
         mIsBinaryFormat = false;
 
     // bin - Binary format
-    else if( strncmp( P + 8, "bin ", 4) == 0)
+    else if( strncmp( mP + 8, "bin ", 4) == 0)
         mIsBinaryFormat = true;
 
     // tzip - Inflate compressed text format
-    else if( strncmp( P + 8, "tzip", 4) == 0)
+    else if( strncmp( mP + 8, "tzip", 4) == 0)
     {
         mIsBinaryFormat = false;
         compressed = true;
     }
     // bzip - Inflate compressed binary format
-    else if( strncmp( P + 8, "bzip", 4) == 0)
+    else if( strncmp( mP + 8, "bzip", 4) == 0)
     {
         mIsBinaryFormat = true;
         compressed = true;
     }
     else ThrowException( format() << "Unsupported xfile format '" <<
-       P[8] << P[9] << P[10] << P[11] << "'");
+       mP[8] << mP[9] << mP[10] << mP[11] << "'");
 
     // float size
-    mBinaryFloatSize = (unsigned int)(P[12] - 48) * 1000
-        + (unsigned int)(P[13] - 48) * 100
-        + (unsigned int)(P[14] - 48) * 10
-        + (unsigned int)(P[15] - 48);
+    mBinaryFloatSize = (unsigned int)(mP[12] - 48) * 1000
+        + (unsigned int)(mP[13] - 48) * 100
+        + (unsigned int)(mP[14] - 48) * 10
+        + (unsigned int)(mP[15] - 48);
 
     if( mBinaryFloatSize != 32 && mBinaryFloatSize != 64)
         ThrowException( format() << "Unknown float size " << mBinaryFloatSize << " specified in xfile header." );
@@ -147,7 +148,7 @@ XFileParser::XFileParser( const std::vector<char>& pBuffer)
     // The x format specifies size in bits, but we work in bytes
     mBinaryFloatSize /= 8;
 
-    P += 16;
+    mP += 16;
 
     // If this is a compressed X file, apply the inflate algorithm to it
     if (compressed)
@@ -186,13 +187,13 @@ XFileParser::XFileParser( const std::vector<char>& pBuffer)
         ::inflateInit2(&stream, -MAX_WBITS);
 
         // skip unknown data (checksum, flags?)
-        P += 6;
+        mP += 6;
 
         // First find out how much storage we'll need. Count sections.
-        const char* P1       = P;
+        const char* P1       = mP;
         unsigned int est_out = 0;
 
-        while (P1 + 3 < End)
+        while (P1 + 3 < mEnd)
         {
             // read next offset
             uint16_t ofs = *((uint16_t*)P1);
@@ -216,18 +217,18 @@ XFileParser::XFileParser( const std::vector<char>& pBuffer)
         // Allocate storage and terminating zero and do the actual uncompressing
         uncompressed.resize(est_out + 1);
         char* out = &uncompressed.front();
-        while (P + 3 < End)
+        while (mP + 3 < mEnd)
         {
-            uint16_t ofs = *((uint16_t*)P);
+            uint16_t ofs = *((uint16_t*)mP);
             AI_SWAP2(ofs);
-            P += 4;
+            mP += 4;
 
-            if (P + ofs > End + 2) {
+            if (mP + ofs > mEnd + 2) {
                 throw DeadlyImportError("X: Unexpected EOF in compressed chunk");
             }
 
             // push data to the stream
-            stream.next_in   = (Bytef*)P;
+            stream.next_in   = (Bytef*)mP;
             stream.avail_in  = ofs;
             stream.next_out  = (Bytef*)out;
             stream.avail_out = MSZIP_BLOCK;
@@ -242,15 +243,15 @@ XFileParser::XFileParser( const std::vector<char>& pBuffer)
 
             // and advance to the next offset
             out +=  MSZIP_BLOCK - stream.avail_out;
-            P   += ofs;
+            mP   += ofs;
         }
 
         // terminate zlib
         ::inflateEnd(&stream);
 
         // ok, update pointers to point to the uncompressed file data
-        P = &uncompressed[0];
-        End = out;
+        mP = &uncompressed[0];
+        mEnd = out;
 
         // FIXME: we don't need the compressed data anymore, could release
         // it already for better memory usage. Consider breaking const-co.
@@ -465,12 +466,11 @@ void XFileParser::ParseDataObjectMesh( Mesh* pMesh)
     // read position faces
     unsigned int numPosFaces = ReadInt();
     pMesh->mPosFaces.resize( numPosFaces);
-    for( unsigned int a = 0; a < numPosFaces; a++)
-    {
+    for( unsigned int a = 0; a < numPosFaces; ++a) {
         // read indices
         unsigned int numIndices = ReadInt();
         Face& face = pMesh->mPosFaces[a];
-        for (unsigned int b = 0; b < numIndices; b++) {
+        for (unsigned int b = 0; b < numIndices; ++b) {
             face.mIndices.push_back( ReadInt() );
         }
         TestForSeparator();
@@ -478,11 +478,10 @@ void XFileParser::ParseDataObjectMesh( Mesh* pMesh)
 
     // here, other data objects may follow
     bool running = true;
-    while ( running )
-    {
+    while ( running ) {
         std::string objectName = GetNextToken();
 
-        if( objectName.size() == 0)
+        if( objectName.empty() )
             ThrowException( "Unexpected end of file while parsing mesh structure");
         else
         if( objectName == "}")
@@ -517,8 +516,10 @@ void XFileParser::ParseDataObjectMesh( Mesh* pMesh)
 }
 
 // ------------------------------------------------------------------------------------------------
-void XFileParser::ParseDataObjectSkinWeights( Mesh *pMesh)
-{
+void XFileParser::ParseDataObjectSkinWeights( Mesh *pMesh) {
+    if ( nullptr == pMesh ) {
+        return;
+    }
     readHeadOfDataObject();
 
     std::string transformNodeName;
@@ -647,8 +648,8 @@ void XFileParser::ParseDataObjectMeshVertexColors( Mesh* pMesh)
         if( !mIsBinaryFormat)
         {
             FindNextNoneWhiteSpace();
-            if( *P == ';' || *P == ',')
-                P++;
+            if( *mP == ';' || *mP == ',')
+                mP++;
         }
     }
 
@@ -678,8 +679,8 @@ void XFileParser::ParseDataObjectMeshMaterialList( Mesh* pMesh)
     // commented out version check, as version 03.03 exported from blender also has 2 semicolons
     if( !mIsBinaryFormat) // && MajorVersion == 3 && MinorVersion <= 2)
     {
-        if(P < End && *P == ';')
-            ++P;
+        if(mP < mEnd && *mP == ';')
+            ++mP;
     }
 
     // if there was only a single material index, replicate it on all faces
@@ -1029,12 +1030,12 @@ void XFileParser::TestForSeparator()
     return;
 
   FindNextNoneWhiteSpace();
-  if( P >= End)
+  if( mP >= mEnd)
     return;
 
   // test and skip
-  if( *P == ';' || *P == ',')
-    P++;
+  if( *mP == ';' || *mP == ',')
+    mP++;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -1046,62 +1047,73 @@ void XFileParser::readHeadOfDataObject( std::string* poName)
         if( poName)
             *poName = nameOrBrace;
 
-        if( GetNextToken() != "{")
-            ThrowException( "Opening brace expected.");
+        if ( GetNextToken() != "{" ) {
+            delete mScene;
+            ThrowException( "Opening brace expected." );
+        }
     }
 }
 
 // ------------------------------------------------------------------------------------------------
-std::string XFileParser::GetNextToken()
-{
+std::string XFileParser::GetNextToken() {
     std::string s;
 
     // process binary-formatted file
-    if( mIsBinaryFormat)
-    {
+    if( mIsBinaryFormat) {
         // in binary mode it will only return NAME and STRING token
         // and (correctly) skip over other tokens.
-
-        if( End - P < 2) return s;
+        if ( mEnd - mP < 2 ) {
+            return s;
+        }
         unsigned int tok = ReadBinWord();
         unsigned int len;
 
         // standalone tokens
-        switch( tok)
-        {
-            case 1:
+        switch( tok ) {
+            case 1: {
                 // name token
-                if( End - P < 4) return s;
+                if ( mEnd - mP < 4 ) {
+                    return s;
+                }
                 len = ReadBinDWord();
-                if( End - P < int(len)) return s;
-                s = std::string(P, len);
-                P += len;
-                return s;
+                const int bounds = int( mEnd - mP );
+                const int iLen   = int( len );
+                if ( iLen < 0 ) {
+                    return s;
+                }
+                if ( bounds < iLen ) {
+                    return s;
+                }
+                s = std::string( mP, len );
+                mP += len;
+            }
+            return s;
+
             case 2:
                 // string token
-                if( End - P < 4) return s;
+                if( mEnd - mP < 4) return s;
                 len = ReadBinDWord();
-                if( End - P < int(len)) return s;
-                s = std::string(P, len);
-                P += (len + 2);
+                if( mEnd - mP < int(len)) return s;
+                s = std::string(mP, len);
+                mP += (len + 2);
                 return s;
             case 3:
                 // integer token
-                P += 4;
+                mP += 4;
                 return "<integer>";
             case 5:
                 // GUID token
-                P += 16;
+                mP += 16;
                 return "<guid>";
             case 6:
-                if( End - P < 4) return s;
+                if( mEnd - mP < 4) return s;
                 len = ReadBinDWord();
-                P += (len * 4);
+                mP += (len * 4);
                 return "<int_list>";
             case 7:
-                if( End - P < 4) return s;
+                if( mEnd - mP < 4) return s;
                 len = ReadBinDWord();
-                P += (len * mBinaryFloatSize);
+                mP += (len * mBinaryFloatSize);
                 return "<flt_list>";
             case 0x0a:
                 return "{";
@@ -1159,19 +1171,19 @@ std::string XFileParser::GetNextToken()
     else
     {
         FindNextNoneWhiteSpace();
-        if( P >= End)
+        if( mP >= mEnd)
             return s;
 
-        while( (P < End) && !isspace( (unsigned char) *P))
+        while( (mP < mEnd) && !isspace( (unsigned char) *mP))
         {
             // either keep token delimiters when already holding a token, or return if first valid char
-            if( *P == ';' || *P == '}' || *P == '{' || *P == ',')
+            if( *mP == ';' || *mP == '}' || *mP == '{' || *mP == ',')
             {
                 if( !s.size())
-                    s.append( P++, 1);
+                    s.append( mP++, 1);
                 break; // stop for delimiter
             }
-            s.append( P++, 1);
+            s.append( mP++, 1);
         }
     }
     return s;
@@ -1186,18 +1198,18 @@ void XFileParser::FindNextNoneWhiteSpace()
     bool running = true;
     while( running )
     {
-        while( P < End && isspace( (unsigned char) *P))
+        while( mP < mEnd && isspace( (unsigned char) *mP))
         {
-            if( *P == '\n')
+            if( *mP == '\n')
                 mLineNumber++;
-            ++P;
+            ++mP;
         }
 
-        if( P >= End)
+        if( mP >= mEnd)
             return;
 
         // check if this is a comment
-        if( (P[0] == '/' && P[1] == '/') || P[0] == '#')
+        if( (mP[0] == '/' && mP[1] == '/') || mP[0] == '#')
             ReadUntilEndOfLine();
         else
             break;
@@ -1214,22 +1226,30 @@ void XFileParser::GetNextTokenAsString( std::string& poString)
     }
 
     FindNextNoneWhiteSpace();
-    if( P >= End)
-        ThrowException( "Unexpected end of file while parsing string");
+    if ( mP >= mEnd ) {
+        delete mScene;
+        ThrowException( "Unexpected end of file while parsing string" );
+    }
 
-    if( *P != '"')
-        ThrowException( "Expected quotation mark.");
-    ++P;
+    if ( *mP != '"' ) {
+        delete mScene;
+        ThrowException( "Expected quotation mark." );
+    }
+    ++mP;
 
-    while( P < End && *P != '"')
-        poString.append( P++, 1);
+    while( mP < mEnd && *mP != '"')
+        poString.append( mP++, 1);
 
-    if( P >= End-1)
-        ThrowException( "Unexpected end of file while parsing string");
+    if ( mP >= mEnd - 1 ) {
+        delete mScene;
+        ThrowException( "Unexpected end of file while parsing string" );
+    }
 
-    if( P[1] != ';' || P[0] != '"')
-        ThrowException( "Expected quotation mark and semicolon at the end of a string.");
-    P+=2;
+    if ( mP[ 1 ] != ';' || mP[ 0 ] != '"' ) {
+        delete mScene;
+        ThrowException( "Expected quotation mark and semicolon at the end of a string." );
+    }
+    mP+=2;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -1238,35 +1258,35 @@ void XFileParser::ReadUntilEndOfLine()
     if( mIsBinaryFormat)
         return;
 
-    while( P < End)
+    while( mP < mEnd)
     {
-        if( *P == '\n' || *P == '\r')
+        if( *mP == '\n' || *mP == '\r')
         {
-            ++P; mLineNumber++;
+            ++mP; mLineNumber++;
             return;
         }
 
-        ++P;
+        ++mP;
     }
 }
 
 // ------------------------------------------------------------------------------------------------
 unsigned short XFileParser::ReadBinWord()
 {
-    ai_assert(End - P >= 2);
-    const unsigned char* q = (const unsigned char*) P;
+    ai_assert(mEnd - mP >= 2);
+    const unsigned char* q = (const unsigned char*) mP;
     unsigned short tmp = q[0] | (q[1] << 8);
-    P += 2;
+    mP += 2;
     return tmp;
 }
 
 // ------------------------------------------------------------------------------------------------
-unsigned int XFileParser::ReadBinDWord()
-{
-    ai_assert(End - P >= 4);
-    const unsigned char* q = (const unsigned char*) P;
+unsigned int XFileParser::ReadBinDWord() {
+    ai_assert(mEnd - mP >= 4);
+
+    const unsigned char* q = (const unsigned char*) mP;
     unsigned int tmp = q[0] | (q[1] << 8) | (q[2] << 16) | (q[3] << 24);
-    P += 4;
+    mP += 4;
     return tmp;
 }
 
@@ -1275,20 +1295,20 @@ unsigned int XFileParser::ReadInt()
 {
     if( mIsBinaryFormat)
     {
-        if( mBinaryNumCount == 0 && End - P >= 2)
+        if( mBinaryNumCount == 0 && mEnd - mP >= 2)
         {
             unsigned short tmp = ReadBinWord(); // 0x06 or 0x03
-            if( tmp == 0x06 && End - P >= 4) // array of ints follows
+            if( tmp == 0x06 && mEnd - mP >= 4) // array of ints follows
                 mBinaryNumCount = ReadBinDWord();
             else // single int follows
                 mBinaryNumCount = 1;
         }
 
         --mBinaryNumCount;
-        if ( End - P >= 4) {
+        if ( mEnd - mP >= 4) {
             return ReadBinDWord();
         } else {
-            P = End;
+            mP = mEnd;
             return 0;
         }
     } else
@@ -1299,24 +1319,24 @@ unsigned int XFileParser::ReadInt()
 
         // check preceding minus sign
         bool isNegative = false;
-        if( *P == '-')
+        if( *mP == '-')
         {
             isNegative = true;
-            P++;
+            mP++;
         }
 
         // at least one digit expected
-        if( !isdigit( *P))
+        if( !isdigit( *mP))
             ThrowException( "Number expected.");
 
         // read digits
         unsigned int number = 0;
-        while( P < End)
+        while( mP < mEnd)
         {
-            if( !isdigit( *P))
+            if( !isdigit( *mP))
                 break;
-            number = number * 10 + (*P - 48);
-            P++;
+            number = number * 10 + (*mP - 48);
+            mP++;
         }
 
         CheckForSeparator();
@@ -1329,34 +1349,35 @@ ai_real XFileParser::ReadFloat()
 {
     if( mIsBinaryFormat)
     {
-        if( mBinaryNumCount == 0 && End - P >= 2)
+        if( mBinaryNumCount == 0 && mEnd - mP >= 2)
         {
             unsigned short tmp = ReadBinWord(); // 0x07 or 0x42
-            if( tmp == 0x07 && End - P >= 4) // array of floats following
+            if( tmp == 0x07 && mEnd - mP >= 4) // array of floats following
                 mBinaryNumCount = ReadBinDWord();
             else // single float following
                 mBinaryNumCount = 1;
         }
 
         --mBinaryNumCount;
-        if( mBinaryFloatSize == 8)
-        {
-            if( End - P >= 8) {
-                ai_real result = (ai_real) (*(double*) P);
-                P += 8;
+        if( mBinaryFloatSize == 8) {
+            if( mEnd - mP >= 8) {
+                double res;
+                ::memcpy( &res, mP, 8 );
+                mP += 8;
+                const ai_real result( static_cast<ai_real>( res ) );
                 return result;
             } else {
-                P = End;
+                mP = mEnd;
                 return 0;
             }
-        } else
-        {
-            if( End - P >= 4) {
-                ai_real result = *(ai_real*) P;
-                P += 4;
+        } else {
+            if( mEnd - mP >= 4) {
+                ai_real result;
+                ::memcpy( &result, mP, 4 );
+                mP += 4;
                 return result;
             } else {
-                P = End;
+                mP = mEnd;
                 return 0;
             }
         }
@@ -1367,21 +1388,21 @@ ai_real XFileParser::ReadFloat()
     // check for various special strings to allow reading files from faulty exporters
     // I mean you, Blender!
     // Reading is safe because of the terminating zero
-    if( strncmp( P, "-1.#IND00", 9) == 0 || strncmp( P, "1.#IND00", 8) == 0)
+    if( strncmp( mP, "-1.#IND00", 9) == 0 || strncmp( mP, "1.#IND00", 8) == 0)
     {
-        P += 9;
+        mP += 9;
         CheckForSeparator();
         return 0.0;
     } else
-    if( strncmp( P, "1.#QNAN0", 8) == 0)
+    if( strncmp( mP, "1.#QNAN0", 8) == 0)
     {
-        P += 8;
+        mP += 8;
         CheckForSeparator();
         return 0.0;
     }
 
     ai_real result = 0.0;
-    P = fast_atoreal_move<ai_real>( P, result);
+    mP = fast_atoreal_move<ai_real>( mP, result);
 
     CheckForSeparator();
 
@@ -1438,15 +1459,14 @@ aiColor3D XFileParser::ReadRGB()
 
 // ------------------------------------------------------------------------------------------------
 // Throws an exception with a line number and the given text.
-AI_WONT_RETURN void XFileParser::ThrowException( const std::string& pText)
-{
-    if( mIsBinaryFormat)
-        throw DeadlyImportError( pText);
-    else
+AI_WONT_RETURN void XFileParser::ThrowException( const std::string& pText) {
+    if ( mIsBinaryFormat ) {
+        throw DeadlyImportError( pText );
+    } else {
         throw DeadlyImportError( format() << "Line " << mLineNumber << ": " << pText );
+    }
 }
 
-
 // ------------------------------------------------------------------------------------------------
 // Filters the imported hierarchy for some degenerated cases that some exporters produce.
 void XFileParser::FilterHierarchy( XFile::Node* pNode)

+ 26 - 33
code/XFileParser.h

@@ -49,10 +49,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <assimp/types.h>
 
-namespace Assimp
-{
-    namespace XFile
-    {
+namespace Assimp {
+    namespace XFile {
         struct Node;
         struct Mesh;
         struct Scene;
@@ -61,21 +59,20 @@ namespace Assimp
         struct AnimBone;
     }
 
-/** The XFileParser reads a XFile either in text or binary form and builds a temporary
- * data structure out of it.
- */
-class XFileParser
-{
+/**
+  *     @brief  The XFileParser reads a XFile either in text or binary form and builds a temporary
+  *             data structure out of it.
+  */
+class XFileParser {
 public:
-    /** Constructor. Creates a data structure out of the XFile given in the memory block.
-     * @param pBuffer Null-terminated memory buffer containing the XFile
-     */
+    /// Constructor. Creates a data structure out of the XFile given in the memory block.
+    /// @param pBuffer Null-terminated memory buffer containing the XFile
     explicit XFileParser( const std::vector<char>& pBuffer);
 
-    /** Destructor. Destroys all imported data along with it */
+    /// Destructor. Destroys all imported data along with it
     ~XFileParser();
 
-    /** Returns the temporary representation of the imported data */
+    /// Returns the temporary representation of the imported data.
     XFile::Scene* GetImportedData() const { return mScene; }
 
 protected:
@@ -101,10 +98,10 @@ protected:
     //! places pointer to next begin of a token, and ignores comments
     void FindNextNoneWhiteSpace();
 
-    //! returns next parseable token. Returns empty string if no token there
+    //! returns next valid token. Returns empty string if no token there
     std::string GetNextToken();
 
-    //! reads header of dataobject including the opening brace.
+    //! reads header of data object including the opening brace.
     //! returns false if error happened, and writes name of object
     //! if there is one
     void readHeadOfDataObject( std::string* poName = NULL);
@@ -118,8 +115,8 @@ protected:
     //! checks for a separator char, either a ',' or a ';'
     void CheckForSeparator();
 
-  /// tests and possibly consumes a separator char, but does nothing if there was no separator
-  void TestForSeparator();
+    /// tests and possibly consumes a separator char, but does nothing if there was no separator
+    void TestForSeparator();
 
     //! reads a x file style string
     void GetNextTokenAsString( std::string& poString);
@@ -138,27 +135,23 @@ protected:
     /** Throws an exception with a line number and the given text. */
     AI_WONT_RETURN void ThrowException( const std::string& pText) AI_WONT_RETURN_SUFFIX;
 
-    /** Filters the imported hierarchy for some degenerated cases that some exporters produce.
-     * @param pData The sub-hierarchy to filter
-     */
+    /**
+      * @brief  Filters the imported hierarchy for some degenerated cases that some exporters produce.
+      * @param pData The sub-hierarchy to filter
+      */
     void FilterHierarchy( XFile::Node* pNode);
 
 protected:
     unsigned int mMajorVersion, mMinorVersion; ///< version numbers
     bool mIsBinaryFormat; ///< true if the file is in binary, false if it's in text form
     unsigned int mBinaryFloatSize; ///< float size in bytes, either 4 or 8
-    // counter for number arrays in binary format
-    unsigned int mBinaryNumCount;
-
-    const char* P;
-    const char* End;
-
-    /// Line number when reading in text format
-    unsigned int mLineNumber;
-
-    /// Imported data
-    XFile::Scene* mScene;
+    unsigned int mBinaryNumCount; /// < counter for number arrays in binary format
+    const char* mP;
+    const char* mEnd;
+    unsigned int mLineNumber; ///< Line number when reading in text format
+    XFile::Scene* mScene; ///< Imported data
 };
 
-}
+} //! ns Assimp
+
 #endif // AI_XFILEPARSER_H_INC

+ 5 - 16
code/XGLLoader.cpp

@@ -72,17 +72,6 @@ using namespace irr::io;
 #endif
 
 
-// scopeguard for a malloc'ed buffer
-struct free_it
-{
-    free_it(void* free) : free(free) {}
-    ~free_it() {
-        ::free(this->free);
-    }
-
-    void* free;
-};
-
 namespace Assimp { // this has to be in here because LogFunctions is in ::Assimp
     template<> const char* LogFunctions<XGLImporter>::Prefix()
     {
@@ -155,8 +144,7 @@ void XGLImporter::InternReadFile( const std::string& pFile,
     aiScene* pScene, IOSystem* pIOHandler)
 {
 #ifndef ASSIMP_BUILD_NO_COMPRESSED_XGL
-    Bytef* dest = NULL;
-    free_it free_it_really(dest);
+    std::vector<Bytef> uncompressed;
 #endif
 
     m_scene = pScene;
@@ -192,6 +180,7 @@ void XGLImporter::InternReadFile( const std::string& pFile,
 
         size_t total = 0l;
 
+        // TODO: be smarter about this, decompress directly into heap buffer
         // and decompress the data .... do 1k chunks in the hope that we won't kill the stack
     #define MYBLOCK 1024
         Bytef block[MYBLOCK];
@@ -206,8 +195,8 @@ void XGLImporter::InternReadFile( const std::string& pFile,
             }
             const size_t have = MYBLOCK - zstream.avail_out;
             total += have;
-            dest = reinterpret_cast<Bytef*>( realloc(dest,total) );
-            memcpy(dest + total - have,block,have);
+            uncompressed.resize(total);
+            memcpy(uncompressed.data() + total - have,block,have);
         }
         while (ret != Z_STREAM_END);
 
@@ -215,7 +204,7 @@ void XGLImporter::InternReadFile( const std::string& pFile,
         inflateEnd(&zstream);
 
         // replace the input stream with a memory stream
-        stream.reset(new MemoryIOStream(reinterpret_cast<uint8_t*>(dest),total));
+        stream.reset(new MemoryIOStream(reinterpret_cast<uint8_t*>(uncompressed.data()),total));
 #endif
     }
 

+ 2 - 27
code/glTF2Asset.h

@@ -137,7 +137,7 @@ namespace glTF2
     // Vec/matrix types, as raw float arrays
     typedef float (vec3)[3];
     typedef float (vec4)[4];
-	typedef float (mat4)[16];
+    typedef float (mat4)[16];
 
     namespace Util
     {
@@ -166,33 +166,8 @@ namespace glTF2
 
     //! Magic number for GLB files
 	#define AI_GLB_MAGIC_NUMBER "glTF"
+	#include <assimp/pbrmaterial.h>
 
-    #define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR "$mat.gltf.pbrMetallicRoughness.baseColorFactor", 0, 0
-	#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR "$mat.gltf.pbrMetallicRoughness.metallicFactor", 0, 0
-	#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR "$mat.gltf.pbrMetallicRoughness.roughnessFactor", 0, 0
-    #define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE aiTextureType_DIFFUSE, 1
-	#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE aiTextureType_UNKNOWN, 0
-	#define AI_MATKEY_GLTF_ALPHAMODE "$mat.gltf.alphaMode", 0, 0
-	#define AI_MATKEY_GLTF_ALPHACUTOFF "$mat.gltf.alphaCutoff", 0, 0
-	#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS "$mat.gltf.pbrSpecularGlossiness", 0, 0
-	#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR "$mat.gltf.pbrMetallicRoughness.glossinessFactor", 0, 0
-
-	#define _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE "$tex.file.texCoord"
-	#define _AI_MATKEY_GLTF_MAPPINGNAME_BASE "$tex.mappingname"
-	#define _AI_MATKEY_GLTF_MAPPINGID_BASE "$tex.mappingid"
-	#define _AI_MATKEY_GLTF_MAPPINGFILTER_MAG_BASE "$tex.mappingfiltermag"
-	#define _AI_MATKEY_GLTF_MAPPINGFILTER_MIN_BASE "$tex.mappingfiltermin"
-    #define _AI_MATKEY_GLTF_SCALE_BASE "$tex.scale"
-    #define _AI_MATKEY_GLTF_STRENGTH_BASE "$tex.strength"
-
-	#define AI_MATKEY_GLTF_TEXTURE_TEXCOORD _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, type, N
-	#define AI_MATKEY_GLTF_MAPPINGNAME(type, N) _AI_MATKEY_GLTF_MAPPINGNAME_BASE, type, N
-	#define AI_MATKEY_GLTF_MAPPINGID(type, N) _AI_MATKEY_GLTF_MAPPINGID_BASE, type, N
-	#define AI_MATKEY_GLTF_MAPPINGFILTER_MAG(type, N) _AI_MATKEY_GLTF_MAPPINGFILTER_MAG_BASE, type, N
-	#define AI_MATKEY_GLTF_MAPPINGFILTER_MIN(type, N) _AI_MATKEY_GLTF_MAPPINGFILTER_MIN_BASE, type, N
-    #define AI_MATKEY_GLTF_TEXTURE_SCALE(type, N) _AI_MATKEY_GLTF_SCALE_BASE, type, N
-    #define AI_MATKEY_GLTF_TEXTURE_STRENGTH(type, N) _AI_MATKEY_GLTF_STRENGTH_BASE, type, N
-    
     #ifdef ASSIMP_API
         #include "./../include/assimp/Compiler/pushpack1.h"
     #endif

+ 14 - 1
code/glTF2Asset.inl

@@ -669,7 +669,7 @@ inline Image::Image()
 
 }
 
-inline void Image::Read(Value& obj, Asset& /*r*/)
+inline void Image::Read(Value& obj, Asset& r)
 {
     if (!mDataLength) {
         if (Value* uri = FindString(obj, "uri")) {
@@ -686,6 +686,19 @@ inline void Image::Read(Value& obj, Asset& /*r*/)
                 this->uri = uristr;
             }
         }
+        else if (Value* bufferViewVal = FindUInt(obj, "bufferView")) {
+            this->bufferView = r.bufferViews.Retrieve(bufferViewVal->GetUint());
+            Ref<Buffer> buffer = this->bufferView->buffer;
+
+            this->mDataLength = this->bufferView->byteLength;
+            // maybe this memcpy could be avoided if aiTexture does not delete[] pcData at destruction.
+            this->mData = new uint8_t [this->mDataLength];
+            memcpy(this->mData, buffer->GetPointer() + this->bufferView->byteOffset, this->mDataLength);
+
+            if (Value* mtype = FindString(obj, "mimeType")) {
+                this->mimeType = mtype->GetString();
+            }
+        }
     }
 }
 

+ 3 - 5
code/glTF2Exporter.cpp

@@ -316,11 +316,9 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref<Texture>& texture, aiTe
             std::string path = tex.C_Str();
 
             if (path.size() > 0) {
-                if (path[0] != '*') {
-                    std::map<std::string, unsigned int>::iterator it = mTexturesByPath.find(path);
-                    if (it != mTexturesByPath.end()) {
-                        texture = mAsset->textures.Get(it->second);
-                    }
+                std::map<std::string, unsigned int>::iterator it = mTexturesByPath.find(path);
+                if (it != mTexturesByPath.end()) {
+                    texture = mAsset->textures.Get(it->second);
                 }
 
                 if (!texture) {

+ 8 - 8
code/glTFAsset.inl

@@ -948,24 +948,24 @@ Ref<Buffer> buf = pAsset_Root.buffers.Get(pCompression_Open3DGC.Buffer);
 	size_t size_coordindex = ifs.GetNCoordIndex() * 3;// See float attributes note.
 
 	if(primitives[0].indices->count != size_coordindex)
-		throw DeadlyImportError("GLTF: Open3DGC. Compressed indices count (" + std::to_string(size_coordindex) +
-								") not equal to uncompressed (" + std::to_string(primitives[0].indices->count) + ").");
+		throw DeadlyImportError("GLTF: Open3DGC. Compressed indices count (" + to_string(size_coordindex) +
+								") not equal to uncompressed (" + to_string(primitives[0].indices->count) + ").");
 
 	size_coordindex *= sizeof(IndicesType);
 	// Coordinates
 	size_t size_coord = ifs.GetNCoord();// See float attributes note.
 
 	if(primitives[0].attributes.position[0]->count != size_coord)
-		throw DeadlyImportError("GLTF: Open3DGC. Compressed positions count (" + std::to_string(size_coord) +
-								") not equal to uncompressed (" + std::to_string(primitives[0].attributes.position[0]->count) + ").");
+		throw DeadlyImportError("GLTF: Open3DGC. Compressed positions count (" + to_string(size_coord) +
+								") not equal to uncompressed (" + to_string(primitives[0].attributes.position[0]->count) + ").");
 
 	size_coord *= 3 * sizeof(float);
 	// Normals
 	size_t size_normal = ifs.GetNNormal();// See float attributes note.
 
 	if(primitives[0].attributes.normal[0]->count != size_normal)
-		throw DeadlyImportError("GLTF: Open3DGC. Compressed normals count (" + std::to_string(size_normal) +
-								") not equal to uncompressed (" + std::to_string(primitives[0].attributes.normal[0]->count) + ").");
+		throw DeadlyImportError("GLTF: Open3DGC. Compressed normals count (" + to_string(size_normal) +
+								") not equal to uncompressed (" + to_string(primitives[0].attributes.normal[0]->count) + ").");
 
 	size_normal *= 3 * sizeof(float);
 	// Additional attributes.
@@ -989,8 +989,8 @@ Ref<Buffer> buf = pAsset_Root.buffers.Get(pCompression_Open3DGC.Buffer);
 				if(idx_texcoord < primitives[0].attributes.texcoord.size())
 				{
 					if(primitives[0].attributes.texcoord[idx]->count != tval)
-						throw DeadlyImportError("GLTF: Open3DGC. Compressed texture coordinates count (" + std::to_string(tval) +
-												") not equal to uncompressed (" + std::to_string(primitives[0].attributes.texcoord[idx]->count) + ").");
+						throw DeadlyImportError("GLTF: Open3DGC. Compressed texture coordinates count (" + to_string(tval) +
+												") not equal to uncompressed (" + to_string(primitives[0].attributes.texcoord[idx]->count) + ").");
 
 					idx_texcoord++;
 				}

+ 9 - 2
code/glTFImporter.cpp

@@ -194,9 +194,16 @@ void glTFImporter::ImportMaterials(glTF::Asset& r)
             aimat->AddProperty(&str, AI_MATKEY_NAME);
         }
 
-        SetMaterialColorProperty(embeddedTexIdxs, r, mat.diffuse, aimat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE);
+        SetMaterialColorProperty(embeddedTexIdxs, r, mat.ambient,  aimat, aiTextureType_AMBIENT,  AI_MATKEY_COLOR_AMBIENT );
+        SetMaterialColorProperty(embeddedTexIdxs, r, mat.diffuse,  aimat, aiTextureType_DIFFUSE,  AI_MATKEY_COLOR_DIFFUSE );
         SetMaterialColorProperty(embeddedTexIdxs, r, mat.specular, aimat, aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR);
-        SetMaterialColorProperty(embeddedTexIdxs, r, mat.ambient, aimat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT);
+        SetMaterialColorProperty(embeddedTexIdxs, r, mat.emission, aimat, aiTextureType_EMISSIVE, AI_MATKEY_COLOR_EMISSIVE);
+
+        aimat->AddProperty(&mat.doubleSided, 1, AI_MATKEY_TWOSIDED);
+
+        if (mat.transparent && (mat.transparency != 1.0f)) {
+            aimat->AddProperty(&mat.transparency, 1, AI_MATKEY_OPACITY);
+        }
 
         if (mat.shininess > 0.f) {
             aimat->AddProperty(&mat.shininess, 1, AI_MATKEY_SHININESS);

+ 78 - 0
code/simd.cpp

@@ -0,0 +1,78 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2018, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+#include "simd.h"
+
+namespace Assimp {
+
+bool CPUSupportsSSE2() {
+#if defined(__x86_64__) || defined(_M_X64)
+    //* x86_64 always has SSE2 instructions */
+    return true;
+#elif defined(__GNUC__) && defined(i386)
+    // for GCC x86 we check cpuid
+    unsigned int d;
+    __asm__(
+        "pushl %%ebx\n\t"
+        "cpuid\n\t"
+        "popl %%ebx\n\t"
+        : "=d" ( d )
+        :"a" ( 1 ) );
+    return ( d & 0x04000000 ) != 0;
+#elif (defined(_MSC_VER) && defined(_M_IX86))
+    // also check cpuid for MSVC x86
+    unsigned int d;
+    __asm {
+        xor     eax, eax
+        inc eax
+        push ebx
+        cpuid
+        pop ebx
+        mov d, edx
+    }
+    return ( d & 0x04000000 ) != 0;
+#else
+    return false;
+#endif
+}
+
+} // Namespace Assimp

+ 53 - 0
code/simd.h

@@ -0,0 +1,53 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2018, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+#pragma once
+
+#include <assimp/defs.h>
+
+namespace Assimp {
+
+/// @brief  Checks if the platform supports SSE2 optimization
+/// @return true, if SSE2 is supported. false if SSE2 is not supported.
+bool ASSIMP_API CPUSupportsSSE2();
+
+} // Namespace Assimp

Some files were not shown because too many files changed in this diff