Browse Source

Merge branch 'master' of https://github.com/assimp/assimp

Kim Kulling 2 years ago
parent
commit
d65049a657
100 changed files with 3626 additions and 3186 deletions
  1. 0 6
      .github/workflows/ccpp.yml
  2. 13 3
      .github/workflows/sanitizer.yml
  3. 35 28
      CMakeLists.txt
  4. 128 0
      CODE_OF_CONDUCT.md
  5. 6 10
      Dockerfile
  6. 18 23
      Readme.md
  7. 0 4
      code/AssetLib/3DS/3DSHelper.h
  8. 7 0
      code/AssetLib/3DS/3DSLoader.cpp
  9. 1 1
      code/AssetLib/3DS/3DSLoader.h
  10. 4 4
      code/AssetLib/3MF/3MFTypes.h
  11. 5 5
      code/AssetLib/AMF/AMFImporter.hpp
  12. 1 0
      code/AssetLib/AMF/AMFImporter_Postprocess.cpp
  13. 0 16
      code/AssetLib/ASE/ASELoader.cpp
  14. 5 16
      code/AssetLib/ASE/ASEParser.cpp
  15. 1 1
      code/AssetLib/Assjson/cencode.c
  16. 1 2
      code/AssetLib/B3D/B3DImporter.cpp
  17. 2 1
      code/AssetLib/Blender/BlenderCustomData.cpp
  18. 63 57
      code/AssetLib/Blender/BlenderLoader.cpp
  19. 13 0
      code/AssetLib/Blender/BlenderLoader.h
  20. 1 5
      code/AssetLib/Blender/BlenderScene.cpp
  21. 2 5
      code/AssetLib/Blender/BlenderTessellator.cpp
  22. 1 6
      code/AssetLib/Blender/BlenderTessellator.h
  23. 11 6
      code/AssetLib/Collada/ColladaLoader.cpp
  24. 1 0
      code/AssetLib/Collada/ColladaLoader.h
  25. 0 1
      code/AssetLib/Collada/ColladaParser.cpp
  26. 21 29
      code/AssetLib/DXF/DXFLoader.cpp
  27. 2 2
      code/AssetLib/DXF/DXFLoader.h
  28. 5 6
      code/AssetLib/FBX/FBXBinaryTokenizer.cpp
  29. 48 19
      code/AssetLib/FBX/FBXConverter.cpp
  30. 8 4
      code/AssetLib/FBX/FBXDeformer.cpp
  31. 22 12
      code/AssetLib/FBX/FBXDocument.cpp
  32. 17 7
      code/AssetLib/FBX/FBXDocument.h
  33. 11 9
      code/AssetLib/FBX/FBXImporter.cpp
  34. 0 14
      code/AssetLib/FBX/FBXMaterial.cpp
  35. 5 2
      code/AssetLib/FBX/FBXMeshGeometry.cpp
  36. 5 4
      code/AssetLib/FBX/FBXMeshGeometry.h
  37. 36 13
      code/AssetLib/FBX/FBXParser.cpp
  38. 21 15
      code/AssetLib/FBX/FBXParser.h
  39. 10 10
      code/AssetLib/FBX/FBXTokenizer.cpp
  40. 5 3
      code/AssetLib/FBX/FBXTokenizer.h
  41. 11 0
      code/AssetLib/FBX/FBXUtil.h
  42. 6 10
      code/AssetLib/HMP/HMPLoader.cpp
  43. 1 1
      code/AssetLib/HMP/HMPLoader.h
  44. 23 15
      code/AssetLib/IFC/IFCBoolean.cpp
  45. 31 33
      code/AssetLib/IFC/IFCCurve.cpp
  46. 61 105
      code/AssetLib/IFC/IFCGeometry.cpp
  47. 9 15
      code/AssetLib/IFC/IFCLoader.cpp
  48. 2 2
      code/AssetLib/IFC/IFCLoader.h
  49. 2 5
      code/AssetLib/IFC/IFCMaterial.cpp
  50. 160 260
      code/AssetLib/IFC/IFCOpenings.cpp
  51. 24 28
      code/AssetLib/IFC/IFCProfile.cpp
  52. 79 150
      code/AssetLib/IFC/IFCUtil.cpp
  53. 1201 1210
      code/AssetLib/Irr/IRRLoader.cpp
  54. 76 67
      code/AssetLib/Irr/IRRLoader.h
  55. 431 407
      code/AssetLib/Irr/IRRMeshLoader.cpp
  56. 13 0
      code/AssetLib/Irr/IRRMeshLoader.h
  57. 213 213
      code/AssetLib/Irr/IRRShared.cpp
  58. 11 12
      code/AssetLib/Irr/IRRShared.h
  59. 0 1
      code/AssetLib/LWO/LWOBLoader.cpp
  60. 68 47
      code/AssetLib/LWO/LWOLoader.cpp
  61. 1 1
      code/AssetLib/LWO/LWOMaterial.cpp
  62. 9 10
      code/AssetLib/LWS/LWSLoader.cpp
  63. 47 15
      code/AssetLib/MD5/MD5Parser.cpp
  64. 4 3
      code/AssetLib/MD5/MD5Parser.h
  65. 39 5
      code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp
  66. 11 1
      code/AssetLib/MDL/HalfLife/HL1MDLLoader.h
  67. 9 3
      code/AssetLib/MDL/MDLLoader.cpp
  68. 2 1
      code/AssetLib/MDL/MDLLoader.h
  69. 13 4
      code/AssetLib/MDL/MDLMaterialLoader.cpp
  70. 2 1
      code/AssetLib/MMD/MMDPmxParser.h
  71. 2 2
      code/AssetLib/MS3D/MS3DLoader.cpp
  72. 4 0
      code/AssetLib/NDO/NDOLoader.cpp
  73. 1 1
      code/AssetLib/OFF/OFFLoader.cpp
  74. 0 2
      code/AssetLib/Obj/ObjFileData.h
  75. 6 4
      code/AssetLib/Obj/ObjFileImporter.cpp
  76. 3 2
      code/AssetLib/Obj/ObjFileMtlImporter.cpp
  77. 22 3
      code/AssetLib/Obj/ObjFileParser.cpp
  78. 2 3
      code/AssetLib/Ogre/OgreXmlSerializer.cpp
  79. 0 9
      code/AssetLib/OpenGEX/OpenGEXImporter.cpp
  80. 1 2
      code/AssetLib/Q3D/Q3DLoader.cpp
  81. 1 1
      code/AssetLib/Raw/RawLoader.h
  82. 1 1
      code/AssetLib/SIB/SIBImporter.cpp
  83. 5 2
      code/AssetLib/SMD/SMDLoader.cpp
  84. 1 1
      code/AssetLib/Unreal/UnrealLoader.h
  85. 1 1
      code/AssetLib/X/XFileImporter.cpp
  86. 0 1
      code/AssetLib/X/XFileParser.cpp
  87. 0 2
      code/AssetLib/X3D/X3DExporter.hpp
  88. 12 0
      code/AssetLib/X3D/X3DImporter.hpp
  89. 0 3
      code/AssetLib/X3D/X3DXmlHelper.cpp
  90. 14 13
      code/AssetLib/glTF/glTFAsset.h
  91. 8 2
      code/AssetLib/glTF/glTFImporter.cpp
  92. 36 10
      code/AssetLib/glTF2/glTF2Asset.h
  93. 61 12
      code/AssetLib/glTF2/glTF2Asset.inl
  94. 1 0
      code/AssetLib/glTF2/glTF2AssetWriter.h
  95. 68 5
      code/AssetLib/glTF2/glTF2AssetWriter.inl
  96. 139 49
      code/AssetLib/glTF2/glTF2Exporter.cpp
  97. 2 0
      code/AssetLib/glTF2/glTF2Exporter.h
  98. 137 78
      code/AssetLib/glTF2/glTF2Importer.cpp
  99. 4 1
      code/AssetLib/glTF2/glTF2Importer.h
  100. 0 2
      code/CApi/AssimpCExport.cpp

+ 0 - 6
.github/workflows/ccpp.yml

@@ -74,12 +74,6 @@ jobs:
         repository: cpp-pm/polly
         repository: cpp-pm/polly
         path: cmake/polly
         path: cmake/polly
 
 
-    - name: Remove contrib directory for Hunter builds
-      if: contains(matrix.name, 'hunter')
-      uses: JesseTG/[email protected]
-      with:
-        path: contrib
-
     - name: Cache DX SDK
     - name: Cache DX SDK
       id: dxcache
       id: dxcache
       if: contains(matrix.name, 'windows')
       if: contains(matrix.name, 'windows')

+ 13 - 3
.github/workflows/sanitizer.yml

@@ -14,7 +14,7 @@ jobs:
     name: adress-sanitizer
     name: adress-sanitizer
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: lukka/get-cmake@latest    
     - uses: lukka/get-cmake@latest    
     - uses: lukka/set-shell-env@v1
     - uses: lukka/set-shell-env@v1
       with:
       with:
@@ -38,7 +38,7 @@ jobs:
     name: undefined-behavior-sanitizer
     name: undefined-behavior-sanitizer
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: lukka/get-cmake@latest    
     - uses: lukka/get-cmake@latest    
     - uses: lukka/set-shell-env@v1
     - uses: lukka/set-shell-env@v1
       with:
       with:
@@ -46,7 +46,7 @@ jobs:
         CC: clang
         CC: clang
     
     
     - name: configure and build
     - name: configure and build
-      uses: lukka/run-cmake@v2
+      uses: lukka/run-cmake@v3
       with:
       with:
         cmakeListsOrSettingsJson: CMakeListsTxtAdvanced
         cmakeListsOrSettingsJson: CMakeListsTxtAdvanced
         cmakeListsTxtPath: '${{ github.workspace }}/CMakeLists.txt'
         cmakeListsTxtPath: '${{ github.workspace }}/CMakeLists.txt'
@@ -57,3 +57,13 @@ jobs:
     - name: test
     - name: test
       run: cd build/bin && ./unit
       run: cd build/bin && ./unit
       shell: bash
       shell: bash
+
+  job3:
+    name: printf-sanitizer
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v4
+    
+    - name: run scan_printf script
+      run: ./scripts/scan_printf.sh
+      shell: bash

+ 35 - 28
CMakeLists.txt

@@ -49,10 +49,9 @@ option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" OFF)
 IF(ASSIMP_HUNTER_ENABLED)
 IF(ASSIMP_HUNTER_ENABLED)
   include("cmake-modules/HunterGate.cmake")
   include("cmake-modules/HunterGate.cmake")
   HunterGate(
   HunterGate(
-    URL "https://github.com/cpp-pm/hunter/archive/v0.24.0.tar.gz"
-    SHA1 "a3d7f4372b1dcd52faa6ff4a3bd5358e1d0e5efd"
+    URL "https://github.com/cpp-pm/hunter/archive/v0.24.17.tar.gz"
+    SHA1 "e6396699e414120e32557fe92db097b7655b760b"
   )
   )
-
   add_definitions(-DASSIMP_USE_HUNTER)
   add_definitions(-DASSIMP_USE_HUNTER)
 ENDIF()
 ENDIF()
 
 
@@ -84,10 +83,6 @@ OPTION( ASSIMP_NO_EXPORT
   "Disable Assimp's export functionality."
   "Disable Assimp's export functionality."
   OFF
   OFF
 )
 )
-OPTION( ASSIMP_BUILD_ZLIB
-  "Build your own zlib"
-  OFF
-)
 OPTION( ASSIMP_BUILD_ASSIMP_TOOLS
 OPTION( ASSIMP_BUILD_ASSIMP_TOOLS
   "If the supplementary tools for Assimp are built in addition to the library."
   "If the supplementary tools for Assimp are built in addition to the library."
   OFF
   OFF
@@ -134,6 +129,18 @@ OPTION ( ASSIMP_IGNORE_GIT_HASH
    OFF
    OFF
 )
 )
 
 
+IF (WIN32)
+  OPTION( ASSIMP_BUILD_ZLIB
+    "Build your own zlib"
+    ON
+  )
+ELSE()
+  OPTION( ASSIMP_BUILD_ZLIB
+    "Build your own zlib"
+    ON
+  )
+ENDIF()
+
 IF (WIN32)
 IF (WIN32)
   # Use subset of Windows.h
   # Use subset of Windows.h
   ADD_DEFINITIONS( -DWIN32_LEAN_AND_MEAN )
   ADD_DEFINITIONS( -DWIN32_LEAN_AND_MEAN )
@@ -193,12 +200,9 @@ SET (ASSIMP_VERSION ${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}.${ASSIMP_VER
 SET (ASSIMP_SOVERSION 5)
 SET (ASSIMP_SOVERSION 5)
 
 
 SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" )
 SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" )
-if(NOT ASSIMP_HUNTER_ENABLED)
-  # Enable C++17 support globally
-  set(CMAKE_CXX_STANDARD 17)
-  set(CMAKE_CXX_STANDARD_REQUIRED ON)
-  set(CMAKE_C_STANDARD 99)
-endif()
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_C_STANDARD 99)
 
 
 IF(NOT ASSIMP_IGNORE_GIT_HASH)
 IF(NOT ASSIMP_IGNORE_GIT_HASH)
   # Get the current working branch
   # Get the current working branch
@@ -246,8 +250,7 @@ IF( UNIX )
   # Use GNUInstallDirs for Unix predefined directories
   # Use GNUInstallDirs for Unix predefined directories
   INCLUDE(GNUInstallDirs)
   INCLUDE(GNUInstallDirs)
   # Ensure that we do not run into issues like http://www.tcm.phy.cam.ac.uk/sw/inodes64.html on 32 bit linux
   # 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(NOT ${OPERATING_SYSTEM} MATCHES "Android")
     IF ( CMAKE_SIZEOF_VOID_P EQUAL 4) # only necessary for 32-bit linux
     IF ( CMAKE_SIZEOF_VOID_P EQUAL 4) # only necessary for 32-bit linux
       ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 )
       ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 )
     ENDIF()
     ENDIF()
@@ -257,9 +260,13 @@ ENDIF()
 # Grouped compiler settings ########################################
 # Grouped compiler settings ########################################
 IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT MINGW)
 IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT MINGW)
   IF(NOT ASSIMP_HUNTER_ENABLED)
   IF(NOT ASSIMP_HUNTER_ENABLED)
-    SET(CMAKE_CXX_STANDARD 17)
     SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
     SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
   ENDIF()
   ENDIF()
+  
+  IF(CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 13)
+    MESSAGE(STATUS "GCC13 detected disabling \"-Wdangling-reference\" in Cpp files as it appears to be a false positive")
+    ADD_COMPILE_OPTIONS("$<$<COMPILE_LANGUAGE:CXX>:-Wno-dangling-reference>")
+  ENDIF()
   # hide all not-exported symbols
   # hide all not-exported symbols
   IF(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "mips64" )
   IF(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "mips64" )
     SET(CMAKE_CXX_FLAGS "-mxgot -fvisibility=hidden -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}")
     SET(CMAKE_CXX_FLAGS "-mxgot -fvisibility=hidden -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}")
@@ -273,9 +280,9 @@ IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT MINGW)
 ELSEIF(MSVC)
 ELSEIF(MSVC)
   # enable multi-core compilation with MSVC
   # enable multi-core compilation with MSVC
   IF(CMAKE_CXX_COMPILER_ID MATCHES "Clang" ) # clang-cl
   IF(CMAKE_CXX_COMPILER_ID MATCHES "Clang" ) # clang-cl
-    ADD_COMPILE_OPTIONS(/bigobj /W4 /WX )
+    ADD_COMPILE_OPTIONS(/bigobj)
   ELSE() # msvc
   ELSE() # msvc
-    ADD_COMPILE_OPTIONS(/MP /bigobj /W4 /WX)
+    ADD_COMPILE_OPTIONS(/MP /bigobj)
   ENDIF()
   ENDIF()
   
   
   # disable "elements of array '' will be default initialized" warning on MSVC2013
   # disable "elements of array '' will be default initialized" warning on MSVC2013
@@ -289,7 +296,6 @@ ELSEIF(MSVC)
   SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG:FULL /PDBALTPATH:%_PDB% /OPT:REF /OPT:ICF")
   SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG:FULL /PDBALTPATH:%_PDB% /OPT:REF /OPT:ICF")
 ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang" )
 ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang" )
   IF(NOT ASSIMP_HUNTER_ENABLED)
   IF(NOT ASSIMP_HUNTER_ENABLED)
-    SET(CMAKE_CXX_STANDARD 17)
     SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
     SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
   ENDIF()
   ENDIF()
   SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long ${CMAKE_CXX_FLAGS}" )
   SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long ${CMAKE_CXX_FLAGS}" )
@@ -314,17 +320,17 @@ ENDIF()
 
 
 IF ( IOS AND NOT ASSIMP_HUNTER_ENABLED)
 IF ( IOS AND NOT ASSIMP_HUNTER_ENABLED)
   IF (CMAKE_BUILD_TYPE STREQUAL "Debug")
   IF (CMAKE_BUILD_TYPE STREQUAL "Debug")
-    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -Og")
+    SET(CMAKE_C_FLAGS   "${CMAKE_C_FLAGS} -fembed-bitcode -Og")
     SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -Og")
     SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -Og")
   ELSE()
   ELSE()
-    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -O3")
+    SET(CMAKE_C_FLAGS   "${CMAKE_C_FLAGS} -fembed-bitcode -O3")
     SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3")
     SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3")
-    # Experimental for pdb generation
   ENDIF()
   ENDIF()
 ENDIF()
 ENDIF()
 
 
 IF (ASSIMP_COVERALLS)
 IF (ASSIMP_COVERALLS)
   MESSAGE(STATUS "Coveralls enabled")
   MESSAGE(STATUS "Coveralls enabled")
+  
   INCLUDE(Coveralls)
   INCLUDE(Coveralls)
   SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
   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")
   SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
@@ -332,14 +338,16 @@ ENDIF()
 
 
 IF (ASSIMP_ASAN)
 IF (ASSIMP_ASAN)
   MESSAGE(STATUS "AddressSanitizer enabled")
   MESSAGE(STATUS "AddressSanitizer enabled")
+  
   SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
   SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
-  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
+  SET(CMAKE_C_FLAGS   "${CMAKE_C_FLAGS} -fsanitize=address")
 ENDIF()
 ENDIF()
 
 
 IF (ASSIMP_UBSAN)
 IF (ASSIMP_UBSAN)
   MESSAGE(STATUS "Undefined Behavior sanitizer enabled")
   MESSAGE(STATUS "Undefined Behavior sanitizer enabled")
+
   SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin -fno-sanitize-recover=all")
   SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin -fno-sanitize-recover=all")
-  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin -fno-sanitize-recover=all")
+  SET(CMAKE_C_FLAGS   "${CMAKE_C_FLAGS} -fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin -fno-sanitize-recover=all")
 ENDIF()
 ENDIF()
 
 
 INCLUDE (FindPkgMacros)
 INCLUDE (FindPkgMacros)
@@ -660,13 +668,13 @@ ELSE()
       set_target_properties(draco_encoder draco_decoder PROPERTIES
       set_target_properties(draco_encoder draco_decoder PROPERTIES
         EXCLUDE_FROM_ALL TRUE
         EXCLUDE_FROM_ALL TRUE
         EXCLUDE_FROM_DEFAULT_BUILD TRUE
         EXCLUDE_FROM_DEFAULT_BUILD TRUE
-        )
+      )
 
 
       # Do build the draco shared library
       # Do build the draco shared library
       set_target_properties(${draco_LIBRARIES} PROPERTIES
       set_target_properties(${draco_LIBRARIES} PROPERTIES
         EXCLUDE_FROM_ALL FALSE
         EXCLUDE_FROM_ALL FALSE
         EXCLUDE_FROM_DEFAULT_BUILD FALSE
         EXCLUDE_FROM_DEFAULT_BUILD FALSE
-        )
+      )
 
 
       TARGET_USE_COMMON_OUTPUT_DIRECTORY(${draco_LIBRARIES})
       TARGET_USE_COMMON_OUTPUT_DIRECTORY(${draco_LIBRARIES})
       TARGET_USE_COMMON_OUTPUT_DIRECTORY(draco_encoder)
       TARGET_USE_COMMON_OUTPUT_DIRECTORY(draco_encoder)
@@ -683,8 +691,7 @@ ELSE()
         FRAMEWORK DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
         FRAMEWORK DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
         COMPONENT ${LIBASSIMP_COMPONENT}
         COMPONENT ${LIBASSIMP_COMPONENT}
         INCLUDES DESTINATION include
         INCLUDES DESTINATION include
-    )
-
+      )
     ENDIF()
     ENDIF()
   ENDIF()
   ENDIF()
 ENDIF()
 ENDIF()

+ 128 - 0
CODE_OF_CONDUCT.md

@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+  and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+  overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+  advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+  address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior,  harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.

+ 6 - 10
Dockerfile

@@ -1,14 +1,9 @@
-FROM ubuntu:14.04
+FROM ubuntu:22.04
 
 
-RUN apt-get update && apt-get install -y \
+RUN apt-get update && apt-get install -y ninja-build \
     git cmake build-essential software-properties-common
     git cmake build-essential software-properties-common
 
 
-RUN add-apt-repository ppa:ubuntu-toolchain-r/test && apt-get update && apt-get install -y gcc-4.9 g++-4.9 && \
-    cd /usr/bin && \
-    rm gcc g++ cpp && \
-    ln -s gcc-4.9 gcc && \
-    ln -s g++-4.9 g++ && \
-    ln -s cpp-4.9 cpp
+RUN add-apt-repository ppa:ubuntu-toolchain-r/test && apt-get update 
 
 
 WORKDIR /opt
 WORKDIR /opt
 
 
@@ -19,7 +14,8 @@ WORKDIR /opt/assimp
 
 
 RUN git checkout master \
 RUN git checkout master \
     && mkdir build && cd build && \
     && mkdir build && cd build && \
-    cmake \
+    cmake -G 'Ninja' \
     -DCMAKE_BUILD_TYPE=Release \
     -DCMAKE_BUILD_TYPE=Release \
+    -DASSIMP_BUILD_ASSIMP_TOOLS=ON \
     .. && \
     .. && \
-    make && make install
+    ninja -j4 && ninja install

+ 18 - 23
Readme.md

@@ -1,6 +1,8 @@
 Open Asset Import Library (assimp)
 Open Asset Import Library (assimp)
 ==================================
 ==================================
-A library to import and export various 3d-model-formats including scene-post-processing to generate missing render data.
+
+Open Asset Import Library is a library to load various 3d file formats into a shared, in-memory format. It supports more than __40 file formats__ for import and a growing selection of file formats for export.
+
 ### Current project status ###
 ### Current project status ###
 [![Financial Contributors on Open Collective](https://opencollective.com/assimp/all/badge.svg?label=financial+contributors)](https://opencollective.com/assimp) 
 [![Financial Contributors on Open Collective](https://opencollective.com/assimp/all/badge.svg?label=financial+contributors)](https://opencollective.com/assimp) 
 ![C/C++ CI](https://github.com/assimp/assimp/workflows/C/C++%20CI/badge.svg)
 ![C/C++ CI](https://github.com/assimp/assimp/workflows/C/C++%20CI/badge.svg)
@@ -14,7 +16,6 @@ A library to import and export various 3d-model-formats including scene-post-pro
 [![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 [![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Average time to resolve an issue")
 [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Average time to resolve an issue")
 [![Percentage of issues still open](http://isitmaintained.com/badge/open/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Percentage of issues still open")
 [![Percentage of issues still open](http://isitmaintained.com/badge/open/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Percentage of issues still open")
-[![Total alerts](https://img.shields.io/lgtm/alerts/g/assimp/assimp.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/assimp/assimp/alerts/)
 <br>
 <br>
 
 
 APIs are provided for C and C++. There are various bindings to other languages (C#, Java, Python, Delphi, D). Assimp also runs on Android and iOS.
 APIs are provided for C and C++. There are various bindings to other languages (C#, Java, Python, Delphi, D). Assimp also runs on Android and iOS.
@@ -23,15 +24,19 @@ Additionally, assimp features various __mesh post processing tools__: normals an
 ### Latest Doc's ###
 ### Latest Doc's ###
 Please check the latest documents at [Asset-Importer-Lib-Doc](https://assimp-docs.readthedocs.io/en/latest/). 
 Please check the latest documents at [Asset-Importer-Lib-Doc](https://assimp-docs.readthedocs.io/en/latest/). 
 
 
-### Get involved ###
-This is the development repo containing the latest features and bugfixes. For productive use though, we recommend one of the stable releases available from [Github Assimp Releases](https://github.com/assimp/assimp/releases).
-<br>
-You find a bug in the docs? Use [Doc-Repo](https://github.com/assimp/assimp-docs).
-<br>
-Please check our Wiki as well: https://github.com/assimp/assimp/wiki
+### Prebuild binaries ###
+Please check our [Itchi Projectspace](https://kimkulling.itch.io/the-asset-importer-lib)
 
 
 If you want to check our Model-Database, use the following repo: https://github.com/assimp/assimp-mdb
 If you want to check our Model-Database, use the following repo: https://github.com/assimp/assimp-mdb
 
 
+### Communities ###
+- Ask a question at [The Assimp-Discussion Board](https://github.com/assimp/assimp/discussions)
+- Ask on [Assimp-Community on Reddit](https://www.reddit.com/r/Assimp/)
+- Ask on [StackOverflow with the assimp-tag](http://stackoverflow.com/questions/tagged/assimp?sort=newest). 
+- Nothing has worked? File a question or an issue-report at [The Assimp-Issue Tracker](https://github.com/assimp/assimp/issues)
+
+And we also have a Gitter-channel:Gitter [![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)<br>
+
 #### Supported file formats ####
 #### Supported file formats ####
 You can find the complete list of supported file-formats [here](https://github.com/assimp/assimp/blob/master/doc/Fileformats.md)
 You can find the complete list of supported file-formats [here](https://github.com/assimp/assimp/blob/master/doc/Fileformats.md)
 
 
@@ -66,28 +71,18 @@ Open Asset Import Library is implemented in C++. The directory structure looks l
 	/port		Ports to other languages and scripts to maintain those.
 	/port		Ports to other languages and scripts to maintain those.
 	/test		Unit- and regression tests, test suite of models
 	/test		Unit- and regression tests, test suite of models
 	/tools		Tools (old assimp viewer, command line `assimp`)
 	/tools		Tools (old assimp viewer, command line `assimp`)
-	/samples	A small number of samples to illustrate possible
-                        use cases for Assimp
+	/samples	A small number of samples to illustrate possible use-cases for Assimp
 
 
 The source code is organized in the following way:
 The source code is organized in the following way:
 
 
 	code/Common			The base implementation for importers and the infrastructure
 	code/Common			The base implementation for importers and the infrastructure
+	code/CApi                       Special implementations which are only used for the C-API
+	code/Geometry                   A collection of geometry tools
+	code/Material                   The material system
+	code/PBR                        An exporter for physical based models
 	code/PostProcessing		The post-processing steps
 	code/PostProcessing		The post-processing steps
 	code/AssetLib/<FormatName>	Implementation for import and export for the format
 	code/AssetLib/<FormatName>	Implementation for import and export for the format
 
 
-### Where to get help ###
-To find our documentation, visit [our website](https://assimp.org/) or check out [Wiki](https://github.com/assimp/assimp/wiki)
-
-If the docs don't solve your problem, you can:
-- Ask on [StackOverflow with the assimp-tag](http://stackoverflow.com/questions/tagged/assimp?sort=newest). 
-- Ask on [Assimp-Community on Reddit](https://www.reddit.com/r/Assimp/)
-- Ask a question at [The Assimp-Discussion Board](https://github.com/assimp/assimp/discussions)
-- Nothing has worked? File a question or an issue-report at [The Assimp-Issue Tracker](https://github.com/assimp/assimp/issues)
-
-Open Asset Import Library is a library to load various 3d file formats into a shared, in-memory format. It supports more than __40 file formats__ for import and a growing selection of file formats for export.
-
-And we also have a Gitter-channel:Gitter [![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)<br>
-
 ### Contributing ###
 ### Contributing ###
 Contributions to assimp are highly appreciated. The easiest way to get involved is to submit
 Contributions to assimp are highly appreciated. The easiest way to get involved is to submit
 a pull request with your changes against the main repository's `master` branch.
 a pull request with your changes against the main repository's `master` branch.

+ 0 - 4
code/AssetLib/3DS/3DSHelper.h

@@ -397,10 +397,6 @@ struct Material {
 
 
     Material(const Material &other) = default;
     Material(const Material &other) = default;
 
 
-    Material(Material &&other) AI_NO_EXCEPT = default;
-
-    Material &operator=(Material &&other) AI_NO_EXCEPT = default;
-
     virtual ~Material() = default;
     virtual ~Material() = default;
 
 
     //! Name of the material
     //! Name of the material

+ 7 - 0
code/AssetLib/3DS/3DSLoader.cpp

@@ -266,8 +266,15 @@ void Discreet3DSImporter::ParseMainChunk() {
     };
     };
 
 
     ASSIMP_3DS_END_CHUNK();
     ASSIMP_3DS_END_CHUNK();
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunreachable-code-return"
+#endif
     // recursively continue processing this hierarchy level
     // recursively continue processing this hierarchy level
     return ParseMainChunk();
     return ParseMainChunk();
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------

+ 1 - 1
code/AssetLib/3DS/3DSLoader.h

@@ -68,7 +68,7 @@ using namespace D3DS;
 class Discreet3DSImporter : public BaseImporter {
 class Discreet3DSImporter : public BaseImporter {
 public:
 public:
     Discreet3DSImporter();
     Discreet3DSImporter();
-    ~Discreet3DSImporter();
+    ~Discreet3DSImporter() override;
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Returns whether the class can handle the format of the given file.
     /** Returns whether the class can handle the format of the given file.

+ 4 - 4
code/AssetLib/3MF/3MFTypes.h

@@ -93,7 +93,7 @@ public:
         // empty
         // empty
     }
     }
 
 
-    ~EmbeddedTexture() = default;
+    ~EmbeddedTexture() override = default;
 
 
     ResourceType getType() const override {
     ResourceType getType() const override {
         return ResourceType::RT_EmbeddedTexture2D;
         return ResourceType::RT_EmbeddedTexture2D;
@@ -110,7 +110,7 @@ public:
         // empty
         // empty
     }
     }
 
 
-    ~Texture2DGroup() = default;
+    ~Texture2DGroup() override = default;
 
 
     ResourceType getType() const override {
     ResourceType getType() const override {
         return ResourceType::RT_Texture2DGroup;
         return ResourceType::RT_Texture2DGroup;
@@ -127,7 +127,7 @@ public:
         // empty
         // empty
     }
     }
 
 
-    ~BaseMaterials() = default;
+    ~BaseMaterials() override = default;
 
 
     ResourceType getType() const override {
     ResourceType getType() const override {
         return ResourceType::RT_BaseMaterials;
         return ResourceType::RT_BaseMaterials;
@@ -152,7 +152,7 @@ public:
         // empty
         // empty
     }
     }
 
 
-    ~Object() = default;
+    ~Object() override = default;
 
 
     ResourceType getType() const override {
     ResourceType getType() const override {
         return ResourceType::RT_Object;
         return ResourceType::RT_Object;

+ 5 - 5
code/AssetLib/AMF/AMFImporter.hpp

@@ -282,11 +282,11 @@ public:
     bool Find_NodeElement(const std::string &pID, const AMFNodeElementBase::EType pType, AMFNodeElementBase **pNodeElement) const;
     bool Find_NodeElement(const std::string &pID, const AMFNodeElementBase::EType pType, AMFNodeElementBase **pNodeElement) const;
     bool Find_ConvertedNode(const std::string &pID, NodeArray &nodeArray, aiNode **pNode) const;
     bool Find_ConvertedNode(const std::string &pID, NodeArray &nodeArray, aiNode **pNode) const;
     bool Find_ConvertedMaterial(const std::string &pID, const SPP_Material **pConvertedMaterial) const;
     bool Find_ConvertedMaterial(const std::string &pID, const SPP_Material **pConvertedMaterial) const;
-    void Throw_CloseNotFound(const std::string &nodeName);
-    void Throw_IncorrectAttr(const std::string &nodeName, const std::string &pAttrName);
-    void Throw_IncorrectAttrValue(const std::string &nodeName, const std::string &pAttrName);
-    void Throw_MoreThanOnceDefined(const std::string &nodeName, const std::string &pNodeType, const std::string &pDescription);
-    void Throw_ID_NotFound(const std::string &pID) const;
+    AI_WONT_RETURN void Throw_CloseNotFound(const std::string &nodeName) AI_WONT_RETURN_SUFFIX;
+    AI_WONT_RETURN void Throw_IncorrectAttr(const std::string &nodeName, const std::string &pAttrName) AI_WONT_RETURN_SUFFIX;
+    AI_WONT_RETURN void Throw_IncorrectAttrValue(const std::string &nodeName, const std::string &pAttrName) AI_WONT_RETURN_SUFFIX;
+    AI_WONT_RETURN void Throw_MoreThanOnceDefined(const std::string &nodeName, const std::string &pNodeType, const std::string &pDescription) AI_WONT_RETURN_SUFFIX;
+    AI_WONT_RETURN void Throw_ID_NotFound(const std::string &pID) const AI_WONT_RETURN_SUFFIX;
     void XML_CheckNode_MustHaveChildren(pugi::xml_node &node);
     void XML_CheckNode_MustHaveChildren(pugi::xml_node &node);
     bool XML_SearchNode(const std::string &nodeName);
     bool XML_SearchNode(const std::string &nodeName);
     void ParseHelper_FixTruncatedFloatString(const char *pInStr, std::string &pOutString);
     void ParseHelper_FixTruncatedFloatString(const char *pInStr, std::string &pOutString);

+ 1 - 0
code/AssetLib/AMF/AMFImporter_Postprocess.cpp

@@ -815,6 +815,7 @@ nl_clean_loop:
             for (; next_it != nodeArray.end(); ++next_it) {
             for (; next_it != nodeArray.end(); ++next_it) {
                 if ((*next_it)->FindNode((*nl_it)->mName) != nullptr) {
                 if ((*next_it)->FindNode((*nl_it)->mName) != nullptr) {
                     // if current top node(nl_it) found in another top node then erase it from node_list and restart search loop.
                     // if current top node(nl_it) found in another top node then erase it from node_list and restart search loop.
+                    // FIXME: this leaks memory on test models test8.amf and test9.amf
                     nodeArray.erase(nl_it);
                     nodeArray.erase(nl_it);
 
 
                     goto nl_clean_loop;
                     goto nl_clean_loop;

+ 0 - 16
code/AssetLib/ASE/ASELoader.cpp

@@ -44,7 +44,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
  */
 
 
 #ifndef ASSIMP_BUILD_NO_ASE_IMPORTER
 #ifndef ASSIMP_BUILD_NO_ASE_IMPORTER
-
 #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
 #ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
 
 
 // internal headers
 // internal headers
@@ -322,21 +321,6 @@ void ASEImporter::BuildAnimations(const std::vector<BaseNode *> &nodes) {
                 aiNodeAnim *nd = pcAnim->mChannels[iNum++] = new aiNodeAnim();
                 aiNodeAnim *nd = pcAnim->mChannels[iNum++] = new aiNodeAnim();
                 nd->mNodeName.Set(me->mName + ".Target");
                 nd->mNodeName.Set(me->mName + ".Target");
 
 
-                // If there is no input position channel we will need
-                // to supply the default position from the node's
-                // local transformation matrix.
-                /*TargetAnimationHelper helper;
-                if (me->mAnim.akeyPositions.empty())
-                {
-                    aiMatrix4x4& mat = (*i)->mTransform;
-                    helper.SetFixedMainAnimationChannel(aiVector3D(
-                        mat.a4, mat.b4, mat.c4));
-                }
-                else helper.SetMainAnimationChannel (&me->mAnim.akeyPositions);
-                helper.SetTargetAnimationChannel (&me->mTargetAnim.akeyPositions);
-
-                helper.Process(&me->mTargetAnim.akeyPositions);*/
-
                 // Allocate the key array and fill it
                 // Allocate the key array and fill it
                 nd->mNumPositionKeys = (unsigned int)me->mTargetAnim.akeyPositions.size();
                 nd->mNumPositionKeys = (unsigned int)me->mTargetAnim.akeyPositions.size();
                 nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys];
                 nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys];

+ 5 - 16
code/AssetLib/ASE/ASEParser.cpp

@@ -304,7 +304,6 @@ void Parser::Parse() {
         }
         }
         AI_ASE_HANDLE_TOP_LEVEL_SECTION();
         AI_ASE_HANDLE_TOP_LEVEL_SECTION();
     }
     }
-    return;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -480,6 +479,11 @@ void Parser::ParseLV1MaterialListBlock() {
             if (TokenMatch(filePtr, "MATERIAL_COUNT", 14)) {
             if (TokenMatch(filePtr, "MATERIAL_COUNT", 14)) {
                 ParseLV4MeshLong(iMaterialCount);
                 ParseLV4MeshLong(iMaterialCount);
 
 
+                if (UINT_MAX - iOldMaterialCount < iMaterialCount) {
+                    LogWarning("Out of range: material index is too large");
+                    return;
+                }
+
                 // now allocate enough storage to hold all materials
                 // now allocate enough storage to hold all materials
                 m_vMaterials.resize(iOldMaterialCount + iMaterialCount, Material("INVALID"));
                 m_vMaterials.resize(iOldMaterialCount + iMaterialCount, Material("INVALID"));
                 continue;
                 continue;
@@ -734,7 +738,6 @@ void Parser::ParseLV3MapBlock(Texture &map) {
         }
         }
         AI_ASE_HANDLE_SECTION("3", "*MAP_XXXXXX");
         AI_ASE_HANDLE_SECTION("3", "*MAP_XXXXXX");
     }
     }
-    return;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -859,7 +862,6 @@ void Parser::ParseLV1ObjectBlock(ASE::BaseNode &node) {
         }
         }
         AI_ASE_HANDLE_TOP_LEVEL_SECTION();
         AI_ASE_HANDLE_TOP_LEVEL_SECTION();
     }
     }
-    return;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -883,7 +885,6 @@ void Parser::ParseLV2CameraSettingsBlock(ASE::Camera &camera) {
         }
         }
         AI_ASE_HANDLE_SECTION("2", "CAMERA_SETTINGS");
         AI_ASE_HANDLE_SECTION("2", "CAMERA_SETTINGS");
     }
     }
-    return;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -1189,7 +1190,6 @@ void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode &mesh) {
         }
         }
         AI_ASE_HANDLE_SECTION("2", "*NODE_TM");
         AI_ASE_HANDLE_SECTION("2", "*NODE_TM");
     }
     }
-    return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV2MeshBlock(ASE::Mesh &mesh) {
 void Parser::ParseLV2MeshBlock(ASE::Mesh &mesh) {
@@ -1310,7 +1310,6 @@ void Parser::ParseLV2MeshBlock(ASE::Mesh &mesh) {
         }
         }
         AI_ASE_HANDLE_SECTION("2", "*MESH");
         AI_ASE_HANDLE_SECTION("2", "*MESH");
     }
     }
-    return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MeshWeightsBlock(ASE::Mesh &mesh) {
 void Parser::ParseLV3MeshWeightsBlock(ASE::Mesh &mesh) {
@@ -1344,7 +1343,6 @@ void Parser::ParseLV3MeshWeightsBlock(ASE::Mesh &mesh) {
         }
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_WEIGHTS");
         AI_ASE_HANDLE_SECTION("3", "*MESH_WEIGHTS");
     }
     }
-    return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV4MeshBones(unsigned int iNumBones, ASE::Mesh &mesh) {
 void Parser::ParseLV4MeshBones(unsigned int iNumBones, ASE::Mesh &mesh) {
@@ -1414,7 +1412,6 @@ void Parser::ParseLV4MeshBonesVertices(unsigned int iNumVertices, ASE::Mesh &mes
         }
         }
         AI_ASE_HANDLE_SECTION("4", "*MESH_BONE_VERTEX");
         AI_ASE_HANDLE_SECTION("4", "*MESH_BONE_VERTEX");
     }
     }
-    return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MeshVertexListBlock(
 void Parser::ParseLV3MeshVertexListBlock(
@@ -1443,7 +1440,6 @@ void Parser::ParseLV3MeshVertexListBlock(
         }
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_VERTEX_LIST");
         AI_ASE_HANDLE_SECTION("3", "*MESH_VERTEX_LIST");
     }
     }
-    return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MeshFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh) {
 void Parser::ParseLV3MeshFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh) {
@@ -1470,7 +1466,6 @@ void Parser::ParseLV3MeshFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh)
         }
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_FACE_LIST");
         AI_ASE_HANDLE_SECTION("3", "*MESH_FACE_LIST");
     }
     }
-    return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MeshTListBlock(unsigned int iNumVertices,
 void Parser::ParseLV3MeshTListBlock(unsigned int iNumVertices,
@@ -1503,7 +1498,6 @@ void Parser::ParseLV3MeshTListBlock(unsigned int iNumVertices,
         }
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_TVERT_LIST");
         AI_ASE_HANDLE_SECTION("3", "*MESH_TVERT_LIST");
     }
     }
-    return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MeshTFaceListBlock(unsigned int iNumFaces,
 void Parser::ParseLV3MeshTFaceListBlock(unsigned int iNumFaces,
@@ -1532,7 +1526,6 @@ void Parser::ParseLV3MeshTFaceListBlock(unsigned int iNumFaces,
         }
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_TFACE_LIST");
         AI_ASE_HANDLE_SECTION("3", "*MESH_TFACE_LIST");
     }
     }
-    return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MappingChannel(unsigned int iChannel, ASE::Mesh &mesh) {
 void Parser::ParseLV3MappingChannel(unsigned int iChannel, ASE::Mesh &mesh) {
@@ -1567,7 +1560,6 @@ void Parser::ParseLV3MappingChannel(unsigned int iChannel, ASE::Mesh &mesh) {
         }
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_MAPPING_CHANNEL");
         AI_ASE_HANDLE_SECTION("3", "*MESH_MAPPING_CHANNEL");
     }
     }
-    return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MeshCListBlock(unsigned int iNumVertices, ASE::Mesh &mesh) {
 void Parser::ParseLV3MeshCListBlock(unsigned int iNumVertices, ASE::Mesh &mesh) {
@@ -1595,7 +1587,6 @@ void Parser::ParseLV3MeshCListBlock(unsigned int iNumVertices, ASE::Mesh &mesh)
         }
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_CVERTEX_LIST");
         AI_ASE_HANDLE_SECTION("3", "*MESH_CVERTEX_LIST");
     }
     }
-    return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MeshCFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh) {
 void Parser::ParseLV3MeshCFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh) {
@@ -1623,7 +1614,6 @@ void Parser::ParseLV3MeshCFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh)
         }
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_CFACE_LIST");
         AI_ASE_HANDLE_SECTION("3", "*MESH_CFACE_LIST");
     }
     }
-    return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) {
 void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) {
@@ -1681,7 +1671,6 @@ void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) {
         }
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_NORMALS");
         AI_ASE_HANDLE_SECTION("3", "*MESH_NORMALS");
     }
     }
-    return;
 }
 }
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV4MeshFace(ASE::Face &out) {
 void Parser::ParseLV4MeshFace(ASE::Face &out) {

+ 1 - 1
code/AssetLib/Assjson/cencode.c

@@ -7,7 +7,7 @@ For details, see http://sourceforge.net/projects/libb64
 
 
 #include "cencode.h" // changed from <B64/cencode.h>
 #include "cencode.h" // changed from <B64/cencode.h>
 
 
-const int CHARS_PER_LINE = 72;
+static const int CHARS_PER_LINE = 72;
 
 
 #ifdef _MSC_VER
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(push)

+ 1 - 2
code/AssetLib/B3D/B3DImporter.cpp

@@ -150,7 +150,7 @@ AI_WONT_RETURN void B3DImporter::Fail(const string &str) {
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 int B3DImporter::ReadByte() {
 int B3DImporter::ReadByte() {
-    if (_pos > _buf.size()) {
+    if (_pos >= _buf.size()) {
         Fail("EOF");
         Fail("EOF");
     }
     }
 
 
@@ -418,7 +418,6 @@ void B3DImporter::ReadTRIS(int v0) {
             ASSIMP_LOG_ERROR("Bad triangle index: i0=", i0, ", i1=", i1, ", i2=", i2);
             ASSIMP_LOG_ERROR("Bad triangle index: i0=", i0, ", i1=", i1, ", i2=", i2);
 #endif
 #endif
             Fail("Bad triangle index");
             Fail("Bad triangle index");
-            continue;
         }
         }
         face->mNumIndices = 3;
         face->mNumIndices = 3;
         face->mIndices = new unsigned[3];
         face->mIndices = new unsigned[3];

+ 2 - 1
code/AssetLib/Blender/BlenderCustomData.cpp

@@ -96,7 +96,8 @@ struct CustomDataTypeDescription {
         *           other (like CD_ORCO, ...) uses arrays of rawtypes or even arrays of Structures
         *           other (like CD_ORCO, ...) uses arrays of rawtypes or even arrays of Structures
         *           use a special readfunction for that cases
         *           use a special readfunction for that cases
         */
         */
-std::array<CustomDataTypeDescription, CD_NUMTYPES> customDataTypeDescriptions = { { DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MVert),
+static std::array<CustomDataTypeDescription, CD_NUMTYPES> customDataTypeDescriptions = { {
+        DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MVert),
         DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
         DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
         DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
         DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION,
         DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MEdge),
         DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MEdge),

+ 63 - 57
code/AssetLib/Blender/BlenderLoader.cpp

@@ -115,15 +115,12 @@ BlenderImporter::~BlenderImporter() {
     delete modifier_cache;
     delete modifier_cache;
 }
 }
 
 
-static const char * const Tokens[] = { "BLENDER" };
+static const char Token[] = "BLENDER";
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 // Returns whether the class can handle the format of the given file.
 bool BlenderImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
 bool BlenderImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
-    // note: this won't catch compressed files
-    static const char *tokens[] = { "<BLENDER", "blender" };
-
-    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
+    return ParseMagicToken(pFile, pIOHandler).error.empty();
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -142,63 +139,21 @@ void BlenderImporter::SetupProperties(const Importer * /*pImp*/) {
 // Imports the given file into the given scene structure.
 // Imports the given file into the given scene structure.
 void BlenderImporter::InternReadFile(const std::string &pFile,
 void BlenderImporter::InternReadFile(const std::string &pFile,
         aiScene *pScene, IOSystem *pIOHandler) {
         aiScene *pScene, IOSystem *pIOHandler) {
-#ifndef ASSIMP_BUILD_NO_COMPRESSED_BLEND
-    std::vector<char> uncompressed;
-#endif
-
     FileDatabase file;
     FileDatabase file;
-    std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
-    if (!stream) {
-        ThrowException("Could not open file for reading");
-    }
-
-    char magic[8] = { 0 };
-    stream->Read(magic, 7, 1);
-    if (strcmp(magic, Tokens[0])) {
-        // Check for presence of the gzip header. If yes, assume it is a
-        // compressed blend file and try uncompressing it, else fail. This is to
-        // avoid uncompressing random files which our loader might end up with.
-#ifdef ASSIMP_BUILD_NO_COMPRESSED_BLEND
-        ThrowException("BLENDER magic bytes are missing, is this file compressed (Assimp was built without decompression support)?");
-#else
-        if (magic[0] != 0x1f || static_cast<uint8_t>(magic[1]) != 0x8b) {
-            ThrowException("BLENDER magic bytes are missing, couldn't find GZIP header either");
-        }
-
-        LogDebug("Found no BLENDER magic word but a GZIP header, might be a compressed file");
-        if (magic[2] != 8) {
-            ThrowException("Unsupported GZIP compression method");
-        }
-
-        // http://www.gzip.org/zlib/rfc-gzip.html#header-trailer
-        stream->Seek(0L, aiOrigin_SET);
-        std::shared_ptr<StreamReaderLE> reader = std::shared_ptr<StreamReaderLE>(new StreamReaderLE(stream));
-
-        size_t total = 0;
-        Compression compression;
-        if (compression.open(Compression::Format::Binary, Compression::FlushMode::NoFlush, 16 + Compression::MaxWBits)) {
-            total = compression.decompress((unsigned char *)reader->GetPtr(), reader->GetRemainingSize(), uncompressed);
-            compression.close();
-        }
-
-        // replace the input stream with a memory stream
-        stream = std::make_shared<MemoryIOStream>(reinterpret_cast<uint8_t *>(uncompressed.data()), total);
-
-        // .. and retry
-        stream->Read(magic, 7, 1);
-        if (strcmp(magic, "BLENDER")) {
-            ThrowException("Found no BLENDER magic word in decompressed GZIP file");
-        }
-#endif
+    StreamOrError streamOrError = ParseMagicToken(pFile, pIOHandler);
+    if (!streamOrError.error.empty()) {
+        ThrowException(streamOrError.error);
     }
     }
+    std::shared_ptr<IOStream> stream = std::move(streamOrError.stream);
 
 
-    file.i64bit = (stream->Read(magic, 1, 1), magic[0] == '-');
-    file.little = (stream->Read(magic, 1, 1), magic[0] == 'v');
+    char version[4] = { 0 };
+    file.i64bit = (stream->Read(version, 1, 1), version[0] == '-');
+    file.little = (stream->Read(version, 1, 1), version[0] == 'v');
 
 
-    stream->Read(magic, 3, 1);
-    magic[3] = '\0';
+    stream->Read(version, 3, 1);
+    version[3] = '\0';
 
 
-    LogInfo("Blender version is ", magic[0], ".", magic + 1,
+    LogInfo("Blender version is ", version[0], ".", version + 1,
             " (64bit: ", file.i64bit ? "true" : "false",
             " (64bit: ", file.i64bit ? "true" : "false",
             ", little endian: ", file.little ? "true" : "false", ")");
             ", little endian: ", file.little ? "true" : "false", ")");
 
 
@@ -1338,4 +1293,55 @@ aiNode *BlenderImporter::ConvertNode(const Scene &in, const Object *obj, Convers
     return node.release();
     return node.release();
 }
 }
 
 
+BlenderImporter::StreamOrError BlenderImporter::ParseMagicToken(const std::string &pFile, IOSystem *pIOHandler) const {
+    std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
+    if (stream == nullptr) {
+        return {{}, {}, "Could not open file for reading"};
+    }
+
+    char magic[8] = { 0 };
+    stream->Read(magic, 7, 1);
+    if (strcmp(magic, Token) == 0) {
+        return {stream, {}, {}};
+    }
+
+    // Check for presence of the gzip header. If yes, assume it is a
+    // compressed blend file and try uncompressing it, else fail. This is to
+    // avoid uncompressing random files which our loader might end up with.
+#ifdef ASSIMP_BUILD_NO_COMPRESSED_BLEND
+    return {{}, {}, "BLENDER magic bytes are missing, is this file compressed (Assimp was built without decompression support)?"};
+#else
+    if (magic[0] != 0x1f || static_cast<uint8_t>(magic[1]) != 0x8b) {
+        return {{}, {}, "BLENDER magic bytes are missing, couldn't find GZIP header either"};
+    }
+
+    LogDebug("Found no BLENDER magic word but a GZIP header, might be a compressed file");
+    if (magic[2] != 8) {
+        return {{}, {}, "Unsupported GZIP compression method"};
+    }
+
+    // http://www.gzip.org/zlib/rfc-gzip.html#header-trailer
+    stream->Seek(0L, aiOrigin_SET);
+    std::shared_ptr<StreamReaderLE> reader = std::shared_ptr<StreamReaderLE>(new StreamReaderLE(stream));
+
+    size_t total = 0;
+    Compression compression;
+    auto uncompressed = std::make_shared<std::vector<char>>();
+    if (compression.open(Compression::Format::Binary, Compression::FlushMode::NoFlush, 16 + Compression::MaxWBits)) {
+        total = compression.decompress((unsigned char *)reader->GetPtr(), reader->GetRemainingSize(), *uncompressed);
+        compression.close();
+    }
+
+    // replace the input stream with a memory stream
+    stream = std::make_shared<MemoryIOStream>(reinterpret_cast<uint8_t *>(uncompressed->data()), total);
+
+    // .. and retry
+    stream->Read(magic, 7, 1);
+    if (strcmp(magic, Token) == 0) {
+        return {stream, uncompressed, {}};
+    }
+    return {{}, {}, "Found no BLENDER magic word in decompressed GZIP file"};
+#endif
+}
+
 #endif // ASSIMP_BUILD_NO_BLEND_IMPORTER
 #endif // ASSIMP_BUILD_NO_BLEND_IMPORTER

+ 13 - 0
code/AssetLib/Blender/BlenderLoader.h

@@ -180,6 +180,19 @@ private:
             const Blender::MTex *tex,
             const Blender::MTex *tex,
             Blender::ConversionData &conv_data);
             Blender::ConversionData &conv_data);
 
 
+    // TODO: Move to a std::variant, once c++17 is supported.
+    struct StreamOrError {
+        std::shared_ptr<IOStream> stream;
+        std::shared_ptr<std::vector<char>> input;
+        std::string error;
+    };
+
+    // Returns either a stream (and optional input data for the stream) or
+    // an error if it can't parse the magic token.
+    StreamOrError ParseMagicToken(
+            const std::string &pFile,
+            IOSystem *pIOHandler) const;
+
 private: // static stuff, mostly logging and error reporting.
 private: // static stuff, mostly logging and error reporting.
     // --------------------
     // --------------------
     static void CheckActualType(const Blender::ElemBase *dt,
     static void CheckActualType(const Blender::ElemBase *dt,

+ 1 - 5
code/AssetLib/Blender/BlenderScene.cpp

@@ -102,10 +102,6 @@ void Structure::Convert<CollectionObject>(
 
 
     ReadFieldPtr<ErrorPolicy_Fail>(dest.next, "*next", db);
     ReadFieldPtr<ErrorPolicy_Fail>(dest.next, "*next", db);
     {
     {
-        //std::shared_ptr<CollectionObject> prev;
-        //ReadFieldPtr<ErrorPolicy_Fail>(prev, "*prev", db);
-        //dest.prev = prev.get();
-
         std::shared_ptr<Object> ob;
         std::shared_ptr<Object> ob;
         ReadFieldPtr<ErrorPolicy_Igno>(ob, "*ob", db);
         ReadFieldPtr<ErrorPolicy_Igno>(ob, "*ob", db);
         dest.ob = ob.get();
         dest.ob = ob.get();
@@ -569,7 +565,7 @@ void Structure ::Convert<MVert>(
         const FileDatabase &db) const {
         const FileDatabase &db) const {
 
 
     ReadFieldArray<ErrorPolicy_Fail>(dest.co, "co", db);
     ReadFieldArray<ErrorPolicy_Fail>(dest.co, "co", db);
-    ReadFieldArray<ErrorPolicy_Fail>(dest.no, "no", db);
+    ReadFieldArray<ErrorPolicy_Warn>(dest.no, "no", db);
     ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db);
     ReadField<ErrorPolicy_Igno>(dest.flag, "flag", db);
     //ReadField<ErrorPolicy_Warn>(dest.mat_nr,"mat_nr",db);
     //ReadField<ErrorPolicy_Warn>(dest.mat_nr,"mat_nr",db);
     ReadField<ErrorPolicy_Igno>(dest.bweight, "bweight", db);
     ReadField<ErrorPolicy_Igno>(dest.bweight, "bweight", db);

+ 2 - 5
code/AssetLib/Blender/BlenderTessellator.cpp

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2022, assimp team
 Copyright (c) 2006-2022, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -40,10 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 */
 */
 
 
-/** @file  BlenderTessellator.cpp
- *  @brief A simple tessellation wrapper
- */
-
+/// @file  BlenderTessellator.cpp
+/// @brief A simple tessellation wrapper
 
 
 #ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
 #ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
 
 

+ 1 - 6
code/AssetLib/Blender/BlenderTessellator.h

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2022, assimp team
 Copyright (c) 2006-2022, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -144,11 +143,7 @@ namespace Assimp
 
 
 #if ASSIMP_BLEND_WITH_POLY_2_TRI
 #if ASSIMP_BLEND_WITH_POLY_2_TRI
 
 
-#ifdef ASSIMP_USE_HUNTER
-#  include <poly2tri/poly2tri.h>
-#else
-#  include "../contrib/poly2tri/poly2tri/poly2tri.h"
-#endif
+#include "contrib/poly2tri/poly2tri/poly2tri.h"
 
 
 namespace Assimp
 namespace Assimp
 {
 {

+ 11 - 6
code/AssetLib/Collada/ColladaLoader.cpp

@@ -95,6 +95,7 @@ ColladaLoader::ColladaLoader() :
         noSkeletonMesh(false),
         noSkeletonMesh(false),
         removeEmptyBones(false),
         removeEmptyBones(false),
         ignoreUpDirection(false),
         ignoreUpDirection(false),
+        ignoreUnitSize(false),
         useColladaName(false),
         useColladaName(false),
         mNodeNameCounter(0) {
         mNodeNameCounter(0) {
     // empty
     // empty
@@ -122,6 +123,7 @@ void ColladaLoader::SetupProperties(const Importer *pImp) {
     noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, 0) != 0;
     noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, 0) != 0;
     removeEmptyBones = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true) != 0;
     removeEmptyBones = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true) != 0;
     ignoreUpDirection = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION, 0) != 0;
     ignoreUpDirection = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION, 0) != 0;
+    ignoreUnitSize = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_IGNORE_UNIT_SIZE, 0) != 0;
     useColladaName = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES, 0) != 0;
     useColladaName = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES, 0) != 0;
 }
 }
 
 
@@ -170,12 +172,15 @@ void ColladaLoader::InternReadFile(const std::string &pFile, aiScene *pScene, IO
     // ... then fill the materials with the now adjusted settings
     // ... then fill the materials with the now adjusted settings
     FillMaterials(parser, pScene);
     FillMaterials(parser, pScene);
 
 
-    // Apply unit-size scale calculation
-
-    pScene->mRootNode->mTransformation *= aiMatrix4x4(parser.mUnitSize, 0, 0, 0,
-            0, parser.mUnitSize, 0, 0,
-            0, 0, parser.mUnitSize, 0,
-            0, 0, 0, 1);
+    if (!ignoreUnitSize) {
+        // Apply unit-size scale calculation
+        pScene->mRootNode->mTransformation *= aiMatrix4x4(
+                parser.mUnitSize, 0, 0, 0,
+                0, parser.mUnitSize, 0, 0,
+                0, 0, parser.mUnitSize, 0,
+                0, 0, 0, 1);
+    }
+    
     if (!ignoreUpDirection) {
     if (!ignoreUpDirection) {
         // Convert to Y_UP, if different orientation
         // Convert to Y_UP, if different orientation
         if (parser.mUpDirection == ColladaParser::UP_X) {
         if (parser.mUpDirection == ColladaParser::UP_X) {

+ 1 - 0
code/AssetLib/Collada/ColladaLoader.h

@@ -239,6 +239,7 @@ protected:
     bool noSkeletonMesh;
     bool noSkeletonMesh;
     bool removeEmptyBones;
     bool removeEmptyBones;
     bool ignoreUpDirection;
     bool ignoreUpDirection;
+    bool ignoreUnitSize;
     bool useColladaName;
     bool useColladaName;
 
 
     /** Used by FindNameForNode() to generate unique node names */
     /** Used by FindNameForNode() to generate unique node names */

+ 0 - 1
code/AssetLib/Collada/ColladaParser.cpp

@@ -1855,7 +1855,6 @@ size_t ColladaParser::ReadPrimitives(XmlNode &node, Mesh &pMesh, std::vector<Inp
         default:
         default:
             // LineStrip is not supported due to expected index unmangling
             // LineStrip is not supported due to expected index unmangling
             throw DeadlyImportError("Unsupported primitive type.");
             throw DeadlyImportError("Unsupported primitive type.");
-            break;
         }
         }
 
 
         // store the face size to later reconstruct the face from
         // store the face size to later reconstruct the face from

+ 21 - 29
code/AssetLib/DXF/DXFLoader.cpp

@@ -71,7 +71,7 @@ static const aiColor4D AI_DXF_DEFAULT_COLOR(aiColor4D(0.6f, 0.6f, 0.6f, 0.6f));
 // color indices for DXF - 16 are supported, the table is
 // color indices for DXF - 16 are supported, the table is
 // taken directly from the DXF spec.
 // taken directly from the DXF spec.
 static aiColor4D g_aclrDxfIndexColors[] = {
 static aiColor4D g_aclrDxfIndexColors[] = {
-    aiColor4D (0.6f, 0.6f, 0.6f, 1.0f),
+    aiColor4D(0.6f, 0.6f, 0.6f, 1.0f),
     aiColor4D (1.0f, 0.0f, 0.0f, 1.0f), // red
     aiColor4D (1.0f, 0.0f, 0.0f, 1.0f), // red
     aiColor4D (0.0f, 1.0f, 0.0f, 1.0f), // green
     aiColor4D (0.0f, 1.0f, 0.0f, 1.0f), // green
     aiColor4D (0.0f, 0.0f, 1.0f, 1.0f), // blue
     aiColor4D (0.0f, 0.0f, 1.0f, 1.0f), // blue
@@ -88,6 +88,7 @@ static aiColor4D g_aclrDxfIndexColors[] = {
     aiColor4D (1.0f, 1.0f, 1.0f, 1.0f), // white
     aiColor4D (1.0f, 1.0f, 1.0f, 1.0f), // white
     aiColor4D (0.6f, 0.0f, 1.0f, 1.0f)  // violet
     aiColor4D (0.6f, 0.0f, 1.0f, 1.0f)  // violet
 };
 };
+
 #define AI_DXF_NUM_INDEX_COLORS (sizeof(g_aclrDxfIndexColors)/sizeof(g_aclrDxfIndexColors[0]))
 #define AI_DXF_NUM_INDEX_COLORS (sizeof(g_aclrDxfIndexColors)/sizeof(g_aclrDxfIndexColors[0]))
 #define AI_DXF_ENTITIES_MAGIC_BLOCK "$ASSIMP_ENTITIES_MAGIC"
 #define AI_DXF_ENTITIES_MAGIC_BLOCK "$ASSIMP_ENTITIES_MAGIC"
 
 
@@ -109,14 +110,6 @@ static const aiImporterDesc desc = {
     "dxf"
     "dxf"
 };
 };
 
 
-// ------------------------------------------------------------------------------------------------
-// Constructor to be privately used by Importer
-DXFImporter::DXFImporter() = default;
-
-// ------------------------------------------------------------------------------------------------
-// Destructor, private as well
-DXFImporter::~DXFImporter() = default;
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 // Returns whether the class can handle the format of the given file.
 bool DXFImporter::CanRead( const std::string& filename, IOSystem* pIOHandler, bool /*checkSig*/ ) const {
 bool DXFImporter::CanRead( const std::string& filename, IOSystem* pIOHandler, bool /*checkSig*/ ) const {
@@ -229,7 +222,7 @@ void DXFImporter::ConvertMeshes(aiScene* pScene, DXF::FileData& output) {
         ASSIMP_LOG_VERBOSE_DEBUG("DXF: Unexpanded polycount is ", icount, ", vertex count is ", vcount);
         ASSIMP_LOG_VERBOSE_DEBUG("DXF: Unexpanded polycount is ", icount, ", vertex count is ", vcount);
     }
     }
 
 
-    if (! output.blocks.size()  ) {
+    if (output.blocks.empty()) {
         throw DeadlyImportError("DXF: no data blocks loaded");
         throw DeadlyImportError("DXF: no data blocks loaded");
     }
     }
 
 
@@ -587,10 +580,11 @@ void DXFImporter::ParseInsertion(DXF::LineReader& reader, DXF::FileData& output)
     }
     }
 }
 }
 
 
-#define DXF_POLYLINE_FLAG_CLOSED        0x1
-#define DXF_POLYLINE_FLAG_3D_POLYLINE   0x8
-#define DXF_POLYLINE_FLAG_3D_POLYMESH   0x10
-#define DXF_POLYLINE_FLAG_POLYFACEMESH  0x40
+static constexpr unsigned int DXF_POLYLINE_FLAG_CLOSED = 0x1;
+// Currently unused
+//static constexpr unsigned int DXF_POLYLINE_FLAG_3D_POLYLINE = 0x8;
+//static constexpr unsigned int DXF_POLYLINE_FLAG_3D_POLYMESH = 0x10;
+static constexpr unsigned int DXF_POLYLINE_FLAG_POLYFACEMESH = 0x40;
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void DXFImporter::ParsePolyLine(DXF::LineReader& reader, DXF::FileData& output) {
 void DXFImporter::ParsePolyLine(DXF::LineReader& reader, DXF::FileData& output) {
@@ -639,12 +633,6 @@ void DXFImporter::ParsePolyLine(DXF::LineReader& reader, DXF::FileData& output)
         reader++;
         reader++;
     }
     }
 
 
-    //if (!(line.flags & DXF_POLYLINE_FLAG_POLYFACEMESH))   {
-    //  DefaultLogger::get()->warn((Formatter::format("DXF: polyline not currently supported: "),line.flags));
-    //  output.blocks.back().lines.pop_back();
-    //  return;
-    //}
-
     if (vguess && line.positions.size() != vguess) {
     if (vguess && line.positions.size() != vguess) {
         ASSIMP_LOG_WARN("DXF: unexpected vertex count in polymesh: ",
         ASSIMP_LOG_WARN("DXF: unexpected vertex count in polymesh: ",
             line.positions.size(),", expected ", vguess );
             line.positions.size(),", expected ", vguess );
@@ -734,12 +722,18 @@ void DXFImporter::ParsePolyLineVertex(DXF::LineReader& reader, DXF::PolyLine& li
         case 71:
         case 71:
         case 72:
         case 72:
         case 73:
         case 73:
-        case 74:
-            if (cnti == 4) {
-                ASSIMP_LOG_WARN("DXF: more than 4 indices per face not supported; ignoring");
-                break;
+        case 74: {
+                if (cnti == 4) {
+                    ASSIMP_LOG_WARN("DXF: more than 4 indices per face not supported; ignoring");
+                    break;
+                }
+                const int index = reader.ValueAsSignedInt();
+                if (index >= 0) {
+                    indices[cnti++] = static_cast<unsigned int>(index);
+                } else {
+                    indices[cnti++] = static_cast<unsigned int>(-index);
+                }
             }
             }
-            indices[cnti++] = reader.ValueAsUnsignedInt();
             break;
             break;
 
 
         // color
         // color
@@ -777,8 +771,7 @@ void DXFImporter::ParsePolyLineVertex(DXF::LineReader& reader, DXF::PolyLine& li
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void DXFImporter::Parse3DFace(DXF::LineReader& reader, DXF::FileData& output)
-{
+void DXFImporter::Parse3DFace(DXF::LineReader& reader, DXF::FileData& output) {
     // (note) this is also used for for parsing line entities, so we
     // (note) this is also used for for parsing line entities, so we
     // must handle the vertex_count == 2 case as well.
     // must handle the vertex_count == 2 case as well.
 
 
@@ -795,8 +788,7 @@ void DXFImporter::Parse3DFace(DXF::LineReader& reader, DXF::FileData& output)
         if (reader.GroupCode() == 0) {
         if (reader.GroupCode() == 0) {
             break;
             break;
         }
         }
-        switch (reader.GroupCode())
-        {
+        switch (reader.GroupCode()) {
 
 
         // 8 specifies the layer
         // 8 specifies the layer
         case 8:
         case 8:

+ 2 - 2
code/AssetLib/DXF/DXFLoader.h

@@ -68,8 +68,8 @@ namespace DXF {
  */
  */
 class DXFImporter : public BaseImporter {
 class DXFImporter : public BaseImporter {
 public:
 public:
-    DXFImporter();
-    ~DXFImporter() override;
+    DXFImporter() = default;
+    ~DXFImporter() override = default;
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Returns whether the class can handle the format of the given file.
     /** Returns whether the class can handle the format of the given file.

+ 5 - 6
code/AssetLib/FBX/FBXBinaryTokenizer.cpp

@@ -139,6 +139,7 @@ size_t Offset(const char* begin, const char* cursor) {
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
+AI_WONT_RETURN void TokenizeError(const std::string& message, const char* begin, const char* cursor) AI_WONT_RETURN_SUFFIX;
 void TokenizeError(const std::string& message, const char* begin, const char* cursor) {
 void TokenizeError(const std::string& message, const char* begin, const char* cursor) {
     TokenizeError(message, Offset(begin, cursor));
     TokenizeError(message, Offset(begin, cursor));
 }
 }
@@ -341,8 +342,7 @@ void ReadData(const char*& sbegin_out, const char*& send_out, const char* input,
 
 
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-bool ReadScope(TokenList& output_tokens, const char* input, const char*& cursor, const char* end, bool const is64bits)
-{
+bool ReadScope(TokenList &output_tokens, StackAllocator &token_allocator, const char *input, const char *&cursor, const char *end, bool const is64bits) {
     // the first word contains the offset at which this block ends
     // the first word contains the offset at which this block ends
 	const uint64_t end_offset = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end);
 	const uint64_t end_offset = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end);
 
 
@@ -408,7 +408,7 @@ bool ReadScope(TokenList& output_tokens, const char* input, const char*& cursor,
 
 
         // XXX this is vulnerable to stack overflowing ..
         // XXX this is vulnerable to stack overflowing ..
         while(Offset(input, cursor) < end_offset - sentinel_block_length) {
         while(Offset(input, cursor) < end_offset - sentinel_block_length) {
-			ReadScope(output_tokens, input, cursor, input + end_offset - sentinel_block_length, is64bits);
+            ReadScope(output_tokens, token_allocator, input, cursor, input + end_offset - sentinel_block_length, is64bits);
         }
         }
         output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_CLOSE_BRACKET, Offset(input, cursor) ));
         output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_CLOSE_BRACKET, Offset(input, cursor) ));
 
 
@@ -431,8 +431,7 @@ bool ReadScope(TokenList& output_tokens, const char* input, const char*& cursor,
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // TODO: Test FBX Binary files newer than the 7500 version to check if the 64 bits address behaviour is consistent
 // TODO: Test FBX Binary files newer than the 7500 version to check if the 64 bits address behaviour is consistent
-void TokenizeBinary(TokenList& output_tokens, const char* input, size_t length)
-{
+void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length, StackAllocator &token_allocator) {
 	ai_assert(input);
 	ai_assert(input);
 	ASSIMP_LOG_DEBUG("Tokenizing binary FBX file");
 	ASSIMP_LOG_DEBUG("Tokenizing binary FBX file");
 
 
@@ -465,7 +464,7 @@ void TokenizeBinary(TokenList& output_tokens, const char* input, size_t length)
     try
     try
     {
     {
         while (cursor < end ) {
         while (cursor < end ) {
-		    if (!ReadScope(output_tokens, input, cursor, input + length, is64bits)) {
+            if (!ReadScope(output_tokens, token_allocator, input, cursor, input + length, is64bits)) {
                 break;
                 break;
             }
             }
         }
         }

+ 48 - 19
code/AssetLib/FBX/FBXConverter.cpp

@@ -93,6 +93,8 @@ FBXConverter::FBXConverter(aiScene *out, const Document &doc, bool removeEmptyBo
         mSceneOut(out),
         mSceneOut(out),
         doc(doc),
         doc(doc),
         mRemoveEmptyBones(removeEmptyBones) {
         mRemoveEmptyBones(removeEmptyBones) {
+
+
     // animations need to be converted first since this will
     // animations need to be converted first since this will
     // populate the node_anim_chain_bits map, which is needed
     // populate the node_anim_chain_bits map, which is needed
     // to determine which nodes need to be generated.
     // to determine which nodes need to be generated.
@@ -421,16 +423,32 @@ void FBXConverter::ConvertCamera(const Camera &cam, const std::string &orig_name
 
 
     out_camera->mAspect = cam.AspectWidth() / cam.AspectHeight();
     out_camera->mAspect = cam.AspectWidth() / cam.AspectHeight();
 
 
+    // NOTE: Camera mPosition, mLookAt and mUp must be set to default here.
+    // All transformations to the camera will be handled by its node in the scenegraph.
     out_camera->mPosition = aiVector3D(0.0f);
     out_camera->mPosition = aiVector3D(0.0f);
     out_camera->mLookAt = aiVector3D(1.0f, 0.0f, 0.0f);
     out_camera->mLookAt = aiVector3D(1.0f, 0.0f, 0.0f);
     out_camera->mUp = aiVector3D(0.0f, 1.0f, 0.0f);
     out_camera->mUp = aiVector3D(0.0f, 1.0f, 0.0f);
 
 
-    out_camera->mHorizontalFOV = AI_DEG_TO_RAD(cam.FieldOfView());
-
-    out_camera->mClipPlaneNear = cam.NearPlane();
-    out_camera->mClipPlaneFar = cam.FarPlane();
+    // NOTE: Some software (maya) does not put FieldOfView in FBX, so we compute
+    // mHorizontalFOV from FocalLength and FilmWidth with unit conversion.
+
+    // TODO: This is not a complete solution for how FBX cameras can be stored.
+    // TODO: Incorporate non-square pixel aspect ratio.
+    // TODO: FBX aperture mode might be storing vertical FOV in need of conversion with aspect ratio.
+
+    float fov_deg = cam.FieldOfView();
+    // If FOV not specified in file, compute using FilmWidth and FocalLength.
+    if (fov_deg == kFovUnknown) {
+        float film_width_inches = cam.FilmWidth();
+        float focal_length_mm = cam.FocalLength();
+        ASSIMP_LOG_VERBOSE_DEBUG("FBX FOV unspecified. Computing from FilmWidth (", film_width_inches, "inches) and FocalLength (", focal_length_mm, "mm).");
+        double half_fov_rad = std::atan2(film_width_inches * 25.4 * 0.5, focal_length_mm);
+        out_camera->mHorizontalFOV = static_cast<float>(half_fov_rad);
+    } else {
+        // FBX fov is full-view degrees. We want half-view radians.
+        out_camera->mHorizontalFOV = AI_DEG_TO_RAD(fov_deg) * 0.5f;
+    }
 
 
-    out_camera->mHorizontalFOV = AI_DEG_TO_RAD(cam.FieldOfView());
     out_camera->mClipPlaneNear = cam.NearPlane();
     out_camera->mClipPlaneNear = cam.NearPlane();
     out_camera->mClipPlaneFar = cam.FarPlane();
     out_camera->mClipPlaneFar = cam.FarPlane();
 }
 }
@@ -640,7 +658,7 @@ void FBXConverter::GetRotationMatrix(Model::RotOrder mode, const aiVector3D &rot
 bool FBXConverter::NeedsComplexTransformationChain(const Model &model) {
 bool FBXConverter::NeedsComplexTransformationChain(const Model &model) {
     const PropertyTable &props = model.Props();
     const PropertyTable &props = model.Props();
 
 
-    const auto zero_epsilon = ai_epsilon;
+    const auto zero_epsilon = Math::getEpsilon<ai_real>();
     const aiVector3D all_ones(1.0f, 1.0f, 1.0f);
     const aiVector3D all_ones(1.0f, 1.0f, 1.0f);
     for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
     for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
         const TransformationComp comp = static_cast<TransformationComp>(i);
         const TransformationComp comp = static_cast<TransformationComp>(i);
@@ -873,8 +891,12 @@ void FBXConverter::SetupNodeMetadata(const Model &model, aiNode &nd) {
             data->Set(index++, prop.first, interpretedBool->Value());
             data->Set(index++, prop.first, interpretedBool->Value());
         } else if (const TypedProperty<int> *interpretedInt = prop.second->As<TypedProperty<int>>()) {
         } else if (const TypedProperty<int> *interpretedInt = prop.second->As<TypedProperty<int>>()) {
             data->Set(index++, prop.first, interpretedInt->Value());
             data->Set(index++, prop.first, interpretedInt->Value());
+        } else if (const TypedProperty<uint32_t> *interpretedUInt = prop.second->As<TypedProperty<uint32_t>>()) {
+            data->Set(index++, prop.first, interpretedUInt->Value());
         } else if (const TypedProperty<uint64_t> *interpretedUint64 = prop.second->As<TypedProperty<uint64_t>>()) {
         } else if (const TypedProperty<uint64_t> *interpretedUint64 = prop.second->As<TypedProperty<uint64_t>>()) {
             data->Set(index++, prop.first, interpretedUint64->Value());
             data->Set(index++, prop.first, interpretedUint64->Value());
+        } else if (const TypedProperty<int64_t> *interpretedint64 = prop.second->As<TypedProperty<int64_t>>()) {
+            data->Set(index++, prop.first, interpretedint64->Value());
         } else if (const TypedProperty<float> *interpretedFloat = prop.second->As<TypedProperty<float>>()) {
         } else if (const TypedProperty<float> *interpretedFloat = prop.second->As<TypedProperty<float>>()) {
             data->Set(index++, prop.first, interpretedFloat->Value());
             data->Set(index++, prop.first, interpretedFloat->Value());
         } else if (const TypedProperty<std::string> *interpretedString = prop.second->As<TypedProperty<std::string>>()) {
         } else if (const TypedProperty<std::string> *interpretedString = prop.second->As<TypedProperty<std::string>>()) {
@@ -1176,15 +1198,23 @@ unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, c
     std::vector<aiAnimMesh *> animMeshes;
     std::vector<aiAnimMesh *> animMeshes;
     for (const BlendShape *blendShape : mesh.GetBlendShapes()) {
     for (const BlendShape *blendShape : mesh.GetBlendShapes()) {
         for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) {
         for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) {
-            const std::vector<const ShapeGeometry *> &shapeGeometries = blendShapeChannel->GetShapeGeometries();
-            for (size_t i = 0; i < shapeGeometries.size(); i++) {
+            const auto& shapeGeometries = blendShapeChannel->GetShapeGeometries();
+            for (const ShapeGeometry *shapeGeometry : shapeGeometries) {
                 aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh);
                 aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh);
-                const ShapeGeometry *shapeGeometry = shapeGeometries.at(i);
-                const std::vector<aiVector3D> &curVertices = shapeGeometry->GetVertices();
-                const std::vector<aiVector3D> &curNormals = shapeGeometry->GetNormals();
-                const std::vector<unsigned int> &curIndices = shapeGeometry->GetIndices();
+                const auto &curVertices = shapeGeometry->GetVertices();
+                const auto &curNormals = shapeGeometry->GetNormals();
+                const auto &curIndices = shapeGeometry->GetIndices();
                 //losing channel name if using shapeGeometry->Name()
                 //losing channel name if using shapeGeometry->Name()
-                animMesh->mName.Set(FixAnimMeshName(blendShapeChannel->Name()));
+                // if blendShapeChannel Name is empty or don't have a ".", add geoMetryName;
+                auto aniName = FixAnimMeshName(blendShapeChannel->Name());
+                auto geoMetryName = FixAnimMeshName(shapeGeometry->Name());
+                if (aniName.empty()) {
+                    aniName = geoMetryName;
+                }
+                else if (aniName.find('.') == aniName.npos) {
+                    aniName += "." + geoMetryName;
+                }
+                animMesh->mName.Set(aniName);
                 for (size_t j = 0; j < curIndices.size(); j++) {
                 for (size_t j = 0; j < curIndices.size(); j++) {
                     const unsigned int curIndex = curIndices.at(j);
                     const unsigned int curIndex = curIndices.at(j);
                     aiVector3D vertex = curVertices.at(j);
                     aiVector3D vertex = curVertices.at(j);
@@ -1406,13 +1436,12 @@ unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, co
     std::vector<aiAnimMesh *> animMeshes;
     std::vector<aiAnimMesh *> animMeshes;
     for (const BlendShape *blendShape : mesh.GetBlendShapes()) {
     for (const BlendShape *blendShape : mesh.GetBlendShapes()) {
         for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) {
         for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) {
-            const std::vector<const ShapeGeometry *> &shapeGeometries = blendShapeChannel->GetShapeGeometries();
-            for (size_t i = 0; i < shapeGeometries.size(); i++) {
+            const auto& shapeGeometries = blendShapeChannel->GetShapeGeometries();
+            for (const ShapeGeometry *shapeGeometry : shapeGeometries) {
                 aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh);
                 aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh);
-                const ShapeGeometry *shapeGeometry = shapeGeometries.at(i);
-                const std::vector<aiVector3D> &curVertices = shapeGeometry->GetVertices();
-                const std::vector<aiVector3D> &curNormals = shapeGeometry->GetNormals();
-                const std::vector<unsigned int> &curIndices = shapeGeometry->GetIndices();
+                const auto& curVertices = shapeGeometry->GetVertices();
+                const auto& curNormals = shapeGeometry->GetNormals();
+                const auto& curIndices = shapeGeometry->GetIndices();
                 animMesh->mName.Set(FixAnimMeshName(shapeGeometry->Name()));
                 animMesh->mName.Set(FixAnimMeshName(shapeGeometry->Name()));
                 for (size_t j = 0; j < curIndices.size(); j++) {
                 for (size_t j = 0; j < curIndices.size(); j++) {
                     unsigned int curIndex = curIndices.at(j);
                     unsigned int curIndex = curIndices.at(j);

+ 8 - 4
code/AssetLib/FBX/FBXDeformer.cpp

@@ -154,8 +154,10 @@ BlendShape::BlendShape(uint64_t id, const Element& element, const Document& doc,
     for (const Connection* con : conns) {
     for (const Connection* con : conns) {
         const BlendShapeChannel* const bspc = ProcessSimpleConnection<BlendShapeChannel>(*con, false, "BlendShapeChannel -> BlendShape", element);
         const BlendShapeChannel* const bspc = ProcessSimpleConnection<BlendShapeChannel>(*con, false, "BlendShapeChannel -> BlendShape", element);
         if (bspc) {
         if (bspc) {
-            blendShapeChannels.push_back(bspc);
-            continue;
+            auto pr = blendShapeChannels.insert(bspc);
+            if (!pr.second) {
+                FBXImporter::LogWarn("there is the same blendShapeChannel id ", bspc->ID());
+            }
         }
         }
     }
     }
 }
 }
@@ -179,8 +181,10 @@ BlendShapeChannel::BlendShapeChannel(uint64_t id, const Element& element, const
     for (const Connection* con : conns) {
     for (const Connection* con : conns) {
         const ShapeGeometry* const sg = ProcessSimpleConnection<ShapeGeometry>(*con, false, "Shape -> BlendShapeChannel", element);
         const ShapeGeometry* const sg = ProcessSimpleConnection<ShapeGeometry>(*con, false, "Shape -> BlendShapeChannel", element);
         if (sg) {
         if (sg) {
-            shapeGeometries.push_back(sg);
-            continue;
+            auto pr = shapeGeometries.insert(sg);
+            if (!pr.second) {
+                FBXImporter::LogWarn("there is the same shapeGeometrie id ", sg->ID());
+            }
         }
         }
     }
     }
 }
 }

+ 22 - 12
code/AssetLib/FBX/FBXDocument.cpp

@@ -243,7 +243,7 @@ FileGlobalSettings::FileGlobalSettings(const Document &doc, std::shared_ptr<cons
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-Document::Document(const Parser& parser, const ImportSettings& settings) :
+Document::Document(Parser& parser, const ImportSettings& settings) :
      settings(settings), parser(parser) {
      settings(settings), parser(parser) {
 	ASSIMP_LOG_DEBUG("Creating FBX Document");
 	ASSIMP_LOG_DEBUG("Creating FBX Document");
 
 
@@ -265,13 +265,17 @@ Document::Document(const Parser& parser, const ImportSettings& settings) :
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-Document::~Document() {
-    for(ObjectMap::value_type& v : objects) {
-        delete v.second;
+Document::~Document()
+{
+	// The document does not own the memory for the following objects, but we need to call their d'tor
+	// so they can properly free memory like string members:
+	
+    for (ObjectMap::value_type &v : objects) {
+        delete_LazyObject(v.second);
     }
     }
 
 
-    for(ConnectionMap::value_type& v : src_connections) {
-        delete v.second;
+    for (ConnectionMap::value_type &v : src_connections) {
+        delete_Connection(v.second);
     }
     }
     // |dest_connections| contain the same Connection objects as the |src_connections|
     // |dest_connections| contain the same Connection objects as the |src_connections|
 }
 }
@@ -356,9 +360,11 @@ void Document::ReadObjects() {
         DOMError("no Objects dictionary found");
         DOMError("no Objects dictionary found");
     }
     }
 
 
+    StackAllocator &allocator = parser.GetAllocator();
+
     // add a dummy entry to represent the Model::RootNode object (id 0),
     // add a dummy entry to represent the Model::RootNode object (id 0),
     // which is only indirectly defined in the input file
     // which is only indirectly defined in the input file
-    objects[0] = new LazyObject(0L, *eobjects, *this);
+    objects[0] = new_LazyObject(0L, *eobjects, *this);
 
 
     const Scope& sobjects = *eobjects->Compound();
     const Scope& sobjects = *eobjects->Compound();
     for(const ElementMap::value_type& el : sobjects.Elements()) {
     for(const ElementMap::value_type& el : sobjects.Elements()) {
@@ -381,11 +387,13 @@ void Document::ReadObjects() {
             DOMError("encountered object with implicitly defined id 0",el.second);
             DOMError("encountered object with implicitly defined id 0",el.second);
         }
         }
 
 
-        if(objects.find(id) != objects.end()) {
+        const auto foundObject = objects.find(id);
+        if(foundObject != objects.end()) {
             DOMWarning("encountered duplicate object id, ignoring first occurrence",el.second);
             DOMWarning("encountered duplicate object id, ignoring first occurrence",el.second);
+            delete foundObject->second;
         }
         }
 
 
-        objects[id] = new LazyObject(id, *el.second, *this);
+        objects[id] = new_LazyObject(id, *el.second, *this);
 
 
         // grab all animation stacks upfront since there is no listing of them
         // grab all animation stacks upfront since there is no listing of them
         if(!strcmp(el.first.c_str(),"AnimationStack")) {
         if(!strcmp(el.first.c_str(),"AnimationStack")) {
@@ -452,8 +460,10 @@ void Document::ReadPropertyTemplates() {
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void Document::ReadConnections() {
-    const Scope& sc = parser.GetRootScope();
+void Document::ReadConnections()
+{
+    StackAllocator &allocator = parser.GetAllocator();
+    const Scope &sc = parser.GetRootScope();
     // read property templates from "Definitions" section
     // read property templates from "Definitions" section
     const Element* const econns = sc["Connections"];
     const Element* const econns = sc["Connections"];
     if(!econns || !econns->Compound()) {
     if(!econns || !econns->Compound()) {
@@ -492,7 +502,7 @@ void Document::ReadConnections() {
         }
         }
 
 
         // add new connection
         // add new connection
-        const Connection* const c = new Connection(insertionOrder++,src,dest,prop,*this);
+        const Connection* const c = new_Connection(insertionOrder++,src,dest,prop,*this);
         src_connections.insert(ConnectionMap::value_type(src,c));
         src_connections.insert(ConnectionMap::value_type(src,c));
         dest_connections.insert(ConnectionMap::value_type(dest,c));
         dest_connections.insert(ConnectionMap::value_type(dest,c));
     }
     }

+ 17 - 7
code/AssetLib/FBX/FBXDocument.h

@@ -46,6 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define INCLUDED_AI_FBX_DOCUMENT_H
 #define INCLUDED_AI_FBX_DOCUMENT_H
 
 
 #include <numeric>
 #include <numeric>
+#include <unordered_set>
 #include <stdint.h>
 #include <stdint.h>
 #include <assimp/mesh.h>
 #include <assimp/mesh.h>
 #include "FBXProperties.h"
 #include "FBXProperties.h"
@@ -54,9 +55,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define _AI_CONCAT(a,b)  a ## b
 #define _AI_CONCAT(a,b)  a ## b
 #define  AI_CONCAT(a,b)  _AI_CONCAT(a,b)
 #define  AI_CONCAT(a,b)  _AI_CONCAT(a,b)
 
 
+
 namespace Assimp {
 namespace Assimp {
 namespace FBX {
 namespace FBX {
 
 
+// Use an 'illegal' default FOV value to detect if the FBX camera has set the FOV.
+static const float kFovUnknown = -1.0f;
+
+
 class Parser;
 class Parser;
 class Object;
 class Object;
 struct ImportSettings;
 struct ImportSettings;
@@ -80,6 +86,10 @@ class BlendShape;
 class Skin;
 class Skin;
 class Cluster;
 class Cluster;
 
 
+#define new_LazyObject new (allocator.Allocate(sizeof(LazyObject))) LazyObject
+#define new_Connection new (allocator.Allocate(sizeof(Connection))) Connection
+#define delete_LazyObject(_p) (_p)->~LazyObject()
+#define delete_Connection(_p) (_p)->~Connection()
 
 
 /** Represents a delay-parsed FBX objects. Many objects in the scene
 /** Represents a delay-parsed FBX objects. Many objects in the scene
  *  are not needed by assimp, so it makes no sense to parse them
  *  are not needed by assimp, so it makes no sense to parse them
@@ -242,7 +252,7 @@ public:
     fbx_simple_property(FilmAspectRatio, float, 1.0f)
     fbx_simple_property(FilmAspectRatio, float, 1.0f)
     fbx_simple_property(ApertureMode, int, 0)
     fbx_simple_property(ApertureMode, int, 0)
 
 
-    fbx_simple_property(FieldOfView, float, 1.0f)
+    fbx_simple_property(FieldOfView, float, kFovUnknown)
     fbx_simple_property(FocalLength, float, 1.0f)
     fbx_simple_property(FocalLength, float, 1.0f)
 };
 };
 
 
@@ -855,14 +865,14 @@ public:
         return fullWeights;
         return fullWeights;
     }
     }
 
 
-    const std::vector<const ShapeGeometry*>& GetShapeGeometries() const {
+    const std::unordered_set<const ShapeGeometry*>& GetShapeGeometries() const {
         return shapeGeometries;
         return shapeGeometries;
     }
     }
 
 
 private:
 private:
     float percent;
     float percent;
     WeightArray fullWeights;
     WeightArray fullWeights;
-    std::vector<const ShapeGeometry*> shapeGeometries;
+    std::unordered_set<const ShapeGeometry*> shapeGeometries;
 };
 };
 
 
 /** DOM class for BlendShape deformers */
 /** DOM class for BlendShape deformers */
@@ -872,12 +882,12 @@ public:
 
 
     virtual ~BlendShape();
     virtual ~BlendShape();
 
 
-    const std::vector<const BlendShapeChannel*>& BlendShapeChannels() const {
+    const std::unordered_set<const BlendShapeChannel*>& BlendShapeChannels() const {
         return blendShapeChannels;
         return blendShapeChannels;
     }
     }
 
 
 private:
 private:
-    std::vector<const BlendShapeChannel*> blendShapeChannels;
+    std::unordered_set<const BlendShapeChannel*> blendShapeChannels;
 };
 };
 
 
 /** DOM class for skin deformer clusters (aka sub-deformers) */
 /** DOM class for skin deformer clusters (aka sub-deformers) */
@@ -1072,7 +1082,7 @@ private:
 /** DOM root for a FBX file */
 /** DOM root for a FBX file */
 class Document {
 class Document {
 public:
 public:
-    Document(const Parser& parser, const ImportSettings& settings);
+    Document(Parser& parser, const ImportSettings& settings);
 
 
     ~Document();
     ~Document();
 
 
@@ -1156,7 +1166,7 @@ private:
     const ImportSettings& settings;
     const ImportSettings& settings;
 
 
     ObjectMap objects;
     ObjectMap objects;
-    const Parser& parser;
+    Parser& parser;
 
 
     PropertyTemplateMap templates;
     PropertyTemplateMap templates;
     ConnectionMap src_connections;
     ConnectionMap src_connections;

+ 11 - 9
code/AssetLib/FBX/FBXImporter.cpp

@@ -152,19 +152,19 @@ void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
 	// broad-phase tokenized pass in which we identify the core
 	// broad-phase tokenized pass in which we identify the core
 	// syntax elements of FBX (brackets, commas, key:value mappings)
 	// syntax elements of FBX (brackets, commas, key:value mappings)
 	TokenList tokens;
 	TokenList tokens;
-	try {
-
+    Assimp::StackAllocator tempAllocator;
+    try {
 		bool is_binary = false;
 		bool is_binary = false;
 		if (!strncmp(begin, "Kaydara FBX Binary", 18)) {
 		if (!strncmp(begin, "Kaydara FBX Binary", 18)) {
 			is_binary = true;
 			is_binary = true;
-			TokenizeBinary(tokens, begin, contents.size());
+            TokenizeBinary(tokens, begin, contents.size(), tempAllocator);
 		} else {
 		} else {
-			Tokenize(tokens, begin);
+            Tokenize(tokens, begin, tempAllocator);
 		}
 		}
 
 
 		// use this information to construct a very rudimentary
 		// use this information to construct a very rudimentary
 		// parse-tree representing the FBX scope structure
 		// parse-tree representing the FBX scope structure
-		Parser parser(tokens, is_binary);
+        Parser parser(tokens, tempAllocator, is_binary);
 
 
 		// take the raw parse-tree and convert it to a FBX DOM
 		// take the raw parse-tree and convert it to a FBX DOM
 		Document doc(parser, mSettings);
 		Document doc(parser, mSettings);
@@ -183,10 +183,12 @@ void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
 		// assimp universal format (M)
 		// assimp universal format (M)
 		SetFileScale(size_relative_to_cm * 0.01f);
 		SetFileScale(size_relative_to_cm * 0.01f);
 
 
-		std::for_each(tokens.begin(), tokens.end(), Util::delete_fun<Token>());
-	} catch (std::exception &) {
-		std::for_each(tokens.begin(), tokens.end(), Util::delete_fun<Token>());
-		throw;
+		// This collection does not own the memory for the tokens, but we need to call their d'tor
+        std::for_each(tokens.begin(), tokens.end(), Util::destructor_fun<Token>());
+
+    } catch (std::exception &) {
+        std::for_each(tokens.begin(), tokens.end(), Util::destructor_fun<Token>());
+        throw;
 	}
 	}
 }
 }
 
 

+ 0 - 14
code/AssetLib/FBX/FBXMaterial.cpp

@@ -138,20 +138,6 @@ Material::Material(uint64_t id, const Element& element, const Document& doc, con
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 Material::~Material() = default;
 Material::~Material() = default;
 
 
-    aiVector2D uvTrans;
-    aiVector2D uvScaling;
-    ai_real    uvRotation;
-
-    std::string type;
-    std::string relativeFileName;
-    std::string fileName;
-    std::string alphaSource;
-    std::shared_ptr<const PropertyTable> props;
-
-    unsigned int crop[4]{};
-
-    const Video* media;
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 Texture::Texture(uint64_t id, const Element& element, const Document& doc, const std::string& name) :
 Texture::Texture(uint64_t id, const Element& element, const Document& doc, const std::string& name) :
         Object(id,element,name),
         Object(id,element,name),

+ 5 - 2
code/AssetLib/FBX/FBXMeshGeometry.cpp

@@ -69,13 +69,16 @@ Geometry::Geometry(uint64_t id, const Element& element, const std::string& name,
         }
         }
         const BlendShape* const bsp = ProcessSimpleConnection<BlendShape>(*con, false, "BlendShape -> Geometry", element);
         const BlendShape* const bsp = ProcessSimpleConnection<BlendShape>(*con, false, "BlendShape -> Geometry", element);
         if (bsp) {
         if (bsp) {
-            blendShapes.push_back(bsp);
+            auto pr = blendShapes.insert(bsp);
+            if (!pr.second) {
+                FBXImporter::LogWarn("there is the same blendShape id ", bsp->ID());
+            }
         }
         }
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-const std::vector<const BlendShape*>& Geometry::GetBlendShapes() const {
+const std::unordered_set<const BlendShape*>& Geometry::GetBlendShapes() const {
     return blendShapes;
     return blendShapes;
 }
 }
 
 

+ 5 - 4
code/AssetLib/FBX/FBXMeshGeometry.h

@@ -62,7 +62,7 @@ public:
     /// @param name     The name instance
     /// @param name     The name instance
     /// @param doc      The document instance
     /// @param doc      The document instance
     Geometry( uint64_t id, const Element& element, const std::string& name, const Document& doc );
     Geometry( uint64_t id, const Element& element, const std::string& name, const Document& doc );
-    
+
     /// @brief The class destructor, default.
     /// @brief The class destructor, default.
     virtual ~Geometry() = default;
     virtual ~Geometry() = default;
 
 
@@ -72,11 +72,12 @@ public:
 
 
     /// @brief Get the BlendShape attached to this geometry or nullptr
     /// @brief Get the BlendShape attached to this geometry or nullptr
     /// @return The blendshape arrays.
     /// @return The blendshape arrays.
-    const std::vector<const BlendShape*>& GetBlendShapes() const;
+    const std::unordered_set<const BlendShape*>& GetBlendShapes() const;
 
 
 private:
 private:
     const Skin* skin;
     const Skin* skin;
-    std::vector<const BlendShape*> blendShapes;
+    std::unordered_set<const BlendShape*> blendShapes;
+
 };
 };
 
 
 typedef std::vector<int> MatIndexArray;
 typedef std::vector<int> MatIndexArray;
@@ -112,7 +113,7 @@ public:
     /// @return The binomal vector.
     /// @return The binomal vector.
     const std::vector<aiVector3D>& GetBinormals() const;
     const std::vector<aiVector3D>& GetBinormals() const;
 
 
-    /// @brief Return list of faces - each entry denotes a face and specifies how many vertices it has. 
+    /// @brief Return list of faces - each entry denotes a face and specifies how many vertices it has.
     ///        Vertices are taken from the vertex data arrays in sequential order.
     ///        Vertices are taken from the vertex data arrays in sequential order.
     /// @return The face indices vector.
     /// @return The face indices vector.
     const std::vector<unsigned int>& GetFaceIndexCounts() const;
     const std::vector<unsigned int>& GetFaceIndexCounts() const;

+ 36 - 13
code/AssetLib/FBX/FBXParser.cpp

@@ -88,6 +88,7 @@ namespace {
 
 
 
 
     // ------------------------------------------------------------------------------------------------
     // ------------------------------------------------------------------------------------------------
+    AI_WONT_RETURN void ParseError(const std::string& message, TokenPtr token) AI_WONT_RETURN_SUFFIX;
     void ParseError(const std::string& message, TokenPtr token)
     void ParseError(const std::string& message, TokenPtr token)
     {
     {
         if(token) {
         if(token) {
@@ -115,8 +116,11 @@ namespace Assimp {
 namespace FBX {
 namespace FBX {
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-Element::Element(const Token& key_token, Parser& parser) : key_token(key_token) {
+Element::Element(const Token& key_token, Parser& parser) :
+    key_token(key_token), compound(nullptr)
+{
     TokenPtr n = nullptr;
     TokenPtr n = nullptr;
+    StackAllocator &allocator = parser.GetAllocator();
     do {
     do {
         n = parser.AdvanceToNextToken();
         n = parser.AdvanceToNextToken();
         if(!n) {
         if(!n) {
@@ -145,7 +149,7 @@ Element::Element(const Token& key_token, Parser& parser) : key_token(key_token)
         }
         }
 
 
         if (n->Type() == TokenType_OPEN_BRACKET) {
         if (n->Type() == TokenType_OPEN_BRACKET) {
-            compound.reset(new Scope(parser));
+            compound = new_Scope(parser);
 
 
             // current token should be a TOK_CLOSE_BRACKET
             // current token should be a TOK_CLOSE_BRACKET
             n = parser.CurrentToken();
             n = parser.CurrentToken();
@@ -163,6 +167,15 @@ Element::Element(const Token& key_token, Parser& parser) : key_token(key_token)
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
+Element::~Element()
+{
+    if (compound) {
+        delete_Scope(compound);
+    }
+
+     // no need to delete tokens, they are owned by the parser
+}
+
 Scope::Scope(Parser& parser,bool topLevel)
 Scope::Scope(Parser& parser,bool topLevel)
 {
 {
     if(!topLevel) {
     if(!topLevel) {
@@ -172,6 +185,7 @@ Scope::Scope(Parser& parser,bool topLevel)
         }
         }
     }
     }
 
 
+    StackAllocator &allocator = parser.GetAllocator();
     TokenPtr n = parser.AdvanceToNextToken();
     TokenPtr n = parser.AdvanceToNextToken();
     if (n == nullptr) {
     if (n == nullptr) {
         ParseError("unexpected end of file");
         ParseError("unexpected end of file");
@@ -188,36 +202,45 @@ Scope::Scope(Parser& parser,bool topLevel)
             ParseError("unexpected content: empty string.");
             ParseError("unexpected content: empty string.");
         }
         }
 
 
-        elements.insert(ElementMap::value_type(str,new_Element(*n,parser)));
+        auto *element = new_Element(*n, parser);
 
 
         // Element() should stop at the next Key token (or right after a Close token)
         // Element() should stop at the next Key token (or right after a Close token)
         n = parser.CurrentToken();
         n = parser.CurrentToken();
         if (n == nullptr) {
         if (n == nullptr) {
             if (topLevel) {
             if (topLevel) {
+                elements.insert(ElementMap::value_type(str, element));
                 return;
                 return;
             }
             }
+            delete_Element(element);
             ParseError("unexpected end of file",parser.LastToken());
             ParseError("unexpected end of file",parser.LastToken());
+        } else {
+            elements.insert(ElementMap::value_type(str, element));
         }
         }
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-Scope::~Scope() {
-    for(ElementMap::value_type& v : elements) {
-        delete v.second;
+Scope::~Scope()
+{
+	// This collection does not own the memory for the elements, but we need to call their d'tor:
+
+    for (ElementMap::value_type &v : elements) {
+        delete_Element(v.second);
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-Parser::Parser (const TokenList& tokens, bool is_binary)
-: tokens(tokens)
-, last()
-, current()
-, cursor(tokens.begin())
-, is_binary(is_binary)
+Parser::Parser(const TokenList &tokens, StackAllocator &allocator, bool is_binary) :
+        tokens(tokens), allocator(allocator), last(), current(), cursor(tokens.begin()), is_binary(is_binary)
 {
 {
     ASSIMP_LOG_DEBUG("Parsing FBX tokens");
     ASSIMP_LOG_DEBUG("Parsing FBX tokens");
-    root.reset(new Scope(*this,true));
+    root = new_Scope(*this, true);
+}
+
+// ------------------------------------------------------------------------------------------------
+Parser::~Parser()
+{
+    delete_Scope(root);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------

+ 21 - 15
code/AssetLib/FBX/FBXParser.h

@@ -52,6 +52,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/LogAux.h>
 #include <assimp/LogAux.h>
 #include <assimp/fast_atof.h>
 #include <assimp/fast_atof.h>
 
 
+#include "Common/StackAllocator.h"
 #include "FBXCompileConfig.h"
 #include "FBXCompileConfig.h"
 #include "FBXTokenizer.h"
 #include "FBXTokenizer.h"
 
 
@@ -63,14 +64,14 @@ class Parser;
 class Element;
 class Element;
 
 
 // XXX should use C++11's unique_ptr - but assimp's need to keep working with 03
 // XXX should use C++11's unique_ptr - but assimp's need to keep working with 03
-typedef std::vector< Scope* > ScopeList;
-typedef std::fbx_unordered_multimap< std::string, Element* > ElementMap;
-
-typedef std::pair<ElementMap::const_iterator,ElementMap::const_iterator> ElementCollection;
-
-#   define new_Scope new Scope
-#   define new_Element new Element
+using ScopeList = std::vector<Scope*>;
+using ElementMap = std::fbx_unordered_multimap< std::string, Element*>;
+using ElementCollection = std::pair<ElementMap::const_iterator,ElementMap::const_iterator>;
 
 
+#define new_Scope new (allocator.Allocate(sizeof(Scope))) Scope
+#define new_Element new (allocator.Allocate(sizeof(Element))) Element
+#define delete_Scope(_p) (_p)->~Scope()
+#define delete_Element(_p) (_p)->~Element()
 
 
 /** FBX data entity that consists of a key:value tuple.
 /** FBX data entity that consists of a key:value tuple.
  *
  *
@@ -82,15 +83,16 @@ typedef std::pair<ElementMap::const_iterator,ElementMap::const_iterator> Element
  *  @endverbatim
  *  @endverbatim
  *
  *
  *  As can be seen in this sample, elements can contain nested #Scope
  *  As can be seen in this sample, elements can contain nested #Scope
- *  as their trailing member.  **/
+ *  as their trailing member.  
+**/
 class Element
 class Element
 {
 {
 public:
 public:
     Element(const Token& key_token, Parser& parser);
     Element(const Token& key_token, Parser& parser);
-    ~Element() = default;
+    ~Element();
 
 
     const Scope* Compound() const {
     const Scope* Compound() const {
-        return compound.get();
+        return compound;
     }
     }
 
 
     const Token& KeyToken() const {
     const Token& KeyToken() const {
@@ -104,7 +106,7 @@ public:
 private:
 private:
     const Token& key_token;
     const Token& key_token;
     TokenList tokens;
     TokenList tokens;
-    std::unique_ptr<Scope> compound;
+    Scope* compound;
 };
 };
 
 
 /** FBX data entity that consists of a 'scope', a collection
 /** FBX data entity that consists of a 'scope', a collection
@@ -159,8 +161,8 @@ class Parser
 public:
 public:
     /** Parse given a token list. Does not take ownership of the tokens -
     /** Parse given a token list. Does not take ownership of the tokens -
      *  the objects must persist during the entire parser lifetime */
      *  the objects must persist during the entire parser lifetime */
-    Parser (const TokenList& tokens,bool is_binary);
-    ~Parser() = default;
+    Parser(const TokenList &tokens, StackAllocator &allocator, bool is_binary);
+    ~Parser();
 
 
     const Scope& GetRootScope() const {
     const Scope& GetRootScope() const {
         return *root;
         return *root;
@@ -170,6 +172,10 @@ public:
         return is_binary;
         return is_binary;
     }
     }
 
 
+    StackAllocator &GetAllocator() {
+        return allocator;
+    }
+
 private:
 private:
     friend class Scope;
     friend class Scope;
     friend class Element;
     friend class Element;
@@ -180,10 +186,10 @@ private:
 
 
 private:
 private:
     const TokenList& tokens;
     const TokenList& tokens;
-
+    StackAllocator &allocator;
     TokenPtr last, current;
     TokenPtr last, current;
     TokenList::const_iterator cursor;
     TokenList::const_iterator cursor;
-    std::unique_ptr<Scope> root;
+    Scope *root;
 
 
     const bool is_binary;
     const bool is_binary;
 };
 };

+ 10 - 10
code/AssetLib/FBX/FBXTokenizer.cpp

@@ -94,7 +94,8 @@ AI_WONT_RETURN void TokenizeError(const std::string& message, unsigned int line,
 
 
 // process a potential data token up to 'cur', adding it to 'output_tokens'.
 // process a potential data token up to 'cur', adding it to 'output_tokens'.
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ProcessDataToken( TokenList& output_tokens, const char*& start, const char*& end,
+void ProcessDataToken(TokenList &output_tokens, StackAllocator &token_allocator,
+                      const char*& start, const char*& end,
                       unsigned int line,
                       unsigned int line,
                       unsigned int column,
                       unsigned int column,
                       TokenType type = TokenType_DATA,
                       TokenType type = TokenType_DATA,
@@ -131,8 +132,7 @@ void ProcessDataToken( TokenList& output_tokens, const char*& start, const char*
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void Tokenize(TokenList& output_tokens, const char* input)
-{
+void Tokenize(TokenList &output_tokens, const char *input, StackAllocator &token_allocator) {
 	ai_assert(input);
 	ai_assert(input);
 	ASSIMP_LOG_DEBUG("Tokenizing ASCII FBX file");
 	ASSIMP_LOG_DEBUG("Tokenizing ASCII FBX file");
 
 
@@ -164,7 +164,7 @@ void Tokenize(TokenList& output_tokens, const char* input)
                 in_double_quotes = false;
                 in_double_quotes = false;
                 token_end = cur;
                 token_end = cur;
 
 
-                ProcessDataToken(output_tokens,token_begin,token_end,line,column);
+                ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column);
                 pending_data_token = false;
                 pending_data_token = false;
             }
             }
             continue;
             continue;
@@ -181,30 +181,30 @@ void Tokenize(TokenList& output_tokens, const char* input)
             continue;
             continue;
 
 
         case ';':
         case ';':
-            ProcessDataToken(output_tokens,token_begin,token_end,line,column);
+            ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column);
             comment = true;
             comment = true;
             continue;
             continue;
 
 
         case '{':
         case '{':
-            ProcessDataToken(output_tokens,token_begin,token_end, line, column);
+            ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column);
             output_tokens.push_back(new_Token(cur,cur+1,TokenType_OPEN_BRACKET,line,column));
             output_tokens.push_back(new_Token(cur,cur+1,TokenType_OPEN_BRACKET,line,column));
             continue;
             continue;
 
 
         case '}':
         case '}':
-            ProcessDataToken(output_tokens,token_begin,token_end,line,column);
+            ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column);
             output_tokens.push_back(new_Token(cur,cur+1,TokenType_CLOSE_BRACKET,line,column));
             output_tokens.push_back(new_Token(cur,cur+1,TokenType_CLOSE_BRACKET,line,column));
             continue;
             continue;
 
 
         case ',':
         case ',':
             if (pending_data_token) {
             if (pending_data_token) {
-                ProcessDataToken(output_tokens,token_begin,token_end,line,column,TokenType_DATA,true);
+                ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column, TokenType_DATA, true);
             }
             }
             output_tokens.push_back(new_Token(cur,cur+1,TokenType_COMMA,line,column));
             output_tokens.push_back(new_Token(cur,cur+1,TokenType_COMMA,line,column));
             continue;
             continue;
 
 
         case ':':
         case ':':
             if (pending_data_token) {
             if (pending_data_token) {
-                ProcessDataToken(output_tokens,token_begin,token_end,line,column,TokenType_KEY,true);
+                ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column, TokenType_KEY, true);
             }
             }
             else {
             else {
                 TokenizeError("unexpected colon", line, column);
                 TokenizeError("unexpected colon", line, column);
@@ -226,7 +226,7 @@ void Tokenize(TokenList& output_tokens, const char* input)
                     }
                     }
                 }
                 }
 
 
-                ProcessDataToken(output_tokens,token_begin,token_end,line,column,type);
+                ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column, type);
             }
             }
 
 
             pending_data_token = false;
             pending_data_token = false;

+ 5 - 3
code/AssetLib/FBX/FBXTokenizer.h

@@ -47,6 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define INCLUDED_AI_FBX_TOKENIZER_H
 #define INCLUDED_AI_FBX_TOKENIZER_H
 
 
 #include "FBXCompileConfig.h"
 #include "FBXCompileConfig.h"
+#include "Common/StackAllocator.h"
 #include <assimp/ai_assert.h>
 #include <assimp/ai_assert.h>
 #include <assimp/defs.h>
 #include <assimp/defs.h>
 #include <vector>
 #include <vector>
@@ -157,7 +158,8 @@ private:
 typedef const Token* TokenPtr;
 typedef const Token* TokenPtr;
 typedef std::vector< TokenPtr > TokenList;
 typedef std::vector< TokenPtr > TokenList;
 
 
-#define new_Token new Token
+#define new_Token new (token_allocator.Allocate(sizeof(Token))) Token
+#define delete_Token(_p) (_p)->~Token()
 
 
 
 
 /** Main FBX tokenizer function. Transform input buffer into a list of preprocessed tokens.
 /** Main FBX tokenizer function. Transform input buffer into a list of preprocessed tokens.
@@ -167,7 +169,7 @@ typedef std::vector< TokenPtr > TokenList;
  * @param output_tokens Receives a list of all tokens in the input data.
  * @param output_tokens Receives a list of all tokens in the input data.
  * @param input_buffer Textual input buffer to be processed, 0-terminated.
  * @param input_buffer Textual input buffer to be processed, 0-terminated.
  * @throw DeadlyImportError if something goes wrong */
  * @throw DeadlyImportError if something goes wrong */
-void Tokenize(TokenList& output_tokens, const char* input);
+void Tokenize(TokenList &output_tokens, const char *input, StackAllocator &tokenAllocator);
 
 
 
 
 /** Tokenizer function for binary FBX files.
 /** Tokenizer function for binary FBX files.
@@ -178,7 +180,7 @@ void Tokenize(TokenList& output_tokens, const char* input);
  * @param input_buffer Binary input buffer to be processed.
  * @param input_buffer Binary input buffer to be processed.
  * @param length Length of input buffer, in bytes. There is no 0-terminal.
  * @param length Length of input buffer, in bytes. There is no 0-terminal.
  * @throw DeadlyImportError if something goes wrong */
  * @throw DeadlyImportError if something goes wrong */
-void TokenizeBinary(TokenList& output_tokens, const char* input, size_t length);
+void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length, StackAllocator &tokenAllocator);
 
 
 
 
 } // ! FBX
 } // ! FBX

+ 11 - 0
code/AssetLib/FBX/FBXUtil.h

@@ -66,6 +66,17 @@ struct delete_fun
     }
     }
 };
 };
 
 
+/** helper for std::for_each to call the destructor on all items in a container without freeing their heap*/
+template <typename T>
+struct destructor_fun {
+    void operator()(const volatile T* del) {
+        if (del) {
+            del->~T();
+        }
+    }
+};
+
+
 /** Get a string representation for a #TokenType. */
 /** Get a string representation for a #TokenType. */
 const char* TokenTypeString(TokenType t);
 const char* TokenTypeString(TokenType t);
 
 

+ 6 - 10
code/AssetLib/HMP/HMPLoader.cpp

@@ -115,7 +115,9 @@ void HMPImporter::InternReadFile(const std::string &pFile,
         throw DeadlyImportError("HMP File is too small.");
         throw DeadlyImportError("HMP File is too small.");
 
 
     // Allocate storage and copy the contents of the file to a memory buffer
     // Allocate storage and copy the contents of the file to a memory buffer
-    mBuffer = new uint8_t[fileSize];
+    auto deleter=[this](uint8_t* ptr){ delete[] ptr; mBuffer = nullptr; };
+    std::unique_ptr<uint8_t[], decltype(deleter)> buffer(new uint8_t[fileSize], deleter);
+    mBuffer = buffer.get();
     file->Read((void *)mBuffer, 1, fileSize);
     file->Read((void *)mBuffer, 1, fileSize);
     iFileSize = (unsigned int)fileSize;
     iFileSize = (unsigned int)fileSize;
 
 
@@ -143,9 +145,6 @@ void HMPImporter::InternReadFile(const std::string &pFile,
         // Print the magic word to the logger
         // Print the magic word to the logger
         std::string szBuffer = ai_str_toprintable((const char *)&iMagic, sizeof(iMagic));
         std::string szBuffer = ai_str_toprintable((const char *)&iMagic, sizeof(iMagic));
 
 
-        delete[] mBuffer;
-        mBuffer = nullptr;
-
         // We're definitely unable to load this file
         // We're definitely unable to load this file
         throw DeadlyImportError("Unknown HMP subformat ", pFile,
         throw DeadlyImportError("Unknown HMP subformat ", pFile,
                                 ". Magic word (", szBuffer, ") is not known");
                                 ". Magic word (", szBuffer, ") is not known");
@@ -153,9 +152,6 @@ void HMPImporter::InternReadFile(const std::string &pFile,
 
 
     // Set the AI_SCENE_FLAGS_TERRAIN bit
     // Set the AI_SCENE_FLAGS_TERRAIN bit
     pScene->mFlags |= AI_SCENE_FLAGS_TERRAIN;
     pScene->mFlags |= AI_SCENE_FLAGS_TERRAIN;
-
-    delete[] mBuffer;
-    mBuffer = nullptr;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -445,11 +441,11 @@ void HMPImporter::ReadFirstSkin(unsigned int iNumSkins, const unsigned char *szC
     szCursor += sizeof(uint32_t);
     szCursor += sizeof(uint32_t);
 
 
     // allocate an output material
     // allocate an output material
-    aiMaterial *pcMat = new aiMaterial();
+    std::unique_ptr<aiMaterial> pcMat(new aiMaterial());
 
 
     // read the skin, this works exactly as for MDL7
     // read the skin, this works exactly as for MDL7
     ParseSkinLump_3DGS_MDL7(szCursor, &szCursor,
     ParseSkinLump_3DGS_MDL7(szCursor, &szCursor,
-            pcMat, iType, iWidth, iHeight);
+            pcMat.get(), iType, iWidth, iHeight);
 
 
     // now we need to skip any other skins ...
     // now we need to skip any other skins ...
     for (unsigned int i = 1; i < iNumSkins; ++i) {
     for (unsigned int i = 1; i < iNumSkins; ++i) {
@@ -468,7 +464,7 @@ void HMPImporter::ReadFirstSkin(unsigned int iNumSkins, const unsigned char *szC
     // setup the material ...
     // setup the material ...
     pScene->mNumMaterials = 1;
     pScene->mNumMaterials = 1;
     pScene->mMaterials = new aiMaterial *[1];
     pScene->mMaterials = new aiMaterial *[1];
-    pScene->mMaterials[0] = pcMat;
+    pScene->mMaterials[0] = pcMat.release();
 
 
     *szCursorOut = szCursor;
     *szCursorOut = szCursor;
 }
 }

+ 1 - 1
code/AssetLib/HMP/HMPLoader.h

@@ -86,7 +86,7 @@ protected:
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Import a HMP4 file
     /** Import a HMP4 file
     */
     */
-    void InternReadFile_HMP4();
+    AI_WONT_RETURN void InternReadFile_HMP4() AI_WONT_RETURN_SUFFIX;
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Import a HMP5 file
     /** Import a HMP5 file

+ 23 - 15
code/AssetLib/IFC/IFCBoolean.cpp

@@ -38,9 +38,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 */
 */
 
 
-/** @file  IFCBoolean.cpp
- *  @brief Implements a subset of Ifc boolean operations
- */
+/// @file  IFCBoolean.cpp
+/// @brief Implements a subset of Ifc boolean operations
 
 
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 
 
@@ -48,7 +47,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "Common/PolyTools.h"
 #include "Common/PolyTools.h"
 #include "PostProcessing/ProcessHelper.h"
 #include "PostProcessing/ProcessHelper.h"
 
 
-
 #include <iterator>
 #include <iterator>
 #include <tuple>
 #include <tuple>
 #include <utility>
 #include <utility>
@@ -67,8 +65,9 @@ bool IntersectSegmentPlane(const IfcVector3 &p, const IfcVector3 &n, const IfcVe
 
 
     // if segment ends on plane, do not report a hit. We stay on that side until a following segment starting at this
     // if segment ends on plane, do not report a hit. We stay on that side until a following segment starting at this
     // point leaves the plane through the other side
     // point leaves the plane through the other side
-    if (std::abs(dotOne + dotTwo) < ai_epsilon)
+    if (std::abs(dotOne + dotTwo) < ai_epsilon) {
         return false;
         return false;
+    }
 
 
     // if segment starts on the plane, report a hit only if the end lies on the *other* side
     // if segment starts on the plane, report a hit only if the end lies on the *other* side
     if (std::abs(dotTwo) < ai_epsilon) {
     if (std::abs(dotTwo) < ai_epsilon) {
@@ -82,13 +81,15 @@ bool IntersectSegmentPlane(const IfcVector3 &p, const IfcVector3 &n, const IfcVe
 
 
     // ignore if segment is parallel to plane and far away from it on either side
     // ignore if segment is parallel to plane and far away from it on either side
     // Warning: if there's a few thousand of such segments which slowly accumulate beyond the epsilon, no hit would be registered
     // Warning: if there's a few thousand of such segments which slowly accumulate beyond the epsilon, no hit would be registered
-    if (std::abs(dotOne) < ai_epsilon)
+    if (std::abs(dotOne) < ai_epsilon) {
         return false;
         return false;
+    }
 
 
     // t must be in [0..1] if the intersection point is within the given segment
     // t must be in [0..1] if the intersection point is within the given segment
     const IfcFloat t = dotTwo / dotOne;
     const IfcFloat t = dotTwo / dotOne;
-    if (t > 1.0 || t < 0.0)
+    if (t > 1.0 || t < 0.0) {
         return false;
         return false;
+    }
 
 
     out = e0 + t * seg;
     out = e0 + t * seg;
     return true;
     return true;
@@ -110,11 +111,13 @@ void FilterPolygon(std::vector<IfcVector3> &resultpoly) {
     FuzzyVectorCompare fz(epsilon);
     FuzzyVectorCompare fz(epsilon);
     std::vector<IfcVector3>::iterator e = std::unique(resultpoly.begin(), resultpoly.end(), fz);
     std::vector<IfcVector3>::iterator e = std::unique(resultpoly.begin(), resultpoly.end(), fz);
 
 
-    if (e != resultpoly.end())
+    if (e != resultpoly.end()) {
         resultpoly.erase(e, resultpoly.end());
         resultpoly.erase(e, resultpoly.end());
+    }
 
 
-    if (!resultpoly.empty() && fz(resultpoly.front(), resultpoly.back()))
+    if (!resultpoly.empty() && fz(resultpoly.front(), resultpoly.back())) {
         resultpoly.pop_back();
         resultpoly.pop_back();
+    }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -291,8 +294,9 @@ bool IntersectsBoundaryProfile(const IfcVector3 &e0, const IfcVector3 &e1, const
         }
         }
 
 
         // Line segment ends at boundary -> ignore any hit, it will be handled by possibly following segments
         // Line segment ends at boundary -> ignore any hit, it will be handled by possibly following segments
-        if (endsAtSegment && !halfOpen)
+        if (endsAtSegment && !halfOpen) {
             continue;
             continue;
+        }
 
 
         // Line segment starts at boundary -> generate a hit only if following that line would change the INSIDE/OUTSIDE
         // Line segment starts at boundary -> generate a hit only if following that line would change the INSIDE/OUTSIDE
         // state. This should catch the case where a connected set of segments has a point directly on the boundary,
         // state. This should catch the case where a connected set of segments has a point directly on the boundary,
@@ -301,15 +305,17 @@ bool IntersectsBoundaryProfile(const IfcVector3 &e0, const IfcVector3 &e1, const
         if (startsAtSegment) {
         if (startsAtSegment) {
             IfcVector3 inside_dir = IfcVector3(b.y, -b.x, 0.0) * windingOrder;
             IfcVector3 inside_dir = IfcVector3(b.y, -b.x, 0.0) * windingOrder;
             bool isGoingInside = (inside_dir * e) > 0.0;
             bool isGoingInside = (inside_dir * e) > 0.0;
-            if (isGoingInside == isStartAssumedInside)
+            if (isGoingInside == isStartAssumedInside) {
                 continue;
                 continue;
+            }
 
 
             // only insert the point into the list if it is sufficiently far away from the previous intersection point.
             // only insert the point into the list if it is sufficiently far away from the previous intersection point.
             // This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
             // This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
             if (!intersect_results.empty() && intersect_results.back().first == i - 1) {
             if (!intersect_results.empty() && intersect_results.back().first == i - 1) {
                 const IfcVector3 diff = intersect_results.back().second - e0;
                 const IfcVector3 diff = intersect_results.back().second - e0;
-                if (IfcVector2(diff.x, diff.y).SquareLength() < 1e-10)
+                if (IfcVector2(diff.x, diff.y).SquareLength() < 1e-10) {
                     continue;
                     continue;
+                }
             }
             }
             intersect_results.emplace_back(i, e0);
             intersect_results.emplace_back(i, e0);
             continue;
             continue;
@@ -322,8 +328,9 @@ bool IntersectsBoundaryProfile(const IfcVector3 &e0, const IfcVector3 &e1, const
             // This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
             // This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
             if (!intersect_results.empty() && intersect_results.back().first == i - 1) {
             if (!intersect_results.empty() && intersect_results.back().first == i - 1) {
                 const IfcVector3 diff = intersect_results.back().second - p;
                 const IfcVector3 diff = intersect_results.back().second - p;
-                if (IfcVector2(diff.x, diff.y).SquareLength() < 1e-10)
+                if (IfcVector2(diff.x, diff.y).SquareLength() < 1e-10) {
                     continue;
                     continue;
+                }
             }
             }
             intersect_results.emplace_back(i, p);
             intersect_results.emplace_back(i, p);
         }
         }
@@ -662,7 +669,8 @@ void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const Schema_2x3::IfcPoly
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedAreaSolid *as, TempMesh &result,
+void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedAreaSolid *as, 
+        TempMesh &result,
         const TempMesh &first_operand,
         const TempMesh &first_operand,
         ConversionData &conv) {
         ConversionData &conv) {
     ai_assert(as != nullptr);
     ai_assert(as != nullptr);
@@ -763,4 +771,4 @@ void ProcessBoolean(const Schema_2x3::IfcBooleanResult &boolean, TempMesh &resul
 } // namespace IFC
 } // namespace IFC
 } // namespace Assimp
 } // namespace Assimp
 
 
-#endif
+#endif // ASSIMP_BUILD_NO_IFC_IMPORTER

+ 31 - 33
code/AssetLib/IFC/IFCCurve.cpp

@@ -39,15 +39,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 */
 */
 
 
-/** @file  IFCProfile.cpp
- *  @brief Read profile and curves entities from IFC files
- */
+/// @file  IFCProfile.cpp
+/// @brief Read profile and curves entities from IFC files
 
 
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 #include "IFCUtil.h"
 #include "IFCUtil.h"
 
 
 namespace Assimp {
 namespace Assimp {
 namespace IFC {
 namespace IFC {
+
 namespace {
 namespace {
 
 
 // --------------------------------------------------------------------------------
 // --------------------------------------------------------------------------------
@@ -56,8 +56,7 @@ namespace {
 class Conic : public Curve {
 class Conic : public Curve {
 public:
 public:
     // --------------------------------------------------
     // --------------------------------------------------
-    Conic(const Schema_2x3::IfcConic& entity, ConversionData& conv)
-    : Curve(entity,conv) {
+    Conic(const Schema_2x3::IfcConic& entity, ConversionData& conv) : Curve(entity,conv) {
         IfcMatrix4 trafo;
         IfcMatrix4 trafo;
         ConvertAxisPlacement(trafo,*entity.Position,conv);
         ConvertAxisPlacement(trafo,*entity.Position,conv);
 
 
@@ -69,12 +68,12 @@ public:
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    bool IsClosed() const {
+    bool IsClosed() const override {
         return true;
         return true;
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
+    size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override {
         ai_assert( InRange( a ) );
         ai_assert( InRange( a ) );
         ai_assert( InRange( b ) );
         ai_assert( InRange( b ) );
 
 
@@ -88,7 +87,7 @@ public:
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    ParamRange GetParametricRange() const {
+    ParamRange GetParametricRange() const override {
         return std::make_pair(static_cast<IfcFloat>( 0. ), static_cast<IfcFloat>( AI_MATH_TWO_PI / conv.angle_scale ));
         return std::make_pair(static_cast<IfcFloat>( 0. ), static_cast<IfcFloat>( AI_MATH_TWO_PI / conv.angle_scale ));
     }
     }
 
 
@@ -102,14 +101,13 @@ protected:
 class Circle : public Conic {
 class Circle : public Conic {
 public:
 public:
     // --------------------------------------------------
     // --------------------------------------------------
-    Circle(const Schema_2x3::IfcCircle& entity, ConversionData& conv)
-        : Conic(entity,conv)
-        , entity(entity)
-    {
-    }
-
+    Circle(const Schema_2x3::IfcCircle& entity, ConversionData& conv) : Conic(entity,conv) , entity(entity) {}
+    
+    // --------------------------------------------------
+    ~Circle() override = default;
+    
     // --------------------------------------------------
     // --------------------------------------------------
-    IfcVector3 Eval(IfcFloat u) const {
+    IfcVector3 Eval(IfcFloat u) const override {
         u = -conv.angle_scale * u;
         u = -conv.angle_scale * u;
         return location + static_cast<IfcFloat>(entity.Radius)*(static_cast<IfcFloat>(std::cos(u))*p[0] +
         return location + static_cast<IfcFloat>(entity.Radius)*(static_cast<IfcFloat>(std::cos(u))*p[0] +
             static_cast<IfcFloat>(std::sin(u))*p[1]);
             static_cast<IfcFloat>(std::sin(u))*p[1]);
@@ -132,7 +130,7 @@ public:
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    IfcVector3 Eval(IfcFloat u) const {
+    IfcVector3 Eval(IfcFloat u) const override {
         u = -conv.angle_scale * u;
         u = -conv.angle_scale * u;
         return location + static_cast<IfcFloat>(entity.SemiAxis1)*static_cast<IfcFloat>(std::cos(u))*p[0] +
         return location + static_cast<IfcFloat>(entity.SemiAxis1)*static_cast<IfcFloat>(std::cos(u))*p[0] +
             static_cast<IfcFloat>(entity.SemiAxis2)*static_cast<IfcFloat>(std::sin(u))*p[1];
             static_cast<IfcFloat>(entity.SemiAxis2)*static_cast<IfcFloat>(std::sin(u))*p[1];
@@ -155,17 +153,17 @@ public:
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    bool IsClosed() const {
+    bool IsClosed() const override {
         return false;
         return false;
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    IfcVector3 Eval(IfcFloat u) const {
+    IfcVector3 Eval(IfcFloat u) const override {
         return p + u*v;
         return p + u*v;
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
+    size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override {
         ai_assert( InRange( a ) );
         ai_assert( InRange( a ) );
         ai_assert( InRange( b ) );
         ai_assert( InRange( b ) );
         // two points are always sufficient for a line segment
         // two points are always sufficient for a line segment
@@ -174,7 +172,7 @@ public:
 
 
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const {
+    void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const override {
         ai_assert( InRange( a ) );
         ai_assert( InRange( a ) );
         ai_assert( InRange( b ) );
         ai_assert( InRange( b ) );
 
 
@@ -188,7 +186,7 @@ public:
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    ParamRange GetParametricRange() const {
+    ParamRange GetParametricRange() const override {
         const IfcFloat inf = std::numeric_limits<IfcFloat>::infinity();
         const IfcFloat inf = std::numeric_limits<IfcFloat>::infinity();
 
 
         return std::make_pair(-inf,+inf);
         return std::make_pair(-inf,+inf);
@@ -234,7 +232,7 @@ public:
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    IfcVector3 Eval(IfcFloat u) const {
+    IfcVector3 Eval(IfcFloat u) const override {
         if (curves.empty()) {
         if (curves.empty()) {
             return IfcVector3();
             return IfcVector3();
         }
         }
@@ -254,7 +252,7 @@ public:
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
+    size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override {
         ai_assert( InRange( a ) );
         ai_assert( InRange( a ) );
         ai_assert( InRange( b ) );
         ai_assert( InRange( b ) );
         size_t cnt = 0;
         size_t cnt = 0;
@@ -275,7 +273,7 @@ public:
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const {
+    void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const override {
         ai_assert( InRange( a ) );
         ai_assert( InRange( a ) );
         ai_assert( InRange( b ) );
         ai_assert( InRange( b ) );
 
 
@@ -293,7 +291,7 @@ public:
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    ParamRange GetParametricRange() const {
+    ParamRange GetParametricRange() const override {
         return std::make_pair(static_cast<IfcFloat>( 0. ),total);
         return std::make_pair(static_cast<IfcFloat>( 0. ),total);
     }
     }
 
 
@@ -373,27 +371,27 @@ public:
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    IfcVector3 Eval(IfcFloat p) const {
+    IfcVector3 Eval(IfcFloat p) const override {
         ai_assert(InRange(p));
         ai_assert(InRange(p));
         return base->Eval( TrimParam(p) );
         return base->Eval( TrimParam(p) );
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
+    size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override {
         ai_assert( InRange( a ) );
         ai_assert( InRange( a ) );
         ai_assert( InRange( b ) );
         ai_assert( InRange( b ) );
         return base->EstimateSampleCount(TrimParam(a),TrimParam(b));
         return base->EstimateSampleCount(TrimParam(a),TrimParam(b));
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    void SampleDiscrete(TempMesh& out,IfcFloat a,IfcFloat b) const {
+    void SampleDiscrete(TempMesh& out,IfcFloat a,IfcFloat b) const override {
         ai_assert(InRange(a));
         ai_assert(InRange(a));
         ai_assert(InRange(b));
         ai_assert(InRange(b));
         return base->SampleDiscrete(out,TrimParam(a),TrimParam(b));
         return base->SampleDiscrete(out,TrimParam(a),TrimParam(b));
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    ParamRange GetParametricRange() const {
+    ParamRange GetParametricRange() const override {
         return std::make_pair(static_cast<IfcFloat>( 0. ),maxval);
         return std::make_pair(static_cast<IfcFloat>( 0. ),maxval);
     }
     }
 
 
@@ -431,7 +429,7 @@ public:
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    IfcVector3 Eval(IfcFloat p) const {
+    IfcVector3 Eval(IfcFloat p) const override {
         ai_assert(InRange(p));
         ai_assert(InRange(p));
 
 
         const size_t b = static_cast<size_t>(std::floor(p));
         const size_t b = static_cast<size_t>(std::floor(p));
@@ -444,14 +442,14 @@ public:
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
+    size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override {
         ai_assert(InRange(a));
         ai_assert(InRange(a));
         ai_assert(InRange(b));
         ai_assert(InRange(b));
         return static_cast<size_t>( std::ceil(b) - std::floor(a) );
         return static_cast<size_t>( std::ceil(b) - std::floor(a) );
     }
     }
 
 
     // --------------------------------------------------
     // --------------------------------------------------
-    ParamRange GetParametricRange() const {
+    ParamRange GetParametricRange() const override {
         return std::make_pair(static_cast<IfcFloat>( 0. ),static_cast<IfcFloat>(points.size()-1));
         return std::make_pair(static_cast<IfcFloat>( 0. ),static_cast<IfcFloat>(points.size()-1));
     }
     }
 
 
@@ -516,7 +514,7 @@ size_t Curve::EstimateSampleCount(IfcFloat a, IfcFloat b) const {
     ai_assert( InRange( a ) );
     ai_assert( InRange( a ) );
     ai_assert( InRange( b ) );
     ai_assert( InRange( b ) );
 
 
-    // arbitrary default value, deriving classes should supply better suited values
+    // arbitrary default value, deriving classes should supply better-suited values
     return 16;
     return 16;
 }
 }
 
 

+ 61 - 105
code/AssetLib/IFC/IFCGeometry.cpp

@@ -38,24 +38,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 */
 */
 
 
-/** @file  IFCGeometry.cpp
- *  @brief Geometry conversion and synthesis for IFC
- */
-
-
+/// @file  IFCGeometry.cpp
+/// @brief Geometry conversion and synthesis for IFC
 
 
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 #include "IFCUtil.h"
 #include "IFCUtil.h"
 #include "Common/PolyTools.h"
 #include "Common/PolyTools.h"
 #include "PostProcessing/ProcessHelper.h"
 #include "PostProcessing/ProcessHelper.h"
-
-#ifdef ASSIMP_USE_HUNTER
-#  include <poly2tri/poly2tri.h>
-#  include <polyclipping/clipper.hpp>
-#else
-#  include "../contrib/poly2tri/poly2tri/poly2tri.h"
-#  include "../contrib/clipper/clipper.hpp"
-#endif
+#include "contrib/poly2tri/poly2tri/poly2tri.h"
+#include "contrib/clipper/clipper.hpp"
 
 
 #include <iterator>
 #include <iterator>
 #include <memory>
 #include <memory>
@@ -65,8 +56,7 @@ namespace Assimp {
 namespace IFC {
 namespace IFC {
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-bool ProcessPolyloop(const Schema_2x3::IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/)
-{
+bool ProcessPolyloop(const Schema_2x3::IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/) {
     size_t cnt = 0;
     size_t cnt = 0;
     for(const Schema_2x3::IfcCartesianPoint& c : loop.Polygon) {
     for(const Schema_2x3::IfcCartesianPoint& c : loop.Polygon) {
         IfcVector3 tmp;
         IfcVector3 tmp;
@@ -91,8 +81,7 @@ bool ProcessPolyloop(const Schema_2x3::IfcPolyLoop& loop, TempMesh& meshout, Con
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = (size_t)-1)
-{
+void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = (size_t)-1) {
     // handle all trivial cases
     // handle all trivial cases
     if(inmesh.mVertcnt.empty()) {
     if(inmesh.mVertcnt.empty()) {
         return;
         return;
@@ -127,8 +116,7 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m
     if (master_bounds != (size_t)-1) {
     if (master_bounds != (size_t)-1) {
         ai_assert(master_bounds < inmesh.mVertcnt.size());
         ai_assert(master_bounds < inmesh.mVertcnt.size());
         outer_polygon_it = begin + master_bounds;
         outer_polygon_it = begin + master_bounds;
-    }
-    else {
+    } else {
         for(iit = begin; iit != end; ++iit) {
         for(iit = begin; iit != end; ++iit) {
             // find the polygon with the largest area and take it as the outer bound.
             // find the polygon with the largest area and take it as the outer bound.
             IfcVector3& n = normals[std::distance(begin,iit)];
             IfcVector3& n = normals[std::distance(begin,iit)];
@@ -139,7 +127,8 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m
             }
             }
         }
         }
     }
     }
-	if (outer_polygon_it == end) {
+	
+    if (outer_polygon_it == end) {
 		return;
 		return;
 	}
 	}
 
 
@@ -205,40 +194,20 @@ void ProcessConnectedFaceSet(const Schema_2x3::IfcConnectedFaceSet& fset, TempMe
 
 
             if(const Schema_2x3::IfcPolyLoop* const polyloop = bound.Bound->ToPtr<Schema_2x3::IfcPolyLoop>()) {
             if(const Schema_2x3::IfcPolyLoop* const polyloop = bound.Bound->ToPtr<Schema_2x3::IfcPolyLoop>()) {
                 if(ProcessPolyloop(*polyloop, meshout,conv)) {
                 if(ProcessPolyloop(*polyloop, meshout,conv)) {
-
                     // The outer boundary is better determined by checking which
                     // The outer boundary is better determined by checking which
                     // polygon covers the largest area.
                     // polygon covers the largest area.
-
-                    //if(bound.ToPtr<IfcFaceOuterBound>()) {
-                    //  ob = cnt;
-                    //}
-                    //++cnt;
-
                 }
                 }
-            }
-            else {
+            } else {
                 IFCImporter::LogWarn("skipping unknown IfcFaceBound entity, type is ", bound.Bound->GetClassName());
                 IFCImporter::LogWarn("skipping unknown IfcFaceBound entity, type is ", bound.Bound->GetClassName());
                 continue;
                 continue;
             }
             }
-
-            // And this, even though it is sometimes TRUE and sometimes FALSE,
-            // does not really improve results.
-
-            /*if(!IsTrue(bound.Orientation)) {
-                size_t c = 0;
-                for(unsigned int& c : meshout.vertcnt) {
-                    std::reverse(result.verts.begin() + cnt,result.verts.begin() + cnt + c);
-                    cnt += c;
-                }
-            }*/
         }
         }
         ProcessPolygonBoundaries(result, meshout);
         ProcessPolygonBoundaries(result, meshout);
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv)
-{
+void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv) {
     TempMesh meshout;
     TempMesh meshout;
 
 
     // first read the profile description
     // first read the profile description
@@ -265,7 +234,8 @@ void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, Tem
         return;
         return;
     }
     }
 
 
-    const unsigned int cnt_segments = std::max(2u,static_cast<unsigned int>(conv.settings.cylindricalTessellation * std::fabs(max_angle)/AI_MATH_HALF_PI_F));
+    const unsigned int cnt_segments = 
+        std::max(2u,static_cast<unsigned int>(conv.settings.cylindricalTessellation * std::fabs(max_angle)/AI_MATH_HALF_PI_F));
     const IfcFloat delta = max_angle/cnt_segments;
     const IfcFloat delta = max_angle/cnt_segments;
 
 
     has_area = has_area && std::fabs(max_angle) < AI_MATH_TWO_PI_F*0.99;
     has_area = has_area && std::fabs(max_angle) < AI_MATH_TWO_PI_F*0.99;
@@ -324,8 +294,9 @@ void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, Tem
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ProcessSweptDiskSolid(const Schema_2x3::IfcSweptDiskSolid &solid, TempMesh& result, ConversionData& conv)
-{
+void ProcessSweptDiskSolid(const Schema_2x3::IfcSweptDiskSolid &solid, 
+        TempMesh& result, 
+        ConversionData& conv) {
     const Curve* const curve = Curve::Convert(*solid.Directrix, conv);
     const Curve* const curve = Curve::Convert(*solid.Directrix, conv);
     if(!curve) {
     if(!curve) {
         IFCImporter::LogError("failed to convert Directrix curve (IfcSweptDiskSolid)");
         IFCImporter::LogError("failed to convert Directrix curve (IfcSweptDiskSolid)");
@@ -460,8 +431,7 @@ void ProcessSweptDiskSolid(const Schema_2x3::IfcSweptDiskSolid &solid, TempMesh&
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut)
-{
+IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut) {
     const std::vector<IfcVector3>& out = curmesh.mVerts;
     const std::vector<IfcVector3>& out = curmesh.mVerts;
     IfcMatrix3 m;
     IfcMatrix3 m;
 
 
@@ -504,10 +474,6 @@ IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVect
     IfcVector3 r = (out[idx]-any_point);
     IfcVector3 r = (out[idx]-any_point);
     r.Normalize();
     r.Normalize();
 
 
-    //if(d) {
-    //  *d = -any_point * nor;
-    //}
-
     // Reconstruct orthonormal basis
     // Reconstruct orthonormal basis
     // XXX use Gram Schmidt for increased robustness
     // XXX use Gram Schmidt for increased robustness
     IfcVector3 u = r ^ nor;
     IfcVector3 u = r ^ nor;
@@ -531,8 +497,7 @@ IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVect
 const auto closeDistance = ai_epsilon;
 const auto closeDistance = ai_epsilon;
 
 
 bool areClose(Schema_2x3::IfcCartesianPoint pt1,Schema_2x3::IfcCartesianPoint pt2) {
 bool areClose(Schema_2x3::IfcCartesianPoint pt1,Schema_2x3::IfcCartesianPoint pt2) {
-    if(pt1.Coordinates.size() != pt2.Coordinates.size())
-    {
+    if(pt1.Coordinates.size() != pt2.Coordinates.size()) {
         IFCImporter::LogWarn("unable to compare differently-dimensioned points");
         IFCImporter::LogWarn("unable to compare differently-dimensioned points");
         return false;
         return false;
     }
     }
@@ -540,10 +505,10 @@ bool areClose(Schema_2x3::IfcCartesianPoint pt1,Schema_2x3::IfcCartesianPoint pt
     auto coord2 = pt2.Coordinates.begin();
     auto coord2 = pt2.Coordinates.begin();
     // we're just testing each dimension separately rather than doing euclidean distance, as we're
     // we're just testing each dimension separately rather than doing euclidean distance, as we're
     // looking for very close coordinates
     // looking for very close coordinates
-    for(; coord1 != pt1.Coordinates.end(); coord1++,coord2++)
-    {
-        if(std::fabs(*coord1 - *coord2) > closeDistance)
+    for(; coord1 != pt1.Coordinates.end(); coord1++,coord2++) {
+        if(std::fabs(*coord1 - *coord2) > closeDistance) {
             return false;
             return false;
+        }
     }
     }
     return true;
     return true;
 }
 }
@@ -553,6 +518,7 @@ bool areClose(IfcVector3 pt1,IfcVector3 pt2) {
         std::fabs(pt1.y - pt2.y) < closeDistance &&
         std::fabs(pt1.y - pt2.y) < closeDistance &&
         std::fabs(pt1.z - pt2.z) < closeDistance);
         std::fabs(pt1.z - pt2.z) < closeDistance);
 }
 }
+
 // Extrudes the given polygon along the direction, converts it into an opening or applies all openings as necessary.
 // Extrudes the given polygon along the direction, converts it into an opening or applies all openings as necessary.
 void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const TempMesh& curve,
 void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const TempMesh& curve,
     const IfcVector3& extrusionDir, TempMesh& result, ConversionData &conv, bool collect_openings)
     const IfcVector3& extrusionDir, TempMesh& result, ConversionData &conv, bool collect_openings)
@@ -590,8 +556,9 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
 
 
     // reverse profile polygon if it's winded in the wrong direction in relation to the extrusion direction
     // reverse profile polygon if it's winded in the wrong direction in relation to the extrusion direction
     IfcVector3 profileNormal = TempMesh::ComputePolygonNormal(in.data(), in.size());
     IfcVector3 profileNormal = TempMesh::ComputePolygonNormal(in.data(), in.size());
-    if( profileNormal * dir < 0.0 )
+    if( profileNormal * dir < 0.0 ) {
         std::reverse(in.begin(), in.end());
         std::reverse(in.begin(), in.end());
+    }
 
 
     std::vector<IfcVector3> nors;
     std::vector<IfcVector3> nors;
     const bool openings = !!conv.apply_openings && conv.apply_openings->size();
     const bool openings = !!conv.apply_openings && conv.apply_openings->size();
@@ -678,8 +645,7 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
             if(n > 0) {
             if(n > 0) {
                 for(size_t i = 0; i < in.size(); ++i)
                 for(size_t i = 0; i < in.size(); ++i)
                     out.push_back(in[i] + dir);
                     out.push_back(in[i] + dir);
-            }
-            else {
+            } else {
                 for(size_t i = in.size(); i--; )
                 for(size_t i = in.size(); i--; )
                     out.push_back(in[i]);
                     out.push_back(in[i]);
             }
             }
@@ -721,9 +687,10 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, TempMesh& result,
-    ConversionData& conv, bool collect_openings)
-{
+void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, 
+        TempMesh& result,
+        ConversionData& conv, 
+        bool collect_openings) {
     TempMesh meshout;
     TempMesh meshout;
 
 
     // First read the profile description.
     // First read the profile description.
@@ -761,24 +728,23 @@ void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, Tem
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ProcessSweptAreaSolid(const Schema_2x3::IfcSweptAreaSolid& swept, TempMesh& meshout,
-    ConversionData& conv)
-{
+void ProcessSweptAreaSolid(const Schema_2x3::IfcSweptAreaSolid& swept, 
+        TempMesh& meshout,
+        ConversionData& conv) {
     if(const Schema_2x3::IfcExtrudedAreaSolid* const solid = swept.ToPtr<Schema_2x3::IfcExtrudedAreaSolid>()) {
     if(const Schema_2x3::IfcExtrudedAreaSolid* const solid = swept.ToPtr<Schema_2x3::IfcExtrudedAreaSolid>()) {
         ProcessExtrudedAreaSolid(*solid,meshout,conv, !!conv.collect_openings);
         ProcessExtrudedAreaSolid(*solid,meshout,conv, !!conv.collect_openings);
-    }
-    else if(const Schema_2x3::IfcRevolvedAreaSolid* const rev = swept.ToPtr<Schema_2x3::IfcRevolvedAreaSolid>()) {
+    } else if(const Schema_2x3::IfcRevolvedAreaSolid* const rev = swept.ToPtr<Schema_2x3::IfcRevolvedAreaSolid>()) {
         ProcessRevolvedAreaSolid(*rev,meshout,conv);
         ProcessRevolvedAreaSolid(*rev,meshout,conv);
-    }
-    else {
+    } else {
         IFCImporter::LogWarn("skipping unknown IfcSweptAreaSolid entity, type is ", swept.GetClassName());
         IFCImporter::LogWarn("skipping unknown IfcSweptAreaSolid entity, type is ", swept.GetClassName());
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned int matid, std::set<unsigned int>& mesh_indices,
-    ConversionData& conv)
-{
+bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, 
+        unsigned int matid, 
+        std::set<unsigned int>& mesh_indices,
+        ConversionData& conv) {
     bool fix_orientation = false;
     bool fix_orientation = false;
     std::shared_ptr< TempMesh > meshtmp = std::make_shared<TempMesh>();
     std::shared_ptr< TempMesh > meshtmp = std::make_shared<TempMesh>();
     if(const Schema_2x3::IfcShellBasedSurfaceModel* shellmod = geo.ToPtr<Schema_2x3::IfcShellBasedSurfaceModel>()) {
     if(const Schema_2x3::IfcShellBasedSurfaceModel* shellmod = geo.ToPtr<Schema_2x3::IfcShellBasedSurfaceModel>()) {
@@ -788,41 +754,32 @@ bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned
                 const Schema_2x3::IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To<Schema_2x3::IfcConnectedFaceSet>();
                 const Schema_2x3::IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To<Schema_2x3::IfcConnectedFaceSet>();
 
 
                 ProcessConnectedFaceSet(fs, *meshtmp, conv);
                 ProcessConnectedFaceSet(fs, *meshtmp, conv);
-            }
-            catch(std::bad_cast&) {
+            } catch(std::bad_cast&) {
                 IFCImporter::LogWarn("unexpected type error, IfcShell ought to inherit from IfcConnectedFaceSet");
                 IFCImporter::LogWarn("unexpected type error, IfcShell ought to inherit from IfcConnectedFaceSet");
             }
             }
         }
         }
         fix_orientation = true;
         fix_orientation = true;
-    }
-    else  if(const Schema_2x3::IfcConnectedFaceSet* fset = geo.ToPtr<Schema_2x3::IfcConnectedFaceSet>()) {
+    } else  if(const Schema_2x3::IfcConnectedFaceSet* fset = geo.ToPtr<Schema_2x3::IfcConnectedFaceSet>()) {
         ProcessConnectedFaceSet(*fset, *meshtmp, conv);
         ProcessConnectedFaceSet(*fset, *meshtmp, conv);
         fix_orientation = true;
         fix_orientation = true;
-    }
-    else  if(const Schema_2x3::IfcSweptAreaSolid* swept = geo.ToPtr<Schema_2x3::IfcSweptAreaSolid>()) {
+    } else  if(const Schema_2x3::IfcSweptAreaSolid* swept = geo.ToPtr<Schema_2x3::IfcSweptAreaSolid>()) {
         ProcessSweptAreaSolid(*swept, *meshtmp, conv);
         ProcessSweptAreaSolid(*swept, *meshtmp, conv);
-    }
-    else  if(const Schema_2x3::IfcSweptDiskSolid* disk = geo.ToPtr<Schema_2x3::IfcSweptDiskSolid>()) {
+    } else  if(const Schema_2x3::IfcSweptDiskSolid* disk = geo.ToPtr<Schema_2x3::IfcSweptDiskSolid>()) {
         ProcessSweptDiskSolid(*disk, *meshtmp, conv);
         ProcessSweptDiskSolid(*disk, *meshtmp, conv);
-    }
-    else if(const Schema_2x3::IfcManifoldSolidBrep* brep = geo.ToPtr<Schema_2x3::IfcManifoldSolidBrep>()) {
+    } else if(const Schema_2x3::IfcManifoldSolidBrep* brep = geo.ToPtr<Schema_2x3::IfcManifoldSolidBrep>()) {
         ProcessConnectedFaceSet(brep->Outer, *meshtmp, conv);
         ProcessConnectedFaceSet(brep->Outer, *meshtmp, conv);
         fix_orientation = true;
         fix_orientation = true;
-    }
-    else if(const Schema_2x3::IfcFaceBasedSurfaceModel* surf = geo.ToPtr<Schema_2x3::IfcFaceBasedSurfaceModel>()) {
+    } else if(const Schema_2x3::IfcFaceBasedSurfaceModel* surf = geo.ToPtr<Schema_2x3::IfcFaceBasedSurfaceModel>()) {
         for(const Schema_2x3::IfcConnectedFaceSet& fc : surf->FbsmFaces) {
         for(const Schema_2x3::IfcConnectedFaceSet& fc : surf->FbsmFaces) {
             ProcessConnectedFaceSet(fc, *meshtmp, conv);
             ProcessConnectedFaceSet(fc, *meshtmp, conv);
         }
         }
         fix_orientation = true;
         fix_orientation = true;
-    }
-    else  if(const Schema_2x3::IfcBooleanResult* boolean = geo.ToPtr<Schema_2x3::IfcBooleanResult>()) {
+    } else  if(const Schema_2x3::IfcBooleanResult* boolean = geo.ToPtr<Schema_2x3::IfcBooleanResult>()) {
         ProcessBoolean(*boolean, *meshtmp, conv);
         ProcessBoolean(*boolean, *meshtmp, conv);
-    }
-    else if(geo.ToPtr<Schema_2x3::IfcBoundingBox>()) {
+    } else if(geo.ToPtr<Schema_2x3::IfcBoundingBox>()) {
         // silently skip over bounding boxes
         // silently skip over bounding boxes
         return false;
         return false;
-    }
-    else {
+    } else {
         std::stringstream toLog;
         std::stringstream toLog;
         toLog << "skipping unknown IfcGeometricRepresentationItem entity, type is " << geo.GetClassName() << " id is " << geo.GetID();
         toLog << "skipping unknown IfcGeometricRepresentationItem entity, type is " << geo.GetClassName() << " id is " << geo.GetID();
         IFCImporter::LogWarn(toLog.str().c_str());
         IFCImporter::LogWarn(toLog.str().c_str());
@@ -868,9 +825,7 @@ bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void AssignAddedMeshes(std::set<unsigned int>& mesh_indices,aiNode* nd,
-    ConversionData& /*conv*/)
-{
+void AssignAddedMeshes(std::set<unsigned int>& mesh_indices,aiNode* nd, ConversionData& /*conv*/) {
     if (!mesh_indices.empty()) {
     if (!mesh_indices.empty()) {
 		std::set<unsigned int>::const_iterator it = mesh_indices.cbegin();
 		std::set<unsigned int>::const_iterator it = mesh_indices.cbegin();
 		std::set<unsigned int>::const_iterator end = mesh_indices.cend();
 		std::set<unsigned int>::const_iterator end = mesh_indices.cend();
@@ -886,9 +841,9 @@ void AssignAddedMeshes(std::set<unsigned int>& mesh_indices,aiNode* nd,
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 bool TryQueryMeshCache(const Schema_2x3::IfcRepresentationItem& item,
 bool TryQueryMeshCache(const Schema_2x3::IfcRepresentationItem& item,
-    std::set<unsigned int>& mesh_indices, unsigned int mat_index,
-    ConversionData& conv)
-{
+        std::set<unsigned int>& mesh_indices, 
+        unsigned int mat_index,
+        ConversionData& conv) {
     ConversionData::MeshCacheIndex idx(&item, mat_index);
     ConversionData::MeshCacheIndex idx(&item, mat_index);
     ConversionData::MeshCache::const_iterator it = conv.cached_meshes.find(idx);
     ConversionData::MeshCache::const_iterator it = conv.cached_meshes.find(idx);
     if (it != conv.cached_meshes.end()) {
     if (it != conv.cached_meshes.end()) {
@@ -900,18 +855,18 @@ bool TryQueryMeshCache(const Schema_2x3::IfcRepresentationItem& item,
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void PopulateMeshCache(const Schema_2x3::IfcRepresentationItem& item,
 void PopulateMeshCache(const Schema_2x3::IfcRepresentationItem& item,
-    const std::set<unsigned int>& mesh_indices, unsigned int mat_index,
-    ConversionData& conv)
-{
+        const std::set<unsigned int>& mesh_indices, 
+        unsigned int mat_index,
+        ConversionData& conv) {
     ConversionData::MeshCacheIndex idx(&item, mat_index);
     ConversionData::MeshCacheIndex idx(&item, mat_index);
     conv.cached_meshes[idx] = mesh_indices;
     conv.cached_meshes[idx] = mesh_indices;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, unsigned int matid,
-    std::set<unsigned int>& mesh_indices,
-    ConversionData& conv)
-{
+bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, 
+        unsigned int matid,
+        std::set<unsigned int>& mesh_indices,
+        ConversionData& conv) {
     // determine material
     // determine material
     unsigned int localmatid = ProcessMaterials(item.GetID(), matid, conv, true);
     unsigned int localmatid = ProcessMaterials(item.GetID(), matid, conv, true);
 
 
@@ -920,8 +875,9 @@ bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, un
             if(mesh_indices.size()) {
             if(mesh_indices.size()) {
                 PopulateMeshCache(item,mesh_indices,localmatid,conv);
                 PopulateMeshCache(item,mesh_indices,localmatid,conv);
             }
             }
+        } else {
+            return false;
         }
         }
-        else return false;
     }
     }
     return true;
     return true;
 }
 }
@@ -930,4 +886,4 @@ bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, un
 } // ! IFC
 } // ! IFC
 } // ! Assimp
 } // ! Assimp
 
 
-#endif
+#endif // ASSIMP_BUILD_NO_IFC_IMPORTER

+ 9 - 15
code/AssetLib/IFC/IFCLoader.cpp

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2022, assimp team
 Copyright (c) 2006-2022, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -40,9 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 */
 */
 
 
-/** @file  IFCLoad.cpp
- *  @brief Implementation of the Industry Foundation Classes loader.
- */
+/// @file  IFCLoad.cpp
+/// @brief Implementation of the Industry Foundation Classes loader.
 
 
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 
 
@@ -92,7 +90,6 @@ using namespace Assimp::IFC;
   IfcUnitAssignment
   IfcUnitAssignment
   IfcClosedShell
   IfcClosedShell
   IfcDoor
   IfcDoor
-
  */
  */
 
 
 namespace {
 namespace {
@@ -119,14 +116,6 @@ static const aiImporterDesc desc = {
     "ifc ifczip step stp"
     "ifc ifczip step stp"
 };
 };
 
 
-// ------------------------------------------------------------------------------------------------
-// Constructor to be privately used by Importer
-IFCImporter::IFCImporter() = default;
-
-// ------------------------------------------------------------------------------------------------
-// Destructor, private as well
-IFCImporter::~IFCImporter() = default;
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 // Returns whether the class can handle the format of the given file.
 bool IFCImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
 bool IFCImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
@@ -256,7 +245,12 @@ void IFCImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
 
 
     // tell the reader for which types we need to simulate STEPs reverse indices
     // tell the reader for which types we need to simulate STEPs reverse indices
     static const char *const inverse_indices_to_track[] = {
     static const char *const inverse_indices_to_track[] = {
-        "ifcrelcontainedinspatialstructure", "ifcrelaggregates", "ifcrelvoidselement", "ifcreldefinesbyproperties", "ifcpropertyset", "ifcstyleditem"
+        "ifcrelcontainedinspatialstructure", 
+        "ifcrelaggregates", 
+        "ifcrelvoidselement", 
+        "ifcreldefinesbyproperties", 
+        "ifcpropertyset", 
+        "ifcstyleditem"
     };
     };
 
 
     // feed the IFC schema into the reader and pre-parse all lines
     // feed the IFC schema into the reader and pre-parse all lines
@@ -928,4 +922,4 @@ void MakeTreeRelative(ConversionData &conv) {
 
 
 } // namespace
 } // namespace
 
 
-#endif
+#endif // ASSIMP_BUILD_NO_IFC_IMPORTER

+ 2 - 2
code/AssetLib/IFC/IFCLoader.h

@@ -87,8 +87,8 @@ public:
         int cylindricalTessellation;
         int cylindricalTessellation;
     };
     };
 
 
-    IFCImporter();
-    ~IFCImporter() override;
+    IFCImporter() = default;
+    ~IFCImporter() override = default;
 
 
     // --------------------
     // --------------------
     bool CanRead(const std::string &pFile,
     bool CanRead(const std::string &pFile,

+ 2 - 5
code/AssetLib/IFC/IFCMaterial.cpp

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2022, assimp team
 Copyright (c) 2006-2022, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -40,9 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 */
 */
 
 
-/** @file  IFCMaterial.cpp
- *  @brief Implementation of conversion routines to convert IFC materials to aiMaterial
- */
+/// @file  IFCMaterial.cpp
+/// @brief Implementation of conversion routines to convert IFC materials to aiMaterial
 
 
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 
 
@@ -174,7 +172,6 @@ unsigned int ProcessMaterials(uint64_t id, unsigned int prevMatId, ConversionDat
 
 
     aiString name;
     aiString name;
     name.Set("<IFCDefault>");
     name.Set("<IFCDefault>");
-    //  ConvertColorToString( color, name);
 
 
     // look if there's already a default material with this base color
     // look if there's already a default material with this base color
     for( size_t a = 0; a < conv.materials.size(); ++a ) {
     for( size_t a = 0; a < conv.materials.size(); ++a ) {

File diff suppressed because it is too large
+ 160 - 260
code/AssetLib/IFC/IFCOpenings.cpp


+ 24 - 28
code/AssetLib/IFC/IFCProfile.cpp

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2022, assimp team
 Copyright (c) 2006-2022, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -40,9 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 */
 */
 
 
-/** @file  IFCProfile.cpp
- *  @brief Read profile and curves entities from IFC files
- */
+/// @file  IFCProfile.cpp
+/// @brief Read profile and curves entities from IFC files
 
 
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 
 
@@ -52,8 +50,9 @@ namespace Assimp {
 namespace IFC {
 namespace IFC {
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ProcessPolyLine(const Schema_2x3::IfcPolyline& def, TempMesh& meshout, ConversionData& /*conv*/)
-{
+void ProcessPolyLine(const Schema_2x3::IfcPolyline& def, 
+        TempMesh& meshout, 
+        ConversionData& /*conv*/) {
     // this won't produce a valid mesh, it just spits out a list of vertices
     // this won't produce a valid mesh, it just spits out a list of vertices
     IfcVector3 t;
     IfcVector3 t;
     for(const Schema_2x3::IfcCartesianPoint& cp : def.Points) {
     for(const Schema_2x3::IfcCartesianPoint& cp : def.Points) {
@@ -64,8 +63,9 @@ void ProcessPolyLine(const Schema_2x3::IfcPolyline& def, TempMesh& meshout, Conv
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-bool ProcessCurve(const Schema_2x3::IfcCurve& curve,  TempMesh& meshout, ConversionData& conv)
-{
+bool ProcessCurve(const Schema_2x3::IfcCurve& curve,  
+        TempMesh& meshout, 
+        ConversionData& conv) {
     std::unique_ptr<const Curve> cv(Curve::Convert(curve,conv));
     std::unique_ptr<const Curve> cv(Curve::Convert(curve,conv));
     if (!cv) {
     if (!cv) {
         IFCImporter::LogWarn("skipping unknown IfcCurve entity, type is ", curve.GetClassName());
         IFCImporter::LogWarn("skipping unknown IfcCurve entity, type is ", curve.GetClassName());
@@ -90,20 +90,23 @@ bool ProcessCurve(const Schema_2x3::IfcCurve& curve,  TempMesh& meshout, Convers
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ProcessClosedProfile(const Schema_2x3::IfcArbitraryClosedProfileDef& def, TempMesh& meshout, ConversionData& conv)
-{
+void ProcessClosedProfile(const Schema_2x3::IfcArbitraryClosedProfileDef& def, 
+        TempMesh& meshout, 
+        ConversionData& conv) {
     ProcessCurve(def.OuterCurve,meshout,conv);
     ProcessCurve(def.OuterCurve,meshout,conv);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ProcessOpenProfile(const Schema_2x3::IfcArbitraryOpenProfileDef& def, TempMesh& meshout, ConversionData& conv)
-{
+void ProcessOpenProfile(const Schema_2x3::IfcArbitraryOpenProfileDef& def, 
+        TempMesh& meshout, 
+        ConversionData& conv) {
     ProcessCurve(def.Curve,meshout,conv);
     ProcessCurve(def.Curve,meshout,conv);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& def, TempMesh& meshout, ConversionData& conv)
-{
+void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& def, 
+        TempMesh& meshout, 
+        ConversionData& conv) {
     if(const Schema_2x3::IfcRectangleProfileDef* const cprofile = def.ToPtr<Schema_2x3::IfcRectangleProfileDef>()) {
     if(const Schema_2x3::IfcRectangleProfileDef* const cprofile = def.ToPtr<Schema_2x3::IfcRectangleProfileDef>()) {
         const IfcFloat x = cprofile->XDim*0.5f, y = cprofile->YDim*0.5f;
         const IfcFloat x = cprofile->XDim*0.5f, y = cprofile->YDim*0.5f;
 
 
@@ -113,8 +116,7 @@ void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& de
         meshout.mVerts.emplace_back(-x,-y, 0.f );
         meshout.mVerts.emplace_back(-x,-y, 0.f );
         meshout.mVerts.emplace_back( x,-y, 0.f );
         meshout.mVerts.emplace_back( x,-y, 0.f );
         meshout.mVertcnt.push_back(4);
         meshout.mVertcnt.push_back(4);
-    }
-    else if( const Schema_2x3::IfcCircleProfileDef* const circle = def.ToPtr<Schema_2x3::IfcCircleProfileDef>()) {
+    } else if( const Schema_2x3::IfcCircleProfileDef* const circle = def.ToPtr<Schema_2x3::IfcCircleProfileDef>()) {
         if(def.ToPtr<Schema_2x3::IfcCircleHollowProfileDef>()) {
         if(def.ToPtr<Schema_2x3::IfcCircleHollowProfileDef>()) {
             // TODO
             // TODO
         }
         }
@@ -129,8 +131,7 @@ void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& de
         }
         }
 
 
         meshout.mVertcnt.push_back(static_cast<unsigned int>(segments));
         meshout.mVertcnt.push_back(static_cast<unsigned int>(segments));
-    }
-    else if( const Schema_2x3::IfcIShapeProfileDef* const ishape = def.ToPtr<Schema_2x3::IfcIShapeProfileDef>()) {
+    } else if( const Schema_2x3::IfcIShapeProfileDef* const ishape = def.ToPtr<Schema_2x3::IfcIShapeProfileDef>()) {
         // construct simplified IBeam shape
         // construct simplified IBeam shape
         const IfcFloat offset = (ishape->OverallWidth - ishape->WebThickness) / 2;
         const IfcFloat offset = (ishape->OverallWidth - ishape->WebThickness) / 2;
         const IfcFloat inner_height = ishape->OverallDepth - ishape->FlangeThickness * 2;
         const IfcFloat inner_height = ishape->OverallDepth - ishape->FlangeThickness * 2;
@@ -150,8 +151,7 @@ void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& de
         meshout.mVerts.emplace_back(ishape->OverallWidth,0,0);
         meshout.mVerts.emplace_back(ishape->OverallWidth,0,0);
 
 
         meshout.mVertcnt.push_back(12);
         meshout.mVertcnt.push_back(12);
-    }
-    else {
+    } else {
         IFCImporter::LogWarn("skipping unknown IfcParameterizedProfileDef entity, type is ", def.GetClassName());
         IFCImporter::LogWarn("skipping unknown IfcParameterizedProfileDef entity, type is ", def.GetClassName());
         return;
         return;
     }
     }
@@ -162,18 +162,14 @@ void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& de
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-bool ProcessProfile(const Schema_2x3::IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv)
-{
+bool ProcessProfile(const Schema_2x3::IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv) {
     if(const Schema_2x3::IfcArbitraryClosedProfileDef* const cprofile = prof.ToPtr<Schema_2x3::IfcArbitraryClosedProfileDef>()) {
     if(const Schema_2x3::IfcArbitraryClosedProfileDef* const cprofile = prof.ToPtr<Schema_2x3::IfcArbitraryClosedProfileDef>()) {
         ProcessClosedProfile(*cprofile,meshout,conv);
         ProcessClosedProfile(*cprofile,meshout,conv);
-    }
-    else if(const Schema_2x3::IfcArbitraryOpenProfileDef* const copen = prof.ToPtr<Schema_2x3::IfcArbitraryOpenProfileDef>()) {
+    } else if(const Schema_2x3::IfcArbitraryOpenProfileDef* const copen = prof.ToPtr<Schema_2x3::IfcArbitraryOpenProfileDef>()) {
         ProcessOpenProfile(*copen,meshout,conv);
         ProcessOpenProfile(*copen,meshout,conv);
-    }
-    else if(const Schema_2x3::IfcParameterizedProfileDef* const cparam = prof.ToPtr<Schema_2x3::IfcParameterizedProfileDef>()) {
+    } else if(const Schema_2x3::IfcParameterizedProfileDef* const cparam = prof.ToPtr<Schema_2x3::IfcParameterizedProfileDef>()) {
         ProcessParametrizedProfile(*cparam,meshout,conv);
         ProcessParametrizedProfile(*cparam,meshout,conv);
-    }
-    else {
+    } else {
         IFCImporter::LogWarn("skipping unknown IfcProfileDef entity, type is ", prof.GetClassName());
         IFCImporter::LogWarn("skipping unknown IfcProfileDef entity, type is ", prof.GetClassName());
         return false;
         return false;
     }
     }

+ 79 - 150
code/AssetLib/IFC/IFCUtil.cpp

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2022, assimp team
 Copyright (c) 2006-2022, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -40,14 +39,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 */
 */
 
 
-/** @file  IFCUtil.cpp
- *  @brief Implementation of conversion routines for some common Ifc helper entities.
- */
+/// @file  IFCUtil.cpp
+/// @brief Implementation of conversion routines for some common Ifc helper entities.
 
 
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
 
 
 #include "AssetLib/IFC/IFCUtil.h"
 #include "AssetLib/IFC/IFCUtil.h"
 #include "Common/PolyTools.h"
 #include "Common/PolyTools.h"
+#include "Geometry/GeometryUtils.h"
 #include "PostProcessing/ProcessHelper.h"
 #include "PostProcessing/ProcessHelper.h"
 
 
 namespace Assimp {
 namespace Assimp {
@@ -65,8 +64,7 @@ void TempOpening::Transform(const IfcMatrix4& mat) {
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-aiMesh* TempMesh::ToMesh()
-{
+aiMesh* TempMesh::ToMesh() {
     ai_assert(mVerts.size() == std::accumulate(mVertcnt.begin(),mVertcnt.end(),size_t(0)));
     ai_assert(mVerts.size() == std::accumulate(mVertcnt.begin(),mVertcnt.end(),size_t(0)));
 
 
     if (mVerts.empty()) {
     if (mVerts.empty()) {
@@ -104,36 +102,31 @@ aiMesh* TempMesh::ToMesh()
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void TempMesh::Clear()
-{
+void TempMesh::Clear() {
     mVerts.clear();
     mVerts.clear();
     mVertcnt.clear();
     mVertcnt.clear();
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void TempMesh::Transform(const IfcMatrix4& mat)
-{
+void TempMesh::Transform(const IfcMatrix4& mat) {
     for(IfcVector3& v : mVerts) {
     for(IfcVector3& v : mVerts) {
         v *= mat;
         v *= mat;
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------
-IfcVector3 TempMesh::Center() const
-{
-    return (mVerts.size() == 0) ? IfcVector3(0.0f, 0.0f, 0.0f) : (std::accumulate(mVerts.begin(),mVerts.end(),IfcVector3()) / static_cast<IfcFloat>(mVerts.size()));
+IfcVector3 TempMesh::Center() const {
+    return mVerts.empty() ? IfcVector3(0.0f, 0.0f, 0.0f) : (std::accumulate(mVerts.begin(),mVerts.end(),IfcVector3()) / static_cast<IfcFloat>(mVerts.size()));
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void TempMesh::Append(const TempMesh& other)
-{
+void TempMesh::Append(const TempMesh& other) {
     mVerts.insert(mVerts.end(),other.mVerts.begin(),other.mVerts.end());
     mVerts.insert(mVerts.end(),other.mVerts.begin(),other.mVerts.end());
     mVertcnt.insert(mVertcnt.end(),other.mVertcnt.begin(),other.mVertcnt.end());
     mVertcnt.insert(mVertcnt.end(),other.mVertcnt.begin(),other.mVertcnt.end());
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void TempMesh::RemoveDegenerates()
-{
+void TempMesh::RemoveDegenerates() {
     // The strategy is simple: walk the mesh and compute normals using
     // The strategy is simple: walk the mesh and compute normals using
     // Newell's algorithm. The length of the normals gives the area
     // Newell's algorithm. The length of the normals gives the area
     // of the polygons, which is close to zero for lines.
     // of the polygons, which is close to zero for lines.
@@ -166,11 +159,9 @@ void TempMesh::RemoveDegenerates()
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-IfcVector3 TempMesh::ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bool normalize)
-{
+IfcVector3 TempMesh::ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bool normalize) {
     std::vector<IfcFloat> temp((cnt+2)*3);
     std::vector<IfcFloat> temp((cnt+2)*3);
-    for( size_t vofs = 0, i = 0; vofs < cnt; ++vofs )
-    {
+    for( size_t vofs = 0, i = 0; vofs < cnt; ++vofs ) {
         const IfcVector3& v = vtcs[vofs];
         const IfcVector3& v = vtcs[vofs];
         temp[i++] = v.x;
         temp[i++] = v.x;
         temp[i++] = v.y;
         temp[i++] = v.y;
@@ -184,9 +175,8 @@ IfcVector3 TempMesh::ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bo
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void TempMesh::ComputePolygonNormals(std::vector<IfcVector3>& normals,
 void TempMesh::ComputePolygonNormals(std::vector<IfcVector3>& normals,
-    bool normalize,
-    size_t ofs) const
-{
+        bool normalize,
+        size_t ofs) const {
     size_t max_vcount = 0;
     size_t max_vcount = 0;
     std::vector<unsigned int>::const_iterator begin = mVertcnt.begin()+ofs, end = mVertcnt.end(),  iit;
     std::vector<unsigned int>::const_iterator begin = mVertcnt.begin()+ofs, end = mVertcnt.end(),  iit;
     for(iit = begin; iit != end; ++iit) {
     for(iit = begin; iit != end; ++iit) {
@@ -235,7 +225,7 @@ IfcVector3 TempMesh::ComputeLastPolygonNormal(bool normalize) const {
 struct CompareVector {
 struct CompareVector {
     bool operator () (const IfcVector3& a, const IfcVector3& b) const {
     bool operator () (const IfcVector3& a, const IfcVector3& b) const {
         IfcVector3 d = a - b;
         IfcVector3 d = a - b;
-        IfcFloat eps = ai_epsilon;
+        constexpr IfcFloat eps = ai_epsilon;
         return d.x < -eps || (std::abs(d.x) < eps && d.y < -eps) || (std::abs(d.x) < eps && std::abs(d.y) < eps && d.z < -eps);
         return d.x < -eps || (std::abs(d.x) < eps && d.y < -eps) || (std::abs(d.x) < eps && std::abs(d.y) < eps && d.z < -eps);
     }
     }
 };
 };
@@ -249,29 +239,27 @@ struct FindVector {
 };
 };
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void TempMesh::FixupFaceOrientation()
-{
+void TempMesh::FixupFaceOrientation() {
     const IfcVector3 vavg = Center();
     const IfcVector3 vavg = Center();
 
 
     // create a list of start indices for all faces to allow random access to faces
     // create a list of start indices for all faces to allow random access to faces
     std::vector<size_t> faceStartIndices(mVertcnt.size());
     std::vector<size_t> faceStartIndices(mVertcnt.size());
-    for( size_t i = 0, a = 0; a < mVertcnt.size(); i += mVertcnt[a], ++a )
+    for( size_t i = 0, a = 0; a < mVertcnt.size(); i += mVertcnt[a], ++a ) {
         faceStartIndices[a] = i;
         faceStartIndices[a] = i;
+    }
 
 
     // list all faces on a vertex
     // list all faces on a vertex
     std::map<IfcVector3, std::vector<size_t>, CompareVector> facesByVertex;
     std::map<IfcVector3, std::vector<size_t>, CompareVector> facesByVertex;
-    for( size_t a = 0; a < mVertcnt.size(); ++a )
-    {
-        for( size_t b = 0; b < mVertcnt[a]; ++b )
+    for( size_t a = 0; a < mVertcnt.size(); ++a ) {
+        for( size_t b = 0; b < mVertcnt[a]; ++b ) {
             facesByVertex[mVerts[faceStartIndices[a] + b]].push_back(a);
             facesByVertex[mVerts[faceStartIndices[a] + b]].push_back(a);
+        }
     }
     }
     // determine neighbourhood for all polys
     // determine neighbourhood for all polys
     std::vector<size_t> neighbour(mVerts.size(), SIZE_MAX);
     std::vector<size_t> neighbour(mVerts.size(), SIZE_MAX);
     std::vector<size_t> tempIntersect(10);
     std::vector<size_t> tempIntersect(10);
-    for( size_t a = 0; a < mVertcnt.size(); ++a )
-    {
-        for( size_t b = 0; b < mVertcnt[a]; ++b )
-        {
+    for( size_t a = 0; a < mVertcnt.size(); ++a ) {
+        for( size_t b = 0; b < mVertcnt[a]; ++b ) {
             size_t ib = faceStartIndices[a] + b, nib = faceStartIndices[a] + (b + 1) % mVertcnt[a];
             size_t ib = faceStartIndices[a] + b, nib = faceStartIndices[a] + (b + 1) % mVertcnt[a];
             const std::vector<size_t>& facesOnB = facesByVertex[mVerts[ib]];
             const std::vector<size_t>& facesOnB = facesByVertex[mVerts[ib]];
             const std::vector<size_t>& facesOnNB = facesByVertex[mVerts[nib]];
             const std::vector<size_t>& facesOnNB = facesByVertex[mVerts[nib]];
@@ -280,10 +268,12 @@ void TempMesh::FixupFaceOrientation()
             std::vector<size_t>::iterator sectend = std::set_intersection(
             std::vector<size_t>::iterator sectend = std::set_intersection(
                 facesOnB.begin(), facesOnB.end(), facesOnNB.begin(), facesOnNB.end(), sectstart);
                 facesOnB.begin(), facesOnB.end(), facesOnNB.begin(), facesOnNB.end(), sectstart);
 
 
-            if( std::distance(sectstart, sectend) != 2 )
+            if( std::distance(sectstart, sectend) != 2 ) {
                 continue;
                 continue;
-            if( *sectstart == a )
+            }
+            if( *sectstart == a ) {
                 ++sectstart;
                 ++sectstart;
+            }
             neighbour[ib] = *sectstart;
             neighbour[ib] = *sectstart;
         }
         }
     }
     }
@@ -292,15 +282,14 @@ void TempMesh::FixupFaceOrientation()
     // facing outwards. So we reverse this face to point outwards in relation to the center. Then we adapt neighbouring
     // facing outwards. So we reverse this face to point outwards in relation to the center. Then we adapt neighbouring
     // faces to have the same winding until all faces have been tested.
     // faces to have the same winding until all faces have been tested.
     std::vector<bool> faceDone(mVertcnt.size(), false);
     std::vector<bool> faceDone(mVertcnt.size(), false);
-    while( std::count(faceDone.begin(), faceDone.end(), false) != 0 )
-    {
+    while( std::count(faceDone.begin(), faceDone.end(), false) != 0 ) {
         // find the farthest of the remaining faces
         // find the farthest of the remaining faces
         size_t farthestIndex = SIZE_MAX;
         size_t farthestIndex = SIZE_MAX;
         IfcFloat farthestDistance = -1.0;
         IfcFloat farthestDistance = -1.0;
-        for( size_t a = 0; a < mVertcnt.size(); ++a )
-        {
-            if( faceDone[a] )
+        for( size_t a = 0; a < mVertcnt.size(); ++a ) {
+            if( faceDone[a] ) {
                 continue;
                 continue;
+            }
             IfcVector3 faceCenter = std::accumulate(mVerts.begin() + faceStartIndices[a],
             IfcVector3 faceCenter = std::accumulate(mVerts.begin() + faceStartIndices[a],
                 mVerts.begin() + faceStartIndices[a] + mVertcnt[a], IfcVector3(0.0)) / IfcFloat(mVertcnt[a]);
                 mVerts.begin() + faceStartIndices[a] + mVertcnt[a], IfcVector3(0.0)) / IfcFloat(mVertcnt[a]);
             IfcFloat dst = (faceCenter - vavg).SquareLength();
             IfcFloat dst = (faceCenter - vavg).SquareLength();
@@ -314,8 +303,7 @@ void TempMesh::FixupFaceOrientation()
             / IfcFloat(mVertcnt[farthestIndex]);
             / IfcFloat(mVertcnt[farthestIndex]);
         // We accept a bit of negative orientation without reversing. In case of doubt, prefer the orientation given in
         // We accept a bit of negative orientation without reversing. In case of doubt, prefer the orientation given in
         // the file.
         // the file.
-        if( (farthestNormal * (farthestCenter - vavg).Normalize()) < -0.4 )
-        {
+        if( (farthestNormal * (farthestCenter - vavg).Normalize()) < -0.4 ) {
             size_t fsi = faceStartIndices[farthestIndex], fvc = mVertcnt[farthestIndex];
             size_t fsi = faceStartIndices[farthestIndex], fvc = mVertcnt[farthestIndex];
             std::reverse(mVerts.begin() + fsi, mVerts.begin() + fsi + fvc);
             std::reverse(mVerts.begin() + fsi, mVerts.begin() + fsi + fvc);
             std::reverse(neighbour.begin() + fsi, neighbour.begin() + fsi + fvc);
             std::reverse(neighbour.begin() + fsi, neighbour.begin() + fsi + fvc);
@@ -332,19 +320,18 @@ void TempMesh::FixupFaceOrientation()
         todo.push_back(farthestIndex);
         todo.push_back(farthestIndex);
 
 
         // go over its neighbour faces recursively and adapt their winding order to match the farthest face
         // go over its neighbour faces recursively and adapt their winding order to match the farthest face
-        while( !todo.empty() )
-        {
+        while( !todo.empty() ) {
             size_t tdf = todo.back();
             size_t tdf = todo.back();
             size_t vsi = faceStartIndices[tdf], vc = mVertcnt[tdf];
             size_t vsi = faceStartIndices[tdf], vc = mVertcnt[tdf];
             todo.pop_back();
             todo.pop_back();
 
 
             // check its neighbours
             // check its neighbours
-            for( size_t a = 0; a < vc; ++a )
-            {
+            for( size_t a = 0; a < vc; ++a ) {
                 // ignore neighbours if we already checked them
                 // ignore neighbours if we already checked them
                 size_t nbi = neighbour[vsi + a];
                 size_t nbi = neighbour[vsi + a];
-                if( nbi == SIZE_MAX || faceDone[nbi] )
+                if( nbi == SIZE_MAX || faceDone[nbi] ) {
                     continue;
                     continue;
+                }
 
 
                 const IfcVector3& vp = mVerts[vsi + a];
                 const IfcVector3& vp = mVerts[vsi + a];
                 size_t nbvsi = faceStartIndices[nbi], nbvc = mVertcnt[nbi];
                 size_t nbvsi = faceStartIndices[nbi], nbvc = mVertcnt[nbi];
@@ -387,32 +374,8 @@ void TempMesh::RemoveAdjacentDuplicates() {
         IfcVector3 vmin,vmax;
         IfcVector3 vmin,vmax;
         ArrayBounds(&*base, cnt ,vmin,vmax);
         ArrayBounds(&*base, cnt ,vmin,vmax);
 
 
-
         const IfcFloat epsilon = (vmax-vmin).SquareLength() / static_cast<IfcFloat>(1e9);
         const IfcFloat epsilon = (vmax-vmin).SquareLength() / static_cast<IfcFloat>(1e9);
-        //const IfcFloat dotepsilon = 1e-9;
-
-        //// look for vertices that lie directly on the line between their predecessor and their
-        //// successor and replace them with either of them.
-
-        //for(size_t i = 0; i < cnt; ++i) {
-        //  IfcVector3& v1 = *(base+i), &v0 = *(base+(i?i-1:cnt-1)), &v2 = *(base+(i+1)%cnt);
-        //  const IfcVector3& d0 = (v1-v0), &d1 = (v2-v1);
-        //  const IfcFloat l0 = d0.SquareLength(), l1 = d1.SquareLength();
-        //  if (!l0 || !l1) {
-        //      continue;
-        //  }
-
-        //  const IfcFloat d = (d0/std::sqrt(l0))*(d1/std::sqrt(l1));
-
-        //  if ( d >= 1.f-dotepsilon ) {
-        //      v1 = v0;
-        //  }
-        //  else if ( d < -1.f+dotepsilon ) {
-        //      v2 = v1;
-        //      continue;
-        //  }
-        //}
-
+        
         // drop any identical, adjacent vertices. this pass will collect the dropouts
         // drop any identical, adjacent vertices. this pass will collect the dropouts
         // of the previous pass as a side-effect.
         // of the previous pass as a side-effect.
         FuzzyVectorCompare fz(epsilon);
         FuzzyVectorCompare fz(epsilon);
@@ -439,78 +402,58 @@ void TempMesh::RemoveAdjacentDuplicates() {
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void TempMesh::Swap(TempMesh& other)
-{
+void TempMesh::Swap(TempMesh& other) {
     mVertcnt.swap(other.mVertcnt);
     mVertcnt.swap(other.mVertcnt);
     mVerts.swap(other.mVerts);
     mVerts.swap(other.mVerts);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-bool IsTrue(const ::Assimp::STEP::EXPRESS::BOOLEAN& in)
-{
+bool IsTrue(const ::Assimp::STEP::EXPRESS::BOOLEAN& in) {
     return (std::string)in == "TRUE" || (std::string)in == "T";
     return (std::string)in == "TRUE" || (std::string)in == "T";
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-IfcFloat ConvertSIPrefix(const std::string& prefix)
-{
+IfcFloat ConvertSIPrefix(const std::string& prefix) {
     if (prefix == "EXA") {
     if (prefix == "EXA") {
         return 1e18f;
         return 1e18f;
-    }
-    else if (prefix == "PETA") {
+    } else if (prefix == "PETA") {
         return 1e15f;
         return 1e15f;
-    }
-    else if (prefix == "TERA") {
+    } else if (prefix == "TERA") {
         return 1e12f;
         return 1e12f;
-    }
-    else if (prefix == "GIGA") {
+    } else if (prefix == "GIGA") {
         return 1e9f;
         return 1e9f;
-    }
-    else if (prefix == "MEGA") {
+    } else if (prefix == "MEGA") {
         return 1e6f;
         return 1e6f;
-    }
-    else if (prefix == "KILO") {
+    } else if (prefix == "KILO") {
         return 1e3f;
         return 1e3f;
-    }
-    else if (prefix == "HECTO") {
+    } else if (prefix == "HECTO") {
         return 1e2f;
         return 1e2f;
-    }
-    else if (prefix == "DECA") {
+    } else if (prefix == "DECA") {
         return 1e-0f;
         return 1e-0f;
-    }
-    else if (prefix == "DECI") {
+    } else if (prefix == "DECI") {
         return 1e-1f;
         return 1e-1f;
-    }
-    else if (prefix == "CENTI") {
+    } else if (prefix == "CENTI") {
         return 1e-2f;
         return 1e-2f;
-    }
-    else if (prefix == "MILLI") {
+    } else if (prefix == "MILLI") {
         return 1e-3f;
         return 1e-3f;
-    }
-    else if (prefix == "MICRO") {
+    } else if (prefix == "MICRO") {
         return 1e-6f;
         return 1e-6f;
-    }
-    else if (prefix == "NANO") {
+    } else if (prefix == "NANO") {
         return 1e-9f;
         return 1e-9f;
-    }
-    else if (prefix == "PICO") {
+    } else if (prefix == "PICO") {
         return 1e-12f;
         return 1e-12f;
-    }
-    else if (prefix == "FEMTO") {
+    } else if (prefix == "FEMTO") {
         return 1e-15f;
         return 1e-15f;
-    }
-    else if (prefix == "ATTO") {
+    } else if (prefix == "ATTO") {
         return 1e-18f;
         return 1e-18f;
-    }
-    else {
+    } else {
         IFCImporter::LogError("Unrecognized SI prefix: ", prefix);
         IFCImporter::LogError("Unrecognized SI prefix: ", prefix);
         return 1;
         return 1;
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in)
-{
+void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in) {
     out.r = static_cast<float>( in.Red );
     out.r = static_cast<float>( in.Red );
     out.g = static_cast<float>( in.Green );
     out.g = static_cast<float>( in.Green );
     out.b = static_cast<float>( in.Blue );
     out.b = static_cast<float>( in.Blue );
@@ -518,8 +461,10 @@ void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in)
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourOrFactor& in,ConversionData& conv,const aiColor4D* base)
-{
+void ConvertColor(aiColor4D& out, 
+        const Schema_2x3::IfcColourOrFactor& in,
+        ConversionData& conv,
+        const aiColor4D* base) {
     if (const ::Assimp::STEP::EXPRESS::REAL* const r = in.ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
     if (const ::Assimp::STEP::EXPRESS::REAL* const r = in.ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
         out.r = out.g = out.b = static_cast<float>(*r);
         out.r = out.g = out.b = static_cast<float>(*r);
         if(base) {
         if(base) {
@@ -527,20 +472,18 @@ void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourOrFactor& in,Conver
             out.g *= static_cast<float>( base->g );
             out.g *= static_cast<float>( base->g );
             out.b *= static_cast<float>( base->b );
             out.b *= static_cast<float>( base->b );
             out.a = static_cast<float>( base->a );
             out.a = static_cast<float>( base->a );
+        } else {
+            out.a = 1.0;
         }
         }
-        else out.a = 1.0;
-    }
-    else if (const Schema_2x3::IfcColourRgb* const rgb = in.ResolveSelectPtr<Schema_2x3::IfcColourRgb>(conv.db)) {
+    } else if (const Schema_2x3::IfcColourRgb* const rgb = in.ResolveSelectPtr<Schema_2x3::IfcColourRgb>(conv.db)) {
         ConvertColor(out,*rgb);
         ConvertColor(out,*rgb);
-    }
-    else {
+    } else {
         IFCImporter::LogWarn("skipping unknown IfcColourOrFactor entity");
         IFCImporter::LogWarn("skipping unknown IfcColourOrFactor entity");
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint& in)
-{
+void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint& in) {
     out = IfcVector3();
     out = IfcVector3();
     for(size_t i = 0; i < in.Coordinates.size(); ++i) {
     for(size_t i = 0; i < in.Coordinates.size(); ++i) {
         out[static_cast<unsigned int>(i)] = in.Coordinates[i];
         out[static_cast<unsigned int>(i)] = in.Coordinates[i];
@@ -548,15 +491,13 @@ void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint&
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ConvertVector(IfcVector3& out, const Schema_2x3::IfcVector& in)
-{
+void ConvertVector(IfcVector3& out, const Schema_2x3::IfcVector& in) {
     ConvertDirection(out,in.Orientation);
     ConvertDirection(out,in.Orientation);
     out *= in.Magnitude;
     out *= in.Magnitude;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in)
-{
+void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in) {
     out = IfcVector3();
     out = IfcVector3();
     for(size_t i = 0; i < in.DirectionRatios.size(); ++i) {
     for(size_t i = 0; i < in.DirectionRatios.size(); ++i) {
         out[static_cast<unsigned int>(i)] = in.DirectionRatios[i];
         out[static_cast<unsigned int>(i)] = in.DirectionRatios[i];
@@ -570,8 +511,7 @@ void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in)
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y, const IfcVector3& z)
-{
+void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y, const IfcVector3& z) {
     out.a1 = x.x;
     out.a1 = x.x;
     out.b1 = x.y;
     out.b1 = x.y;
     out.c1 = x.z;
     out.c1 = x.z;
@@ -586,8 +526,7 @@ void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y,
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D& in)
-{
+void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D& in) {
     IfcVector3 loc;
     IfcVector3 loc;
     ConvertCartesianPoint(loc,in.Location);
     ConvertCartesianPoint(loc,in.Location);
 
 
@@ -611,8 +550,7 @@ void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D& in)
-{
+void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D& in) {
     IfcVector3 loc;
     IfcVector3 loc;
     ConvertCartesianPoint(loc,in.Location);
     ConvertCartesianPoint(loc,in.Location);
 
 
@@ -628,34 +566,28 @@ void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ConvertAxisPlacement(IfcVector3& axis, IfcVector3& pos, const Schema_2x3::IfcAxis1Placement& in)
-{
+void ConvertAxisPlacement(IfcVector3& axis, IfcVector3& pos, const Schema_2x3::IfcAxis1Placement& in) {
     ConvertCartesianPoint(pos,in.Location);
     ConvertCartesianPoint(pos,in.Location);
     if (in.Axis) {
     if (in.Axis) {
         ConvertDirection(axis,in.Axis.Get());
         ConvertDirection(axis,in.Axis.Get());
-    }
-    else {
+    } else {
         axis = IfcVector3(0.f,0.f,1.f);
         axis = IfcVector3(0.f,0.f,1.f);
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement& in, ConversionData& conv)
-{
+void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement& in, ConversionData& conv) {
     if(const Schema_2x3::IfcAxis2Placement3D* pl3 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement3D>(conv.db)) {
     if(const Schema_2x3::IfcAxis2Placement3D* pl3 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement3D>(conv.db)) {
         ConvertAxisPlacement(out,*pl3);
         ConvertAxisPlacement(out,*pl3);
-    }
-    else if(const Schema_2x3::IfcAxis2Placement2D* pl2 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement2D>(conv.db)) {
+    } else if(const Schema_2x3::IfcAxis2Placement2D* pl2 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement2D>(conv.db)) {
         ConvertAxisPlacement(out,*pl2);
         ConvertAxisPlacement(out,*pl2);
-    }
-    else {
+    } else {
         IFCImporter::LogWarn("skipping unknown IfcAxis2Placement entity");
         IFCImporter::LogWarn("skipping unknown IfcAxis2Placement entity");
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTransformationOperator& op)
-{
+void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTransformationOperator& op) {
     IfcVector3 loc;
     IfcVector3 loc;
     ConvertCartesianPoint(loc,op.LocalOrigin);
     ConvertCartesianPoint(loc,op.LocalOrigin);
 
 
@@ -676,14 +608,12 @@ void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTra
     IfcMatrix4::Translation(loc,locm);
     IfcMatrix4::Translation(loc,locm);
     AssignMatrixAxes(out,x,y,z);
     AssignMatrixAxes(out,x,y,z);
 
 
-
     IfcVector3 vscale;
     IfcVector3 vscale;
     if (const Schema_2x3::IfcCartesianTransformationOperator3DnonUniform* nuni = op.ToPtr<Schema_2x3::IfcCartesianTransformationOperator3DnonUniform>()) {
     if (const Schema_2x3::IfcCartesianTransformationOperator3DnonUniform* nuni = op.ToPtr<Schema_2x3::IfcCartesianTransformationOperator3DnonUniform>()) {
         vscale.x = nuni->Scale?op.Scale.Get():1.f;
         vscale.x = nuni->Scale?op.Scale.Get():1.f;
         vscale.y = nuni->Scale2?nuni->Scale2.Get():1.f;
         vscale.y = nuni->Scale2?nuni->Scale2.Get():1.f;
         vscale.z = nuni->Scale3?nuni->Scale3.Get():1.f;
         vscale.z = nuni->Scale3?nuni->Scale3.Get():1.f;
-    }
-    else {
+    } else {
         const IfcFloat sc = op.Scale?op.Scale.Get():1.f;
         const IfcFloat sc = op.Scale?op.Scale.Get():1.f;
         vscale = IfcVector3(sc,sc,sc);
         vscale = IfcVector3(sc,sc,sc);
     }
     }
@@ -694,8 +624,7 @@ void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTra
     out = locm * out * s;
     out = locm * out * s;
 }
 }
 
 
-
 } // ! IFC
 } // ! IFC
 } // ! Assimp
 } // ! Assimp
 
 
-#endif
+#endif // ASSIMP_BUILD_NO_IFC_IMPORTER

+ 1201 - 1210
code/AssetLib/Irr/IRRLoader.cpp

@@ -43,6 +43,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *  @brief Implementation of the Irr importer class
  *  @brief Implementation of the Irr importer class
  */
  */
 
 
+#include "assimp/Exceptional.h"
+#include "assimp/StringComparison.h"
 #ifndef ASSIMP_BUILD_NO_IRR_IMPORTER
 #ifndef ASSIMP_BUILD_NO_IRR_IMPORTER
 
 
 #include "AssetLib/Irr/IRRLoader.h"
 #include "AssetLib/Irr/IRRLoader.h"
@@ -62,28 +64,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/IOSystem.hpp>
 #include <assimp/IOSystem.hpp>
 
 
-#include <memory>
-
 using namespace Assimp;
 using namespace Assimp;
 
 
 static const aiImporterDesc desc = {
 static const aiImporterDesc desc = {
-	"Irrlicht Scene Reader",
-	"",
-	"",
-	"http://irrlicht.sourceforge.net/",
-	aiImporterFlags_SupportTextFlavour,
-	0,
-	0,
-	0,
-	0,
-	"irr xml"
+    "Irrlicht Scene Reader",
+    "",
+    "",
+    "http://irrlicht.sourceforge.net/",
+    aiImporterFlags_SupportTextFlavour,
+    0,
+    0,
+    0,
+    0,
+    "irr xml"
 };
 };
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 // Constructor to be privately used by Importer
 IRRImporter::IRRImporter() :
 IRRImporter::IRRImporter() :
-		fps(), configSpeedFlag() {
-	// empty
+        fps(), configSpeedFlag() {
+    // empty
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -93,154 +93,154 @@ IRRImporter::~IRRImporter() = default;
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 // Returns whether the class can handle the format of the given file.
 bool IRRImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
 bool IRRImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
-	static const char *tokens[] = { "irr_scene" };
-	return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
+    static const char *tokens[] = { "irr_scene" };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 const aiImporterDesc *IRRImporter::GetInfo() const {
 const aiImporterDesc *IRRImporter::GetInfo() const {
-	return &desc;
+    return &desc;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void IRRImporter::SetupProperties(const Importer *pImp) {
 void IRRImporter::SetupProperties(const Importer *pImp) {
-	// read the output frame rate of all node animation channels
-	fps = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IRR_ANIM_FPS, 100);
-	if (fps < 10.) {
-		ASSIMP_LOG_ERROR("IRR: Invalid FPS configuration");
-		fps = 100;
-	}
-
-	// AI_CONFIG_FAVOUR_SPEED
-	configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED, 0));
+    // read the output frame rate of all node animation channels
+    fps = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IRR_ANIM_FPS, 100);
+    if (fps < 10.) {
+        ASSIMP_LOG_ERROR("IRR: Invalid FPS configuration");
+        fps = 100;
+    }
+
+    // AI_CONFIG_FAVOUR_SPEED
+    configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED, 0));
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Build a mesh that consists of a single squad (a side of a skybox)
 // Build a mesh that consists of a single squad (a side of a skybox)
 aiMesh *IRRImporter::BuildSingleQuadMesh(const SkyboxVertex &v1,
 aiMesh *IRRImporter::BuildSingleQuadMesh(const SkyboxVertex &v1,
-		const SkyboxVertex &v2,
-		const SkyboxVertex &v3,
-		const SkyboxVertex &v4) {
-	// allocate and prepare the mesh
-	aiMesh *out = new aiMesh();
-
-	out->mPrimitiveTypes = aiPrimitiveType_POLYGON;
-	out->mNumFaces = 1;
-
-	// build the face
-	out->mFaces = new aiFace[1];
-	aiFace &face = out->mFaces[0];
-
-	face.mNumIndices = 4;
-	face.mIndices = new unsigned int[4];
-	for (unsigned int i = 0; i < 4; ++i)
-		face.mIndices[i] = i;
-
-	out->mNumVertices = 4;
-
-	// copy vertex positions
-	aiVector3D *vec = out->mVertices = new aiVector3D[4];
-	*vec++ = v1.position;
-	*vec++ = v2.position;
-	*vec++ = v3.position;
-	*vec = v4.position;
-
-	// copy vertex normals
-	vec = out->mNormals = new aiVector3D[4];
-	*vec++ = v1.normal;
-	*vec++ = v2.normal;
-	*vec++ = v3.normal;
-	*vec = v4.normal;
-
-	// copy texture coordinates
-	vec = out->mTextureCoords[0] = new aiVector3D[4];
-	*vec++ = v1.uv;
-	*vec++ = v2.uv;
-	*vec++ = v3.uv;
-	*vec = v4.uv;
-	return out;
+        const SkyboxVertex &v2,
+        const SkyboxVertex &v3,
+        const SkyboxVertex &v4) {
+    // allocate and prepare the mesh
+    aiMesh *out = new aiMesh();
+
+    out->mPrimitiveTypes = aiPrimitiveType_POLYGON;
+    out->mNumFaces = 1;
+
+    // build the face
+    out->mFaces = new aiFace[1];
+    aiFace &face = out->mFaces[0];
+
+    face.mNumIndices = 4;
+    face.mIndices = new unsigned int[4];
+    for (unsigned int i = 0; i < 4; ++i)
+        face.mIndices[i] = i;
+
+    out->mNumVertices = 4;
+
+    // copy vertex positions
+    aiVector3D *vec = out->mVertices = new aiVector3D[4];
+    *vec++ = v1.position;
+    *vec++ = v2.position;
+    *vec++ = v3.position;
+    *vec = v4.position;
+
+    // copy vertex normals
+    vec = out->mNormals = new aiVector3D[4];
+    *vec++ = v1.normal;
+    *vec++ = v2.normal;
+    *vec++ = v3.normal;
+    *vec = v4.normal;
+
+    // copy texture coordinates
+    vec = out->mTextureCoords[0] = new aiVector3D[4];
+    *vec++ = v1.uv;
+    *vec++ = v2.uv;
+    *vec++ = v3.uv;
+    *vec = v4.uv;
+    return out;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void IRRImporter::BuildSkybox(std::vector<aiMesh *> &meshes, std::vector<aiMaterial *> materials) {
 void IRRImporter::BuildSkybox(std::vector<aiMesh *> &meshes, std::vector<aiMaterial *> materials) {
-	// Update the material of the skybox - replace the name and disable shading for skyboxes.
-	for (unsigned int i = 0; i < 6; ++i) {
-		aiMaterial *out = (aiMaterial *)(*(materials.end() - (6 - i)));
-
-		aiString s;
-		s.length = ::ai_snprintf(s.data, MAXLEN, "SkyboxSide_%u", i);
-		out->AddProperty(&s, AI_MATKEY_NAME);
-
-		int shading = aiShadingMode_NoShading;
-		out->AddProperty(&shading, 1, AI_MATKEY_SHADING_MODEL);
-	}
-
-	// Skyboxes are much more difficult. They are represented
-	// by six single planes with different textures, so we'll
-	// need to build six meshes.
-
-	const ai_real l = 10.0; // the size used by Irrlicht
-
-	// FRONT SIDE
-	meshes.push_back(BuildSingleQuadMesh(
-			SkyboxVertex(-l, -l, -l, 0, 0, 1, 1.0, 1.0),
-			SkyboxVertex(l, -l, -l, 0, 0, 1, 0.0, 1.0),
-			SkyboxVertex(l, l, -l, 0, 0, 1, 0.0, 0.0),
-			SkyboxVertex(-l, l, -l, 0, 0, 1, 1.0, 0.0)));
-	meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 6u);
-
-	// LEFT SIDE
-	meshes.push_back(BuildSingleQuadMesh(
-			SkyboxVertex(l, -l, -l, -1, 0, 0, 1.0, 1.0),
-			SkyboxVertex(l, -l, l, -1, 0, 0, 0.0, 1.0),
-			SkyboxVertex(l, l, l, -1, 0, 0, 0.0, 0.0),
-			SkyboxVertex(l, l, -l, -1, 0, 0, 1.0, 0.0)));
-	meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 5u);
-
-	// BACK SIDE
-	meshes.push_back(BuildSingleQuadMesh(
-			SkyboxVertex(l, -l, l, 0, 0, -1, 1.0, 1.0),
-			SkyboxVertex(-l, -l, l, 0, 0, -1, 0.0, 1.0),
-			SkyboxVertex(-l, l, l, 0, 0, -1, 0.0, 0.0),
-			SkyboxVertex(l, l, l, 0, 0, -1, 1.0, 0.0)));
-	meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 4u);
-
-	// RIGHT SIDE
-	meshes.push_back(BuildSingleQuadMesh(
-			SkyboxVertex(-l, -l, l, 1, 0, 0, 1.0, 1.0),
-			SkyboxVertex(-l, -l, -l, 1, 0, 0, 0.0, 1.0),
-			SkyboxVertex(-l, l, -l, 1, 0, 0, 0.0, 0.0),
-			SkyboxVertex(-l, l, l, 1, 0, 0, 1.0, 0.0)));
-	meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 3u);
-
-	// TOP SIDE
-	meshes.push_back(BuildSingleQuadMesh(
-			SkyboxVertex(l, l, -l, 0, -1, 0, 1.0, 1.0),
-			SkyboxVertex(l, l, l, 0, -1, 0, 0.0, 1.0),
-			SkyboxVertex(-l, l, l, 0, -1, 0, 0.0, 0.0),
-			SkyboxVertex(-l, l, -l, 0, -1, 0, 1.0, 0.0)));
-	meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 2u);
-
-	// BOTTOM SIDE
-	meshes.push_back(BuildSingleQuadMesh(
-			SkyboxVertex(l, -l, l, 0, 1, 0, 0.0, 0.0),
-			SkyboxVertex(l, -l, -l, 0, 1, 0, 1.0, 0.0),
-			SkyboxVertex(-l, -l, -l, 0, 1, 0, 1.0, 1.0),
-			SkyboxVertex(-l, -l, l, 0, 1, 0, 0.0, 1.0)));
-	meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 1u);
+    // Update the material of the skybox - replace the name and disable shading for skyboxes.
+    for (unsigned int i = 0; i < 6; ++i) {
+        aiMaterial *out = (aiMaterial *)(*(materials.end() - (6 - i)));
+
+        aiString s;
+        s.length = ::ai_snprintf(s.data, MAXLEN, "SkyboxSide_%u", i);
+        out->AddProperty(&s, AI_MATKEY_NAME);
+
+        int shading = aiShadingMode_NoShading;
+        out->AddProperty(&shading, 1, AI_MATKEY_SHADING_MODEL);
+    }
+
+    // Skyboxes are much more difficult. They are represented
+    // by six single planes with different textures, so we'll
+    // need to build six meshes.
+
+    const ai_real l = 10.0; // the size used by Irrlicht
+
+    // FRONT SIDE
+    meshes.push_back(BuildSingleQuadMesh(
+            SkyboxVertex(-l, -l, -l, 0, 0, 1, 1.0, 1.0),
+            SkyboxVertex(l, -l, -l, 0, 0, 1, 0.0, 1.0),
+            SkyboxVertex(l, l, -l, 0, 0, 1, 0.0, 0.0),
+            SkyboxVertex(-l, l, -l, 0, 0, 1, 1.0, 0.0)));
+    meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 6u);
+
+    // LEFT SIDE
+    meshes.push_back(BuildSingleQuadMesh(
+            SkyboxVertex(l, -l, -l, -1, 0, 0, 1.0, 1.0),
+            SkyboxVertex(l, -l, l, -1, 0, 0, 0.0, 1.0),
+            SkyboxVertex(l, l, l, -1, 0, 0, 0.0, 0.0),
+            SkyboxVertex(l, l, -l, -1, 0, 0, 1.0, 0.0)));
+    meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 5u);
+
+    // BACK SIDE
+    meshes.push_back(BuildSingleQuadMesh(
+            SkyboxVertex(l, -l, l, 0, 0, -1, 1.0, 1.0),
+            SkyboxVertex(-l, -l, l, 0, 0, -1, 0.0, 1.0),
+            SkyboxVertex(-l, l, l, 0, 0, -1, 0.0, 0.0),
+            SkyboxVertex(l, l, l, 0, 0, -1, 1.0, 0.0)));
+    meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 4u);
+
+    // RIGHT SIDE
+    meshes.push_back(BuildSingleQuadMesh(
+            SkyboxVertex(-l, -l, l, 1, 0, 0, 1.0, 1.0),
+            SkyboxVertex(-l, -l, -l, 1, 0, 0, 0.0, 1.0),
+            SkyboxVertex(-l, l, -l, 1, 0, 0, 0.0, 0.0),
+            SkyboxVertex(-l, l, l, 1, 0, 0, 1.0, 0.0)));
+    meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 3u);
+
+    // TOP SIDE
+    meshes.push_back(BuildSingleQuadMesh(
+            SkyboxVertex(l, l, -l, 0, -1, 0, 1.0, 1.0),
+            SkyboxVertex(l, l, l, 0, -1, 0, 0.0, 1.0),
+            SkyboxVertex(-l, l, l, 0, -1, 0, 0.0, 0.0),
+            SkyboxVertex(-l, l, -l, 0, -1, 0, 1.0, 0.0)));
+    meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 2u);
+
+    // BOTTOM SIDE
+    meshes.push_back(BuildSingleQuadMesh(
+            SkyboxVertex(l, -l, l, 0, 1, 0, 0.0, 0.0),
+            SkyboxVertex(l, -l, -l, 0, 1, 0, 1.0, 0.0),
+            SkyboxVertex(-l, -l, -l, 0, 1, 0, 1.0, 1.0),
+            SkyboxVertex(-l, -l, l, 0, 1, 0, 0.0, 1.0)));
+    meshes.back()->mMaterialIndex = static_cast<unsigned int>(materials.size() - 1u);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void IRRImporter::CopyMaterial(std::vector<aiMaterial *> &materials,
 void IRRImporter::CopyMaterial(std::vector<aiMaterial *> &materials,
-		std::vector<std::pair<aiMaterial *, unsigned int>> &inmaterials,
-		unsigned int &defMatIdx,
-		aiMesh *mesh) {
-	if (inmaterials.empty()) {
-		// Do we have a default material? If not we need to create one
-		if (UINT_MAX == defMatIdx) {
-			defMatIdx = (unsigned int)materials.size();
-			//TODO: add this materials to someone?
-			/*aiMaterial* mat = new aiMaterial();
+        std::vector<std::pair<aiMaterial *, unsigned int>> &inmaterials,
+        unsigned int &defMatIdx,
+        aiMesh *mesh) {
+    if (inmaterials.empty()) {
+        // Do we have a default material? If not we need to create one
+        if (UINT_MAX == defMatIdx) {
+            defMatIdx = (unsigned int)materials.size();
+            // TODO: add this materials to someone?
+            /*aiMaterial* mat = new aiMaterial();
 
 
             aiString s;
             aiString s;
             s.Set(AI_DEFAULT_MATERIAL_NAME);
             s.Set(AI_DEFAULT_MATERIAL_NAME);
@@ -248,1110 +248,1101 @@ void IRRImporter::CopyMaterial(std::vector<aiMaterial *> &materials,
 
 
             aiColor3D c(0.6f,0.6f,0.6f);
             aiColor3D c(0.6f,0.6f,0.6f);
             mat->AddProperty(&c,1,AI_MATKEY_COLOR_DIFFUSE);*/
             mat->AddProperty(&c,1,AI_MATKEY_COLOR_DIFFUSE);*/
-		}
-		mesh->mMaterialIndex = defMatIdx;
-		return;
-	} else if (inmaterials.size() > 1) {
-		ASSIMP_LOG_INFO("IRR: Skipping additional materials");
-	}
-
-	mesh->mMaterialIndex = (unsigned int)materials.size();
-	materials.push_back(inmaterials[0].first);
+        }
+        mesh->mMaterialIndex = defMatIdx;
+        return;
+    } else if (inmaterials.size() > 1) {
+        ASSIMP_LOG_INFO("IRR: Skipping additional materials");
+    }
+
+    mesh->mMaterialIndex = (unsigned int)materials.size();
+    materials.push_back(inmaterials[0].first);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 inline int ClampSpline(int idx, int size) {
 inline int ClampSpline(int idx, int size) {
-	return (idx < 0 ? size + idx : (idx >= size ? idx - size : idx));
+    return (idx < 0 ? size + idx : (idx >= size ? idx - size : idx));
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 inline void FindSuitableMultiple(int &angle) {
 inline void FindSuitableMultiple(int &angle) {
-	if (angle < 3)
-		angle = 3;
-	else if (angle < 10)
-		angle = 10;
-	else if (angle < 20)
-		angle = 20;
-	else if (angle < 30)
-		angle = 30;
+    if (angle < 3)
+        angle = 3;
+    else if (angle < 10)
+        angle = 10;
+    else if (angle < 20)
+        angle = 20;
+    else if (angle < 30)
+        angle = 30;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void IRRImporter::ComputeAnimations(Node *root, aiNode *real, std::vector<aiNodeAnim *> &anims) {
 void IRRImporter::ComputeAnimations(Node *root, aiNode *real, std::vector<aiNodeAnim *> &anims) {
-	ai_assert(nullptr != root && nullptr != real);
-
-	// XXX totally WIP - doesn't produce proper results, need to evaluate
-	// whether there's any use for Irrlicht's proprietary scene format
-	// outside Irrlicht ...
-	// This also applies to the above function of FindSuitableMultiple and ClampSpline which are
-	// solely used in this function
-
-	if (root->animators.empty()) {
-		return;
-	}
-	unsigned int total(0);
-	for (std::list<Animator>::iterator it = root->animators.begin(); it != root->animators.end(); ++it) {
-		if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER) {
-			ASSIMP_LOG_WARN("IRR: Skipping unknown or unsupported animator");
-			continue;
-		}
-		++total;
-	}
-	if (!total) {
-		return;
-	} else if (1 == total) {
-		ASSIMP_LOG_WARN("IRR: Adding dummy nodes to simulate multiple animators");
-	}
-
-	// NOTE: 1 tick == i millisecond
-
-	unsigned int cur = 0;
-	for (std::list<Animator>::iterator it = root->animators.begin();
-			it != root->animators.end(); ++it) {
-		if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER) continue;
-
-		Animator &in = *it;
-		aiNodeAnim *anim = new aiNodeAnim();
-
-		if (cur != total - 1) {
-			// Build a new name - a prefix instead of a suffix because it is
-			// easier to check against
-			anim->mNodeName.length = ::ai_snprintf(anim->mNodeName.data, MAXLEN,
-					"$INST_DUMMY_%i_%s", total - 1,
-					(root->name.length() ? root->name.c_str() : ""));
-
-			// we'll also need to insert a dummy in the node hierarchy.
-			aiNode *dummy = new aiNode();
-
-			for (unsigned int i = 0; i < real->mParent->mNumChildren; ++i)
-				if (real->mParent->mChildren[i] == real)
-					real->mParent->mChildren[i] = dummy;
-
-			dummy->mParent = real->mParent;
-			dummy->mName = anim->mNodeName;
-
-			dummy->mNumChildren = 1;
-			dummy->mChildren = new aiNode *[dummy->mNumChildren];
-			dummy->mChildren[0] = real;
-
-			// the transformation matrix of the dummy node is the identity
-
-			real->mParent = dummy;
-		} else
-			anim->mNodeName.Set(root->name);
-		++cur;
-
-		switch (in.type) {
-			case Animator::ROTATION: {
-				// -----------------------------------------------------
-				// find out how long a full rotation will take
-				// This is the least common multiple of 360.f and all
-				// three euler angles. Although we'll surely find a
-				// possible multiple (haha) it could be somewhat large
-				// for our purposes. So we need to modify the angles
-				// here in order to get good results.
-				// -----------------------------------------------------
-				int angles[3];
-				angles[0] = (int)(in.direction.x * 100);
-				angles[1] = (int)(in.direction.y * 100);
-				angles[2] = (int)(in.direction.z * 100);
-
-				angles[0] %= 360;
-				angles[1] %= 360;
-				angles[2] %= 360;
-
-				if ((angles[0] * angles[1]) != 0 && (angles[1] * angles[2]) != 0) {
-					FindSuitableMultiple(angles[0]);
-					FindSuitableMultiple(angles[1]);
-					FindSuitableMultiple(angles[2]);
-				}
-
-				int lcm = 360;
-
-				if (angles[0])
-					lcm = Math::lcm(lcm, angles[0]);
-
-				if (angles[1])
-					lcm = Math::lcm(lcm, angles[1]);
-
-				if (angles[2])
-					lcm = Math::lcm(lcm, angles[2]);
-
-				if (360 == lcm)
-					break;
-
-
-				// find out how many time units we'll need for the finest
-				// track (in seconds) - this defines the number of output
-				// keys (fps * seconds)
-				float max = 0.f;
-				if (angles[0])
-					max = (float)lcm / angles[0];
-				if (angles[1])
-					max = std::max(max, (float)lcm / angles[1]);
-				if (angles[2])
-					max = std::max(max, (float)lcm / angles[2]);
-
-				anim->mNumRotationKeys = (unsigned int)(max * fps);
-				anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys];
-
-				// begin with a zero angle
-				aiVector3D angle;
-				for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) {
-					// build the quaternion for the given euler angles
-					aiQuatKey &q = anim->mRotationKeys[i];
-
-					q.mValue = aiQuaternion(angle.x, angle.y, angle.z);
-					q.mTime = (double)i;
-
-					// increase the angle
-					angle += in.direction;
-				}
-
-				// This animation is repeated and repeated ...
-				anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT;
-			} break;
-
-			case Animator::FLY_CIRCLE: {
-				// -----------------------------------------------------
-				// Find out how much time we'll need to perform a
-				// full circle.
-				// -----------------------------------------------------
-				const double seconds = (1. / in.speed) / 1000.;
-				const double tdelta = 1000. / fps;
-
-				anim->mNumPositionKeys = (unsigned int)(fps * seconds);
-				anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
-
-				// from Irrlicht, what else should we do than copying it?
-				aiVector3D vecU, vecV;
-				if (in.direction.y) {
-					vecV = aiVector3D(50, 0, 0) ^ in.direction;
-				} else
-					vecV = aiVector3D(0, 50, 00) ^ in.direction;
-				vecV.Normalize();
-				vecU = (vecV ^ in.direction).Normalize();
-
-				// build the output keys
-				for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) {
-					aiVectorKey &key = anim->mPositionKeys[i];
-					key.mTime = i * tdelta;
-
-					const ai_real t = (ai_real)(in.speed * key.mTime);
-					key.mValue = in.circleCenter + in.circleRadius * ((vecU * std::cos(t)) + (vecV * std::sin(t)));
-				}
-
-				// This animation is repeated and repeated ...
-				anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT;
-			} break;
-
-			case Animator::FLY_STRAIGHT: {
-				anim->mPostState = anim->mPreState = (in.loop ? aiAnimBehaviour_REPEAT : aiAnimBehaviour_CONSTANT);
-				const double seconds = in.timeForWay / 1000.;
-				const double tdelta = 1000. / fps;
-
-				anim->mNumPositionKeys = (unsigned int)(fps * seconds);
-				anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
-
-				aiVector3D diff = in.direction - in.circleCenter;
-				const ai_real lengthOfWay = diff.Length();
-				diff.Normalize();
-
-				const double timeFactor = lengthOfWay / in.timeForWay;
-
-				// build the output keys
-				for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) {
-					aiVectorKey &key = anim->mPositionKeys[i];
-					key.mTime = i * tdelta;
-					key.mValue = in.circleCenter + diff * ai_real(timeFactor * key.mTime);
-				}
-			} break;
-
-			case Animator::FOLLOW_SPLINE: {
-				// repeat outside the defined time range
-				anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT;
-				const int size = (int)in.splineKeys.size();
-				if (!size) {
-					// We have no point in the spline. That's bad. Really bad.
-					ASSIMP_LOG_WARN("IRR: Spline animators with no points defined");
-
-					delete anim;
-					anim = nullptr;
-					break;
-				} else if (size == 1) {
-					// We have just one point in the spline so we don't need the full calculation
-					anim->mNumPositionKeys = 1;
-					anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
-
-					anim->mPositionKeys[0].mValue = in.splineKeys[0].mValue;
-					anim->mPositionKeys[0].mTime = 0.f;
-					break;
-				}
-
-				unsigned int ticksPerFull = 15;
-				anim->mNumPositionKeys = (unsigned int)(ticksPerFull * fps);
-				anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
-
-				for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) {
-					aiVectorKey &key = anim->mPositionKeys[i];
-
-					const ai_real dt = (i * in.speed * ai_real(0.001));
-					const ai_real u = dt - std::floor(dt);
-					const int idx = (int)std::floor(dt) % size;
-
-					// get the 4 current points to evaluate the spline
-					const aiVector3D &p0 = in.splineKeys[ClampSpline(idx - 1, size)].mValue;
-					const aiVector3D &p1 = in.splineKeys[ClampSpline(idx + 0, size)].mValue;
-					const aiVector3D &p2 = in.splineKeys[ClampSpline(idx + 1, size)].mValue;
-					const aiVector3D &p3 = in.splineKeys[ClampSpline(idx + 2, size)].mValue;
-
-					// compute polynomials
-					const ai_real u2 = u * u;
-					const ai_real u3 = u2 * 2;
-
-					const ai_real h1 = ai_real(2.0) * u3 - ai_real(3.0) * u2 + ai_real(1.0);
-					const ai_real h2 = ai_real(-2.0) * u3 + ai_real(3.0) * u3;
-					const ai_real h3 = u3 - ai_real(2.0) * u3;
-					const ai_real h4 = u3 - u2;
-
-					// compute the spline tangents
-					const aiVector3D t1 = (p2 - p0) * in.tightness;
-					aiVector3D t2 = (p3 - p1) * in.tightness;
-
-					// and use them to get the interpolated point
-					t2 = (h1 * p1 + p2 * h2 + t1 * h3 + h4 * t2);
-
-					// build a simple translation matrix from it
-					key.mValue = t2;
-					key.mTime = (double)i;
-				}
-			} break;
-			default:
-				// UNKNOWN , OTHER
-				break;
-		};
-		if (anim) {
-			anims.push_back(anim);
-			++total;
-		}
-	}
+    ai_assert(nullptr != root && nullptr != real);
+
+    // XXX totally WIP - doesn't produce proper results, need to evaluate
+    // whether there's any use for Irrlicht's proprietary scene format
+    // outside Irrlicht ...
+    // This also applies to the above function of FindSuitableMultiple and ClampSpline which are
+    // solely used in this function
+
+    if (root->animators.empty()) {
+        return;
+    }
+    unsigned int total(0);
+    for (std::list<Animator>::iterator it = root->animators.begin(); it != root->animators.end(); ++it) {
+        if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER) {
+            ASSIMP_LOG_WARN("IRR: Skipping unknown or unsupported animator");
+            continue;
+        }
+        ++total;
+    }
+    if (!total) {
+        return;
+    } else if (1 == total) {
+        ASSIMP_LOG_WARN("IRR: Adding dummy nodes to simulate multiple animators");
+    }
+
+    // NOTE: 1 tick == i millisecond
+
+    unsigned int cur = 0;
+    for (std::list<Animator>::iterator it = root->animators.begin();
+            it != root->animators.end(); ++it) {
+        if ((*it).type == Animator::UNKNOWN || (*it).type == Animator::OTHER) continue;
+
+        Animator &in = *it;
+        aiNodeAnim *anim = new aiNodeAnim();
+
+        if (cur != total - 1) {
+            // Build a new name - a prefix instead of a suffix because it is
+            // easier to check against
+            anim->mNodeName.length = ::ai_snprintf(anim->mNodeName.data, MAXLEN,
+                    "$INST_DUMMY_%i_%s", total - 1,
+                    (root->name.length() ? root->name.c_str() : ""));
+
+            // we'll also need to insert a dummy in the node hierarchy.
+            aiNode *dummy = new aiNode();
+
+            for (unsigned int i = 0; i < real->mParent->mNumChildren; ++i)
+                if (real->mParent->mChildren[i] == real)
+                    real->mParent->mChildren[i] = dummy;
+
+            dummy->mParent = real->mParent;
+            dummy->mName = anim->mNodeName;
+
+            dummy->mNumChildren = 1;
+            dummy->mChildren = new aiNode *[dummy->mNumChildren];
+            dummy->mChildren[0] = real;
+
+            // the transformation matrix of the dummy node is the identity
+
+            real->mParent = dummy;
+        } else
+            anim->mNodeName.Set(root->name);
+        ++cur;
+
+        switch (in.type) {
+        case Animator::ROTATION: {
+            // -----------------------------------------------------
+            // find out how long a full rotation will take
+            // This is the least common multiple of 360.f and all
+            // three euler angles. Although we'll surely find a
+            // possible multiple (haha) it could be somewhat large
+            // for our purposes. So we need to modify the angles
+            // here in order to get good results.
+            // -----------------------------------------------------
+            int angles[3];
+            angles[0] = (int)(in.direction.x * 100);
+            angles[1] = (int)(in.direction.y * 100);
+            angles[2] = (int)(in.direction.z * 100);
+
+            angles[0] %= 360;
+            angles[1] %= 360;
+            angles[2] %= 360;
+
+            if ((angles[0] * angles[1]) != 0 && (angles[1] * angles[2]) != 0) {
+                FindSuitableMultiple(angles[0]);
+                FindSuitableMultiple(angles[1]);
+                FindSuitableMultiple(angles[2]);
+            }
+
+            int lcm = 360;
+
+            if (angles[0])
+                lcm = Math::lcm(lcm, angles[0]);
+
+            if (angles[1])
+                lcm = Math::lcm(lcm, angles[1]);
+
+            if (angles[2])
+                lcm = Math::lcm(lcm, angles[2]);
+
+            if (360 == lcm)
+                break;
+
+            // find out how many time units we'll need for the finest
+            // track (in seconds) - this defines the number of output
+            // keys (fps * seconds)
+            float max = 0.f;
+            if (angles[0])
+                max = (float)lcm / angles[0];
+            if (angles[1])
+                max = std::max(max, (float)lcm / angles[1]);
+            if (angles[2])
+                max = std::max(max, (float)lcm / angles[2]);
+
+            anim->mNumRotationKeys = (unsigned int)(max * fps);
+            anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys];
+
+            // begin with a zero angle
+            aiVector3D angle;
+            for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) {
+                // build the quaternion for the given euler angles
+                aiQuatKey &q = anim->mRotationKeys[i];
+
+                q.mValue = aiQuaternion(angle.x, angle.y, angle.z);
+                q.mTime = (double)i;
+
+                // increase the angle
+                angle += in.direction;
+            }
+
+            // This animation is repeated and repeated ...
+            anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT;
+        } break;
+
+        case Animator::FLY_CIRCLE: {
+            // -----------------------------------------------------
+            // Find out how much time we'll need to perform a
+            // full circle.
+            // -----------------------------------------------------
+            const double seconds = (1. / in.speed) / 1000.;
+            const double tdelta = 1000. / fps;
+
+            anim->mNumPositionKeys = (unsigned int)(fps * seconds);
+            anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
+
+            // from Irrlicht, what else should we do than copying it?
+            aiVector3D vecU, vecV;
+            if (in.direction.y) {
+                vecV = aiVector3D(50, 0, 0) ^ in.direction;
+            } else
+                vecV = aiVector3D(0, 50, 00) ^ in.direction;
+            vecV.Normalize();
+            vecU = (vecV ^ in.direction).Normalize();
+
+            // build the output keys
+            for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) {
+                aiVectorKey &key = anim->mPositionKeys[i];
+                key.mTime = i * tdelta;
+
+                const ai_real t = (ai_real)(in.speed * key.mTime);
+                key.mValue = in.circleCenter + in.circleRadius * ((vecU * std::cos(t)) + (vecV * std::sin(t)));
+            }
+
+            // This animation is repeated and repeated ...
+            anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT;
+        } break;
+
+        case Animator::FLY_STRAIGHT: {
+            anim->mPostState = anim->mPreState = (in.loop ? aiAnimBehaviour_REPEAT : aiAnimBehaviour_CONSTANT);
+            const double seconds = in.timeForWay / 1000.;
+            const double tdelta = 1000. / fps;
+
+            anim->mNumPositionKeys = (unsigned int)(fps * seconds);
+            anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
+
+            aiVector3D diff = in.direction - in.circleCenter;
+            const ai_real lengthOfWay = diff.Length();
+            diff.Normalize();
+
+            const double timeFactor = lengthOfWay / in.timeForWay;
+
+            // build the output keys
+            for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) {
+                aiVectorKey &key = anim->mPositionKeys[i];
+                key.mTime = i * tdelta;
+                key.mValue = in.circleCenter + diff * ai_real(timeFactor * key.mTime);
+            }
+        } break;
+
+        case Animator::FOLLOW_SPLINE: {
+            // repeat outside the defined time range
+            anim->mPostState = anim->mPreState = aiAnimBehaviour_REPEAT;
+            const int size = (int)in.splineKeys.size();
+            if (!size) {
+                // We have no point in the spline. That's bad. Really bad.
+                ASSIMP_LOG_WARN("IRR: Spline animators with no points defined");
+
+                delete anim;
+                anim = nullptr;
+                break;
+            } else if (size == 1) {
+                // We have just one point in the spline so we don't need the full calculation
+                anim->mNumPositionKeys = 1;
+                anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
+
+                anim->mPositionKeys[0].mValue = in.splineKeys[0].mValue;
+                anim->mPositionKeys[0].mTime = 0.f;
+                break;
+            }
+
+            unsigned int ticksPerFull = 15;
+            anim->mNumPositionKeys = (unsigned int)(ticksPerFull * fps);
+            anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys];
+
+            for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) {
+                aiVectorKey &key = anim->mPositionKeys[i];
+
+                const ai_real dt = (i * in.speed * ai_real(0.001));
+                const ai_real u = dt - std::floor(dt);
+                const int idx = (int)std::floor(dt) % size;
+
+                // get the 4 current points to evaluate the spline
+                const aiVector3D &p0 = in.splineKeys[ClampSpline(idx - 1, size)].mValue;
+                const aiVector3D &p1 = in.splineKeys[ClampSpline(idx + 0, size)].mValue;
+                const aiVector3D &p2 = in.splineKeys[ClampSpline(idx + 1, size)].mValue;
+                const aiVector3D &p3 = in.splineKeys[ClampSpline(idx + 2, size)].mValue;
+
+                // compute polynomials
+                const ai_real u2 = u * u;
+                const ai_real u3 = u2 * 2;
+
+                const ai_real h1 = ai_real(2.0) * u3 - ai_real(3.0) * u2 + ai_real(1.0);
+                const ai_real h2 = ai_real(-2.0) * u3 + ai_real(3.0) * u3;
+                const ai_real h3 = u3 - ai_real(2.0) * u3;
+                const ai_real h4 = u3 - u2;
+
+                // compute the spline tangents
+                const aiVector3D t1 = (p2 - p0) * in.tightness;
+                aiVector3D t2 = (p3 - p1) * in.tightness;
+
+                // and use them to get the interpolated point
+                t2 = (h1 * p1 + p2 * h2 + t1 * h3 + h4 * t2);
+
+                // build a simple translation matrix from it
+                key.mValue = t2;
+                key.mTime = (double)i;
+            }
+        } break;
+        default:
+            // UNKNOWN , OTHER
+            break;
+        };
+        if (anim) {
+            anims.push_back(anim);
+            ++total;
+        }
+    }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // This function is maybe more generic than we'd need it here
 // This function is maybe more generic than we'd need it here
 void SetupMapping(aiMaterial *mat, aiTextureMapping mode, const aiVector3D &axis = aiVector3D(0.f, 0.f, -1.f)) {
 void SetupMapping(aiMaterial *mat, aiTextureMapping mode, const aiVector3D &axis = aiVector3D(0.f, 0.f, -1.f)) {
-	if (nullptr == mat) {
-		return;
-	}
+    if (nullptr == mat) {
+        return;
+    }
 
 
     // Check whether there are texture properties defined - setup
     // Check whether there are texture properties defined - setup
-	// the desired texture mapping mode for all of them and ignore
-	// all UV settings we might encounter. WE HAVE NO UVS!
-
-	std::vector<aiMaterialProperty *> p;
-	p.reserve(mat->mNumProperties + 1);
-
-	for (unsigned int i = 0; i < mat->mNumProperties; ++i) {
-		aiMaterialProperty *prop = mat->mProperties[i];
-		if (!::strcmp(prop->mKey.data, "$tex.file")) {
-			// Setup the mapping key
-			aiMaterialProperty *m = new aiMaterialProperty();
-			m->mKey.Set("$tex.mapping");
-			m->mIndex = prop->mIndex;
-			m->mSemantic = prop->mSemantic;
-			m->mType = aiPTI_Integer;
-
-			m->mDataLength = 4;
-			m->mData = new char[4];
-			*((int *)m->mData) = mode;
-
-			p.push_back(prop);
-			p.push_back(m);
-
-			// Setup the mapping axis
-			if (mode == aiTextureMapping_CYLINDER || mode == aiTextureMapping_PLANE || mode == aiTextureMapping_SPHERE) {
-				m = new aiMaterialProperty();
-				m->mKey.Set("$tex.mapaxis");
-				m->mIndex = prop->mIndex;
-				m->mSemantic = prop->mSemantic;
-				m->mType = aiPTI_Float;
-
-				m->mDataLength = 12;
-				m->mData = new char[12];
-				*((aiVector3D *)m->mData) = axis;
-				p.push_back(m);
-			}
-		} else if (!::strcmp(prop->mKey.data, "$tex.uvwsrc")) {
-			delete mat->mProperties[i];
-		} else
-			p.push_back(prop);
-	}
-
-	if (p.empty()) return;
-
-	// rebuild the output array
-	if (p.size() > mat->mNumAllocated) {
-		delete[] mat->mProperties;
-		mat->mProperties = new aiMaterialProperty *[p.size() * 2];
-
-		mat->mNumAllocated = static_cast<unsigned int>(p.size() * 2);
-	}
-	mat->mNumProperties = (unsigned int)p.size();
-	::memcpy(mat->mProperties, &p[0], sizeof(void *) * mat->mNumProperties);
+    // the desired texture mapping mode for all of them and ignore
+    // all UV settings we might encounter. WE HAVE NO UVS!
+
+    std::vector<aiMaterialProperty *> p;
+    p.reserve(mat->mNumProperties + 1);
+
+    for (unsigned int i = 0; i < mat->mNumProperties; ++i) {
+        aiMaterialProperty *prop = mat->mProperties[i];
+        if (!::strcmp(prop->mKey.data, "$tex.file")) {
+            // Setup the mapping key
+            aiMaterialProperty *m = new aiMaterialProperty();
+            m->mKey.Set("$tex.mapping");
+            m->mIndex = prop->mIndex;
+            m->mSemantic = prop->mSemantic;
+            m->mType = aiPTI_Integer;
+
+            m->mDataLength = 4;
+            m->mData = new char[4];
+            *((int *)m->mData) = mode;
+
+            p.push_back(prop);
+            p.push_back(m);
+
+            // Setup the mapping axis
+            if (mode == aiTextureMapping_CYLINDER || mode == aiTextureMapping_PLANE || mode == aiTextureMapping_SPHERE) {
+                m = new aiMaterialProperty();
+                m->mKey.Set("$tex.mapaxis");
+                m->mIndex = prop->mIndex;
+                m->mSemantic = prop->mSemantic;
+                m->mType = aiPTI_Float;
+
+                m->mDataLength = 12;
+                m->mData = new char[12];
+                *((aiVector3D *)m->mData) = axis;
+                p.push_back(m);
+            }
+        } else if (!::strcmp(prop->mKey.data, "$tex.uvwsrc")) {
+            delete mat->mProperties[i];
+        } else
+            p.push_back(prop);
+    }
+
+    if (p.empty()) return;
+
+    // rebuild the output array
+    if (p.size() > mat->mNumAllocated) {
+        delete[] mat->mProperties;
+        mat->mProperties = new aiMaterialProperty *[p.size() * 2];
+
+        mat->mNumAllocated = static_cast<unsigned int>(p.size() * 2);
+    }
+    mat->mNumProperties = (unsigned int)p.size();
+    ::memcpy(mat->mProperties, &p[0], sizeof(void *) * mat->mNumProperties);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void IRRImporter::GenerateGraph(Node *root, aiNode *rootOut, aiScene *scene,
 void IRRImporter::GenerateGraph(Node *root, aiNode *rootOut, aiScene *scene,
-		BatchLoader &batch,
-		std::vector<aiMesh *> &meshes,
-		std::vector<aiNodeAnim *> &anims,
-		std::vector<AttachmentInfo> &attach,
-		std::vector<aiMaterial *> &materials,
-		unsigned int &defMatIdx) {
-	unsigned int oldMeshSize = (unsigned int)meshes.size();
-	//unsigned int meshTrafoAssign = 0;
-
-	// Now determine the type of the node
-	switch (root->type) {
-		case Node::ANIMMESH:
-		case Node::MESH: {
-			if (!root->meshPath.length())
-				break;
-
-			// Get the loaded mesh from the scene and add it to
-			// the list of all scenes to be attached to the
-			// graph we're currently building
-			aiScene *localScene = batch.GetImport(root->id);
-			if (!localScene) {
-				ASSIMP_LOG_ERROR("IRR: Unable to load external file: ", root->meshPath);
-				break;
-			}
-			attach.emplace_back(localScene, rootOut);
-
-			// Now combine the material we've loaded for this mesh
-			// with the real materials we got from the file. As we
-			// don't execute any pp-steps on the file, the numbers
-			// should be equal. If they are not, we can impossibly
-			// do this  ...
-			if (root->materials.size() != (unsigned int)localScene->mNumMaterials) {
-				ASSIMP_LOG_WARN("IRR: Failed to match imported materials "
-								"with the materials found in the IRR scene file");
-
-				break;
-			}
-			for (unsigned int i = 0; i < localScene->mNumMaterials; ++i) {
-				// Delete the old material, we don't need it anymore
-				delete localScene->mMaterials[i];
-
-				std::pair<aiMaterial *, unsigned int> &src = root->materials[i];
-				localScene->mMaterials[i] = src.first;
-			}
-
-			// NOTE: Each mesh should have exactly one material assigned,
-			// but we do it in a separate loop if this behavior changes
-			// in future.
-			for (unsigned int i = 0; i < localScene->mNumMeshes; ++i) {
-				// Process material flags
-				aiMesh *mesh = localScene->mMeshes[i];
-
-				// If "trans_vertex_alpha" mode is enabled, search all vertex colors
-				// and check whether they have a common alpha value. This is quite
-				// often the case so we can simply extract it to a shared oacity
-				// value.
-				std::pair<aiMaterial *, unsigned int> &src = root->materials[mesh->mMaterialIndex];
-				aiMaterial *mat = (aiMaterial *)src.first;
-
-				if (mesh->HasVertexColors(0) && src.second & AI_IRRMESH_MAT_trans_vertex_alpha) {
-					bool bdo = true;
-					for (unsigned int a = 1; a < mesh->mNumVertices; ++a) {
-
-						if (mesh->mColors[0][a].a != mesh->mColors[0][a - 1].a) {
-							bdo = false;
-							break;
-						}
-					}
-					if (bdo) {
-						ASSIMP_LOG_INFO("IRR: Replacing mesh vertex alpha with common opacity");
-
-						for (unsigned int a = 0; a < mesh->mNumVertices; ++a)
-							mesh->mColors[0][a].a = 1.f;
-
-						mat->AddProperty(&mesh->mColors[0][0].a, 1, AI_MATKEY_OPACITY);
-					}
-				}
-
-				// If we have a second texture coordinate set and a second texture
-				// (either light-map, normal-map, 2layered material) we need to
-				// setup the correct UV index for it. The texture can either
-				// be diffuse (light-map & 2layer) or a normal map (normal & parallax)
-				if (mesh->HasTextureCoords(1)) {
-
-					int idx = 1;
-					if (src.second & (AI_IRRMESH_MAT_solid_2layer | AI_IRRMESH_MAT_lightmap)) {
-						mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_DIFFUSE(0));
-					} else if (src.second & AI_IRRMESH_MAT_normalmap_solid) {
-						mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_NORMALS(0));
-					}
-				}
-			}
-		} break;
-
-		case Node::LIGHT:
-		case Node::CAMERA:
-
-			// We're already finished with lights and cameras
-			break;
-
-		case Node::SPHERE: {
-			// Generate the sphere model. Our input parameter to
-			// the sphere generation algorithm is the number of
-			// subdivisions of each triangle - but here we have
-			// the number of polygons on a specific axis. Just
-			// use some hard-coded limits to approximate this ...
-			unsigned int mul = root->spherePolyCountX * root->spherePolyCountY;
-			if (mul < 100)
-				mul = 2;
-			else if (mul < 300)
-				mul = 3;
-			else
-				mul = 4;
-
-			meshes.push_back(StandardShapes::MakeMesh(mul,
-					&StandardShapes::MakeSphere));
-
-			// Adjust scaling
-			root->scaling *= root->sphereRadius / 2;
-
-			// Copy one output material
-			CopyMaterial(materials, root->materials, defMatIdx, meshes.back());
-
-			// Now adjust this output material - if there is a first texture
-			// set, setup spherical UV mapping around the Y axis.
-			SetupMapping((aiMaterial *)materials.back(), aiTextureMapping_SPHERE);
-		} break;
-
-		case Node::CUBE: {
-			// Generate an unit cube first
-			meshes.push_back(StandardShapes::MakeMesh(
-					&StandardShapes::MakeHexahedron));
-
-			// Adjust scaling
-			root->scaling *= root->sphereRadius;
-
-			// Copy one output material
-			CopyMaterial(materials, root->materials, defMatIdx, meshes.back());
-
-			// Now adjust this output material - if there is a first texture
-			// set, setup cubic UV mapping
-			SetupMapping((aiMaterial *)materials.back(), aiTextureMapping_BOX);
-		} break;
-
-		case Node::SKYBOX: {
-			// A sky-box is defined by six materials
-			if (root->materials.size() < 6) {
-				ASSIMP_LOG_ERROR("IRR: There should be six materials for a skybox");
-				break;
-			}
-
-			// copy those materials and generate 6 meshes for our new sky-box
-			materials.reserve(materials.size() + 6);
-			for (unsigned int i = 0; i < 6; ++i)
-				materials.insert(materials.end(), root->materials[i].first);
-
-			BuildSkybox(meshes, materials);
-
-			// *************************************************************
-			// Skyboxes will require a different code path for rendering,
-			// so there must be a way for the user to add special support
-			// for IRR skyboxes. We add a 'IRR.SkyBox_' prefix to the node.
-			// *************************************************************
-			root->name = "IRR.SkyBox_" + root->name;
-			ASSIMP_LOG_INFO("IRR: Loading skybox, this will "
-							"require special handling to be displayed correctly");
-		} break;
-
-		case Node::TERRAIN: {
-			// to support terrains, we'd need to have a texture decoder
-			ASSIMP_LOG_ERROR("IRR: Unsupported node - TERRAIN");
-		} break;
-		default:
-			// DUMMY
-			break;
-	};
-
-	// Check whether we added a mesh (or more than one ...). In this case
-	// we'll also need to attach it to the node
-	if (oldMeshSize != (unsigned int)meshes.size()) {
-
-		rootOut->mNumMeshes = (unsigned int)meshes.size() - oldMeshSize;
-		rootOut->mMeshes = new unsigned int[rootOut->mNumMeshes];
-
-		for (unsigned int a = 0; a < rootOut->mNumMeshes; ++a) {
-			rootOut->mMeshes[a] = oldMeshSize + a;
-		}
-	}
-
-	// Setup the name of this node
-	rootOut->mName.Set(root->name);
-
-	// Now compute the final local transformation matrix of the
-	// node from the given translation, rotation and scaling values.
-	// (the rotation is given in Euler angles, XYZ order)
-	//std::swap((float&)root->rotation.z,(float&)root->rotation.y);
-	rootOut->mTransformation.FromEulerAnglesXYZ(AI_DEG_TO_RAD(root->rotation));
-
-	// apply scaling
-	aiMatrix4x4 &mat = rootOut->mTransformation;
-	mat.a1 *= root->scaling.x;
-	mat.b1 *= root->scaling.x;
-	mat.c1 *= root->scaling.x;
-	mat.a2 *= root->scaling.y;
-	mat.b2 *= root->scaling.y;
-	mat.c2 *= root->scaling.y;
-	mat.a3 *= root->scaling.z;
-	mat.b3 *= root->scaling.z;
-	mat.c3 *= root->scaling.z;
-
-	// apply translation
-	mat.a4 += root->position.x;
-	mat.b4 += root->position.y;
-	mat.c4 += root->position.z;
-
-	// now compute animations for the node
-	ComputeAnimations(root, rootOut, anims);
-
-	// Add all children recursively. First allocate enough storage
-	// for them, then call us again
-	rootOut->mNumChildren = (unsigned int)root->children.size();
-	if (rootOut->mNumChildren) {
-
-		rootOut->mChildren = new aiNode *[rootOut->mNumChildren];
-		for (unsigned int i = 0; i < rootOut->mNumChildren; ++i) {
-
-			aiNode *node = rootOut->mChildren[i] = new aiNode();
-			node->mParent = rootOut;
-			GenerateGraph(root->children[i], node, scene, batch, meshes,
-					anims, attach, materials, defMatIdx);
-		}
-	}
+        BatchLoader &batch,
+        std::vector<aiMesh *> &meshes,
+        std::vector<aiNodeAnim *> &anims,
+        std::vector<AttachmentInfo> &attach,
+        std::vector<aiMaterial *> &materials,
+        unsigned int &defMatIdx) {
+    unsigned int oldMeshSize = (unsigned int)meshes.size();
+    // unsigned int meshTrafoAssign = 0;
+
+    // Now determine the type of the node
+    switch (root->type) {
+    case Node::ANIMMESH:
+    case Node::MESH: {
+        if (!root->meshPath.length())
+            break;
+
+        // Get the loaded mesh from the scene and add it to
+        // the list of all scenes to be attached to the
+        // graph we're currently building
+        aiScene *localScene = batch.GetImport(root->id);
+        if (!localScene) {
+            ASSIMP_LOG_ERROR("IRR: Unable to load external file: ", root->meshPath);
+            break;
+        }
+        attach.emplace_back(localScene, rootOut);
+
+        // Now combine the material we've loaded for this mesh
+        // with the real materials we got from the file. As we
+        // don't execute any pp-steps on the file, the numbers
+        // should be equal. If they are not, we can impossibly
+        // do this  ...
+        if (root->materials.size() != (unsigned int)localScene->mNumMaterials) {
+            ASSIMP_LOG_WARN("IRR: Failed to match imported materials "
+                            "with the materials found in the IRR scene file");
+
+            break;
+        }
+        for (unsigned int i = 0; i < localScene->mNumMaterials; ++i) {
+            // Delete the old material, we don't need it anymore
+            delete localScene->mMaterials[i];
+
+            std::pair<aiMaterial *, unsigned int> &src = root->materials[i];
+            localScene->mMaterials[i] = src.first;
+        }
+
+        // NOTE: Each mesh should have exactly one material assigned,
+        // but we do it in a separate loop if this behavior changes
+        // in future.
+        for (unsigned int i = 0; i < localScene->mNumMeshes; ++i) {
+            // Process material flags
+            aiMesh *mesh = localScene->mMeshes[i];
+
+            // If "trans_vertex_alpha" mode is enabled, search all vertex colors
+            // and check whether they have a common alpha value. This is quite
+            // often the case so we can simply extract it to a shared oacity
+            // value.
+            std::pair<aiMaterial *, unsigned int> &src = root->materials[mesh->mMaterialIndex];
+            aiMaterial *mat = (aiMaterial *)src.first;
+
+            if (mesh->HasVertexColors(0) && src.second & AI_IRRMESH_MAT_trans_vertex_alpha) {
+                bool bdo = true;
+                for (unsigned int a = 1; a < mesh->mNumVertices; ++a) {
+
+                    if (mesh->mColors[0][a].a != mesh->mColors[0][a - 1].a) {
+                        bdo = false;
+                        break;
+                    }
+                }
+                if (bdo) {
+                    ASSIMP_LOG_INFO("IRR: Replacing mesh vertex alpha with common opacity");
+
+                    for (unsigned int a = 0; a < mesh->mNumVertices; ++a)
+                        mesh->mColors[0][a].a = 1.f;
+
+                    mat->AddProperty(&mesh->mColors[0][0].a, 1, AI_MATKEY_OPACITY);
+                }
+            }
+
+            // If we have a second texture coordinate set and a second texture
+            // (either light-map, normal-map, 2layered material) we need to
+            // setup the correct UV index for it. The texture can either
+            // be diffuse (light-map & 2layer) or a normal map (normal & parallax)
+            if (mesh->HasTextureCoords(1)) {
+
+                int idx = 1;
+                if (src.second & (AI_IRRMESH_MAT_solid_2layer | AI_IRRMESH_MAT_lightmap)) {
+                    mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_DIFFUSE(0));
+                } else if (src.second & AI_IRRMESH_MAT_normalmap_solid) {
+                    mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_NORMALS(0));
+                }
+            }
+        }
+    } break;
+
+    case Node::LIGHT:
+    case Node::CAMERA:
+
+        // We're already finished with lights and cameras
+        break;
+
+    case Node::SPHERE: {
+        // Generate the sphere model. Our input parameter to
+        // the sphere generation algorithm is the number of
+        // subdivisions of each triangle - but here we have
+        // the number of polygons on a specific axis. Just
+        // use some hard-coded limits to approximate this ...
+        unsigned int mul = root->spherePolyCountX * root->spherePolyCountY;
+        if (mul < 100)
+            mul = 2;
+        else if (mul < 300)
+            mul = 3;
+        else
+            mul = 4;
+
+        meshes.push_back(StandardShapes::MakeMesh(mul,
+                &StandardShapes::MakeSphere));
+
+        // Adjust scaling
+        root->scaling *= root->sphereRadius / 2;
+
+        // Copy one output material
+        CopyMaterial(materials, root->materials, defMatIdx, meshes.back());
+
+        // Now adjust this output material - if there is a first texture
+        // set, setup spherical UV mapping around the Y axis.
+        SetupMapping((aiMaterial *)materials.back(), aiTextureMapping_SPHERE);
+    } break;
+
+    case Node::CUBE: {
+        // Generate an unit cube first
+        meshes.push_back(StandardShapes::MakeMesh(
+                &StandardShapes::MakeHexahedron));
+
+        // Adjust scaling
+        root->scaling *= root->sphereRadius;
+
+        // Copy one output material
+        CopyMaterial(materials, root->materials, defMatIdx, meshes.back());
+
+        // Now adjust this output material - if there is a first texture
+        // set, setup cubic UV mapping
+        SetupMapping((aiMaterial *)materials.back(), aiTextureMapping_BOX);
+    } break;
+
+    case Node::SKYBOX: {
+        // A sky-box is defined by six materials
+        if (root->materials.size() < 6) {
+            ASSIMP_LOG_ERROR("IRR: There should be six materials for a skybox");
+            break;
+        }
+
+        // copy those materials and generate 6 meshes for our new sky-box
+        materials.reserve(materials.size() + 6);
+        for (unsigned int i = 0; i < 6; ++i)
+            materials.insert(materials.end(), root->materials[i].first);
+
+        BuildSkybox(meshes, materials);
+
+        // *************************************************************
+        // Skyboxes will require a different code path for rendering,
+        // so there must be a way for the user to add special support
+        // for IRR skyboxes. We add a 'IRR.SkyBox_' prefix to the node.
+        // *************************************************************
+        root->name = "IRR.SkyBox_" + root->name;
+        ASSIMP_LOG_INFO("IRR: Loading skybox, this will "
+                        "require special handling to be displayed correctly");
+    } break;
+
+    case Node::TERRAIN: {
+        // to support terrains, we'd need to have a texture decoder
+        ASSIMP_LOG_ERROR("IRR: Unsupported node - TERRAIN");
+    } break;
+    default:
+        // DUMMY
+        break;
+    };
+
+    // Check whether we added a mesh (or more than one ...). In this case
+    // we'll also need to attach it to the node
+    if (oldMeshSize != (unsigned int)meshes.size()) {
+
+        rootOut->mNumMeshes = (unsigned int)meshes.size() - oldMeshSize;
+        rootOut->mMeshes = new unsigned int[rootOut->mNumMeshes];
+
+        for (unsigned int a = 0; a < rootOut->mNumMeshes; ++a) {
+            rootOut->mMeshes[a] = oldMeshSize + a;
+        }
+    }
+
+    // Setup the name of this node
+    rootOut->mName.Set(root->name);
+
+    // Now compute the final local transformation matrix of the
+    // node from the given translation, rotation and scaling values.
+    // (the rotation is given in Euler angles, XYZ order)
+    // std::swap((float&)root->rotation.z,(float&)root->rotation.y);
+    rootOut->mTransformation.FromEulerAnglesXYZ(AI_DEG_TO_RAD(root->rotation));
+
+    // apply scaling
+    aiMatrix4x4 &mat = rootOut->mTransformation;
+    mat.a1 *= root->scaling.x;
+    mat.b1 *= root->scaling.x;
+    mat.c1 *= root->scaling.x;
+    mat.a2 *= root->scaling.y;
+    mat.b2 *= root->scaling.y;
+    mat.c2 *= root->scaling.y;
+    mat.a3 *= root->scaling.z;
+    mat.b3 *= root->scaling.z;
+    mat.c3 *= root->scaling.z;
+
+    // apply translation
+    mat.a4 += root->position.x;
+    mat.b4 += root->position.y;
+    mat.c4 += root->position.z;
+
+    // now compute animations for the node
+    ComputeAnimations(root, rootOut, anims);
+
+    // Add all children recursively. First allocate enough storage
+    // for them, then call us again
+    rootOut->mNumChildren = (unsigned int)root->children.size();
+    if (rootOut->mNumChildren) {
+
+        rootOut->mChildren = new aiNode *[rootOut->mNumChildren];
+        for (unsigned int i = 0; i < rootOut->mNumChildren; ++i) {
+
+            aiNode *node = rootOut->mChildren[i] = new aiNode();
+            node->mParent = rootOut;
+            GenerateGraph(root->children[i], node, scene, batch, meshes,
+                    anims, attach, materials, defMatIdx);
+        }
+    }
+}
+
+void IRRImporter::ParseNodeAttributes(pugi::xml_node &attributesNode, IRRImporter::Node *nd, BatchLoader &batch) {
+    ai_assert(!ASSIMP_stricmp(attributesNode.name(), "attributes")); // Node must be <attributes>
+    ai_assert(nd != nullptr); // dude
+
+    // Big switch statement that tests for various tags inside <attributes>
+    // and applies them to nd
+    // I don't believe nodes have boolean attributes
+    for (pugi::xml_node &attribute : attributesNode.children()) {
+        if (attribute.type() != pugi::node_element) continue;
+        if (!ASSIMP_stricmp(attribute.name(), "vector3d")) { // <vector3d />
+            VectorProperty prop;
+            ReadVectorProperty(prop, attribute);
+            if (prop.name == "Position") {
+                nd->position = prop.value;
+            } else if (prop.name == "Rotation") {
+                nd->rotation = prop.value;
+            } else if (prop.name == "Scale") {
+                nd->scaling = prop.value;
+            } else if (Node::CAMERA == nd->type) {
+                aiCamera *cam = cameras.back();
+                if (prop.name == "Target") {
+                    cam->mLookAt = prop.value;
+                } else if (prop.name == "UpVector") {
+                    cam->mUp = prop.value;
+                }
+            }
+        } else if (!ASSIMP_stricmp(attribute.name(), "float")) { // <float />
+            FloatProperty prop;
+            ReadFloatProperty(prop, attribute);
+            if (prop.name == "FramesPerSecond" && Node::ANIMMESH == nd->type) {
+                nd->framesPerSecond = prop.value;
+            } else if (Node::CAMERA == nd->type) {
+                /*  This is the vertical, not the horizontal FOV.
+                 *  We need to compute the right FOV from the
+                 *  screen aspect which we don't know yet.
+                 */
+                if (prop.name == "Fovy") {
+                    cameras.back()->mHorizontalFOV = prop.value;
+                } else if (prop.name == "Aspect") {
+                    cameras.back()->mAspect = prop.value;
+                } else if (prop.name == "ZNear") {
+                    cameras.back()->mClipPlaneNear = prop.value;
+                } else if (prop.name == "ZFar") {
+                    cameras.back()->mClipPlaneFar = prop.value;
+                }
+            } else if (Node::LIGHT == nd->type) {
+                /*  Additional light information
+                 */
+                if (prop.name == "Attenuation") {
+                    lights.back()->mAttenuationLinear = prop.value;
+                } else if (prop.name == "OuterCone") {
+                    lights.back()->mAngleOuterCone = AI_DEG_TO_RAD(prop.value);
+                } else if (prop.name == "InnerCone") {
+                    lights.back()->mAngleInnerCone = AI_DEG_TO_RAD(prop.value);
+                }
+            }
+            // radius of the sphere to be generated -
+            // or alternatively, size of the cube
+            else if ((Node::SPHERE == nd->type && prop.name == "Radius") ||
+                     (Node::CUBE == nd->type && prop.name == "Size")) {
+                nd->sphereRadius = prop.value;
+            }
+        } else if (!ASSIMP_stricmp(attribute.name(), "int")) { // <int />
+            // Only sphere nodes make use of integer attributes
+            if (Node::SPHERE == nd->type) {
+                IntProperty prop;
+                ReadIntProperty(prop, attribute);
+                if (prop.name == "PolyCountX") {
+                    nd->spherePolyCountX = prop.value;
+                } else if (prop.name == "PolyCountY") {
+                    nd->spherePolyCountY = prop.value;
+                }
+            }
+        } else if (!ASSIMP_stricmp(attribute.name(), "string") || !ASSIMP_stricmp(attribute.name(), "enum")) { // <string /> or < enum />
+            StringProperty prop;
+            ReadStringProperty(prop, attribute);
+            if (prop.value.length() == 0) continue; // skip empty strings
+            if (prop.name == "Name") {
+                nd->name = prop.value;
+
+                /*  If we're either a camera or a light source
+                 *  we need to update the name in the aiLight/
+                 *  aiCamera structure, too.
+                 */
+                if (Node::CAMERA == nd->type) {
+                    cameras.back()->mName.Set(prop.value);
+                } else if (Node::LIGHT == nd->type) {
+                    lights.back()->mName.Set(prop.value);
+                }
+            } else if (Node::LIGHT == nd->type && "LightType" == prop.name) {
+                if (prop.value == "Spot")
+                    lights.back()->mType = aiLightSource_SPOT;
+                else if (prop.value == "Point")
+                    lights.back()->mType = aiLightSource_POINT;
+                else if (prop.value == "Directional")
+                    lights.back()->mType = aiLightSource_DIRECTIONAL;
+                else {
+                    // We won't pass the validation with aiLightSourceType_UNDEFINED,
+                    // so we remove the light and replace it with a silly dummy node
+                    delete lights.back();
+                    lights.pop_back();
+                    nd->type = Node::DUMMY;
+
+                    ASSIMP_LOG_ERROR("Ignoring light of unknown type: ", prop.value);
+                }
+            } else if ((prop.name == "Mesh" && Node::MESH == nd->type) ||
+                       Node::ANIMMESH == nd->type) {
+                /*  This is the file name of the mesh - either
+                 *  animated or not. We need to make sure we setup
+                 *  the correct post-processing settings here.
+                 */
+                unsigned int pp = 0;
+                BatchLoader::PropertyMap map;
+
+                /* If the mesh is a static one remove all animations from the impor data
+                 */
+                if (Node::ANIMMESH != nd->type) {
+                    pp |= aiProcess_RemoveComponent;
+                    SetGenericProperty<int>(map.ints, AI_CONFIG_PP_RVC_FLAGS,
+                            aiComponent_ANIMATIONS | aiComponent_BONEWEIGHTS);
+                }
+
+                /*  TODO: maybe implement the protection against recursive
+                 *  loading calls directly in BatchLoader? The current
+                 *  implementation is not absolutely safe. A LWS and an IRR
+                 *  file referencing each other *could* cause the system to
+                 *  recurse forever.
+                 */
+
+                const std::string extension = GetExtension(prop.value);
+                if ("irr" == extension) {
+                    ASSIMP_LOG_ERROR("IRR: Can't load another IRR file recursively");
+                } else {
+                    nd->id = batch.AddLoadRequest(prop.value, pp, &map);
+                    nd->meshPath = prop.value;
+                }
+            }
+        }
+    }
+}
+
+void IRRImporter::ParseAnimators(pugi::xml_node &animatorNode, IRRImporter::Node *nd) {
+    Animator *curAnim = nullptr;
+    // Make empty animator
+    nd->animators.emplace_back();
+    curAnim = &nd->animators.back(); // Push it back
+    pugi::xml_node attributes = animatorNode.child("attributes");
+    if (!attributes) {
+        ASSIMP_LOG_WARN("Animator node does not contain attributes. ");
+        return;
+    }
+
+    for (pugi::xml_node attrib : attributes.children()) {
+        // XML may contain useless noes like CDATA
+        if (!ASSIMP_stricmp(attrib.name(), "vector3d")) {
+            VectorProperty prop;
+            ReadVectorProperty(prop, attrib);
+
+            if (curAnim->type == Animator::ROTATION && prop.name == "Rotation") {
+                // We store the rotation euler angles in 'direction'
+                curAnim->direction = prop.value;
+            } else if (curAnim->type == Animator::FOLLOW_SPLINE) {
+                // Check whether the vector follows the PointN naming scheme,
+                // here N is the ONE-based index of the point
+                if (prop.name.length() >= 6 && prop.name.substr(0, 5) == "Point") {
+                    // Add a new key to the list
+                    curAnim->splineKeys.emplace_back();
+                    aiVectorKey &key = curAnim->splineKeys.back();
+
+                    // and parse its properties
+                    key.mValue = prop.value;
+                    key.mTime = strtoul10(&prop.name[5]);
+                }
+            } else if (curAnim->type == Animator::FLY_CIRCLE) {
+                if (prop.name == "Center") {
+                    curAnim->circleCenter = prop.value;
+                } else if (prop.name == "Direction") {
+                    curAnim->direction = prop.value;
+
+                    // From Irrlicht's source - a workaround for backward compatibility with Irrlicht 1.1
+                    if (curAnim->direction == aiVector3D()) {
+                        curAnim->direction = aiVector3D(0.f, 1.f, 0.f);
+                    } else
+                        curAnim->direction.Normalize();
+                }
+            } else if (curAnim->type == Animator::FLY_STRAIGHT) {
+                if (prop.name == "Start") {
+                    // We reuse the field here
+                    curAnim->circleCenter = prop.value;
+                } else if (prop.name == "End") {
+                    // We reuse the field here
+                    curAnim->direction = prop.value;
+                }
+            }
+
+            //} else if (!ASSIMP_stricmp(reader->getNodeName(), "bool")) {
+        } else if (!ASSIMP_stricmp(attrib.name(), "bool")) {
+            BoolProperty prop;
+            ReadBoolProperty(prop, attrib);
+
+            if (curAnim->type == Animator::FLY_CIRCLE && prop.name == "Loop") {
+                curAnim->loop = prop.value;
+            }
+            //} else if (!ASSIMP_stricmp(reader->getNodeName(), "float")) {
+        } else if (!ASSIMP_stricmp(attrib.name(), "float")) {
+            FloatProperty prop;
+            ReadFloatProperty(prop, attrib);
+
+            // The speed property exists for several animators
+            if (prop.name == "Speed") {
+                curAnim->speed = prop.value;
+            } else if (curAnim->type == Animator::FLY_CIRCLE && prop.name == "Radius") {
+                curAnim->circleRadius = prop.value;
+            } else if (curAnim->type == Animator::FOLLOW_SPLINE && prop.name == "Tightness") {
+                curAnim->tightness = prop.value;
+            }
+            //} else if (!ASSIMP_stricmp(reader->getNodeName(), "int")) {
+        } else if (!ASSIMP_stricmp(attrib.name(), "int")) {
+            IntProperty prop;
+            ReadIntProperty(prop, attrib);
+
+            if (curAnim->type == Animator::FLY_STRAIGHT && prop.name == "TimeForWay") {
+                curAnim->timeForWay = prop.value;
+            }
+            //} else if (!ASSIMP_stricmp(reader->getNodeName(), "string") || !ASSIMP_stricmp(reader->getNodeName(), "enum")) {
+        } else if (!ASSIMP_stricmp(attrib.name(), "string") || !ASSIMP_stricmp(attrib.name(), "enum")) {
+            StringProperty prop;
+            ReadStringProperty(prop, attrib);
+
+            if (prop.name == "Type") {
+                // type of the animator
+                if (prop.value == "rotation") {
+                    curAnim->type = Animator::ROTATION;
+                } else if (prop.value == "flyCircle") {
+                    curAnim->type = Animator::FLY_CIRCLE;
+                } else if (prop.value == "flyStraight") {
+                    curAnim->type = Animator::FLY_CIRCLE;
+                } else if (prop.value == "followSpline") {
+                    curAnim->type = Animator::FOLLOW_SPLINE;
+                } else {
+                    ASSIMP_LOG_WARN("IRR: Ignoring unknown animator: ", prop.value);
+
+                    curAnim->type = Animator::UNKNOWN;
+                }
+            }
+        }
+    }
+}
+
+IRRImporter::Node *IRRImporter::ParseNode(pugi::xml_node &node, BatchLoader &batch) {
+    // Parse <node> tags.
+    // <node> tags have various types
+    // <node> tags can contain <attribute>, <material>
+    // they can also contain other <node> tags, (and can reference other files as well?)
+    // ***********************************************************************
+    /*  What we're going to do with the node depends
+     *  on its type:
+     *
+     *  "mesh" - Load a mesh from an external file
+     *  "cube" - Generate a cube
+     *  "skybox" - Generate a skybox
+     *  "light" - A light source
+     *  "sphere" - Generate a sphere mesh
+     *  "animatedMesh" - Load an animated mesh from an external file
+     *    and join its animation channels with ours.
+     *  "empty" - A dummy node
+     *  "camera" - A camera
+     *  "terrain" - a terrain node (data comes from a heightmap)
+     *  "billboard", ""
+     *
+     *  Each of these nodes can be animated and all can have multiple
+     *  materials assigned (except lights, cameras and dummies, of course).
+     *  Said materials and animators are all collected at the bottom
+     */
+    // ***********************************************************************
+    Node *nd;
+    pugi::xml_attribute nodeTypeAttrib = node.attribute("type");
+    if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "mesh") || !ASSIMP_stricmp(nodeTypeAttrib.value(), "octTree")) {
+        // OctTree's and meshes are treated equally
+        nd = new Node(Node::MESH);
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "cube")) {
+        nd = new Node(Node::CUBE);
+        guessedMeshCnt += 1; // Cube is only one mesh
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "skybox")) {
+        nd = new Node(Node::SKYBOX);
+        guessedMeshCnt += 6; // Skybox is a box, with 6 meshes?
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "camera")) {
+        nd = new Node(Node::CAMERA);
+        // Setup a temporary name for the camera
+        aiCamera *cam = new aiCamera();
+        cam->mName.Set(nd->name);
+        cameras.push_back(cam);
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "light")) {
+        nd = new Node(Node::LIGHT);
+        // Setup a temporary name for the light
+        aiLight *cam = new aiLight();
+        cam->mName.Set(nd->name);
+        lights.push_back(cam);
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "sphere")) {
+        nd = new Node(Node::SPHERE);
+        guessedMeshCnt += 1;
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "animatedMesh")) {
+        nd = new Node(Node::ANIMMESH);
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "empty")) {
+        nd = new Node(Node::DUMMY);
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "terrain")) {
+        nd = new Node(Node::TERRAIN);
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "billBoard")) {
+        // We don't support billboards, so ignore them
+        ASSIMP_LOG_ERROR("IRR: Billboards are not supported by Assimp");
+        nd = new Node(Node::DUMMY);
+    } else {
+        ASSIMP_LOG_WARN("IRR: Found unknown node: ", nodeTypeAttrib.value());
+
+        /*  We skip the contents of nodes we don't know.
+         *  We parse the transformation and all animators
+         *  and skip the rest.
+         */
+        nd = new Node(Node::DUMMY);
+    }
+
+    // TODO: consolidate all into one loop
+    for (pugi::xml_node subNode : node.children()) {
+        // Collect node attributes first
+        if (!ASSIMP_stricmp(subNode.name(), "attributes")) {
+            ParseNodeAttributes(subNode, nd, batch); // Parse attributes into this node
+        } else if (!ASSIMP_stricmp(subNode.name(), "animators")) {
+            // Then parse any animators
+            // All animators should contain an <attributes> tag
+
+            //  This is an animation path - add a new animator
+            //  to the list.
+            ParseAnimators(subNode, nd); // Function modifies nd's animator vector
+            guessedAnimCnt += 1;
+        }
+
+        // Then parse any materials
+        // Materials are available to almost all node types
+        if (nd->type != Node::DUMMY) {
+            if (!ASSIMP_stricmp(subNode.name(), "materials")) {
+                // Parse material description directly
+                // Each material should contain an <attributes> node
+                // with everything specified
+                nd->materials.emplace_back();
+                std::pair<aiMaterial *, unsigned int> &p = nd->materials.back();
+                p.first = ParseMaterial(subNode, p.second);
+                guessedMatCnt += 1;
+            }
+        }
+    }
+
+    // Then parse any child nodes
+    // Attach the newly created node to the scene-graph
+    for (pugi::xml_node child : node.children()) {
+        if (!ASSIMP_stricmp(child.name(), "node")) { // Is a child node
+            Node *childNd = ParseNode(child, batch); // Repeat this function for all children
+            nd->children.push_back(childNd);
+        };
+    }
+
+    // Return fully specified node
+    return nd;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure.
 // Imports the given file into the given scene structure.
 void IRRImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
 void IRRImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
-	std::unique_ptr<IOStream> file(pIOHandler->Open(pFile));
-
-	// Check whether we can read from the file
+    std::unique_ptr<IOStream> file(pIOHandler->Open(pFile));
+    // Check whether we can read from the file
     if (file == nullptr) {
     if (file == nullptr) {
         throw DeadlyImportError("Failed to open IRR file ", pFile);
         throw DeadlyImportError("Failed to open IRR file ", pFile);
     }
     }
 
 
     // Construct the irrXML parser
     // Construct the irrXML parser
-	XmlParser st;
-    if (!st.parse( file.get() )) {
+    XmlParser st;
+    if (!st.parse(file.get())) {
         throw DeadlyImportError("XML parse error while loading IRR file ", pFile);
         throw DeadlyImportError("XML parse error while loading IRR file ", pFile);
     }
     }
-    pugi::xml_node rootElement = st.getRootNode();
-
-	// The root node of the scene
-	Node *root = new Node(Node::DUMMY);
-	root->parent = nullptr;
-	root->name = "<IRRSceneRoot>";
-
-	// Current node parent
-	Node *curParent = root;
-
-	// Scene-graph node we're currently working on
-	Node *curNode = nullptr;
-
-	// List of output cameras
-	std::vector<aiCamera *> cameras;
-
-	// List of output lights
-	std::vector<aiLight *> lights;
-
-	// Batch loader used to load external models
-	BatchLoader batch(pIOHandler);
-	//batch.SetBasePath(pFile);
-
-	cameras.reserve(5);
-	lights.reserve(5);
-
-	bool inMaterials = false, inAnimator = false;
-	unsigned int guessedAnimCnt = 0, guessedMeshCnt = 0, guessedMatCnt = 0;
-
-	// Parse the XML file
-
-	//while (reader->read())  {
-	for (pugi::xml_node child : rootElement.children())
-		switch (child.type()) {
-			case pugi::node_element:
-				if (!ASSIMP_stricmp(child.name(), "node")) {
-					// ***********************************************************************
-					/*  What we're going to do with the node depends
-                     *  on its type:
-                     *
-                     *  "mesh" - Load a mesh from an external file
-                     *  "cube" - Generate a cube
-                     *  "skybox" - Generate a skybox
-                     *  "light" - A light source
-                     *  "sphere" - Generate a sphere mesh
-                     *  "animatedMesh" - Load an animated mesh from an external file
-                     *    and join its animation channels with ours.
-                     *  "empty" - A dummy node
-                     *  "camera" - A camera
-                     *  "terrain" - a terrain node (data comes from a heightmap)
-                     *  "billboard", ""
-                     *
-                     *  Each of these nodes can be animated and all can have multiple
-                     *  materials assigned (except lights, cameras and dummies, of course).
-                     */
-					// ***********************************************************************
-					//const char *sz = reader->getAttributeValueSafe("type");
-					pugi::xml_attribute attrib = child.attribute("type");
-					Node *nd;
-					if (!ASSIMP_stricmp(attrib.name(), "mesh") || !ASSIMP_stricmp(attrib.name(), "octTree")) {
-						// OctTree's and meshes are treated equally
-						nd = new Node(Node::MESH);
-					} else if (!ASSIMP_stricmp(attrib.name(), "cube")) {
-						nd = new Node(Node::CUBE);
-						++guessedMeshCnt;
-					} else if (!ASSIMP_stricmp(attrib.name(), "skybox")) {
-						nd = new Node(Node::SKYBOX);
-						guessedMeshCnt += 6;
-					} else if (!ASSIMP_stricmp(attrib.name(), "camera")) {
-						nd = new Node(Node::CAMERA);
-
-						// Setup a temporary name for the camera
-						aiCamera *cam = new aiCamera();
-						cam->mName.Set(nd->name);
-						cameras.push_back(cam);
-					} else if (!ASSIMP_stricmp(attrib.name(), "light")) {
-						nd = new Node(Node::LIGHT);
-
-						// Setup a temporary name for the light
-						aiLight *cam = new aiLight();
-						cam->mName.Set(nd->name);
-						lights.push_back(cam);
-					} else if (!ASSIMP_stricmp(attrib.name(), "sphere")) {
-						nd = new Node(Node::SPHERE);
-						++guessedMeshCnt;
-					} else if (!ASSIMP_stricmp(attrib.name(), "animatedMesh")) {
-						nd = new Node(Node::ANIMMESH);
-					} else if (!ASSIMP_stricmp(attrib.name(), "empty")) {
-						nd = new Node(Node::DUMMY);
-					} else if (!ASSIMP_stricmp(attrib.name(), "terrain")) {
-						nd = new Node(Node::TERRAIN);
-					} else if (!ASSIMP_stricmp(attrib.name(), "billBoard")) {
-						// We don't support billboards, so ignore them
-						ASSIMP_LOG_ERROR("IRR: Billboards are not supported by Assimp");
-						nd = new Node(Node::DUMMY);
-					} else {
-						ASSIMP_LOG_WARN("IRR: Found unknown node: ", attrib.name());
-
-						/*  We skip the contents of nodes we don't know.
-                         *  We parse the transformation and all animators
-                         *  and skip the rest.
-                         */
-						nd = new Node(Node::DUMMY);
-					}
-
-					/* Attach the newly created node to the scene-graph
-                     */
-					curNode = nd;
-					nd->parent = curParent;
-					curParent->children.push_back(nd);
-				} else if (!ASSIMP_stricmp(child.name(), "materials")) {
-					inMaterials = true;
-				} else if (!ASSIMP_stricmp(child.name(), "animators")) {
-					inAnimator = true;
-				} else if (!ASSIMP_stricmp(child.name(), "attributes")) {
-					//  We should have a valid node here
-					//  FIX: no ... the scene root node is also contained in an attributes block
-					if (!curNode) {
-						continue;
-					}
-
-					Animator *curAnim = nullptr;
-
-					// Materials can occur for nearly any type of node
-					if (inMaterials && curNode->type != Node::DUMMY) {
-						//  This is a material description - parse it!
-						curNode->materials.emplace_back();
-						std::pair<aiMaterial *, unsigned int> &p = curNode->materials.back();
-
-						p.first = ParseMaterial(p.second);
-						++guessedMatCnt;
-						continue;
-					} else if (inAnimator) {
-						//  This is an animation path - add a new animator
-						//  to the list.
-						curNode->animators.emplace_back();
-						curAnim = &curNode->animators.back();
-
-						++guessedAnimCnt;
-					}
-
-					/*  Parse all elements in the attributes block
-                     *  and process them.
-                     */
-					//					while (reader->read()) {
-					for (pugi::xml_node attrib : child.children()) {
-						if (attrib.type() == pugi::node_element) {
-							//if (reader->getNodeType() == EXN_ELEMENT) {
-							//if (!ASSIMP_stricmp(reader->getNodeName(), "vector3d")) {
-							if (!ASSIMP_stricmp(attrib.name(), "vector3d")) {
-								VectorProperty prop;
-								ReadVectorProperty(prop);
-
-								if (inAnimator) {
-									if (curAnim->type == Animator::ROTATION && prop.name == "Rotation") {
-										// We store the rotation euler angles in 'direction'
-										curAnim->direction = prop.value;
-									} else if (curAnim->type == Animator::FOLLOW_SPLINE) {
-										// Check whether the vector follows the PointN naming scheme,
-										// here N is the ONE-based index of the point
-										if (prop.name.length() >= 6 && prop.name.substr(0, 5) == "Point") {
-											// Add a new key to the list
-											curAnim->splineKeys.emplace_back();
-											aiVectorKey &key = curAnim->splineKeys.back();
-
-											// and parse its properties
-											key.mValue = prop.value;
-											key.mTime = strtoul10(&prop.name[5]);
-										}
-									} else if (curAnim->type == Animator::FLY_CIRCLE) {
-										if (prop.name == "Center") {
-											curAnim->circleCenter = prop.value;
-										} else if (prop.name == "Direction") {
-											curAnim->direction = prop.value;
-
-											// From Irrlicht's source - a workaround for backward compatibility with Irrlicht 1.1
-											if (curAnim->direction == aiVector3D()) {
-												curAnim->direction = aiVector3D(0.f, 1.f, 0.f);
-											} else
-												curAnim->direction.Normalize();
-										}
-									} else if (curAnim->type == Animator::FLY_STRAIGHT) {
-										if (prop.name == "Start") {
-											// We reuse the field here
-											curAnim->circleCenter = prop.value;
-										} else if (prop.name == "End") {
-											// We reuse the field here
-											curAnim->direction = prop.value;
-										}
-									}
-								} else {
-									if (prop.name == "Position") {
-										curNode->position = prop.value;
-									} else if (prop.name == "Rotation") {
-										curNode->rotation = prop.value;
-									} else if (prop.name == "Scale") {
-										curNode->scaling = prop.value;
-									} else if (Node::CAMERA == curNode->type) {
-										aiCamera *cam = cameras.back();
-										if (prop.name == "Target") {
-											cam->mLookAt = prop.value;
-										} else if (prop.name == "UpVector") {
-											cam->mUp = prop.value;
-										}
-									}
-								}
-								//} else if (!ASSIMP_stricmp(reader->getNodeName(), "bool")) {
-							} else if (!ASSIMP_stricmp(attrib.name(), "bool")) {
-								BoolProperty prop;
-								ReadBoolProperty(prop);
-
-								if (inAnimator && curAnim->type == Animator::FLY_CIRCLE && prop.name == "Loop") {
-									curAnim->loop = prop.value;
-								}
-								//} else if (!ASSIMP_stricmp(reader->getNodeName(), "float")) {
-							} else if (!ASSIMP_stricmp(attrib.name(), "float")) {
-								FloatProperty prop;
-								ReadFloatProperty(prop);
-
-								if (inAnimator) {
-									// The speed property exists for several animators
-									if (prop.name == "Speed") {
-										curAnim->speed = prop.value;
-									} else if (curAnim->type == Animator::FLY_CIRCLE && prop.name == "Radius") {
-										curAnim->circleRadius = prop.value;
-									} else if (curAnim->type == Animator::FOLLOW_SPLINE && prop.name == "Tightness") {
-										curAnim->tightness = prop.value;
-									}
-								} else {
-									if (prop.name == "FramesPerSecond" && Node::ANIMMESH == curNode->type) {
-										curNode->framesPerSecond = prop.value;
-									} else if (Node::CAMERA == curNode->type) {
-										/*  This is the vertical, not the horizontal FOV.
-                                    *  We need to compute the right FOV from the
-                                    *  screen aspect which we don't know yet.
-                                    */
-										if (prop.name == "Fovy") {
-											cameras.back()->mHorizontalFOV = prop.value;
-										} else if (prop.name == "Aspect") {
-											cameras.back()->mAspect = prop.value;
-										} else if (prop.name == "ZNear") {
-											cameras.back()->mClipPlaneNear = prop.value;
-										} else if (prop.name == "ZFar") {
-											cameras.back()->mClipPlaneFar = prop.value;
-										}
-									} else if (Node::LIGHT == curNode->type) {
-										/*  Additional light information
-                                     */
-										if (prop.name == "Attenuation") {
-											lights.back()->mAttenuationLinear = prop.value;
-										} else if (prop.name == "OuterCone") {
-											lights.back()->mAngleOuterCone = AI_DEG_TO_RAD(prop.value);
-										} else if (prop.name == "InnerCone") {
-											lights.back()->mAngleInnerCone = AI_DEG_TO_RAD(prop.value);
-										}
-									}
-									// radius of the sphere to be generated -
-									// or alternatively, size of the cube
-									else if ((Node::SPHERE == curNode->type && prop.name == "Radius") || (Node::CUBE == curNode->type && prop.name == "Size")) {
-
-										curNode->sphereRadius = prop.value;
-									}
-								}
-								//} else if (!ASSIMP_stricmp(reader->getNodeName(), "int")) {
-							} else if (!ASSIMP_stricmp(attrib.name(), "int")) {
-								IntProperty prop;
-								ReadIntProperty(prop);
-
-								if (inAnimator) {
-									if (curAnim->type == Animator::FLY_STRAIGHT && prop.name == "TimeForWay") {
-										curAnim->timeForWay = prop.value;
-									}
-								} else {
-									// sphere polygon numbers in each direction
-									if (Node::SPHERE == curNode->type) {
-
-										if (prop.name == "PolyCountX") {
-											curNode->spherePolyCountX = prop.value;
-										} else if (prop.name == "PolyCountY") {
-											curNode->spherePolyCountY = prop.value;
-										}
-									}
-								}
-								//} else if (!ASSIMP_stricmp(reader->getNodeName(), "string") || !ASSIMP_stricmp(reader->getNodeName(), "enum")) {
-							} else if (!ASSIMP_stricmp(attrib.name(), "string") || !ASSIMP_stricmp(attrib.name(), "enum")) {
-								StringProperty prop;
-								ReadStringProperty(prop);
-								if (prop.value.length()) {
-									if (prop.name == "Name") {
-										curNode->name = prop.value;
-
-										/*  If we're either a camera or a light source
-                                     *  we need to update the name in the aiLight/
-                                     *  aiCamera structure, too.
-                                     */
-										if (Node::CAMERA == curNode->type) {
-											cameras.back()->mName.Set(prop.value);
-										} else if (Node::LIGHT == curNode->type) {
-											lights.back()->mName.Set(prop.value);
-										}
-									} else if (Node::LIGHT == curNode->type && "LightType" == prop.name) {
-										if (prop.value == "Spot")
-											lights.back()->mType = aiLightSource_SPOT;
-										else if (prop.value == "Point")
-											lights.back()->mType = aiLightSource_POINT;
-										else if (prop.value == "Directional")
-											lights.back()->mType = aiLightSource_DIRECTIONAL;
-										else {
-											// We won't pass the validation with aiLightSourceType_UNDEFINED,
-											// so we remove the light and replace it with a silly dummy node
-											delete lights.back();
-											lights.pop_back();
-											curNode->type = Node::DUMMY;
-
-											ASSIMP_LOG_ERROR("Ignoring light of unknown type: ", prop.value);
-										}
-									} else if ((prop.name == "Mesh" && Node::MESH == curNode->type) ||
-											   Node::ANIMMESH == curNode->type) {
-    								/*  This is the file name of the mesh - either
-                                     *  animated or not. We need to make sure we setup
-                                     *  the correct post-processing settings here.
-                                     */
-										unsigned int pp = 0;
-										BatchLoader::PropertyMap map;
-
-										/* If the mesh is a static one remove all animations from the impor data
-                                     */
-										if (Node::ANIMMESH != curNode->type) {
-											pp |= aiProcess_RemoveComponent;
-											SetGenericProperty<int>(map.ints, AI_CONFIG_PP_RVC_FLAGS,
-													aiComponent_ANIMATIONS | aiComponent_BONEWEIGHTS);
-										}
-
-										/*  TODO: maybe implement the protection against recursive
-                                        *  loading calls directly in BatchLoader? The current
-                                        *  implementation is not absolutely safe. A LWS and an IRR
-                                        *  file referencing each other *could* cause the system to
-                                        *  recurse forever.
-                                        */
-
-										const std::string extension = GetExtension(prop.value);
-										if ("irr" == extension) {
-											ASSIMP_LOG_ERROR("IRR: Can't load another IRR file recursively");
-										} else {
-											curNode->id = batch.AddLoadRequest(prop.value, pp, &map);
-											curNode->meshPath = prop.value;
-										}
-									} else if (inAnimator && prop.name == "Type") {
-										// type of the animator
-										if (prop.value == "rotation") {
-											curAnim->type = Animator::ROTATION;
-										} else if (prop.value == "flyCircle") {
-											curAnim->type = Animator::FLY_CIRCLE;
-										} else if (prop.value == "flyStraight") {
-											curAnim->type = Animator::FLY_CIRCLE;
-										} else if (prop.value == "followSpline") {
-											curAnim->type = Animator::FOLLOW_SPLINE;
-										} else {
-											ASSIMP_LOG_WARN("IRR: Ignoring unknown animator: ", prop.value);
-
-											curAnim->type = Animator::UNKNOWN;
-										}
-									}
-								}
-							}
-							//} else if (reader->getNodeType() == EXN_ELEMENT_END && !ASSIMP_stricmp(reader->getNodeName(), "attributes")) {
-						} else if (attrib.type() == pugi::node_null && !ASSIMP_stricmp(attrib.name(), "attributes")) {
-							break;
-						}
-					}
-				}
-				break;
-
-				/*case EXN_ELEMENT_END:
-
-				// If we reached the end of a node, we need to continue processing its parent
-				if (!ASSIMP_stricmp(reader->getNodeName(), "node")) {
-					if (!curNode) {
-						// currently is no node set. We need to go
-						// back in the node hierarchy
-						if (!curParent) {
-							curParent = root;
-							ASSIMP_LOG_ERROR("IRR: Too many closing <node> elements");
-						} else
-							curParent = curParent->parent;
-					} else
-						curNode = nullptr;
-				}
-				// clear all flags
-				else if (!ASSIMP_stricmp(reader->getNodeName(), "materials")) {
-					inMaterials = false;
-				} else if (!ASSIMP_stricmp(reader->getNodeName(), "animators")) {
-					inAnimator = false;
-				}
-				break;*/
-
-			default:
-				// GCC complains that not all enumeration values are handled
-				break;
-		}
-	//}
-
-	//  Now iterate through all cameras and compute their final (horizontal) FOV
-	for (aiCamera *cam : cameras) {
-		// screen aspect could be missing
-		if (cam->mAspect) {
-			cam->mHorizontalFOV *= cam->mAspect;
-		} else {
-			ASSIMP_LOG_WARN("IRR: Camera aspect is not given, can't compute horizontal FOV");
-		}
-	}
-
-	batch.LoadAll();
-
-	// Allocate a temporary scene data structure
-	aiScene *tempScene = new aiScene();
-	tempScene->mRootNode = new aiNode();
-	tempScene->mRootNode->mName.Set("<IRRRoot>");
-
-	// Copy the cameras to the output array
-	if (!cameras.empty()) {
-		tempScene->mNumCameras = (unsigned int)cameras.size();
-		tempScene->mCameras = new aiCamera *[tempScene->mNumCameras];
-		::memcpy(tempScene->mCameras, &cameras[0], sizeof(void *) * tempScene->mNumCameras);
-	}
-
-	// Copy the light sources to the output array
-	if (!lights.empty()) {
-		tempScene->mNumLights = (unsigned int)lights.size();
-		tempScene->mLights = new aiLight *[tempScene->mNumLights];
-		::memcpy(tempScene->mLights, &lights[0], sizeof(void *) * tempScene->mNumLights);
-	}
-
-	// temporary data
-	std::vector<aiNodeAnim *> anims;
-	std::vector<aiMaterial *> materials;
-	std::vector<AttachmentInfo> attach;
-	std::vector<aiMesh *> meshes;
-
-	// try to guess how much storage we'll need
-	anims.reserve(guessedAnimCnt + (guessedAnimCnt >> 2));
-	meshes.reserve(guessedMeshCnt + (guessedMeshCnt >> 2));
-	materials.reserve(guessedMatCnt + (guessedMatCnt >> 2));
-
-	// Now process our scene-graph recursively: generate final
-	// meshes and generate animation channels for all nodes.
-	unsigned int defMatIdx = UINT_MAX;
-	GenerateGraph(root, tempScene->mRootNode, tempScene,
-			batch, meshes, anims, attach, materials, defMatIdx);
-
-	if (!anims.empty()) {
-		tempScene->mNumAnimations = 1;
-		tempScene->mAnimations = new aiAnimation *[tempScene->mNumAnimations];
-		aiAnimation *an = tempScene->mAnimations[0] = new aiAnimation();
-
-		// ***********************************************************
-		// This is only the global animation channel of the scene.
-		// If there are animated models, they will have separate
-		// animation channels in the scene. To display IRR scenes
-		// correctly, users will need to combine the global anim
-		// channel with all the local animations they want to play
-		// ***********************************************************
-		an->mName.Set("Irr_GlobalAnimChannel");
-
-		// copy all node animation channels to the global channel
-		an->mNumChannels = (unsigned int)anims.size();
-		an->mChannels = new aiNodeAnim *[an->mNumChannels];
-		::memcpy(an->mChannels, &anims[0], sizeof(void *) * an->mNumChannels);
-	}
-	if (!meshes.empty()) {
-		// copy all meshes to the temporary scene
-		tempScene->mNumMeshes = (unsigned int)meshes.size();
-		tempScene->mMeshes = new aiMesh *[tempScene->mNumMeshes];
-		::memcpy(tempScene->mMeshes, &meshes[0], tempScene->mNumMeshes * sizeof(void *));
-	}
-
-	// Copy all materials to the output array
-	if (!materials.empty()) {
-		tempScene->mNumMaterials = (unsigned int)materials.size();
-		tempScene->mMaterials = new aiMaterial *[tempScene->mNumMaterials];
-		::memcpy(tempScene->mMaterials, &materials[0], sizeof(void *) * tempScene->mNumMaterials);
-	}
-
-	//  Now merge all sub scenes and attach them to the correct
-	//  attachment points in the scenegraph.
-	SceneCombiner::MergeScenes(&pScene, tempScene, attach,
-			AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | (!configSpeedFlag ? (
-																			  AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY | AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) :
-																	  0));
-
-	// If we have no meshes | no materials now set the INCOMPLETE
-	// scene flag. This is necessary if we failed to load all
-	// models from external files
-	if (!pScene->mNumMeshes || !pScene->mNumMaterials) {
-		ASSIMP_LOG_WARN("IRR: No meshes loaded, setting AI_SCENE_FLAGS_INCOMPLETE");
-		pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
-	}
-
-	// Finished ... everything destructs automatically and all
-	// temporary scenes have already been deleted by MergeScenes()
-	delete root;
+    pugi::xml_node documentRoot = st.getRootNode();
+
+    // The root node of the scene
+    Node *root = new Node(Node::DUMMY);
+    root->parent = nullptr;
+    root->name = "<IRRSceneRoot>";
+
+    // Batch loader used to load external models
+    BatchLoader batch(pIOHandler);
+    // batch.SetBasePath(pFile);
+
+    cameras.reserve(1); // Probably only one camera in entire scene
+    lights.reserve(5);
+
+    this->guessedAnimCnt = 0;
+    this->guessedMeshCnt = 0;
+    this->guessedMatCnt = 0;
+
+    // Parse the XML
+    // Find the scene root from document root.
+    const pugi::xml_node &sceneRoot = documentRoot.child("irr_scene");
+    if (!sceneRoot) throw new DeadlyImportError("IRR: <irr_scene> not found in file");
+    for (pugi::xml_node &child : sceneRoot.children()) {
+        // XML elements are either nodes, animators, attributes, or materials
+        if (!ASSIMP_stricmp(child.name(), "node")) {
+            // Recursive collect subtree children
+            Node *nd = ParseNode(child, batch);
+            // Attach to root
+            root->children.push_back(nd);
+        }
+    }
+
+    //  Now iterate through all cameras and compute their final (horizontal) FOV
+    for (aiCamera *cam : cameras) {
+        // screen aspect could be missing
+        if (cam->mAspect) {
+            cam->mHorizontalFOV *= cam->mAspect;
+        } else {
+            ASSIMP_LOG_WARN("IRR: Camera aspect is not given, can't compute horizontal FOV");
+        }
+    }
+
+    batch.LoadAll();
+
+    // Allocate a temporary scene data structure
+    aiScene *tempScene = new aiScene();
+    tempScene->mRootNode = new aiNode();
+    tempScene->mRootNode->mName.Set("<IRRRoot>");
+
+    // Copy the cameras to the output array
+    if (!cameras.empty()) {
+        tempScene->mNumCameras = (unsigned int)cameras.size();
+        tempScene->mCameras = new aiCamera *[tempScene->mNumCameras];
+        ::memcpy(tempScene->mCameras, &cameras[0], sizeof(void *) * tempScene->mNumCameras);
+    }
+
+    // Copy the light sources to the output array
+    if (!lights.empty()) {
+        tempScene->mNumLights = (unsigned int)lights.size();
+        tempScene->mLights = new aiLight *[tempScene->mNumLights];
+        ::memcpy(tempScene->mLights, &lights[0], sizeof(void *) * tempScene->mNumLights);
+    }
+
+    // temporary data
+    std::vector<aiNodeAnim *> anims;
+    std::vector<aiMaterial *> materials;
+    std::vector<AttachmentInfo> attach;
+    std::vector<aiMesh *> meshes;
+
+    // try to guess how much storage we'll need
+    anims.reserve(guessedAnimCnt + (guessedAnimCnt >> 2));
+    meshes.reserve(guessedMeshCnt + (guessedMeshCnt >> 2));
+    materials.reserve(guessedMatCnt + (guessedMatCnt >> 2));
+
+    // Now process our scene-graph recursively: generate final
+    // meshes and generate animation channels for all nodes.
+    unsigned int defMatIdx = UINT_MAX;
+    GenerateGraph(root, tempScene->mRootNode, tempScene,
+            batch, meshes, anims, attach, materials, defMatIdx);
+
+    if (!anims.empty()) {
+        tempScene->mNumAnimations = 1;
+        tempScene->mAnimations = new aiAnimation *[tempScene->mNumAnimations];
+        aiAnimation *an = tempScene->mAnimations[0] = new aiAnimation();
+
+        // ***********************************************************
+        // This is only the global animation channel of the scene.
+        // If there are animated models, they will have separate
+        // animation channels in the scene. To display IRR scenes
+        // correctly, users will need to combine the global anim
+        // channel with all the local animations they want to play
+        // ***********************************************************
+        an->mName.Set("Irr_GlobalAnimChannel");
+
+        // copy all node animation channels to the global channel
+        an->mNumChannels = (unsigned int)anims.size();
+        an->mChannels = new aiNodeAnim *[an->mNumChannels];
+        ::memcpy(an->mChannels, &anims[0], sizeof(void *) * an->mNumChannels);
+    }
+    if (!meshes.empty()) {
+        // copy all meshes to the temporary scene
+        tempScene->mNumMeshes = (unsigned int)meshes.size();
+        tempScene->mMeshes = new aiMesh *[tempScene->mNumMeshes];
+        ::memcpy(tempScene->mMeshes, &meshes[0], tempScene->mNumMeshes * sizeof(void *));
+    }
+
+    // Copy all materials to the output array
+    if (!materials.empty()) {
+        tempScene->mNumMaterials = (unsigned int)materials.size();
+        tempScene->mMaterials = new aiMaterial *[tempScene->mNumMaterials];
+        ::memcpy(tempScene->mMaterials, &materials[0], sizeof(void *) * tempScene->mNumMaterials);
+    }
+
+    //  Now merge all sub scenes and attach them to the correct
+    //  attachment points in the scenegraph.
+    SceneCombiner::MergeScenes(&pScene, tempScene, attach,
+            AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | (!configSpeedFlag ? (AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY | AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) :
+                                                                      0));
+
+    // If we have no meshes | no materials now set the INCOMPLETE
+    // scene flag. This is necessary if we failed to load all
+    // models from external files
+    if (!pScene->mNumMeshes || !pScene->mNumMaterials) {
+        ASSIMP_LOG_WARN("IRR: No meshes loaded, setting AI_SCENE_FLAGS_INCOMPLETE");
+        pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
+    }
+
+    // Finished ... everything destructs automatically and all
+    // temporary scenes have already been deleted by MergeScenes()
+    delete root;
 }
 }
 
 
 #endif // !! ASSIMP_BUILD_NO_IRR_IMPORTER
 #endif // !! ASSIMP_BUILD_NO_IRR_IMPORTER

+ 76 - 67
code/AssetLib/Irr/IRRLoader.h

@@ -53,7 +53,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/StringUtils.h>
 #include <assimp/StringUtils.h>
 #include <assimp/anim.h>
 #include <assimp/anim.h>
 
 
-namespace Assimp    {
+namespace Assimp {
 
 
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
 /** Irr importer class.
 /** Irr importer class.
@@ -71,13 +71,13 @@ public:
     /** Returns whether the class can handle the format of the given file.
     /** Returns whether the class can handle the format of the given file.
      *  See BaseImporter::CanRead() for details.
      *  See BaseImporter::CanRead() for details.
      */
      */
-    bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
-        bool checkSig) const override;
+    bool CanRead(const std::string &pFile, IOSystem *pIOHandler,
+            bool checkSig) const override;
 
 
 protected:
 protected:
-    const aiImporterDesc* GetInfo () const override;
-    void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) override;
-    void SetupProperties(const Importer* pImp) override;
+    const aiImporterDesc *GetInfo() const override;
+    void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
+    void SetupProperties(const Importer *pImp) override;
 
 
 private:
 private:
     /** Data structure for a scene-graph node animator
     /** Data structure for a scene-graph node animator
@@ -85,27 +85,19 @@ private:
     struct Animator {
     struct Animator {
         // Type of the animator
         // Type of the animator
         enum AT {
         enum AT {
-            UNKNOWN       = 0x0,
-            ROTATION      = 0x1,
-            FLY_CIRCLE    = 0x2,
-            FLY_STRAIGHT  = 0x3,
+            UNKNOWN = 0x0,
+            ROTATION = 0x1,
+            FLY_CIRCLE = 0x2,
+            FLY_STRAIGHT = 0x3,
             FOLLOW_SPLINE = 0x4,
             FOLLOW_SPLINE = 0x4,
-            OTHER         = 0x5
+            OTHER = 0x5
 
 
         } type;
         } type;
 
 
-        explicit Animator(AT t = UNKNOWN)
-            : type              (t)
-            , speed             ( ai_real( 0.001 ) )
-            , direction         ( ai_real( 0.0 ), ai_real( 1.0 ), ai_real( 0.0 ) )
-            , circleRadius      ( ai_real( 1.0) )
-            , tightness         ( ai_real( 0.5 ) )
-            , loop              (true)
-            , timeForWay        (100)
-        {
+        explicit Animator(AT t = UNKNOWN) :
+                type(t), speed(ai_real(0.001)), direction(ai_real(0.0), ai_real(1.0), ai_real(0.0)), circleRadius(ai_real(1.0)), tightness(ai_real(0.5)), loop(true), timeForWay(100) {
         }
         }
 
 
-
         // common parameters
         // common parameters
         ai_real speed;
         ai_real speed;
         aiVector3D direction;
         aiVector3D direction;
@@ -128,11 +120,9 @@ private:
 
 
     /** Data structure for a scene-graph node in an IRR file
     /** Data structure for a scene-graph node in an IRR file
      */
      */
-    struct Node
-    {
+    struct Node {
         // Type of the node
         // Type of the node
-        enum ET
-        {
+        enum ET {
             LIGHT,
             LIGHT,
             CUBE,
             CUBE,
             MESH,
             MESH,
@@ -144,21 +134,20 @@ private:
             ANIMMESH
             ANIMMESH
         } type;
         } type;
 
 
-        explicit Node(ET t)
-            :   type                (t)
-            ,   scaling             (1.0,1.0,1.0) // assume uniform scaling by default
-            ,   parent()
-            ,   framesPerSecond     (0.0)
-            ,   id()
-            ,   sphereRadius        (1.0)
-            ,   spherePolyCountX    (100)
-            ,   spherePolyCountY    (100)
-        {
+        explicit Node(ET t) :
+                type(t), scaling(1.0, 1.0, 1.0) // assume uniform scaling by default
+                ,
+                parent(),
+                framesPerSecond(0.0),
+                id(),
+                sphereRadius(1.0),
+                spherePolyCountX(100),
+                spherePolyCountY(100) {
 
 
             // Generate a default name for the node
             // Generate a default name for the node
             char buffer[128];
             char buffer[128];
             static int cnt;
             static int cnt;
-            ai_snprintf(buffer, 128, "IrrNode_%i",cnt++);
+            ai_snprintf(buffer, 128, "IrrNode_%i", cnt++);
             name = std::string(buffer);
             name = std::string(buffer);
 
 
             // reserve space for up to 5 materials
             // reserve space for up to 5 materials
@@ -175,10 +164,10 @@ private:
         std::string name;
         std::string name;
 
 
         // List of all child nodes
         // List of all child nodes
-        std::vector<Node*> children;
+        std::vector<Node *> children;
 
 
         // Parent node
         // Parent node
-        Node* parent;
+        Node *parent;
 
 
         // Animated meshes: frames per second
         // Animated meshes: frames per second
         // 0.f if not specified
         // 0.f if not specified
@@ -190,13 +179,13 @@ private:
 
 
         // Meshes: List of materials to be assigned
         // Meshes: List of materials to be assigned
         // along with their corresponding material flags
         // along with their corresponding material flags
-        std::vector< std::pair<aiMaterial*, unsigned int> > materials;
+        std::vector<std::pair<aiMaterial *, unsigned int>> materials;
 
 
         // Spheres: radius of the sphere to be generates
         // Spheres: radius of the sphere to be generates
         ai_real sphereRadius;
         ai_real sphereRadius;
 
 
         // Spheres: Number of polygons in the x,y direction
         // Spheres: Number of polygons in the x,y direction
-        unsigned int spherePolyCountX,spherePolyCountY;
+        unsigned int spherePolyCountX, spherePolyCountY;
 
 
         // List of all animators assigned to the node
         // List of all animators assigned to the node
         std::list<Animator> animators;
         std::list<Animator> animators;
@@ -204,40 +193,54 @@ private:
 
 
     /** Data structure for a vertex in an IRR skybox
     /** Data structure for a vertex in an IRR skybox
      */
      */
-    struct SkyboxVertex
-    {
+    struct SkyboxVertex {
         SkyboxVertex() = default;
         SkyboxVertex() = default;
 
 
         //! Construction from single vertex components
         //! Construction from single vertex components
         SkyboxVertex(ai_real px, ai_real py, ai_real pz,
         SkyboxVertex(ai_real px, ai_real py, ai_real pz,
-            ai_real nx, ai_real ny, ai_real nz,
-            ai_real uvx, ai_real uvy)
+                ai_real nx, ai_real ny, ai_real nz,
+                ai_real uvx, ai_real uvy)
 
 
-            :   position    (px,py,pz)
-            ,   normal      (nx,ny,nz)
-            ,   uv          (uvx,uvy,0.0)
-        {}
+                :
+                position(px, py, pz), normal(nx, ny, nz), uv(uvx, uvy, 0.0) {}
 
 
         aiVector3D position, normal, uv;
         aiVector3D position, normal, uv;
     };
     };
 
 
+    // -------------------------------------------------------------------
+    // Parse <node> tag from XML file and extract child node
+    // @param node XML node
+    // @param guessedMeshesContained number of extra guessed meshes
+    IRRImporter::Node *ParseNode(pugi::xml_node &node, BatchLoader& batch);
+
+    // -------------------------------------------------------------------
+    // Parse <attributes> tags within <node> tags and apply to scene node
+    // @param attributeNode XML child node
+    // @param nd Attributed scene node
+    void ParseNodeAttributes(pugi::xml_node &attributeNode, IRRImporter::Node *nd, BatchLoader& batch);
+
+    // -------------------------------------------------------------------
+    // Parse an <animator> node and attach an animator to a node
+    // @param animatorNode XML animator node
+    // @param nd Animated scene node
+    void ParseAnimators(pugi::xml_node &animatorNode, IRRImporter::Node *nd);
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /// Fill the scene-graph recursively
     /// Fill the scene-graph recursively
-    void GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene,
-        BatchLoader& batch,
-        std::vector<aiMesh*>& meshes,
-        std::vector<aiNodeAnim*>& anims,
-        std::vector<AttachmentInfo>& attach,
-        std::vector<aiMaterial*>& materials,
-        unsigned int& defaultMatIdx);
+    void GenerateGraph(Node *root, aiNode *rootOut, aiScene *scene,
+            BatchLoader &batch,
+            std::vector<aiMesh *> &meshes,
+            std::vector<aiNodeAnim *> &anims,
+            std::vector<AttachmentInfo> &attach,
+            std::vector<aiMaterial *> &materials,
+            unsigned int &defaultMatIdx);
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /// Generate a mesh that consists of just a single quad
     /// Generate a mesh that consists of just a single quad
-    aiMesh* BuildSingleQuadMesh(const SkyboxVertex& v1,
-        const SkyboxVertex& v2,
-        const SkyboxVertex& v3,
-        const SkyboxVertex& v4);
+    aiMesh *BuildSingleQuadMesh(const SkyboxVertex &v1,
+            const SkyboxVertex &v2,
+            const SkyboxVertex &v3,
+            const SkyboxVertex &v4);
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /// Build a sky-box
     /// Build a sky-box
@@ -245,8 +248,8 @@ private:
     /// @param meshes Receives 6 output meshes
     /// @param meshes Receives 6 output meshes
     /// @param materials The last 6 materials are assigned to the newly
     /// @param materials The last 6 materials are assigned to the newly
     ///                  created meshes. The names of the materials are adjusted.
     ///                  created meshes. The names of the materials are adjusted.
-    void BuildSkybox(std::vector<aiMesh*>& meshes,
-        std::vector<aiMaterial*> materials);
+    void BuildSkybox(std::vector<aiMesh *> &meshes,
+            std::vector<aiMaterial *> materials);
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Copy a material for a mesh to the output material list
     /** Copy a material for a mesh to the output material list
@@ -256,10 +259,10 @@ private:
      *  @param defMatIdx Default material index - UINT_MAX if not present
      *  @param defMatIdx Default material index - UINT_MAX if not present
      *  @param mesh Mesh to work on
      *  @param mesh Mesh to work on
      */
      */
-    void CopyMaterial(std::vector<aiMaterial*>&  materials,
-        std::vector< std::pair<aiMaterial*, unsigned int> >& inmaterials,
-        unsigned int& defMatIdx,
-        aiMesh* mesh);
+    void CopyMaterial(std::vector<aiMaterial *> &materials,
+            std::vector<std::pair<aiMaterial *, unsigned int>> &inmaterials,
+            unsigned int &defMatIdx,
+            aiMesh *mesh);
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Compute animations for a specific node
     /** Compute animations for a specific node
@@ -267,8 +270,8 @@ private:
      *  @param root Node to be processed
      *  @param root Node to be processed
      *  @param anims The list of output animations
      *  @param anims The list of output animations
      */
      */
-    void ComputeAnimations(Node* root, aiNode* real,
-        std::vector<aiNodeAnim*>& anims);
+    void ComputeAnimations(Node *root, aiNode *real,
+            std::vector<aiNodeAnim *> &anims);
 
 
 private:
 private:
     /// Configuration option: desired output FPS
     /// Configuration option: desired output FPS
@@ -276,6 +279,12 @@ private:
 
 
     /// Configuration option: speed flag was set?
     /// Configuration option: speed flag was set?
     bool configSpeedFlag;
     bool configSpeedFlag;
+
+    std::vector<aiCamera*> cameras;
+    std::vector<aiLight*> lights;
+    unsigned int guessedMeshCnt;
+    unsigned int guessedMatCnt;
+    unsigned int guessedAnimCnt;
 };
 };
 
 
 } // end of namespace Assimp
 } // end of namespace Assimp

+ 431 - 407
code/AssetLib/Irr/IRRMeshLoader.cpp

@@ -57,16 +57,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 using namespace Assimp;
 using namespace Assimp;
 
 
 static const aiImporterDesc desc = {
 static const aiImporterDesc desc = {
-	"Irrlicht Mesh Reader",
-	"",
-	"",
-	"http://irrlicht.sourceforge.net/",
-	aiImporterFlags_SupportTextFlavour,
-	0,
-	0,
-	0,
-	0,
-	"xml irrmesh"
+    "Irrlicht Mesh Reader",
+    "",
+    "",
+    "http://irrlicht.sourceforge.net/",
+    aiImporterFlags_SupportTextFlavour,
+    0,
+    0,
+    0,
+    0,
+    "xml irrmesh"
 };
 };
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -80,419 +80,443 @@ IRRMeshImporter::~IRRMeshImporter() = default;
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 // Returns whether the class can handle the format of the given file.
 bool IRRMeshImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
 bool IRRMeshImporter::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.
-	 */
-	static const char *tokens[] = { "irrmesh" };
-	return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
+    /* 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.
+     */
+    static const char *tokens[] = { "irrmesh" };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get a list of all file extensions which are handled by this class
 // Get a list of all file extensions which are handled by this class
 const aiImporterDesc *IRRMeshImporter::GetInfo() const {
 const aiImporterDesc *IRRMeshImporter::GetInfo() const {
-	return &desc;
+    return &desc;
 }
 }
 
 
 static void releaseMaterial(aiMaterial **mat) {
 static void releaseMaterial(aiMaterial **mat) {
-	if (*mat != nullptr) {
-		delete *mat;
-		*mat = nullptr;
-	}
+    if (*mat != nullptr) {
+        delete *mat;
+        *mat = nullptr;
+    }
 }
 }
 
 
 static void releaseMesh(aiMesh **mesh) {
 static void releaseMesh(aiMesh **mesh) {
-	if (*mesh != nullptr) {
-		delete *mesh;
-		*mesh = nullptr;
-	}
+    if (*mesh != nullptr) {
+        delete *mesh;
+        *mesh = nullptr;
+    }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure.
 // Imports the given file into the given scene structure.
 void IRRMeshImporter::InternReadFile(const std::string &pFile,
 void IRRMeshImporter::InternReadFile(const std::string &pFile,
-		aiScene *pScene, IOSystem *pIOHandler) {
-	std::unique_ptr<IOStream> file(pIOHandler->Open(pFile));
-
-	// Check whether we can read from the file
-	if (file == nullptr)
-		throw DeadlyImportError("Failed to open IRRMESH file ", pFile);
-
-	// Construct the irrXML parser
-	XmlParser parser;
-	if (!parser.parse( file.get() )) {
-		throw DeadlyImportError("XML parse error while loading IRRMESH file ", pFile);
-	}
-	XmlNode root = parser.getRootNode();
-
-	// final data
-	std::vector<aiMaterial *> materials;
-	std::vector<aiMesh *> meshes;
-	materials.reserve(5);
-	meshes.reserve(5);
-
-	// temporary data - current mesh buffer
-	aiMaterial *curMat = nullptr;
-	aiMesh *curMesh = nullptr;
-	unsigned int curMatFlags = 0;
-
-	std::vector<aiVector3D> curVertices, curNormals, curTangents, curBitangents;
-	std::vector<aiColor4D> curColors;
-	std::vector<aiVector3D> curUVs, curUV2s;
-
-	// some temporary variables
-	int textMeaning = 0;
-	int vertexFormat = 0; // 0 = normal; 1 = 2 tcoords, 2 = tangents
-	bool useColors = false;
-
-	// Parse the XML file
-	for (pugi::xml_node child : root.children()) {
-		if (child.type() == pugi::node_element) {
-			if (!ASSIMP_stricmp(child.name(), "buffer") && (curMat || curMesh)) {
-				// end of previous buffer. A material and a mesh should be there
-				if (!curMat || !curMesh) {
-					ASSIMP_LOG_ERROR("IRRMESH: A buffer must contain a mesh and a material");
-					releaseMaterial(&curMat);
-					releaseMesh(&curMesh);
-				} else {
-					materials.push_back(curMat);
-					meshes.push_back(curMesh);
-				}
-				curMat = nullptr;
-				curMesh = nullptr;
-
-				curVertices.clear();
-				curColors.clear();
-				curNormals.clear();
-				curUV2s.clear();
-				curUVs.clear();
-				curTangents.clear();
-				curBitangents.clear();
-			}
-
-			if (!ASSIMP_stricmp(child.name(), "material")) {
-				if (curMat) {
-					ASSIMP_LOG_WARN("IRRMESH: Only one material description per buffer, please");
-					releaseMaterial(&curMat);
-				}
-				curMat = ParseMaterial(curMatFlags);
-			}
-			/* no else here! */ if (!ASSIMP_stricmp(child.name(), "vertices")) {
-				pugi::xml_attribute attr = child.attribute("vertexCount");
-				int num = attr.as_int();
-                //int num = reader->getAttributeValueAsInt("vertexCount");
-
-				if (!num) {
-					// This is possible ... remove the mesh from the list and skip further reading
-					ASSIMP_LOG_WARN("IRRMESH: Found mesh with zero vertices");
-
-					releaseMaterial(&curMat);
-					releaseMesh(&curMesh);
-					textMeaning = 0;
-					continue;
-				}
-
-				curVertices.reserve(num);
-				curNormals.reserve(num);
-				curColors.reserve(num);
-				curUVs.reserve(num);
-
-				// Determine the file format
-				//const char *t = reader->getAttributeValueSafe("type");
-                pugi::xml_attribute t = child.attribute("type");
-				if (!ASSIMP_stricmp("2tcoords", t.name())) {
-					curUV2s.reserve(num);
-					vertexFormat = 1;
-
-					if (curMatFlags & AI_IRRMESH_EXTRA_2ND_TEXTURE) {
-						// *********************************************************
-						// We have a second texture! So use this UV channel
-						// for it. The 2nd texture can be either a normal
-						// texture (solid_2layer or lightmap_xxx) or a normal
-						// map (normal_..., parallax_...)
-						// *********************************************************
-						int idx = 1;
-						aiMaterial *mat = (aiMaterial *)curMat;
-
-						if (curMatFlags & AI_IRRMESH_MAT_lightmap) {
-							mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_LIGHTMAP(0));
-						} else if (curMatFlags & AI_IRRMESH_MAT_normalmap_solid) {
-							mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_NORMALS(0));
-						} else if (curMatFlags & AI_IRRMESH_MAT_solid_2layer) {
-							mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_DIFFUSE(1));
-						}
-					}
-				} else if (!ASSIMP_stricmp("tangents", t.name())) {
-					curTangents.reserve(num);
-					curBitangents.reserve(num);
-					vertexFormat = 2;
-				} else if (ASSIMP_stricmp("standard", t.name())) {
-					releaseMaterial(&curMat);
-					ASSIMP_LOG_WARN("IRRMESH: Unknown vertex format");
-				} else
-					vertexFormat = 0;
-				textMeaning = 1;
-			} else if (!ASSIMP_stricmp(child.name(), "indices")) {
-				if (curVertices.empty() && curMat) {
-					releaseMaterial(&curMat);
-					throw DeadlyImportError("IRRMESH: indices must come after vertices");
-				}
-
-				textMeaning = 2;
-
-				// start a new mesh
-				curMesh = new aiMesh();
-
-				// allocate storage for all faces
-				pugi::xml_attribute attr = child.attribute("indexCount");
-				curMesh->mNumVertices = attr.as_int();
-				if (!curMesh->mNumVertices) {
-					// This is possible ... remove the mesh from the list and skip further reading
-					ASSIMP_LOG_WARN("IRRMESH: Found mesh with zero indices");
-
-					// mesh - away
-					releaseMesh(&curMesh);
-
-					// material - away
-					releaseMaterial(&curMat);
-
-					textMeaning = 0;
-					continue;
-				}
-
-				if (curMesh->mNumVertices % 3) {
-					ASSIMP_LOG_WARN("IRRMESH: Number if indices isn't divisible by 3");
-				}
-
-				curMesh->mNumFaces = curMesh->mNumVertices / 3;
-				curMesh->mFaces = new aiFace[curMesh->mNumFaces];
-
-				// setup some members
-				curMesh->mMaterialIndex = (unsigned int)materials.size();
-				curMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
-
-				// allocate storage for all vertices
-				curMesh->mVertices = new aiVector3D[curMesh->mNumVertices];
-
-				if (curNormals.size() == curVertices.size()) {
-					curMesh->mNormals = new aiVector3D[curMesh->mNumVertices];
-				}
-				if (curTangents.size() == curVertices.size()) {
-					curMesh->mTangents = new aiVector3D[curMesh->mNumVertices];
-				}
-				if (curBitangents.size() == curVertices.size()) {
-					curMesh->mBitangents = new aiVector3D[curMesh->mNumVertices];
-				}
-				if (curColors.size() == curVertices.size() && useColors) {
-					curMesh->mColors[0] = new aiColor4D[curMesh->mNumVertices];
-				}
-				if (curUVs.size() == curVertices.size()) {
-					curMesh->mTextureCoords[0] = new aiVector3D[curMesh->mNumVertices];
-				}
-				if (curUV2s.size() == curVertices.size()) {
-					curMesh->mTextureCoords[1] = new aiVector3D[curMesh->mNumVertices];
-				}
-			}
-			//break;
-
-			//case EXN_TEXT: {
-			const char *sz = child.child_value();
-			if (textMeaning == 1) {
-				textMeaning = 0;
-
-				// read vertices
-				do {
-					SkipSpacesAndLineEnd(&sz);
-					aiVector3D temp;
-					aiColor4D c;
-
-					// Read the vertex position
-					sz = fast_atoreal_move<float>(sz, (float &)temp.x);
-					SkipSpaces(&sz);
-
-					sz = fast_atoreal_move<float>(sz, (float &)temp.y);
-					SkipSpaces(&sz);
-
-					sz = fast_atoreal_move<float>(sz, (float &)temp.z);
-					SkipSpaces(&sz);
-					curVertices.push_back(temp);
-
-					// Read the vertex normals
-					sz = fast_atoreal_move<float>(sz, (float &)temp.x);
-					SkipSpaces(&sz);
-
-					sz = fast_atoreal_move<float>(sz, (float &)temp.y);
-					SkipSpaces(&sz);
-
-					sz = fast_atoreal_move<float>(sz, (float &)temp.z);
-					SkipSpaces(&sz);
-					curNormals.push_back(temp);
-
-					// read the vertex colors
-					uint32_t clr = strtoul16(sz, &sz);
-					ColorFromARGBPacked(clr, c);
-
-					if (!curColors.empty() && c != *(curColors.end() - 1))
-						useColors = true;
-
-					curColors.push_back(c);
-					SkipSpaces(&sz);
-
-					// read the first UV coordinate set
-					sz = fast_atoreal_move<float>(sz, (float &)temp.x);
-					SkipSpaces(&sz);
-
-					sz = fast_atoreal_move<float>(sz, (float &)temp.y);
-					SkipSpaces(&sz);
-					temp.z = 0.f;
-					temp.y = 1.f - temp.y; // DX to OGL
-					curUVs.push_back(temp);
-
-					// read the (optional) second UV coordinate set
-					if (vertexFormat == 1) {
-						sz = fast_atoreal_move<float>(sz, (float &)temp.x);
-						SkipSpaces(&sz);
-
-						sz = fast_atoreal_move<float>(sz, (float &)temp.y);
-						temp.y = 1.f - temp.y; // DX to OGL
-						curUV2s.push_back(temp);
-					}
-					// read optional tangent and bitangent vectors
-					else if (vertexFormat == 2) {
-						// tangents
-						sz = fast_atoreal_move<float>(sz, (float &)temp.x);
-						SkipSpaces(&sz);
-
-						sz = fast_atoreal_move<float>(sz, (float &)temp.z);
-						SkipSpaces(&sz);
-
-						sz = fast_atoreal_move<float>(sz, (float &)temp.y);
-						SkipSpaces(&sz);
-						temp.y *= -1.0f;
-						curTangents.push_back(temp);
-
-						// bitangents
-						sz = fast_atoreal_move<float>(sz, (float &)temp.x);
-						SkipSpaces(&sz);
-
-						sz = fast_atoreal_move<float>(sz, (float &)temp.z);
-						SkipSpaces(&sz);
-
-						sz = fast_atoreal_move<float>(sz, (float &)temp.y);
-						SkipSpaces(&sz);
-						temp.y *= -1.0f;
-						curBitangents.push_back(temp);
-					}
-				}
-
-				/* IMPORTANT: We assume that each vertex is specified in one
-                line. So we can skip the rest of the line - unknown vertex
-                elements are ignored.
-                */
-
-				while (SkipLine(&sz));
-			} else if (textMeaning == 2) {
-				textMeaning = 0;
-
-				// read indices
-				aiFace *curFace = curMesh->mFaces;
-				aiFace *const faceEnd = curMesh->mFaces + curMesh->mNumFaces;
-
-				aiVector3D *pcV = curMesh->mVertices;
-				aiVector3D *pcN = curMesh->mNormals;
-				aiVector3D *pcT = curMesh->mTangents;
-				aiVector3D *pcB = curMesh->mBitangents;
-				aiColor4D *pcC0 = curMesh->mColors[0];
-				aiVector3D *pcT0 = curMesh->mTextureCoords[0];
-				aiVector3D *pcT1 = curMesh->mTextureCoords[1];
-
-				unsigned int curIdx = 0;
-				unsigned int total = 0;
-				while (SkipSpacesAndLineEnd(&sz)) {
-					if (curFace >= faceEnd) {
-						ASSIMP_LOG_ERROR("IRRMESH: Too many indices");
-						break;
-					}
-					if (!curIdx) {
-						curFace->mNumIndices = 3;
-						curFace->mIndices = new unsigned int[3];
-					}
-
-					unsigned int idx = strtoul10(sz, &sz);
-					if (idx >= curVertices.size()) {
-						ASSIMP_LOG_ERROR("IRRMESH: Index out of range");
-						idx = 0;
-					}
-
-					curFace->mIndices[curIdx] = total++;
-
-					*pcV++ = curVertices[idx];
-					if (pcN) *pcN++ = curNormals[idx];
-					if (pcT) *pcT++ = curTangents[idx];
-					if (pcB) *pcB++ = curBitangents[idx];
-					if (pcC0) *pcC0++ = curColors[idx];
-					if (pcT0) *pcT0++ = curUVs[idx];
-					if (pcT1) *pcT1++ = curUV2s[idx];
-
-					if (++curIdx == 3) {
-						++curFace;
-						curIdx = 0;
-					}
-				}
-
-				if (curFace != faceEnd)
-					ASSIMP_LOG_ERROR("IRRMESH: Not enough indices");
-
-				// Finish processing the mesh - do some small material workarounds
-				if (curMatFlags & AI_IRRMESH_MAT_trans_vertex_alpha && !useColors) {
-					// Take the opacity value of the current material
-					// from the common vertex color alpha
-					aiMaterial *mat = (aiMaterial *)curMat;
-					mat->AddProperty(&curColors[0].a, 1, AI_MATKEY_OPACITY);
-				}
-			}
-		}
-	}
-
-	// End of the last buffer. A material and a mesh should be there
-	if (curMat || curMesh) {
-		if (!curMat || !curMesh) {
-			ASSIMP_LOG_ERROR("IRRMESH: A buffer must contain a mesh and a material");
-			releaseMaterial(&curMat);
-			releaseMesh(&curMesh);
-		} else {
-			materials.push_back(curMat);
-			meshes.push_back(curMesh);
-		}
-	}
-
-	if (materials.empty()) {
-		throw DeadlyImportError("IRRMESH: Unable to read a mesh from this file");
-	}
-
-	// now generate the output scene
-	pScene->mNumMeshes = (unsigned int)meshes.size();
-	pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
-	for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
-		pScene->mMeshes[i] = meshes[i];
-
-		// clean this value ...
-		pScene->mMeshes[i]->mNumUVComponents[3] = 0;
-	}
-
-	pScene->mNumMaterials = (unsigned int)materials.size();
-	pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
-	::memcpy(pScene->mMaterials, &materials[0], sizeof(void *) * pScene->mNumMaterials);
-
-	pScene->mRootNode = new aiNode();
-	pScene->mRootNode->mName.Set("<IRRMesh>");
-	pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
-	pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes];
-
-	for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
-		pScene->mRootNode->mMeshes[i] = i;
-	}
+        aiScene *pScene, IOSystem *pIOHandler) {
+    std::unique_ptr<IOStream> file(pIOHandler->Open(pFile));
+
+    // Check whether we can read from the file
+    if (file == nullptr)
+        throw DeadlyImportError("Failed to open IRRMESH file ", pFile);
+
+    // Construct the irrXML parser
+    XmlParser parser;
+    if (!parser.parse(file.get())) {
+        throw DeadlyImportError("XML parse error while loading IRRMESH file ", pFile);
+    }
+    XmlNode root = parser.getRootNode();
+
+    // final data
+    std::vector<aiMaterial *> materials;
+    std::vector<aiMesh *> meshes;
+    materials.reserve(5);
+    meshes.reserve(5);
+
+    // temporary data - current mesh buffer
+    // TODO move all these to inside loop
+    aiMaterial *curMat = nullptr;
+    aiMesh *curMesh = nullptr;
+    unsigned int curMatFlags = 0;
+
+    std::vector<aiVector3D> curVertices, curNormals, curTangents, curBitangents;
+    std::vector<aiColor4D> curColors;
+    std::vector<aiVector3D> curUVs, curUV2s;
+
+    // some temporary variables
+    // textMeaning is a 15 year old variable, that could've been an enum
+    // int textMeaning = 0; // 0=none? 1=vertices 2=indices
+    // int vertexFormat = 0; // 0 = normal; 1 = 2 tcoords, 2 = tangents
+    bool useColors = false;
+
+    /*
+    ** irrmesh files have a top level <mesh> owning multiple <buffer> nodes.
+    ** Each <buffer> contains <material>, <vertices>, and <indices>
+    ** <material> tags here directly owns the material data specs
+    ** <vertices> are a vertex per line, contains position, UV1 coords, maybe UV2, normal, tangent, bitangent
+    ** <boundingbox> is ignored, I think assimp recalculates those?
+    */
+
+    // Parse the XML file
+    pugi::xml_node const &meshNode = root.child("mesh");
+    for (pugi::xml_node bufferNode : meshNode.children()) {
+        if (ASSIMP_stricmp(bufferNode.name(), "buffer")) {
+            // Might be a useless warning
+            ASSIMP_LOG_WARN("IRRMESH: Ignoring non buffer node <", bufferNode.name(), "> in mesh declaration");
+            continue;
+        }
+
+        curMat = nullptr;
+        curMesh = nullptr;
+
+        curVertices.clear();
+        curColors.clear();
+        curNormals.clear();
+        curUV2s.clear();
+        curUVs.clear();
+        curTangents.clear();
+        curBitangents.clear();
+
+        // TODO ensure all three nodes are present and populated
+        // before allocating everything
+
+        // Get first material node
+        pugi::xml_node materialNode = bufferNode.child("material");
+        if (materialNode) {
+            curMat = ParseMaterial(materialNode, curMatFlags);
+            // Warn if there's more materials
+            if (materialNode.next_sibling("material")) {
+                ASSIMP_LOG_WARN("IRRMESH: Only one material description per buffer, please");
+            }
+        } else {
+            ASSIMP_LOG_ERROR("IRRMESH: Buffer must contain one material");
+            continue;
+        }
+
+        // Get first vertices node
+        pugi::xml_node verticesNode = bufferNode.child("vertices");
+        if (verticesNode) {
+            pugi::xml_attribute vertexCountAttrib = verticesNode.attribute("vertexCount");
+            int vertexCount = vertexCountAttrib.as_int();
+            if (vertexCount == 0) {
+                // This is possible ... remove the mesh from the list and skip further reading
+                ASSIMP_LOG_WARN("IRRMESH: Found mesh with zero vertices");
+                releaseMaterial(&curMat);
+                // releaseMesh(&curMesh);
+                continue; // Bail out early
+            };
+
+            curVertices.reserve(vertexCount);
+            curNormals.reserve(vertexCount);
+            curColors.reserve(vertexCount);
+            curUVs.reserve(vertexCount);
+
+            VertexFormat vertexFormat;
+            // Determine the file format
+            pugi::xml_attribute typeAttrib = verticesNode.attribute("type");
+            if (!ASSIMP_stricmp("2tcoords", typeAttrib.value())) {
+                curUV2s.reserve(vertexCount);
+                vertexFormat = VertexFormat::t2coord;
+                if (curMatFlags & AI_IRRMESH_EXTRA_2ND_TEXTURE) {
+                    // *********************************************************
+                    // We have a second texture! So use this UV channel
+                    // for it. The 2nd texture can be either a normal
+                    // texture (solid_2layer or lightmap_xxx) or a normal
+                    // map (normal_..., parallax_...)
+                    // *********************************************************
+                    int idx = 1;
+                    aiMaterial *mat = (aiMaterial *)curMat;
+
+                    if (curMatFlags & AI_IRRMESH_MAT_lightmap) {
+                        mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_LIGHTMAP(0));
+                    } else if (curMatFlags & AI_IRRMESH_MAT_normalmap_solid) {
+                        mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_NORMALS(0));
+                    } else if (curMatFlags & AI_IRRMESH_MAT_solid_2layer) {
+                        mat->AddProperty(&idx, 1, AI_MATKEY_UVWSRC_DIFFUSE(1));
+                    }
+                }
+            } else if (!ASSIMP_stricmp("tangents", typeAttrib.value())) {
+                curTangents.reserve(vertexCount);
+                curBitangents.reserve(vertexCount);
+                vertexFormat = VertexFormat::tangent;
+            } else if (!ASSIMP_stricmp("standard", typeAttrib.value())) {
+                vertexFormat = VertexFormat::standard;
+            } else {
+                // Unsupported format, discard whole buffer/mesh
+                // Assuming we have a correct material, then release it
+                // We don't have a correct mesh for sure here
+                releaseMaterial(&curMat);
+                ASSIMP_LOG_ERROR("IRRMESH: Unknown vertex format");
+                continue; // Skip rest of buffer
+            };
+
+            // We know what format buffer is, collect numbers
+            ParseBufferVertices(verticesNode.text().get(), vertexFormat,
+                    curVertices, curNormals,
+                    curTangents, curBitangents,
+                    curUVs, curUV2s, curColors, useColors);
+        }
+
+        // Get indices
+        // At this point we have some vertices and a valid material
+        // Collect indices and create aiMesh at the same time
+        pugi::xml_node indicesNode = bufferNode.child("indices");
+        if (indicesNode) {
+            // start a new mesh
+            curMesh = new aiMesh();
+
+            // allocate storage for all faces
+            pugi::xml_attribute attr = indicesNode.attribute("indexCount");
+            curMesh->mNumVertices = attr.as_int();
+            if (!curMesh->mNumVertices) {
+                // This is possible ... remove the mesh from the list and skip further reading
+                ASSIMP_LOG_WARN("IRRMESH: Found mesh with zero indices");
+
+                // mesh - away
+                releaseMesh(&curMesh);
+
+                // material - away
+                releaseMaterial(&curMat);
+                continue; // Go to next buffer
+            }
+
+            if (curMesh->mNumVertices % 3) {
+                ASSIMP_LOG_WARN("IRRMESH: Number if indices isn't divisible by 3");
+            }
+
+            curMesh->mNumFaces = curMesh->mNumVertices / 3;
+            curMesh->mFaces = new aiFace[curMesh->mNumFaces];
+
+            // setup some members
+            curMesh->mMaterialIndex = (unsigned int)materials.size();
+            curMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+
+            // allocate storage for all vertices
+            curMesh->mVertices = new aiVector3D[curMesh->mNumVertices];
+
+            if (curNormals.size() == curVertices.size()) {
+                curMesh->mNormals = new aiVector3D[curMesh->mNumVertices];
+            }
+            if (curTangents.size() == curVertices.size()) {
+                curMesh->mTangents = new aiVector3D[curMesh->mNumVertices];
+            }
+            if (curBitangents.size() == curVertices.size()) {
+                curMesh->mBitangents = new aiVector3D[curMesh->mNumVertices];
+            }
+            if (curColors.size() == curVertices.size() && useColors) {
+                curMesh->mColors[0] = new aiColor4D[curMesh->mNumVertices];
+            }
+            if (curUVs.size() == curVertices.size()) {
+                curMesh->mTextureCoords[0] = new aiVector3D[curMesh->mNumVertices];
+            }
+            if (curUV2s.size() == curVertices.size()) {
+                curMesh->mTextureCoords[1] = new aiVector3D[curMesh->mNumVertices];
+            }
+
+            // read indices
+            aiFace *curFace = curMesh->mFaces;
+            aiFace *const faceEnd = curMesh->mFaces + curMesh->mNumFaces;
+
+            aiVector3D *pcV = curMesh->mVertices;
+            aiVector3D *pcN = curMesh->mNormals;
+            aiVector3D *pcT = curMesh->mTangents;
+            aiVector3D *pcB = curMesh->mBitangents;
+            aiColor4D *pcC0 = curMesh->mColors[0];
+            aiVector3D *pcT0 = curMesh->mTextureCoords[0];
+            aiVector3D *pcT1 = curMesh->mTextureCoords[1];
+
+            unsigned int curIdx = 0;
+            unsigned int total = 0;
+
+            // NOTE this might explode for UTF-16 and wchars
+            const char *sz = indicesNode.text().get();
+            // For each index loop over aiMesh faces
+            while (SkipSpacesAndLineEnd(&sz)) {
+                if (curFace >= faceEnd) {
+                    ASSIMP_LOG_ERROR("IRRMESH: Too many indices");
+                    break;
+                }
+                // if new face
+                if (!curIdx) {
+                    curFace->mNumIndices = 3;
+                    curFace->mIndices = new unsigned int[3];
+                }
+
+                // Read index base 10
+                // function advances the pointer
+                unsigned int idx = strtoul10(sz, &sz);
+                if (idx >= curVertices.size()) {
+                    ASSIMP_LOG_ERROR("IRRMESH: Index out of range");
+                    idx = 0;
+                }
+
+                // make up our own indices?
+                curFace->mIndices[curIdx] = total++;
+
+                // Copy over data to aiMesh
+                *pcV++ = curVertices[idx];
+                if (pcN) *pcN++ = curNormals[idx];
+                if (pcT) *pcT++ = curTangents[idx];
+                if (pcB) *pcB++ = curBitangents[idx];
+                if (pcC0) *pcC0++ = curColors[idx];
+                if (pcT0) *pcT0++ = curUVs[idx];
+                if (pcT1) *pcT1++ = curUV2s[idx];
+
+                // start new face
+                if (++curIdx == 3) {
+                    ++curFace;
+                    curIdx = 0;
+                }
+            }
+            // We should be at the end of mFaces
+            if (curFace != faceEnd)
+                ASSIMP_LOG_ERROR("IRRMESH: Not enough indices");
+        }
+
+        // Finish processing the mesh - do some small material workarounds
+        if (curMatFlags & AI_IRRMESH_MAT_trans_vertex_alpha && !useColors) {
+            // Take the opacity value of the current material
+            // from the common vertex color alpha
+            aiMaterial *mat = (aiMaterial *)curMat;
+            mat->AddProperty(&curColors[0].a, 1, AI_MATKEY_OPACITY);
+        }
+        // textMeaning = 2;
+
+        // end of previous buffer. A material and a mesh should be there
+        if (!curMat || !curMesh) {
+            ASSIMP_LOG_ERROR("IRRMESH: A buffer must contain a mesh and a material");
+            releaseMaterial(&curMat);
+            releaseMesh(&curMesh);
+        } else {
+            materials.push_back(curMat);
+            meshes.push_back(curMesh);
+        }
+    }
+
+    // If one is empty then so is the other
+    if (materials.empty() || meshes.empty()) {
+        throw DeadlyImportError("IRRMESH: Unable to read a mesh from this file");
+    }
+
+    // now generate the output scene
+    pScene->mNumMeshes = (unsigned int)meshes.size();
+    pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
+    for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
+        pScene->mMeshes[i] = meshes[i];
+
+        // clean this value ...
+        pScene->mMeshes[i]->mNumUVComponents[3] = 0;
+    }
+
+    pScene->mNumMaterials = (unsigned int)materials.size();
+    pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
+    ::memcpy(pScene->mMaterials, &materials[0], sizeof(void *) * pScene->mNumMaterials);
+
+    pScene->mRootNode = new aiNode();
+    pScene->mRootNode->mName.Set("<IRRMesh>");
+    pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
+    pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes];
+
+    for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
+        pScene->mRootNode->mMeshes[i] = i;
+    };
+}
+
+void IRRMeshImporter::ParseBufferVertices(const char *sz, VertexFormat vertexFormat,
+        std::vector<aiVector3D> &vertices, std::vector<aiVector3D> &normals,
+        std::vector<aiVector3D> &tangents, std::vector<aiVector3D> &bitangents,
+        std::vector<aiVector3D> &UVs, std::vector<aiVector3D> &UV2s,
+        std::vector<aiColor4D> &colors, bool &useColors) {
+    // read vertices
+    do {
+        SkipSpacesAndLineEnd(&sz);
+        aiVector3D temp;
+        aiColor4D c;
+
+        // Read the vertex position
+        sz = fast_atoreal_move<float>(sz, (float &)temp.x);
+        SkipSpaces(&sz);
+
+        sz = fast_atoreal_move<float>(sz, (float &)temp.y);
+        SkipSpaces(&sz);
+
+        sz = fast_atoreal_move<float>(sz, (float &)temp.z);
+        SkipSpaces(&sz);
+        vertices.push_back(temp);
+
+        // Read the vertex normals
+        sz = fast_atoreal_move<float>(sz, (float &)temp.x);
+        SkipSpaces(&sz);
+
+        sz = fast_atoreal_move<float>(sz, (float &)temp.y);
+        SkipSpaces(&sz);
+
+        sz = fast_atoreal_move<float>(sz, (float &)temp.z);
+        SkipSpaces(&sz);
+        normals.push_back(temp);
+
+        // read the vertex colors
+        uint32_t clr = strtoul16(sz, &sz);
+        ColorFromARGBPacked(clr, c);
+
+        // If we're pushing more than one distinct color
+        if (!colors.empty() && c != *(colors.end() - 1))
+            useColors = true;
+
+        colors.push_back(c);
+        SkipSpaces(&sz);
+
+        // read the first UV coordinate set
+        sz = fast_atoreal_move<float>(sz, (float &)temp.x);
+        SkipSpaces(&sz);
+
+        sz = fast_atoreal_move<float>(sz, (float &)temp.y);
+        SkipSpaces(&sz);
+        temp.z = 0.f;
+        temp.y = 1.f - temp.y; // DX to OGL
+        UVs.push_back(temp);
+
+        // NOTE these correspond to specific S3DVertex* structs in irr sourcecode
+        // So by definition, all buffers have either UV2 or tangents or neither
+        // read the (optional) second UV coordinate set
+        if (vertexFormat == VertexFormat::t2coord) {
+            sz = fast_atoreal_move<float>(sz, (float &)temp.x);
+            SkipSpaces(&sz);
+
+            sz = fast_atoreal_move<float>(sz, (float &)temp.y);
+            temp.y = 1.f - temp.y; // DX to OGL
+            UV2s.push_back(temp);
+        }
+        // read optional tangent and bitangent vectors
+        else if (vertexFormat == VertexFormat::tangent) {
+            // tangents
+            sz = fast_atoreal_move<float>(sz, (float &)temp.x);
+            SkipSpaces(&sz);
+
+            sz = fast_atoreal_move<float>(sz, (float &)temp.z);
+            SkipSpaces(&sz);
+
+            sz = fast_atoreal_move<float>(sz, (float &)temp.y);
+            SkipSpaces(&sz);
+            temp.y *= -1.0f;
+            tangents.push_back(temp);
+
+            // bitangents
+            sz = fast_atoreal_move<float>(sz, (float &)temp.x);
+            SkipSpaces(&sz);
+
+            sz = fast_atoreal_move<float>(sz, (float &)temp.z);
+            SkipSpaces(&sz);
+
+            sz = fast_atoreal_move<float>(sz, (float &)temp.y);
+            SkipSpaces(&sz);
+            temp.y *= -1.0f;
+            bitangents.push_back(temp);
+        }
+    } while (SkipLine(&sz));
+    /* IMPORTANT: We assume that each vertex is specified in one
+    line. So we can skip the rest of the line - unknown vertex
+    elements are ignored.
+    */
 }
 }
 
 
 #endif // !! ASSIMP_BUILD_NO_IRRMESH_IMPORTER
 #endif // !! ASSIMP_BUILD_NO_IRRMESH_IMPORTER

+ 13 - 0
code/AssetLib/Irr/IRRMeshLoader.h

@@ -85,6 +85,19 @@ protected:
      */
      */
     void InternReadFile(const std::string &pFile, aiScene *pScene,
     void InternReadFile(const std::string &pFile, aiScene *pScene,
             IOSystem *pIOHandler) override;
             IOSystem *pIOHandler) override;
+
+private:
+    enum class VertexFormat {
+        standard = 0, // "standard" - also noted as 'normal' format elsewhere
+        t2coord = 1, // "2tcoord" - standard + 2 UV maps
+        tangent = 2, // "tangents" - standard + tangents and bitangents
+    };
+
+    void ParseBufferVertices(const char *sz, VertexFormat vertexFormat,
+            std::vector<aiVector3D> &vertices, std::vector<aiVector3D> &normals,
+            std::vector<aiVector3D> &tangents, std::vector<aiVector3D> &bitangents,
+            std::vector<aiVector3D> &UVs, std::vector<aiVector3D> &UV2s,
+            std::vector<aiColor4D> &colors, bool &useColors);
 };
 };
 
 
 } // end of namespace Assimp
 } // end of namespace Assimp

+ 213 - 213
code/AssetLib/Irr/IRRShared.cpp

@@ -43,302 +43,302 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *  @brief Shared utilities for the IRR and IRRMESH loaders
  *  @brief Shared utilities for the IRR and IRRMESH loaders
  */
  */
 
 
-//This section should be excluded only if both the Irrlicht AND the Irrlicht Mesh importers were omitted.
+// This section should be excluded only if both the Irrlicht AND the Irrlicht Mesh importers were omitted.
 #if !(defined(ASSIMP_BUILD_NO_IRR_IMPORTER) && defined(ASSIMP_BUILD_NO_IRRMESH_IMPORTER))
 #if !(defined(ASSIMP_BUILD_NO_IRR_IMPORTER) && defined(ASSIMP_BUILD_NO_IRRMESH_IMPORTER))
 
 
 #include "IRRShared.h"
 #include "IRRShared.h"
 #include <assimp/ParsingUtils.h>
 #include <assimp/ParsingUtils.h>
 #include <assimp/fast_atof.h>
 #include <assimp/fast_atof.h>
-#include <assimp/DefaultLogger.hpp>
 #include <assimp/material.h>
 #include <assimp/material.h>
+#include <assimp/DefaultLogger.hpp>
 
 
 using namespace Assimp;
 using namespace Assimp;
 
 
 // Transformation matrix to convert from Assimp to IRR space
 // Transformation matrix to convert from Assimp to IRR space
-const aiMatrix4x4 Assimp::AI_TO_IRR_MATRIX = aiMatrix4x4 (
-    1.0f, 0.0f, 0.0f, 0.0f,
-    0.0f, 0.0f, 1.0f, 0.0f,
-    0.0f, 1.0f, 0.0f, 0.0f,
-    0.0f, 0.0f, 0.0f, 1.0f);
+const aiMatrix4x4 Assimp::AI_TO_IRR_MATRIX = aiMatrix4x4(
+        1.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 1.0f, 0.0f,
+        0.0f, 1.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 0.0f, 1.0f);
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // read a property in hexadecimal format (i.e. ffffffff)
 // read a property in hexadecimal format (i.e. ffffffff)
-void IrrlichtBase::ReadHexProperty(HexProperty &out ) {
-	for (pugi::xml_attribute attrib : mNode->attributes()) {
+void IrrlichtBase::ReadHexProperty(HexProperty &out, pugi::xml_node& hexnode) {
+    for (pugi::xml_attribute attrib : hexnode.attributes()) {
         if (!ASSIMP_stricmp(attrib.name(), "name")) {
         if (!ASSIMP_stricmp(attrib.name(), "name")) {
-            out.name = std::string( attrib.value() );
-        } else if (!ASSIMP_stricmp(attrib.name(),"value")) {
+            out.name = std::string(attrib.value());
+        } else if (!ASSIMP_stricmp(attrib.name(), "value")) {
             // parse the hexadecimal value
             // parse the hexadecimal value
-			out.value = strtoul16(attrib.name());
+            out.value = strtoul16(attrib.value());
         }
         }
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // read a decimal property
 // read a decimal property
-void IrrlichtBase::ReadIntProperty(IntProperty & out) {
-	for (pugi::xml_attribute attrib : mNode->attributes()) {
-		if (!ASSIMP_stricmp(attrib.name(), "name")) {
-			out.name = std::string(attrib.value());
-        } else if (!ASSIMP_stricmp(attrib.value(),"value")) {
+void IrrlichtBase::ReadIntProperty(IntProperty &out, pugi::xml_node& intnode) {
+    for (pugi::xml_attribute attrib : intnode.attributes()) {
+        if (!ASSIMP_stricmp(attrib.name(), "name")) {
+            out.name = std::string(attrib.value());
+        } else if (!ASSIMP_stricmp(attrib.name(), "value")) {
             // parse the int value
             // parse the int value
-			out.value = strtol10(attrib.name());
+            out.value = strtol10(attrib.value());
         }
         }
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // read a string property
 // read a string property
-void IrrlichtBase::ReadStringProperty( StringProperty& out) {
-	for (pugi::xml_attribute attrib : mNode->attributes()) {
-		if (!ASSIMP_stricmp(attrib.name(), "name")) {
-			out.name = std::string(attrib.value());
-		} else if (!ASSIMP_stricmp(attrib.name(), "value")) {
+void IrrlichtBase::ReadStringProperty(StringProperty &out, pugi::xml_node& stringnode) {
+    for (pugi::xml_attribute attrib : stringnode.attributes()) {
+        if (!ASSIMP_stricmp(attrib.name(), "name")) {
+            out.name = std::string(attrib.value());
+        } else if (!ASSIMP_stricmp(attrib.name(), "value")) {
             // simple copy the string
             // simple copy the string
-			out.value = std::string(attrib.value());
+            out.value = std::string(attrib.value());
         }
         }
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // read a boolean property
 // read a boolean property
-void IrrlichtBase::ReadBoolProperty(BoolProperty &out) {
-	for (pugi::xml_attribute attrib : mNode->attributes()) {
-		if (!ASSIMP_stricmp(attrib.name(), "name")){
-			out.name = std::string(attrib.value());
-		} else if (!ASSIMP_stricmp(attrib.name(), "value")) {
+void IrrlichtBase::ReadBoolProperty(BoolProperty &out, pugi::xml_node& boolnode) {
+    for (pugi::xml_attribute attrib : boolnode.attributes()) {
+        if (!ASSIMP_stricmp(attrib.name(), "name")) {
+            out.name = std::string(attrib.value());
+        } else if (!ASSIMP_stricmp(attrib.name(), "value")) {
             // true or false, case insensitive
             // true or false, case insensitive
-			out.value = (ASSIMP_stricmp(attrib.value(), "true") ? false : true);
+            out.value = (ASSIMP_stricmp(attrib.value(), "true") ? false : true);
         }
         }
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // read a float property
 // read a float property
-void IrrlichtBase::ReadFloatProperty(FloatProperty &out) {
-	for (pugi::xml_attribute attrib : mNode->attributes()) {
-		if (!ASSIMP_stricmp(attrib.name(), "name")) {
-			out.name = std::string(attrib.value());
-		} else if (!ASSIMP_stricmp(attrib.name(), "value")) {
+void IrrlichtBase::ReadFloatProperty(FloatProperty &out, pugi::xml_node &floatnode) {
+    for (pugi::xml_attribute attrib : floatnode.attributes()) {
+        if (!ASSIMP_stricmp(attrib.name(), "name")) {
+            out.name = std::string(attrib.value());
+        } else if (!ASSIMP_stricmp(attrib.name(), "value")) {
             // just parse the float
             // just parse the float
-			out.value = fast_atof(attrib.value());
+            out.value = fast_atof(attrib.value());
         }
         }
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // read a vector property
 // read a vector property
-void IrrlichtBase::ReadVectorProperty( VectorProperty &out ) {
-	for (pugi::xml_attribute attrib : mNode->attributes()) {
-		if (!ASSIMP_stricmp(attrib.name(), "name")) {
-			out.name = std::string(attrib.value());
-		} else if (!ASSIMP_stricmp(attrib.name(), "value")) {
+void IrrlichtBase::ReadVectorProperty(VectorProperty &out, pugi::xml_node& vectornode) {
+    for (pugi::xml_attribute attrib : vectornode.attributes()) {
+        if (!ASSIMP_stricmp(attrib.name(), "name")) {
+            out.name = std::string(attrib.value());
+        } else if (!ASSIMP_stricmp(attrib.name(), "value")) {
             // three floats, separated with commas
             // three floats, separated with commas
             const char *ptr = attrib.value();
             const char *ptr = attrib.value();
 
 
             SkipSpaces(&ptr);
             SkipSpaces(&ptr);
-            ptr = fast_atoreal_move<float>( ptr,(float&)out.value.x );
+            ptr = fast_atoreal_move<float>(ptr, (float &)out.value.x);
             SkipSpaces(&ptr);
             SkipSpaces(&ptr);
             if (',' != *ptr) {
             if (',' != *ptr) {
                 ASSIMP_LOG_ERROR("IRR(MESH): Expected comma in vector definition");
                 ASSIMP_LOG_ERROR("IRR(MESH): Expected comma in vector definition");
-			} else {
-				SkipSpaces(ptr + 1, &ptr);
-			}
-            ptr = fast_atoreal_move<float>( ptr,(float&)out.value.y );
+            } else {
+                SkipSpaces(ptr + 1, &ptr);
+            }
+            ptr = fast_atoreal_move<float>(ptr, (float &)out.value.y);
             SkipSpaces(&ptr);
             SkipSpaces(&ptr);
             if (',' != *ptr) {
             if (',' != *ptr) {
                 ASSIMP_LOG_ERROR("IRR(MESH): Expected comma in vector definition");
                 ASSIMP_LOG_ERROR("IRR(MESH): Expected comma in vector definition");
-			} else {
-				SkipSpaces(ptr + 1, &ptr);
-			}
-            ptr = fast_atoreal_move<float>( ptr,(float&)out.value.z );
+            } else {
+                SkipSpaces(ptr + 1, &ptr);
+            }
+            ptr = fast_atoreal_move<float>(ptr, (float &)out.value.z);
         }
         }
     }
     }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Convert a string to a proper aiMappingMode
 // Convert a string to a proper aiMappingMode
-int ConvertMappingMode(const std::string& mode) {
+int ConvertMappingMode(const std::string &mode) {
     if (mode == "texture_clamp_repeat") {
     if (mode == "texture_clamp_repeat") {
         return aiTextureMapMode_Wrap;
         return aiTextureMapMode_Wrap;
-	} else if (mode == "texture_clamp_mirror") {
-		return aiTextureMapMode_Mirror;
-	}
+    } else if (mode == "texture_clamp_mirror") {
+        return aiTextureMapMode_Mirror;
+    }
 
 
     return aiTextureMapMode_Clamp;
     return aiTextureMapMode_Clamp;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Parse a material from the XML file
 // Parse a material from the XML file
-aiMaterial* IrrlichtBase::ParseMaterial(unsigned int& matFlags) {
-    aiMaterial* mat = new aiMaterial();
+aiMaterial *IrrlichtBase::ParseMaterial(pugi::xml_node& materialNode, unsigned int &matFlags) {
+    aiMaterial *mat = new aiMaterial();
     aiColor4D clr;
     aiColor4D clr;
     aiString s;
     aiString s;
 
 
     matFlags = 0; // zero output flags
     matFlags = 0; // zero output flags
-    int cnt  = 0; // number of used texture channels
+    int cnt = 0; // number of used texture channels
     unsigned int nd = 0;
     unsigned int nd = 0;
 
 
-    for (pugi::xml_node child : mNode->children()) {
-		if (!ASSIMP_stricmp(child.name(), "color")) { // Hex properties
-			HexProperty prop;
-			ReadHexProperty(prop);
-			if (prop.name == "Diffuse") {
-				ColorFromARGBPacked(prop.value, clr);
-				mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE);
-			} else if (prop.name == "Ambient") {
-				ColorFromARGBPacked(prop.value, clr);
-				mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_AMBIENT);
-			} else if (prop.name == "Specular") {
-				ColorFromARGBPacked(prop.value, clr);
-				mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_SPECULAR);
-			}
-
-			// NOTE: The 'emissive' property causes problems. It is
-			// often != 0, even if there is obviously no light
-			// emitted by the described surface. In fact I think
-			// IRRLICHT ignores this property, too.
+    for (pugi::xml_node child : materialNode.children()) {
+        if (!ASSIMP_stricmp(child.name(), "color")) { // Hex properties
+            HexProperty prop;
+            ReadHexProperty(prop, child);
+            if (prop.name == "Diffuse") {
+                ColorFromARGBPacked(prop.value, clr);
+                mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE);
+            } else if (prop.name == "Ambient") {
+                ColorFromARGBPacked(prop.value, clr);
+                mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_AMBIENT);
+            } else if (prop.name == "Specular") {
+                ColorFromARGBPacked(prop.value, clr);
+                mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_SPECULAR);
+            }
+
+            // NOTE: The 'emissive' property causes problems. It is
+            // often != 0, even if there is obviously no light
+            // emitted by the described surface. In fact I think
+            // IRRLICHT ignores this property, too.
 #if 0
 #if 0
             else if (prop.name == "Emissive") {
             else if (prop.name == "Emissive") {
                 ColorFromARGBPacked(prop.value,clr);
                 ColorFromARGBPacked(prop.value,clr);
                 mat->AddProperty(&clr,1,AI_MATKEY_COLOR_EMISSIVE);
                 mat->AddProperty(&clr,1,AI_MATKEY_COLOR_EMISSIVE);
             }
             }
 #endif
 #endif
-		} else if (!ASSIMP_stricmp(child.name(), "float")) { // Float properties
-			FloatProperty prop;
-			ReadFloatProperty(prop);
-			if (prop.name == "Shininess") {
-				mat->AddProperty(&prop.value, 1, AI_MATKEY_SHININESS);
-			}
-		} else if (!ASSIMP_stricmp(child.name(), "bool")) { // Bool properties
-			BoolProperty prop;
-			ReadBoolProperty(prop);
-			if (prop.name == "Wireframe") {
-				int val = (prop.value ? true : false);
-				mat->AddProperty(&val, 1, AI_MATKEY_ENABLE_WIREFRAME);
-			} else if (prop.name == "GouraudShading") {
-				int val = (prop.value ? aiShadingMode_Gouraud : aiShadingMode_NoShading);
-				mat->AddProperty(&val, 1, AI_MATKEY_SHADING_MODEL);
-			} else if (prop.name == "BackfaceCulling") {
-				int val = (!prop.value);
-				mat->AddProperty(&val, 1, AI_MATKEY_TWOSIDED);
-			}
-		} else if (!ASSIMP_stricmp(child.name(), "texture") ||
-				   !ASSIMP_stricmp(child.name(), "enum")) { // String properties - textures and texture related properties
-			StringProperty prop;
-			ReadStringProperty(prop);
-			if (prop.value.length()) {
-				// material type (shader)
-				if (prop.name == "Type") {
-					if (prop.value == "solid") {
-						// default material ...
-					} else if (prop.value == "trans_vertex_alpha") {
-						matFlags = AI_IRRMESH_MAT_trans_vertex_alpha;
-					} else if (prop.value == "lightmap") {
-						matFlags = AI_IRRMESH_MAT_lightmap;
-					} else if (prop.value == "solid_2layer") {
-						matFlags = AI_IRRMESH_MAT_solid_2layer;
-					} else if (prop.value == "lightmap_m2") {
-						matFlags = AI_IRRMESH_MAT_lightmap_m2;
-					} else if (prop.value == "lightmap_m4") {
-						matFlags = AI_IRRMESH_MAT_lightmap_m4;
-					} else if (prop.value == "lightmap_light") {
-						matFlags = AI_IRRMESH_MAT_lightmap_light;
-					} else if (prop.value == "lightmap_light_m2") {
-						matFlags = AI_IRRMESH_MAT_lightmap_light_m2;
-					} else if (prop.value == "lightmap_light_m4") {
-						matFlags = AI_IRRMESH_MAT_lightmap_light_m4;
-					} else if (prop.value == "lightmap_add") {
-						matFlags = AI_IRRMESH_MAT_lightmap_add;
-					} else if (prop.value == "normalmap_solid" ||
-							   prop.value == "parallaxmap_solid") { // Normal and parallax maps are treated equally
-						matFlags = AI_IRRMESH_MAT_normalmap_solid;
-					} else if (prop.value == "normalmap_trans_vertex_alpha" ||
-							   prop.value == "parallaxmap_trans_vertex_alpha") {
-						matFlags = AI_IRRMESH_MAT_normalmap_tva;
-					} else if (prop.value == "normalmap_trans_add" ||
-							   prop.value == "parallaxmap_trans_add") {
-						matFlags = AI_IRRMESH_MAT_normalmap_ta;
-					} else {
-						ASSIMP_LOG_WARN("IRRMat: Unrecognized material type: ", prop.value);
-					}
-				}
-
-				// Up to 4 texture channels are supported
-				if (prop.name == "Texture1") {
-					// Always accept the primary texture channel
-					++cnt;
-					s.Set(prop.value);
-					mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0));
-				} else if (prop.name == "Texture2" && cnt == 1) {
-					// 2-layer material lightmapped?
-					if (matFlags & AI_IRRMESH_MAT_lightmap) {
-						++cnt;
-						s.Set(prop.value);
-						mat->AddProperty(&s, AI_MATKEY_TEXTURE_LIGHTMAP(0));
-
-						// set the corresponding material flag
-						matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE;
-					} else if (matFlags & AI_IRRMESH_MAT_normalmap_solid) { // alternatively: normal or parallax mapping
-						++cnt;
-						s.Set(prop.value);
-						mat->AddProperty(&s, AI_MATKEY_TEXTURE_NORMALS(0));
-
-						// set the corresponding material flag
-						matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE;
-					} else if (matFlags & AI_IRRMESH_MAT_solid_2layer) { // or just as second diffuse texture
-						++cnt;
-						s.Set(prop.value);
-						mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(1));
-						++nd;
-
-						// set the corresponding material flag
-						matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE;
-					} else {
-						ASSIMP_LOG_WARN("IRRmat: Skipping second texture");
-					}
-				} else if (prop.name == "Texture3" && cnt == 2) {
-					// Irrlicht does not seem to use these channels.
-					++cnt;
-					s.Set(prop.value);
-					mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(nd + 1));
-				} else if (prop.name == "Texture4" && cnt == 3) {
-					// Irrlicht does not seem to use these channels.
-					++cnt;
-					s.Set(prop.value);
-					mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(nd + 2));
-				}
-
-				// Texture mapping options
-				if (prop.name == "TextureWrap1" && cnt >= 1) {
-					int map = ConvertMappingMode(prop.value);
-					mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0));
-					mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0));
-				} else if (prop.name == "TextureWrap2" && cnt >= 2) {
-					int map = ConvertMappingMode(prop.value);
-					if (matFlags & AI_IRRMESH_MAT_lightmap) {
-						mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_LIGHTMAP(0));
-						mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_LIGHTMAP(0));
-					} else if (matFlags & (AI_IRRMESH_MAT_normalmap_solid)) {
-						mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_NORMALS(0));
-						mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_NORMALS(0));
-					} else if (matFlags & AI_IRRMESH_MAT_solid_2layer) {
-						mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(1));
-						mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(1));
-					}
-				} else if (prop.name == "TextureWrap3" && cnt >= 3) {
-					int map = ConvertMappingMode(prop.value);
-					mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(nd + 1));
-					mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(nd + 1));
-				} else if (prop.name == "TextureWrap4" && cnt >= 4) {
-					int map = ConvertMappingMode(prop.value);
-					mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(nd + 2));
-					mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(nd + 2));
-				}
-			}
-		}
-		//break;
-		/*case EXN_ELEMENT_END:
+        } else if (!ASSIMP_stricmp(child.name(), "float")) { // Float properties
+            FloatProperty prop;
+            ReadFloatProperty(prop, child);
+            if (prop.name == "Shininess") {
+                mat->AddProperty(&prop.value, 1, AI_MATKEY_SHININESS);
+            }
+        } else if (!ASSIMP_stricmp(child.name(), "bool")) { // Bool properties
+            BoolProperty prop;
+            ReadBoolProperty(prop, child);
+            if (prop.name == "Wireframe") {
+                int val = (prop.value ? true : false);
+                mat->AddProperty(&val, 1, AI_MATKEY_ENABLE_WIREFRAME);
+            } else if (prop.name == "GouraudShading") {
+                int val = (prop.value ? aiShadingMode_Gouraud : aiShadingMode_NoShading);
+                mat->AddProperty(&val, 1, AI_MATKEY_SHADING_MODEL);
+            } else if (prop.name == "BackfaceCulling") {
+                int val = (!prop.value);
+                mat->AddProperty(&val, 1, AI_MATKEY_TWOSIDED);
+            }
+        } else if (!ASSIMP_stricmp(child.name(), "texture") ||
+                   !ASSIMP_stricmp(child.name(), "enum")) { // String properties - textures and texture related properties
+            StringProperty prop;
+            ReadStringProperty(prop, child);
+            if (prop.value.length()) {
+                // material type (shader)
+                if (prop.name == "Type") {
+                    if (prop.value == "solid") {
+                        // default material ...
+                    } else if (prop.value == "trans_vertex_alpha") {
+                        matFlags = AI_IRRMESH_MAT_trans_vertex_alpha;
+                    } else if (prop.value == "lightmap") {
+                        matFlags = AI_IRRMESH_MAT_lightmap;
+                    } else if (prop.value == "solid_2layer") {
+                        matFlags = AI_IRRMESH_MAT_solid_2layer;
+                    } else if (prop.value == "lightmap_m2") {
+                        matFlags = AI_IRRMESH_MAT_lightmap_m2;
+                    } else if (prop.value == "lightmap_m4") {
+                        matFlags = AI_IRRMESH_MAT_lightmap_m4;
+                    } else if (prop.value == "lightmap_light") {
+                        matFlags = AI_IRRMESH_MAT_lightmap_light;
+                    } else if (prop.value == "lightmap_light_m2") {
+                        matFlags = AI_IRRMESH_MAT_lightmap_light_m2;
+                    } else if (prop.value == "lightmap_light_m4") {
+                        matFlags = AI_IRRMESH_MAT_lightmap_light_m4;
+                    } else if (prop.value == "lightmap_add") {
+                        matFlags = AI_IRRMESH_MAT_lightmap_add;
+                    } else if (prop.value == "normalmap_solid" ||
+                               prop.value == "parallaxmap_solid") { // Normal and parallax maps are treated equally
+                        matFlags = AI_IRRMESH_MAT_normalmap_solid;
+                    } else if (prop.value == "normalmap_trans_vertex_alpha" ||
+                               prop.value == "parallaxmap_trans_vertex_alpha") {
+                        matFlags = AI_IRRMESH_MAT_normalmap_tva;
+                    } else if (prop.value == "normalmap_trans_add" ||
+                               prop.value == "parallaxmap_trans_add") {
+                        matFlags = AI_IRRMESH_MAT_normalmap_ta;
+                    } else {
+                        ASSIMP_LOG_WARN("IRRMat: Unrecognized material type: ", prop.value);
+                    }
+                }
+
+                // Up to 4 texture channels are supported
+                if (prop.name == "Texture1") {
+                    // Always accept the primary texture channel
+                    ++cnt;
+                    s.Set(prop.value);
+                    mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0));
+                } else if (prop.name == "Texture2" && cnt == 1) {
+                    // 2-layer material lightmapped?
+                    if (matFlags & AI_IRRMESH_MAT_lightmap) {
+                        ++cnt;
+                        s.Set(prop.value);
+                        mat->AddProperty(&s, AI_MATKEY_TEXTURE_LIGHTMAP(0));
+
+                        // set the corresponding material flag
+                        matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE;
+                    } else if (matFlags & AI_IRRMESH_MAT_normalmap_solid) { // alternatively: normal or parallax mapping
+                        ++cnt;
+                        s.Set(prop.value);
+                        mat->AddProperty(&s, AI_MATKEY_TEXTURE_NORMALS(0));
+
+                        // set the corresponding material flag
+                        matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE;
+                    } else if (matFlags & AI_IRRMESH_MAT_solid_2layer) { // or just as second diffuse texture
+                        ++cnt;
+                        s.Set(prop.value);
+                        mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(1));
+                        ++nd;
+
+                        // set the corresponding material flag
+                        matFlags |= AI_IRRMESH_EXTRA_2ND_TEXTURE;
+                    } else {
+                        ASSIMP_LOG_WARN("IRRmat: Skipping second texture");
+                    }
+                } else if (prop.name == "Texture3" && cnt == 2) {
+                    // Irrlicht does not seem to use these channels.
+                    ++cnt;
+                    s.Set(prop.value);
+                    mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(nd + 1));
+                } else if (prop.name == "Texture4" && cnt == 3) {
+                    // Irrlicht does not seem to use these channels.
+                    ++cnt;
+                    s.Set(prop.value);
+                    mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(nd + 2));
+                }
+
+                // Texture mapping options
+                if (prop.name == "TextureWrap1" && cnt >= 1) {
+                    int map = ConvertMappingMode(prop.value);
+                    mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0));
+                    mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0));
+                } else if (prop.name == "TextureWrap2" && cnt >= 2) {
+                    int map = ConvertMappingMode(prop.value);
+                    if (matFlags & AI_IRRMESH_MAT_lightmap) {
+                        mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_LIGHTMAP(0));
+                        mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_LIGHTMAP(0));
+                    } else if (matFlags & (AI_IRRMESH_MAT_normalmap_solid)) {
+                        mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_NORMALS(0));
+                        mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_NORMALS(0));
+                    } else if (matFlags & AI_IRRMESH_MAT_solid_2layer) {
+                        mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(1));
+                        mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(1));
+                    }
+                } else if (prop.name == "TextureWrap3" && cnt >= 3) {
+                    int map = ConvertMappingMode(prop.value);
+                    mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(nd + 1));
+                    mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(nd + 1));
+                } else if (prop.name == "TextureWrap4" && cnt >= 4) {
+                    int map = ConvertMappingMode(prop.value);
+                    mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(nd + 2));
+                    mat->AddProperty(&map, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(nd + 2));
+                }
+            }
+        }
+        // break;
+        /*case EXN_ELEMENT_END:
 
 
                 // Assume there are no further nested nodes in <material> elements
                 // Assume there are no further nested nodes in <material> elements
                 if ( !ASSIMP_stricmp(reader->getNodeName(),"material") ||
                 if ( !ASSIMP_stricmp(reader->getNodeName(),"material") ||
@@ -378,8 +378,8 @@ aiMaterial* IrrlichtBase::ParseMaterial(unsigned int& matFlags) {
                 break;
                 break;
         }
         }
     }*/
     }*/
-	}
-    ASSIMP_LOG_ERROR("IRRMESH: Unexpected end of file. Material is not complete");
+    }
+    //ASSIMP_LOG_ERROR("IRRMESH: Unexpected end of file. Material is not complete");
 
 
     return mat;
     return mat;
 }
 }

+ 11 - 12
code/AssetLib/Irr/IRRShared.h

@@ -1,8 +1,8 @@
 
 
 
 
 /** @file  IRRShared.h
 /** @file  IRRShared.h
-  * @brief Shared utilities for the IRR and IRRMESH loaders
-  */
+ * @brief Shared utilities for the IRR and IRRMESH loaders
+ */
 
 
 #ifndef INCLUDED_AI_IRRSHARED_H
 #ifndef INCLUDED_AI_IRRSHARED_H
 #define INCLUDED_AI_IRRSHARED_H
 #define INCLUDED_AI_IRRSHARED_H
@@ -58,8 +58,7 @@ extern const aiMatrix4x4 AI_TO_IRR_MATRIX;
  */
  */
 class IrrlichtBase {
 class IrrlichtBase {
 protected:
 protected:
-    IrrlichtBase() :
-            mNode(nullptr) {
+    IrrlichtBase() {
         // empty
         // empty
     }
     }
 
 
@@ -82,25 +81,25 @@ protected:
 
 
     /// XML reader instance
     /// XML reader instance
     XmlParser mParser;
     XmlParser mParser;
-    pugi::xml_node *mNode;
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Parse a material description from the XML
     /** Parse a material description from the XML
      *  @return The created material
      *  @return The created material
      *  @param matFlags Receives AI_IRRMESH_MAT_XX flags
      *  @param matFlags Receives AI_IRRMESH_MAT_XX flags
      */
      */
-    aiMaterial *ParseMaterial(unsigned int &matFlags);
+    aiMaterial *ParseMaterial(pugi::xml_node &materialNode, unsigned int &matFlags);
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Read a property of the specified type from the current XML element.
     /** Read a property of the specified type from the current XML element.
      *  @param out Receives output data
      *  @param out Receives output data
+     *  @param node XML attribute element containing data
      */
      */
-    void ReadHexProperty(HexProperty &out);
-    void ReadStringProperty(StringProperty &out);
-    void ReadBoolProperty(BoolProperty &out);
-    void ReadFloatProperty(FloatProperty &out);
-    void ReadVectorProperty(VectorProperty &out);
-    void ReadIntProperty(IntProperty &out);
+    void ReadHexProperty(HexProperty &out, pugi::xml_node& hexnode);
+    void ReadStringProperty(StringProperty &out, pugi::xml_node& stringnode);
+    void ReadBoolProperty(BoolProperty &out, pugi::xml_node& boolnode);
+    void ReadFloatProperty(FloatProperty &out, pugi::xml_node& floatnode);
+    void ReadVectorProperty(VectorProperty &out, pugi::xml_node& vectornode);
+    void ReadIntProperty(IntProperty &out, pugi::xml_node& intnode);
 };
 };
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------

+ 0 - 1
code/AssetLib/LWO/LWOBLoader.cpp

@@ -65,7 +65,6 @@ void LWOImporter::LoadLWOBFile()
         if (mFileBuffer + head.length > end)
         if (mFileBuffer + head.length > end)
         {
         {
             throw DeadlyImportError("LWOB: Invalid chunk length");
             throw DeadlyImportError("LWOB: Invalid chunk length");
-            break;
         }
         }
         uint8_t* const next = mFileBuffer+head.length;
         uint8_t* const next = mFileBuffer+head.length;
         switch (head.type)
         switch (head.type)

+ 68 - 47
code/AssetLib/LWO/LWOLoader.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2022, assimp team
 Copyright (c) 2006-2022, assimp team
 
 
-
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -51,6 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "AssetLib/LWO/LWOLoader.h"
 #include "AssetLib/LWO/LWOLoader.h"
 #include "PostProcessing/ConvertToLHProcess.h"
 #include "PostProcessing/ConvertToLHProcess.h"
 #include "PostProcessing/ProcessHelper.h"
 #include "PostProcessing/ProcessHelper.h"
+#include "Geometry/GeometryUtils.h"
 
 
 #include <assimp/ByteSwapper.h>
 #include <assimp/ByteSwapper.h>
 #include <assimp/SGSpatialSort.h>
 #include <assimp/SGSpatialSort.h>
@@ -178,7 +177,7 @@ void LWOImporter::InternReadFile(const std::string &pFile,
     mLayers->push_back(Layer());
     mLayers->push_back(Layer());
     mCurLayer = &mLayers->back();
     mCurLayer = &mLayers->back();
     mCurLayer->mName = "<LWODefault>";
     mCurLayer->mName = "<LWODefault>";
-    mCurLayer->mIndex = (uint16_t) -1;
+    mCurLayer->mIndex = 1;
 
 
     // old lightwave file format (prior to v6)
     // old lightwave file format (prior to v6)
     mIsLWO2 = false;
     mIsLWO2 = false;
@@ -398,14 +397,6 @@ void LWOImporter::InternReadFile(const std::string &pFile,
                             pvVC[w]++;
                             pvVC[w]++;
                         }
                         }
 
 
-#if 0
-                        // process vertex weights. We can't properly reconstruct the whole skeleton for now,
-                        // but we can create dummy bones for all weight channels which we have.
-                        for (unsigned int w = 0; w < layer.mWeightChannels.size();++w)
-                        {
-                        }
-#endif
-
                         face.mIndices[q] = vert;
                         face.mIndices[q] = vert;
                     }
                     }
                     pf->mIndices = face.mIndices;
                     pf->mIndices = face.mIndices;
@@ -429,7 +420,7 @@ void LWOImporter::InternReadFile(const std::string &pFile,
         // Generate nodes to render the mesh. Store the source layer in the mParent member of the nodes
         // Generate nodes to render the mesh. Store the source layer in the mParent member of the nodes
         unsigned int num = static_cast<unsigned int>(apcMeshes.size() - meshStart);
         unsigned int num = static_cast<unsigned int>(apcMeshes.size() - meshStart);
         if (layer.mName != "<LWODefault>" || num > 0) {
         if (layer.mName != "<LWODefault>" || num > 0) {
-            aiNode *pcNode = new aiNode();
+            std::unique_ptr<aiNode> pcNode(new aiNode());
             pcNode->mName.Set(layer.mName);
             pcNode->mName.Set(layer.mName);
             pcNode->mParent = (aiNode *)&layer;
             pcNode->mParent = (aiNode *)&layer;
             pcNode->mNumMeshes = num;
             pcNode->mNumMeshes = num;
@@ -439,7 +430,8 @@ void LWOImporter::InternReadFile(const std::string &pFile,
                 for (unsigned int p = 0; p < pcNode->mNumMeshes; ++p)
                 for (unsigned int p = 0; p < pcNode->mNumMeshes; ++p)
                     pcNode->mMeshes[p] = p + meshStart;
                     pcNode->mMeshes[p] = p + meshStart;
             }
             }
-            apcNodes[layer.mIndex] = pcNode;
+            ASSIMP_LOG_DEBUG("insert apcNode for layer ", layer.mIndex, " \"", layer.mName, "\"");
+            apcNodes[layer.mIndex] = pcNode.release();
         }
         }
     }
     }
 
 
@@ -535,7 +527,6 @@ void LWOImporter::ComputeNormals(aiMesh *mesh, const std::vector<unsigned int> &
                         continue;
                         continue;
                     vNormals += v;
                     vNormals += v;
                 }
                 }
-                mesh->mNormals[idx] = vNormals.Normalize();
             }
             }
         }
         }
     }
     }
@@ -556,7 +547,6 @@ void LWOImporter::ComputeNormals(aiMesh *mesh, const std::vector<unsigned int> &
                     const aiVector3D &v = faceNormals[*a];
                     const aiVector3D &v = faceNormals[*a];
                     vNormals += v;
                     vNormals += v;
                 }
                 }
-                vNormals.Normalize();
                 for (std::vector<unsigned int>::const_iterator a = poResult.begin(); a != poResult.end(); ++a) {
                 for (std::vector<unsigned int>::const_iterator a = poResult.begin(); a != poResult.end(); ++a) {
                     mesh->mNormals[*a] = vNormals;
                     mesh->mNormals[*a] = vNormals;
                     vertexDone[*a] = true;
                     vertexDone[*a] = true;
@@ -564,6 +554,7 @@ void LWOImporter::ComputeNormals(aiMesh *mesh, const std::vector<unsigned int> &
             }
             }
         }
         }
     }
     }
+    GeometryUtils::normalizeVectorArray(mesh->mNormals, mesh->mNormals, mesh->mNumVertices);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -572,40 +563,64 @@ void LWOImporter::GenerateNodeGraph(std::map<uint16_t, aiNode *> &apcNodes) {
     aiNode *root = mScene->mRootNode = new aiNode();
     aiNode *root = mScene->mRootNode = new aiNode();
     root->mName.Set("<LWORoot>");
     root->mName.Set("<LWORoot>");
 
 
-    //Set parent of all children, inserting pivots
-    std::map<uint16_t, aiNode *> mapPivot;
-    for (auto itapcNodes = apcNodes.begin(); itapcNodes != apcNodes.end(); ++itapcNodes) {
-
-        //Get the parent index
-        LWO::Layer *nodeLayer = (LWO::Layer *)(itapcNodes->second->mParent);
-        uint16_t parentIndex = nodeLayer->mParent;
+    ASSIMP_LOG_DEBUG("apcNodes initial size: ", apcNodes.size());
+    if (!apcNodes.empty()) {
+        ASSIMP_LOG_DEBUG("first apcNode is: ", apcNodes.begin()->first, " \"", apcNodes.begin()->second->mName.C_Str(), "\"");
+    }
 
 
-        //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));
-        itapcNodes->second->mParent = pivotNode;
+    //Set parent of all children, inserting pivots
+    {
+        std::map<uint16_t, aiNode *> mapPivot;
+        for (auto itapcNodes = apcNodes.begin(); itapcNodes != apcNodes.end(); ++itapcNodes) {
+
+            //Get the parent index
+            LWO::Layer *nodeLayer = (LWO::Layer *)(itapcNodes->second->mParent);
+            uint16_t parentIndex = nodeLayer->mParent;
+
+            //Create pivot node, store it into the pivot map, and set the parent as the pivot
+            std::unique_ptr<aiNode> pivotNode(new aiNode());
+            pivotNode->mName.Set("Pivot-" + std::string(itapcNodes->second->mName.data));
+            itapcNodes->second->mParent = pivotNode.get();
+
+            //Look for the parent node to attach the pivot to
+            if (apcNodes.find(parentIndex) != apcNodes.end()) {
+                pivotNode->mParent = apcNodes[parentIndex];
+            } else {
+                //If not, attach to the root node
+                pivotNode->mParent = root;
+            }
 
 
-        //Look for the parent node to attach the pivot to
-        if (apcNodes.find(parentIndex) != apcNodes.end()) {
-            pivotNode->mParent = apcNodes[parentIndex];
-        } else {
-            //If not, attach to the root node
-            pivotNode->mParent = root;
+            //Set the node and the pivot node transformation
+            itapcNodes->second->mTransformation.a4 = -nodeLayer->mPivot.x;
+            itapcNodes->second->mTransformation.b4 = -nodeLayer->mPivot.y;
+            itapcNodes->second->mTransformation.c4 = -nodeLayer->mPivot.z;
+            pivotNode->mTransformation.a4 = nodeLayer->mPivot.x;
+            pivotNode->mTransformation.b4 = nodeLayer->mPivot.y;
+            pivotNode->mTransformation.c4 = nodeLayer->mPivot.z;
+            uint16_t pivotNodeId = static_cast<uint16_t>(-(itapcNodes->first + 2));
+            ASSIMP_LOG_DEBUG("insert pivot node: ", pivotNodeId);
+            auto oldNodeIt = mapPivot.find(pivotNodeId);
+            if (oldNodeIt != mapPivot.end()) {
+                ASSIMP_LOG_ERROR("attempted to insert pivot node which already exists in pivot map ", pivotNodeId, " \"", pivotNode->mName.C_Str(), "\"");
+            } else {
+                mapPivot.emplace(pivotNodeId, pivotNode.release());
+            }
         }
         }
 
 
-        //Set the node and the pivot node transformation
-        itapcNodes->second->mTransformation.a4 = -nodeLayer->mPivot.x;
-        itapcNodes->second->mTransformation.b4 = -nodeLayer->mPivot.y;
-        itapcNodes->second->mTransformation.c4 = -nodeLayer->mPivot.z;
-        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
-    for (auto itMapPivot = mapPivot.begin(); itMapPivot != mapPivot.end(); ++itMapPivot) {
-        apcNodes[itMapPivot->first] = itMapPivot->second;
+        ASSIMP_LOG_DEBUG("pivot nodes: ", mapPivot.size());
+        //Merge pivot map into node map
+        for (auto itMapPivot = mapPivot.begin(); itMapPivot != mapPivot.end();) {
+            uint16_t pivotNodeId = itMapPivot->first;
+            auto oldApcNodeIt = apcNodes.find(pivotNodeId);
+            if (oldApcNodeIt != apcNodes.end()) {
+                ASSIMP_LOG_ERROR("attempted to insert pivot node which already exists in apc nodes ", pivotNodeId, " \"", itMapPivot->second->mName.C_Str(), "\"");
+            } else {
+                apcNodes.emplace(pivotNodeId, itMapPivot->second);
+            }
+            itMapPivot->second = nullptr;
+            itMapPivot = mapPivot.erase(itMapPivot);
+        }
+        ASSIMP_LOG_DEBUG("total nodes: ", apcNodes.size());
     }
     }
 
 
     //Set children of all parents
     //Set children of all parents
@@ -627,8 +642,15 @@ void LWOImporter::GenerateNodeGraph(std::map<uint16_t, aiNode *> &apcNodes) {
         }
         }
     }
     }
 
 
-    if (!mScene->mRootNode->mNumChildren)
+    if (!mScene->mRootNode->mNumChildren) {
+        ASSIMP_LOG_DEBUG("All apcNodes:");
+        for (auto nodeIt = apcNodes.begin(); nodeIt != apcNodes.end(); ) {
+            ASSIMP_LOG_DEBUG("Node ", nodeIt->first, " \"", nodeIt->second->mName.C_Str(), "\"");
+            nodeIt->second = nullptr;
+            nodeIt = apcNodes.erase(nodeIt);
+        }
         throw DeadlyImportError("LWO: Unable to build a valid node graph");
         throw DeadlyImportError("LWO: Unable to build a valid node graph");
+    }
 
 
     // Remove a single root node with no meshes assigned to it ...
     // Remove a single root node with no meshes assigned to it ...
     if (1 == mScene->mRootNode->mNumChildren) {
     if (1 == mScene->mRootNode->mNumChildren) {
@@ -1462,7 +1484,6 @@ void LWOImporter::LoadLWO2File() {
 
 
         if (mFileBuffer + head.length > end) {
         if (mFileBuffer + head.length > end) {
             throw DeadlyImportError("LWO2: Chunk length points behind the file");
             throw DeadlyImportError("LWO2: Chunk length points behind the file");
-            break;
         }
         }
         uint8_t *const next = mFileBuffer + head.length;
         uint8_t *const next = mFileBuffer + head.length;
         mFileBuffer += bufOffset;
         mFileBuffer += bufOffset;

+ 1 - 1
code/AssetLib/LWO/LWOMaterial.cpp

@@ -345,7 +345,7 @@ void LWOImporter::ConvertMaterial(const LWO::Surface &surf, aiMaterial *pcMat) {
 
 
     // (the diffuse value is just a scaling factor)
     // (the diffuse value is just a scaling factor)
     // If a diffuse texture is set, we set this value to 1.0
     // If a diffuse texture is set, we set this value to 1.0
-    clr = (b && false ? aiColor3D(1.0, 1.0, 1.0) : surf.mColor);
+    clr = (b ? aiColor3D(1.0, 1.0, 1.0) : surf.mColor);
     clr.r *= surf.mDiffuseValue;
     clr.r *= surf.mDiffuseValue;
     clr.g *= surf.mDiffuseValue;
     clr.g *= surf.mDiffuseValue;
     clr.b *= surf.mDiffuseValue;
     clr.b *= surf.mDiffuseValue;

+ 9 - 10
code/AssetLib/LWS/LWSLoader.cpp

@@ -632,18 +632,17 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
                     nodes.push_back(d);
                     nodes.push_back(d);
                 }
                 }
                 ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'Channel\'");
                 ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'Channel\'");
-            }
-
-            // important: index of channel
-            nodes.back().channels.emplace_back();
-            LWO::Envelope &env = nodes.back().channels.back();
-
-            env.index = strtoul10(c);
+            } else {
+                // important: index of channel
+                nodes.back().channels.emplace_back();
+                LWO::Envelope &env = nodes.back().channels.back();
 
 
-            // currently we can just interpret the standard channels 0...9
-            // (hack) assume that index-i yields the binary channel type from LWO
-            env.type = (LWO::EnvelopeType)(env.index + 1);
+                env.index = strtoul10(c);
 
 
+                // currently we can just interpret the standard channels 0...9
+                // (hack) assume that index-i yields the binary channel type from LWO
+                env.type = (LWO::EnvelopeType)(env.index + 1);
+            }
         }
         }
         // 'Envelope': a single animation channel
         // 'Envelope': a single animation channel
         else if ((*it).tokens[0] == "Envelope") {
         else if ((*it).tokens[0] == "Envelope") {

+ 47 - 15
code/AssetLib/MD5/MD5Parser.cpp

@@ -138,18 +138,31 @@ bool MD5Parser::ParseSection(Section &out) {
     char *sz = buffer;
     char *sz = buffer;
     while (!IsSpaceOrNewLine(*buffer)) {
     while (!IsSpaceOrNewLine(*buffer)) {
         ++buffer;
         ++buffer;
+        if (buffer == bufferEnd)
+            return false;
     }
     }
     out.mName = std::string(sz, (uintptr_t)(buffer - sz));
     out.mName = std::string(sz, (uintptr_t)(buffer - sz));
-    SkipSpaces();
+    while (IsSpace(*buffer)) {
+        ++buffer;
+        if (buffer == bufferEnd)
+            return false;
+    }
 
 
     bool running = true;
     bool running = true;
     while (running) {
     while (running) {
         if ('{' == *buffer) {
         if ('{' == *buffer) {
             // it is a normal section so read all lines
             // it is a normal section so read all lines
             ++buffer;
             ++buffer;
+            if (buffer == bufferEnd)
+                return false;
             bool run = true;
             bool run = true;
             while (run) {
             while (run) {
-                if (!SkipSpacesAndLineEnd()) {
+                while (IsSpaceOrNewLine(*buffer)) {
+                    ++buffer;
+                    if (buffer == bufferEnd)
+                        return false;
+                }
+                if ('\0' == *buffer) {
                     return false; // seems this was the last section
                     return false; // seems this was the last section
                 }
                 }
                 if ('}' == *buffer) {
                 if ('}' == *buffer) {
@@ -164,25 +177,39 @@ bool MD5Parser::ParseSection(Section &out) {
                 elem.szStart = buffer;
                 elem.szStart = buffer;
 
 
                 // terminate the line with zero
                 // terminate the line with zero
-                while (!IsLineEnd(*buffer))
+                while (!IsLineEnd(*buffer)) {
                     ++buffer;
                     ++buffer;
+                    if (buffer == bufferEnd)
+                        return false;
+                }
                 if (*buffer) {
                 if (*buffer) {
                     ++lineNumber;
                     ++lineNumber;
                     *buffer++ = '\0';
                     *buffer++ = '\0';
+                    if (buffer == bufferEnd)
+                        return false;
                 }
                 }
             }
             }
             break;
             break;
         } else if (!IsSpaceOrNewLine(*buffer)) {
         } else if (!IsSpaceOrNewLine(*buffer)) {
             // it is an element at global scope. Parse its value and go on
             // it is an element at global scope. Parse its value and go on
             sz = buffer;
             sz = buffer;
-            while (!IsSpaceOrNewLine(*buffer++))
-                ;
+            while (!IsSpaceOrNewLine(*buffer++)) {
+                if (buffer == bufferEnd)
+                    return false;
+            }
             out.mGlobalValue = std::string(sz, (uintptr_t)(buffer - sz));
             out.mGlobalValue = std::string(sz, (uintptr_t)(buffer - sz));
             continue;
             continue;
         }
         }
         break;
         break;
     }
     }
-    return SkipSpacesAndLineEnd();
+    if (buffer == bufferEnd)
+        return false;
+    while (IsSpaceOrNewLine(*buffer)) {
+        ++buffer;
+        if (buffer == bufferEnd)
+            return false;
+    }
+    return '\0' != *buffer;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -228,15 +255,20 @@ bool MD5Parser::ParseSection(Section &out) {
     out.data[out.length] = '\0';
     out.data[out.length] = '\0';
 
 
 // parse a string, enclosed in quotation marks
 // parse a string, enclosed in quotation marks
-#define AI_MD5_PARSE_STRING_IN_QUOTATION(out)  \
-    while ('\"' != *sz)                        \
-        ++sz;                                  \
-    const char *szStart = ++sz;                \
-    while ('\"' != *sz)                        \
-        ++sz;                                  \
-    const char *szEnd = (sz++);                \
-    out.length = (ai_uint32)(szEnd - szStart); \
-    ::memcpy(out.data, szStart, out.length);   \
+#define AI_MD5_PARSE_STRING_IN_QUOTATION(out)          \
+    out.length = 0;                                    \
+    while ('\"' != *sz && '\0' != *sz)                 \
+        ++sz;                                          \
+    if ('\0' != *sz) {                                 \
+        const char *szStart = ++sz;                    \
+        while ('\"' != *sz && '\0' != *sz)             \
+            ++sz;                                      \
+        if ('\0' != *sz) {                             \
+            const char *szEnd = (sz++);                \
+            out.length = (ai_uint32)(szEnd - szStart); \
+            ::memcpy(out.data, szStart, out.length);   \
+        }                                              \
+    }                                                  \
     out.data[out.length] = '\0';
     out.data[out.length] = '\0';
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // .MD5MESH parsing function
 // .MD5MESH parsing function

+ 4 - 3
code/AssetLib/MD5/MD5Parser.h

@@ -365,9 +365,7 @@ public:
     static void ReportWarning (const char* warn, unsigned int line);
     static void ReportWarning (const char* warn, unsigned int line);
 
 
 
 
-    void ReportError (const char* error) {
-        return ReportError(error, lineNumber);
-    }
+    AI_WONT_RETURN void ReportError (const char* error) AI_WONT_RETURN_SUFFIX;
 
 
     void ReportWarning (const char* warn) {
     void ReportWarning (const char* warn) {
         return ReportWarning(warn, lineNumber);
         return ReportWarning(warn, lineNumber);
@@ -404,6 +402,9 @@ private:
     unsigned int lineNumber;
     unsigned int lineNumber;
 };
 };
 
 
+inline void MD5Parser::ReportError(const char* error) {
+    ReportError(error, lineNumber);
+}
 // -------------------------------------------------------------------
 // -------------------------------------------------------------------
 inline bool MD5Parser::SkipLine(const char* in, const char** out) {
 inline bool MD5Parser::SkipLine(const char* in, const char** out) {
     ++lineNumber;
     ++lineNumber;

+ 39 - 5
code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp

@@ -470,14 +470,16 @@ void HL1MDLLoader::read_bones() {
 
 
     temp_bones_.resize(header_->numbones);
     temp_bones_.resize(header_->numbones);
 
 
+    // Create the main 'bones' node that will contain all MDL root bones.
     aiNode *bones_node = new aiNode(AI_MDL_HL1_NODE_BONES);
     aiNode *bones_node = new aiNode(AI_MDL_HL1_NODE_BONES);
     rootnode_children_.push_back(bones_node);
     rootnode_children_.push_back(bones_node);
-    bones_node->mNumChildren = static_cast<unsigned int>(header_->numbones);
-    bones_node->mChildren = new aiNode *[bones_node->mNumChildren];
+
+    // Store roots bones IDs temporarily.
+    std::vector<int> roots;
 
 
     // Create bone matrices in local space.
     // Create bone matrices in local space.
     for (int i = 0; i < header_->numbones; ++i) {
     for (int i = 0; i < header_->numbones; ++i) {
-        aiNode *bone_node = temp_bones_[i].node = bones_node->mChildren[i] = new aiNode(unique_bones_names[i]);
+        aiNode *bone_node = temp_bones_[i].node = new aiNode(unique_bones_names[i]);
 
 
         aiVector3D angles(pbone[i].value[3], pbone[i].value[4], pbone[i].value[5]);
         aiVector3D angles(pbone[i].value[3], pbone[i].value[4], pbone[i].value[5]);
         temp_bones_[i].absolute_transform = bone_node->mTransformation =
         temp_bones_[i].absolute_transform = bone_node->mTransformation =
@@ -485,9 +487,11 @@ void HL1MDLLoader::read_bones() {
                         aiVector3D(pbone[i].value[0], pbone[i].value[1], pbone[i].value[2]));
                         aiVector3D(pbone[i].value[0], pbone[i].value[1], pbone[i].value[2]));
 
 
         if (pbone[i].parent == -1) {
         if (pbone[i].parent == -1) {
-            bone_node->mParent = scene_->mRootNode;
+            bone_node->mParent = bones_node;
+            roots.push_back(i); // This bone has no parent. Add it to the roots list.
         } else {
         } else {
-            bone_node->mParent = bones_node->mChildren[pbone[i].parent];
+            bone_node->mParent = temp_bones_[pbone[i].parent].node;
+            temp_bones_[pbone[i].parent].children.push_back(i); // Add this bone to the parent bone's children list.
 
 
             temp_bones_[i].absolute_transform =
             temp_bones_[i].absolute_transform =
                     temp_bones_[pbone[i].parent].absolute_transform * bone_node->mTransformation;
                     temp_bones_[pbone[i].parent].absolute_transform * bone_node->mTransformation;
@@ -496,6 +500,36 @@ void HL1MDLLoader::read_bones() {
         temp_bones_[i].offset_matrix = temp_bones_[i].absolute_transform;
         temp_bones_[i].offset_matrix = temp_bones_[i].absolute_transform;
         temp_bones_[i].offset_matrix.Inverse();
         temp_bones_[i].offset_matrix.Inverse();
     }
     }
+
+    // Allocate memory for each MDL root bone.
+    bones_node->mNumChildren = static_cast<unsigned int>(roots.size());
+    bones_node->mChildren = new aiNode *[bones_node->mNumChildren];
+
+    // Build all bones children hierarchy starting from each MDL root bone.
+    for (size_t i = 0; i < roots.size(); ++i)
+    {
+        const TempBone &root_bone = temp_bones_[roots[i]];
+        bones_node->mChildren[i] = root_bone.node;
+        build_bone_children_hierarchy(root_bone);
+    }
+}
+
+void HL1MDLLoader::build_bone_children_hierarchy(const TempBone &bone)
+{
+    if (bone.children.empty())
+        return;
+
+    aiNode* bone_node = bone.node;
+    bone_node->mNumChildren = static_cast<unsigned int>(bone.children.size());
+    bone_node->mChildren = new aiNode *[bone_node->mNumChildren];
+
+    // Build each child bone's hierarchy recursively.
+    for (size_t i = 0; i < bone.children.size(); ++i)
+    {
+        const TempBone &child_bone = temp_bones_[bone.children[i]];
+        bone_node->mChildren[i] = child_bone.node;
+        build_bone_children_hierarchy(child_bone);
+    }
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------

+ 11 - 1
code/AssetLib/MDL/HalfLife/HL1MDLLoader.h

@@ -143,6 +143,14 @@ private:
      */
      */
     static bool get_num_blend_controllers(const int num_blend_animations, int &num_blend_controllers);
     static bool get_num_blend_controllers(const int num_blend_animations, int &num_blend_controllers);
 
 
+    /**
+     *  \brief Build a bone's node children hierarchy.
+     *
+     * \param[in] bone The bone for which we must build all children hierarchy.
+     */
+    struct TempBone;
+    void build_bone_children_hierarchy(const TempBone& bone);
+
     /** Output scene to be filled */
     /** Output scene to be filled */
     aiScene *scene_;
     aiScene *scene_;
 
 
@@ -198,11 +206,13 @@ private:
         TempBone() :
         TempBone() :
             node(nullptr),
             node(nullptr),
             absolute_transform(),
             absolute_transform(),
-            offset_matrix() {}
+            offset_matrix(),
+            children() {}
 
 
         aiNode *node;
         aiNode *node;
         aiMatrix4x4 absolute_transform;
         aiMatrix4x4 absolute_transform;
         aiMatrix4x4 offset_matrix;
         aiMatrix4x4 offset_matrix;
+        std::vector<int> children; // Bone children
     };
     };
 
 
     std::vector<TempBone> temp_bones_;
     std::vector<TempBone> temp_bones_;

+ 9 - 3
code/AssetLib/MDL/MDLLoader.cpp

@@ -271,10 +271,16 @@ void MDLImporter::InternReadFile(const std::string &pFile,
     }
     }
 }
 }
 
 
+// ------------------------------------------------------------------------------------------------
+// Check whether we're still inside the valid file range
+bool MDLImporter::IsPosValid(const void *szPos) const {
+    return szPos && (const unsigned char *)szPos <= this->mBuffer + this->iFileSize && szPos >= this->mBuffer;
+}
+
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Check whether we're still inside the valid file range
 // Check whether we're still inside the valid file range
 void MDLImporter::SizeCheck(const void *szPos) {
 void MDLImporter::SizeCheck(const void *szPos) {
-    if (!szPos || (const unsigned char *)szPos > this->mBuffer + this->iFileSize) {
+    if (!IsPosValid(szPos)) {
         throw DeadlyImportError("Invalid MDL file. The file is too small "
         throw DeadlyImportError("Invalid MDL file. The file is too small "
                                 "or contains invalid data.");
                                 "or contains invalid data.");
     }
     }
@@ -284,7 +290,7 @@ void MDLImporter::SizeCheck(const void *szPos) {
 // Just for debugging purposes
 // Just for debugging purposes
 void MDLImporter::SizeCheck(const void *szPos, const char *szFile, unsigned int iLine) {
 void MDLImporter::SizeCheck(const void *szPos, const char *szFile, unsigned int iLine) {
     ai_assert(nullptr != szFile);
     ai_assert(nullptr != szFile);
-    if (!szPos || (const unsigned char *)szPos > mBuffer + iFileSize) {
+    if (!IsPosValid(szPos)) {
         // remove a directory if there is one
         // remove a directory if there is one
         const char *szFilePtr = ::strrchr(szFile, '\\');
         const char *szFilePtr = ::strrchr(szFile, '\\');
         if (!szFilePtr) {
         if (!szFilePtr) {
@@ -975,7 +981,7 @@ void MDLImporter::CalcAbsBoneMatrices_3DGS_MDL7(MDL::IntBone_MDL7 **apcOutBones)
                     }
                     }
 
 
                     // store the name of the bone
                     // store the name of the bone
-                    pcOutBone->mName.length = (size_t)iMaxLen;
+                    pcOutBone->mName.length = static_cast<ai_uint32>(iMaxLen);
                     ::memcpy(pcOutBone->mName.data, pcBone->name, pcOutBone->mName.length);
                     ::memcpy(pcOutBone->mName.data, pcBone->name, pcOutBone->mName.length);
                     pcOutBone->mName.data[pcOutBone->mName.length] = '\0';
                     pcOutBone->mName.data[pcOutBone->mName.length] = '\0';
                 }
                 }

+ 2 - 1
code/AssetLib/MDL/MDLLoader.h

@@ -139,7 +139,7 @@ protected:
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Import a CS:S/HL2 MDL file (not fully implemented)
     /** Import a CS:S/HL2 MDL file (not fully implemented)
     */
     */
-    void InternReadFile_HL2( );
+    AI_WONT_RETURN void InternReadFile_HL2( ) AI_WONT_RETURN_SUFFIX;
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Check whether a given position is inside the valid range
     /** Check whether a given position is inside the valid range
@@ -150,6 +150,7 @@ protected:
     */
     */
     void SizeCheck(const void* szPos);
     void SizeCheck(const void* szPos);
     void SizeCheck(const void* szPos, const char* szFile, unsigned int iLine);
     void SizeCheck(const void* szPos, const char* szFile, unsigned int iLine);
+    bool IsPosValid(const void* szPos) const;
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Validate the header data structure of a game studio MDL7 file
     /** Validate the header data structure of a game studio MDL7 file

+ 13 - 4
code/AssetLib/MDL/MDLMaterialLoader.cpp

@@ -481,6 +481,8 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7(
         pcNew->achFormatHint[2] = 's';
         pcNew->achFormatHint[2] = 's';
         pcNew->achFormatHint[3] = '\0';
         pcNew->achFormatHint[3] = '\0';
 
 
+        SizeCheck(szCurrent + pcNew->mWidth);
+
         pcNew->pcData = (aiTexel *)new unsigned char[pcNew->mWidth];
         pcNew->pcData = (aiTexel *)new unsigned char[pcNew->mWidth];
         memcpy(pcNew->pcData, szCurrent, pcNew->mWidth);
         memcpy(pcNew->pcData, szCurrent, pcNew->mWidth);
         szCurrent += iWidth;
         szCurrent += iWidth;
@@ -493,12 +495,12 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7(
 
 
         aiString szFile;
         aiString szFile;
         const size_t iLen = strlen((const char *)szCurrent);
         const size_t iLen = strlen((const char *)szCurrent);
-        size_t iLen2 = iLen + 1;
-        iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2;
+        size_t iLen2 = iLen > (MAXLEN - 1) ? (MAXLEN - 1) : iLen;
         memcpy(szFile.data, (const char *)szCurrent, iLen2);
         memcpy(szFile.data, (const char *)szCurrent, iLen2);
+        szFile.data[iLen2] = '\0';
         szFile.length = static_cast<ai_uint32>(iLen2);
         szFile.length = static_cast<ai_uint32>(iLen2);
 
 
-        szCurrent += iLen2;
+        szCurrent += iLen2 + 1;
 
 
         // place this as diffuse texture
         // place this as diffuse texture
         pcMatOut->AddProperty(&szFile, AI_MATKEY_TEXTURE_DIFFUSE(0));
         pcMatOut->AddProperty(&szFile, AI_MATKEY_TEXTURE_DIFFUSE(0));
@@ -703,7 +705,14 @@ void MDLImporter::SkipSkinLump_3DGS_MDL7(
             tex.pcData = bad_texel;
             tex.pcData = bad_texel;
             tex.mHeight = iHeight;
             tex.mHeight = iHeight;
             tex.mWidth = iWidth;
             tex.mWidth = iWidth;
-            ParseTextureColorData(szCurrent, iMasked, &iSkip, &tex);
+
+            try {
+                ParseTextureColorData(szCurrent, iMasked, &iSkip, &tex);
+            } catch (...) {
+                // FIX: Important, otherwise the destructor will crash
+                tex.pcData = nullptr;
+                throw;
+            }
 
 
             // FIX: Important, otherwise the destructor will crash
             // FIX: Important, otherwise the destructor will crash
             tex.pcData = nullptr;
             tex.pcData = nullptr;

+ 2 - 1
code/AssetLib/MMD/MMDPmxParser.h

@@ -46,6 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <iostream>
 #include <iostream>
 #include <fstream>
 #include <fstream>
 #include <memory>
 #include <memory>
+#include <assimp/types.h>
 #include "MMDCpp14.h"
 #include "MMDCpp14.h"
 
 
 namespace pmx
 namespace pmx
@@ -730,7 +731,7 @@ namespace pmx
 		std::unique_ptr<PmxAncherRigidBody []> anchers;
 		std::unique_ptr<PmxAncherRigidBody []> anchers;
 		int pin_vertex_count;
 		int pin_vertex_count;
 		std::unique_ptr<int []> pin_vertices;
 		std::unique_ptr<int []> pin_vertices;
-		void Read(std::istream *stream, PmxSetting *setting);
+		AI_WONT_RETURN void Read(std::istream *stream, PmxSetting *setting) AI_WONT_RETURN_SUFFIX;
 	};
 	};
 
 
 	class PmxModel
 	class PmxModel

+ 2 - 2
code/AssetLib/MS3D/MS3DLoader.cpp

@@ -486,7 +486,7 @@ void MS3DImporter::InternReadFile( const std::string& pFile,
 
 
         for (unsigned int j = 0,n = 0; j < m->mNumFaces; ++j) {
         for (unsigned int j = 0,n = 0; j < m->mNumFaces; ++j) {
             aiFace& f = m->mFaces[j];
             aiFace& f = m->mFaces[j];
-            if (g.triangles[j]>triangles.size()) {
+            if (g.triangles[j] >= triangles.size()) {
                 throw DeadlyImportError("MS3D: Encountered invalid triangle index, file is malformed");
                 throw DeadlyImportError("MS3D: Encountered invalid triangle index, file is malformed");
             }
             }
 
 
@@ -494,7 +494,7 @@ void MS3DImporter::InternReadFile( const std::string& pFile,
             f.mIndices = new unsigned int[f.mNumIndices=3];
             f.mIndices = new unsigned int[f.mNumIndices=3];
 
 
             for (unsigned int k = 0; k < 3; ++k,++n) {
             for (unsigned int k = 0; k < 3; ++k,++n) {
-                if (t.indices[k]>vertices.size()) {
+                if (t.indices[k] >= vertices.size()) {
                     throw DeadlyImportError("MS3D: Encountered invalid vertex index, file is malformed");
                     throw DeadlyImportError("MS3D: Encountered invalid vertex index, file is malformed");
                 }
                 }
 
 

+ 4 - 0
code/AssetLib/NDO/NDOLoader.cpp

@@ -52,6 +52,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/importerdesc.h>
 #include <assimp/importerdesc.h>
 #include <assimp/StreamReader.h>
 #include <assimp/StreamReader.h>
 #include <map>
 #include <map>
+#include <limits>
 
 
 using namespace Assimp;
 using namespace Assimp;
 
 
@@ -160,6 +161,9 @@ void NDOImporter::InternReadFile( const std::string& pFile,
 
 
         temp = file_format >= 12 ? reader.GetU4() : reader.GetU2();
         temp = file_format >= 12 ? reader.GetU4() : reader.GetU2();
         head = (const char*)reader.GetPtr();
         head = (const char*)reader.GetPtr();
+        if (std::numeric_limits<unsigned int>::max() - 76 < temp) {
+            throw DeadlyImportError("Invalid name length");
+        }
         reader.IncPtr(temp + 76); /* skip unknown stuff */
         reader.IncPtr(temp + 76); /* skip unknown stuff */
 
 
         obj.name = std::string(head, temp);
         obj.name = std::string(head, temp);

+ 1 - 1
code/AssetLib/OFF/OFFLoader.cpp

@@ -284,7 +284,7 @@ void OFFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS
     for (unsigned int i = 0; i < numFaces; ) {
     for (unsigned int i = 0; i < numFaces; ) {
         if(!GetNextLine(buffer,line)) {
         if(!GetNextLine(buffer,line)) {
             ASSIMP_LOG_ERROR("OFF: The number of faces in the header is incorrect");
             ASSIMP_LOG_ERROR("OFF: The number of faces in the header is incorrect");
-            break;
+            throw DeadlyImportError("OFF: The number of faces in the header is incorrect");
         }
         }
         unsigned int idx;
         unsigned int idx;
         sz = line; SkipSpaces(&sz);
         sz = line; SkipSpaces(&sz);

+ 0 - 2
code/AssetLib/Obj/ObjFileData.h

@@ -239,8 +239,6 @@ struct Mesh {
     unsigned int m_uiMaterialIndex;
     unsigned int m_uiMaterialIndex;
     /// True, if normals are stored.
     /// True, if normals are stored.
     bool m_hasNormals;
     bool m_hasNormals;
-    /// True, if vertex colors are stored.
-    bool m_hasVertexColors;
 
 
     /// Constructor
     /// Constructor
     explicit Mesh(const std::string &name) :
     explicit Mesh(const std::string &name) :

+ 6 - 4
code/AssetLib/Obj/ObjFileImporter.cpp

@@ -3,7 +3,7 @@
 Open Asset Import Library (assimp)
 Open Asset Import Library (assimp)
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
 
 
-Copyright (c) 2006-2020, assimp team
+Copyright (c) 2006-2023, assimp team
 
 
 All rights reserved.
 All rights reserved.
 
 
@@ -84,7 +84,6 @@ ObjFileImporter::ObjFileImporter() :
 //  Destructor.
 //  Destructor.
 ObjFileImporter::~ObjFileImporter() {
 ObjFileImporter::~ObjFileImporter() {
     delete m_pRootObject;
     delete m_pRootObject;
-    m_pRootObject = nullptr;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -270,7 +269,7 @@ aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile
     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];
         unsigned int meshId = pObject->m_Meshes[i];
         aiMesh *pMesh = createTopology(pModel, pObject, meshId);
         aiMesh *pMesh = createTopology(pModel, pObject, meshId);
-        if (pMesh) {
+        if (pMesh != nullptr) {
             if (pMesh->mNumFaces > 0) {
             if (pMesh->mNumFaces > 0) {
                 MeshArray.push_back(pMesh);
                 MeshArray.push_back(pMesh);
             } else {
             } else {
@@ -331,7 +330,6 @@ aiMesh *ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjF
 
 
     for (size_t index = 0; index < pObjMesh->m_Faces.size(); index++) {
     for (size_t index = 0; index < pObjMesh->m_Faces.size(); index++) {
         const ObjFile::Face *inp = pObjMesh->m_Faces[index];
         const ObjFile::Face *inp = pObjMesh->m_Faces[index];
-        //ai_assert(nullptr != inp);
 
 
         if (inp->mPrimitiveType == aiPrimitiveType_LINE) {
         if (inp->mPrimitiveType == aiPrimitiveType_LINE) {
             pMesh->mNumFaces += static_cast<unsigned int>(inp->m_vertices.size() - 1);
             pMesh->mNumFaces += static_cast<unsigned int>(inp->m_vertices.size() - 1);
@@ -500,6 +498,10 @@ void ObjFileImporter::createVertexArray(const ObjFile::Model *pModel,
 
 
                 if (vertexIndex) {
                 if (vertexIndex) {
                     if (!last) {
                     if (!last) {
+                        if (pMesh->mNumVertices <= newIndex + 1) {
+                            throw DeadlyImportError("OBJ: bad vertex index");
+                        }
+
                         pMesh->mVertices[newIndex + 1] = pMesh->mVertices[newIndex];
                         pMesh->mVertices[newIndex + 1] = pMesh->mVertices[newIndex];
                         if (!sourceFace->m_normals.empty() && !pModel->mNormals.empty()) {
                         if (!sourceFace->m_normals.empty() && !pModel->mNormals.empty()) {
                             pMesh->mNormals[newIndex + 1] = pMesh->mNormals[newIndex];
                             pMesh->mNormals[newIndex + 1] = pMesh->mNormals[newIndex];

+ 3 - 2
code/AssetLib/Obj/ObjFileMtlImporter.cpp

@@ -252,9 +252,9 @@ void ObjFileMtlImporter::load() {
             case 'a': // Anisotropy
             case 'a': // Anisotropy
             {
             {
                 ++m_DataIt;
                 ++m_DataIt;
-                getFloatValue(m_pModel->mCurrentMaterial->anisotropy);
                 if (m_pModel->mCurrentMaterial != nullptr)
                 if (m_pModel->mCurrentMaterial != nullptr)
-                    m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
+                    getFloatValue(m_pModel->mCurrentMaterial->anisotropy);
+                m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
             } break;
             } break;
 
 
             default: {
             default: {
@@ -371,6 +371,7 @@ void ObjFileMtlImporter::getTexture() {
     if (m_pModel->mCurrentMaterial == nullptr) {
     if (m_pModel->mCurrentMaterial == nullptr) {
         m_pModel->mCurrentMaterial = new ObjFile::Material();
         m_pModel->mCurrentMaterial = new ObjFile::Material();
         m_pModel->mCurrentMaterial->MaterialName.Set("Empty_Material");
         m_pModel->mCurrentMaterial->MaterialName.Set("Empty_Material");
+        m_pModel->mMaterialMap["Empty_Material"] = m_pModel->mCurrentMaterial;
     }
     }
 
 
     const char *pPtr(&(*m_DataIt));
     const char *pPtr(&(*m_DataIt));

+ 22 - 3
code/AssetLib/Obj/ObjFileParser.cpp

@@ -156,9 +156,17 @@ void ObjFileParser::parseFile(IOStreamBuffer<char> &streamBuffer) {
                     // read in vertex definition (homogeneous coords)
                     // read in vertex definition (homogeneous coords)
                     getHomogeneousVector3(m_pModel->mVertices);
                     getHomogeneousVector3(m_pModel->mVertices);
                 } else if (numComponents == 6) {
                 } else if (numComponents == 6) {
+                    // fill previous omitted vertex-colors by default
+                    if (m_pModel->mVertexColors.size() < m_pModel->mVertices.size()) {
+                        m_pModel->mVertexColors.resize(m_pModel->mVertices.size(), aiVector3D(0, 0, 0));
+                    }
                     // read vertex and vertex-color
                     // read vertex and vertex-color
                     getTwoVectors3(m_pModel->mVertices, m_pModel->mVertexColors);
                     getTwoVectors3(m_pModel->mVertices, m_pModel->mVertexColors);
                 }
                 }
+                // append omitted vertex-colors as default for the end if any vertex-color exists
+                if (!m_pModel->mVertexColors.empty() && m_pModel->mVertexColors.size() < m_pModel->mVertices.size()) {
+                    m_pModel->mVertexColors.resize(m_pModel->mVertices.size(), aiVector3D(0, 0, 0));
+                }
             } else if (*m_DataIt == 't') {
             } else if (*m_DataIt == 't') {
                 // read in texture coordinate ( 2D or 3D )
                 // read in texture coordinate ( 2D or 3D )
                 ++m_DataIt;
                 ++m_DataIt;
@@ -236,7 +244,7 @@ void ObjFileParser::parseFile(IOStreamBuffer<char> &streamBuffer) {
             getNameNoSpace(m_DataIt, m_DataItEnd, name);
             getNameNoSpace(m_DataIt, m_DataItEnd, name);
             insideCstype = name == "cstype";
             insideCstype = name == "cstype";
             goto pf_skip_line;
             goto pf_skip_line;
-        } break;
+        }
 
 
         default: {
         default: {
         pf_skip_line:
         pf_skip_line:
@@ -456,8 +464,19 @@ void ObjFileParser::getFace(aiPrimitiveType type) {
             iPos = 0;
             iPos = 0;
         } else {
         } else {
             //OBJ USES 1 Base ARRAYS!!!!
             //OBJ USES 1 Base ARRAYS!!!!
-            const char *token = &(*m_DataIt);
-            const int iVal = ::atoi(token);
+            int iVal;
+            auto end = m_DataIt;
+            // find either the buffer end or the '\0'
+            while (end < m_DataItEnd && *end != '\0')
+                ++end;
+            // avoid temporary string allocation if there is a zero
+            if (end != m_DataItEnd) {
+                iVal = ::atoi(&(*m_DataIt));
+            } else {
+                // otherwise make a zero terminated copy, which is safe to pass to atoi
+                std::string number(&(*m_DataIt), m_DataItEnd - m_DataIt);
+                iVal = ::atoi(number.c_str());
+            }
 
 
             // increment iStep position based off of the sign and # of digits
             // increment iStep position based off of the sign and # of digits
             int tmp = iVal;
             int tmp = iVal;

+ 2 - 3
code/AssetLib/Ogre/OgreXmlSerializer.cpp

@@ -57,7 +57,7 @@ namespace Assimp {
 namespace Ogre {
 namespace Ogre {
 
 
 //AI_WONT_RETURN void ThrowAttibuteError(const XmlParser *reader, const std::string &name, const std::string &error = "") AI_WONT_RETURN_SUFFIX;
 //AI_WONT_RETURN void ThrowAttibuteError(const XmlParser *reader, const std::string &name, const std::string &error = "") AI_WONT_RETURN_SUFFIX;
-
+AI_WONT_RETURN void ThrowAttibuteError(const std::string &nodeName, const std::string &name, const std::string &error) AI_WONT_RETURN_SUFFIX;
 AI_WONT_RETURN void ThrowAttibuteError(const std::string &nodeName, const std::string &name, const std::string &error) {
 AI_WONT_RETURN void ThrowAttibuteError(const std::string &nodeName, const std::string &name, const std::string &error) {
     if (!error.empty()) {
     if (!error.empty()) {
         throw DeadlyImportError(error, " in node '", nodeName, "' and attribute '", name, "'");
         throw DeadlyImportError(error, " in node '", nodeName, "' and attribute '", name, "'");
@@ -128,7 +128,6 @@ bool OgreXmlSerializer::ReadAttribute<bool>(XmlNode &xmlNode, const char *name)
     }
     }
 
 
     ThrowAttibuteError(xmlNode.name(), name, "Boolean value is expected to be 'true' or 'false', encountered '" + value + "'");
     ThrowAttibuteError(xmlNode.name(), name, "Boolean value is expected to be 'true' or 'false', encountered '" + value + "'");
-    return false;
 }
 }
 
 
 // Mesh XML constants
 // Mesh XML constants
@@ -490,7 +489,7 @@ bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *me
     OgreXmlSerializer serializer(xmlParser.get());
     OgreXmlSerializer serializer(xmlParser.get());
     XmlNode root = xmlParser->getRootNode();
     XmlNode root = xmlParser->getRootNode();
     if (std::string(root.name()) != nnSkeleton) {
     if (std::string(root.name()) != nnSkeleton) {
-        printf("\nSkeleton is not a valid root: %s\n", root.name());
+        ASSIMP_LOG_VERBOSE_DEBUG("nSkeleton is not a valid root: ", root.name(), ".");
         for (auto &a : root.children()) {
         for (auto &a : root.children()) {
             if (std::string(a.name()) == nnSkeleton) {
             if (std::string(a.name()) == nnSkeleton) {
                 root = a;
                 root = a;

+ 0 - 9
code/AssetLib/OpenGEX/OpenGEXImporter.cpp

@@ -460,14 +460,12 @@ void OpenGEXImporter::handleMetricNode(DDLNode *node, aiScene * /*pScene*/) {
 void OpenGEXImporter::handleNameNode(DDLNode *node, aiScene * /*pScene*/) {
 void OpenGEXImporter::handleNameNode(DDLNode *node, aiScene * /*pScene*/) {
     if (nullptr == m_currentNode) {
     if (nullptr == m_currentNode) {
         throw DeadlyImportError("No current node for name.");
         throw DeadlyImportError("No current node for name.");
-        return;
     }
     }
 
 
     Value *val(node->getValue());
     Value *val(node->getValue());
     if (nullptr != val) {
     if (nullptr != val) {
         if (Value::ValueType::ddl_string != val->m_type) {
         if (Value::ValueType::ddl_string != val->m_type) {
             throw DeadlyImportError("OpenGEX: invalid data type for value in node name.");
             throw DeadlyImportError("OpenGEX: invalid data type for value in node name.");
-            return;
         }
         }
 
 
         const std::string name(val->getString());
         const std::string name(val->getString());
@@ -508,7 +506,6 @@ static void getRefNames(DDLNode *node, std::vector<std::string> &names) {
 void OpenGEXImporter::handleObjectRefNode(DDLNode *node, aiScene * /*pScene*/) {
 void OpenGEXImporter::handleObjectRefNode(DDLNode *node, aiScene * /*pScene*/) {
     if (nullptr == m_currentNode) {
     if (nullptr == m_currentNode) {
         throw DeadlyImportError("No parent node for name.");
         throw DeadlyImportError("No parent node for name.");
-        return;
     }
     }
 
 
     std::vector<std::string> objRefNames;
     std::vector<std::string> objRefNames;
@@ -532,7 +529,6 @@ void OpenGEXImporter::handleObjectRefNode(DDLNode *node, aiScene * /*pScene*/) {
 void OpenGEXImporter::handleMaterialRefNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) {
 void OpenGEXImporter::handleMaterialRefNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) {
     if (nullptr == m_currentNode) {
     if (nullptr == m_currentNode) {
         throw DeadlyImportError("No parent node for name.");
         throw DeadlyImportError("No parent node for name.");
-        return;
     }
     }
 
 
     std::vector<std::string> matRefNames;
     std::vector<std::string> matRefNames;
@@ -672,14 +668,12 @@ static void setMatrix(aiNode *node, DataArrayList *transformData) {
 void OpenGEXImporter::handleTransformNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) {
 void OpenGEXImporter::handleTransformNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) {
     if (nullptr == m_currentNode) {
     if (nullptr == m_currentNode) {
         throw DeadlyImportError("No parent node for name.");
         throw DeadlyImportError("No parent node for name.");
-        return;
     }
     }
 
 
     DataArrayList *transformData(node->getDataArrayList());
     DataArrayList *transformData(node->getDataArrayList());
     if (nullptr != transformData) {
     if (nullptr != transformData) {
         if (transformData->m_numItems != 16) {
         if (transformData->m_numItems != 16) {
             throw DeadlyImportError("Invalid number of data for transform matrix.");
             throw DeadlyImportError("Invalid number of data for transform matrix.");
-            return;
         }
         }
         setMatrix(m_currentNode, transformData);
         setMatrix(m_currentNode, transformData);
     }
     }
@@ -835,7 +829,6 @@ static void copyColor4DArray(size_t numItems, DataArrayList *vaList, aiColor4D *
 void OpenGEXImporter::handleVertexArrayNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) {
 void OpenGEXImporter::handleVertexArrayNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) {
     if (nullptr == node) {
     if (nullptr == node) {
         throw DeadlyImportError("No parent node for name.");
         throw DeadlyImportError("No parent node for name.");
-        return;
     }
     }
 
 
     Property *prop = node->getProperties();
     Property *prop = node->getProperties();
@@ -876,12 +869,10 @@ void OpenGEXImporter::handleVertexArrayNode(ODDLParser::DDLNode *node, aiScene *
 void OpenGEXImporter::handleIndexArrayNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) {
 void OpenGEXImporter::handleIndexArrayNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) {
     if (nullptr == node) {
     if (nullptr == node) {
         throw DeadlyImportError("No parent node for name.");
         throw DeadlyImportError("No parent node for name.");
-        return;
     }
     }
 
 
     if (nullptr == m_currentMesh) {
     if (nullptr == m_currentMesh) {
         throw DeadlyImportError("No current mesh for index data found.");
         throw DeadlyImportError("No current mesh for index data found.");
-        return;
     }
     }
 
 
     DataArrayList *vaList = node->getDataArrayList();
     DataArrayList *vaList = node->getDataArrayList();

+ 1 - 2
code/AssetLib/Q3D/Q3DLoader.cpp

@@ -382,11 +382,10 @@ void Q3DImporter::InternReadFile(const std::string &pFile,
 
 
             // TODO
             // TODO
             goto outer;
             goto outer;
-        } break;
+        }
 
 
         default:
         default:
             throw DeadlyImportError("Quick3D: Unknown chunk");
             throw DeadlyImportError("Quick3D: Unknown chunk");
-            break;
         };
         };
     }
     }
 outer:
 outer:

+ 1 - 1
code/AssetLib/Raw/RawLoader.h

@@ -58,7 +58,7 @@ namespace Assimp {
 class RAWImporter : public BaseImporter {
 class RAWImporter : public BaseImporter {
 public:
 public:
     RAWImporter();
     RAWImporter();
-    ~RAWImporter();
+    ~RAWImporter() override;
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Returns whether the class can handle the format of the given file.
     /** Returns whether the class can handle the format of the given file.

+ 1 - 1
code/AssetLib/SIB/SIBImporter.cpp

@@ -85,7 +85,7 @@ static const aiImporterDesc desc = {
 struct SIBChunk {
 struct SIBChunk {
     uint32_t Tag;
     uint32_t Tag;
     uint32_t Size;
     uint32_t Size;
-} PACK_STRUCT;
+};
 
 
 enum {
 enum {
     POS,
     POS,

+ 5 - 2
code/AssetLib/SMD/SMDLoader.cpp

@@ -590,7 +590,7 @@ void SMDImporter::CreateOutputMaterials() {
         pScene->mMaterials[iMat] = pcMat;
         pScene->mMaterials[iMat] = pcMat;
 
 
         aiString szName;
         aiString szName;
-        szName.length = (size_t)ai_snprintf(szName.data,MAXLEN,"Texture_%u",iMat);
+        szName.length = static_cast<ai_uint32>(ai_snprintf(szName.data,MAXLEN,"Texture_%u",iMat));
         pcMat->AddProperty(&szName,AI_MATKEY_NAME);
         pcMat->AddProperty(&szName,AI_MATKEY_NAME);
 
 
         if (aszTextures[iMat].length())
         if (aszTextures[iMat].length())
@@ -837,7 +837,10 @@ void SMDImporter::ParseNodeInfo(const char* szCurrent, const char** szCurrentOut
     unsigned int iBone  = 0;
     unsigned int iBone  = 0;
     SkipSpacesAndLineEnd(szCurrent,&szCurrent);
     SkipSpacesAndLineEnd(szCurrent,&szCurrent);
     if ( !ParseUnsignedInt(szCurrent,&szCurrent,iBone) || !SkipSpaces(szCurrent,&szCurrent)) {
     if ( !ParseUnsignedInt(szCurrent,&szCurrent,iBone) || !SkipSpaces(szCurrent,&szCurrent)) {
-        LogErrorNoThrow("Unexpected EOF/EOL while parsing bone index");
+        throw DeadlyImportError("Unexpected EOF/EOL while parsing bone index");
+    }
+    if (iBone == UINT_MAX) {
+        LogErrorNoThrow("Invalid bone number while parsing bone index");
         SMDI_PARSE_RETURN;
         SMDI_PARSE_RETURN;
     }
     }
     // add our bone to the list
     // add our bone to the list

+ 1 - 1
code/AssetLib/Unreal/UnrealLoader.h

@@ -56,7 +56,7 @@ namespace Assimp {
 class UnrealImporter : public BaseImporter {
 class UnrealImporter : public BaseImporter {
 public:
 public:
     UnrealImporter();
     UnrealImporter();
-    ~UnrealImporter();
+    ~UnrealImporter() override;
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** @brief Returns whether we can handle the format of the given file
     /** @brief Returns whether we can handle the format of the given file

+ 1 - 1
code/AssetLib/X/XFileImporter.cpp

@@ -578,7 +578,7 @@ void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector<XFile::Materi
                 aiString name;
                 aiString name;
                 pScene->mMaterials[b]->Get( AI_MATKEY_NAME, name);
                 pScene->mMaterials[b]->Get( AI_MATKEY_NAME, name);
                 if( strcmp( name.C_Str(), oldMat.mName.data()) == 0 ) {
                 if( strcmp( name.C_Str(), oldMat.mName.data()) == 0 ) {
-                    oldMat.sceneIndex = a;
+                    oldMat.sceneIndex = b;
                     break;
                     break;
                 }
                 }
             }
             }

+ 0 - 1
code/AssetLib/X/XFileParser.cpp

@@ -839,7 +839,6 @@ void XFileParser::ParseDataObjectAnimationKey(AnimBone *pAnimBone) {
 
 
         default:
         default:
             ThrowException("Unknown key type ", keyType, " in animation.");
             ThrowException("Unknown key type ", keyType, " in animation.");
-            break;
         } // end switch
         } // end switch
 
 
         // key separator
         // key separator

+ 0 - 2
code/AssetLib/X3D/X3DExporter.hpp

@@ -58,8 +58,6 @@ class X3DExporter {
                 Value(value) {
                 Value(value) {
             // empty
             // empty
         }
         }
-
-        SAttribute(SAttribute &&rhs) AI_NO_EXCEPT = default;
     };
     };
 
 
     /***********************************************/
     /***********************************************/

+ 12 - 0
code/AssetLib/X3D/X3DImporter.hpp

@@ -55,6 +55,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <string>
 #include <string>
 
 
 namespace Assimp {
 namespace Assimp {
+AI_WONT_RETURN inline void Throw_ArgOutOfRange(const std::string &argument) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN inline void Throw_CloseNotFound(const std::string &node) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN inline void Throw_ConvertFail_Str2ArrF(const std::string &nodeName, const std::string &pAttrValue) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN inline void Throw_ConvertFail_Str2ArrD(const std::string &nodeName, const std::string &pAttrValue) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN inline void Throw_ConvertFail_Str2ArrB(const std::string &nodeName, const std::string &pAttrValue) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN inline void Throw_ConvertFail_Str2ArrI(const std::string &nodeName, const std::string &pAttrValue) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN inline void Throw_DEF_And_USE(const std::string &nodeName) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN inline void Throw_IncorrectAttr(const std::string &nodeName, const std::string &pAttrName) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN inline void Throw_IncorrectAttrValue(const std::string &nodeName, const std::string &pAttrName) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN inline void Throw_MoreThanOnceDefined(const std::string &nodeName, const std::string &pNodeType, const std::string &pDescription) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN inline void Throw_TagCountIncorrect(const std::string &pNode) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN inline void Throw_USE_NotFound(const std::string &nodeName, const std::string &pAttrValue) AI_WONT_RETURN_SUFFIX;
 
 
 inline void Throw_ArgOutOfRange(const std::string &argument) {
 inline void Throw_ArgOutOfRange(const std::string &argument) {
     throw DeadlyImportError("Argument value is out of range for: \"" + argument + "\".");
     throw DeadlyImportError("Argument value is out of range for: \"" + argument + "\".");

+ 0 - 3
code/AssetLib/X3D/X3DXmlHelper.cpp

@@ -12,7 +12,6 @@ bool X3DXmlHelper::getColor3DAttribute(XmlNode &node, const char *attributeName,
         tokenize<std::string>(val, values, " ");
         tokenize<std::string>(val, values, " ");
         if (values.size() != 3) {
         if (values.size() != 3) {
             Throw_ConvertFail_Str2ArrF(node.name(), attributeName);
             Throw_ConvertFail_Str2ArrF(node.name(), attributeName);
-            return false;
         }
         }
         auto it = values.begin();
         auto it = values.begin();
         color.r = stof(*it++);
         color.r = stof(*it++);
@@ -30,7 +29,6 @@ bool X3DXmlHelper::getVector2DAttribute(XmlNode &node, const char *attributeName
         tokenize<std::string>(val, values, " ");
         tokenize<std::string>(val, values, " ");
         if (values.size() != 2) {
         if (values.size() != 2) {
             Throw_ConvertFail_Str2ArrF(node.name(), attributeName);
             Throw_ConvertFail_Str2ArrF(node.name(), attributeName);
-            return false;
         }
         }
         auto it = values.begin();
         auto it = values.begin();
         color.x = stof(*it++);
         color.x = stof(*it++);
@@ -47,7 +45,6 @@ bool X3DXmlHelper::getVector3DAttribute(XmlNode &node, const char *attributeName
         tokenize<std::string>(val, values, " ");
         tokenize<std::string>(val, values, " ");
         if (values.size() != 3) {
         if (values.size() != 3) {
             Throw_ConvertFail_Str2ArrF(node.name(), attributeName);
             Throw_ConvertFail_Str2ArrF(node.name(), attributeName);
-            return false;
         }
         }
         auto it = values.begin();
         auto it = values.begin();
         color.x = stof(*it++);
         color.x = stof(*it++);

+ 14 - 13
code/AssetLib/glTF/glTFAsset.h

@@ -513,21 +513,22 @@ struct Camera : public Object {
     };
     };
 
 
     Type type;
     Type type;
+    struct Perspective {
+        float aspectRatio; //!<The floating - point aspect ratio of the field of view. (0 = undefined = use the canvas one)
+        float yfov; //!<The floating - point vertical field of view in radians. (required)
+        float zfar; //!<The floating - point distance to the far clipping plane. (required)
+        float znear; //!< The floating - point distance to the near clipping plane. (required)
+    };
 
 
+    struct Ortographic {
+        float xmag; //! The floating-point horizontal magnification of the view. (required)
+        float ymag; //! The floating-point vertical magnification of the view. (required)
+        float zfar; //! The floating-point distance to the far clipping plane. (required)
+        float znear; //! The floating-point distance to the near clipping plane. (required)
+    };
     union {
     union {
-        struct {
-            float aspectRatio; //!<The floating - point aspect ratio of the field of view. (0 = undefined = use the canvas one)
-            float yfov; //!<The floating - point vertical field of view in radians. (required)
-            float zfar; //!<The floating - point distance to the far clipping plane. (required)
-            float znear; //!< The floating - point distance to the near clipping plane. (required)
-        } perspective;
-
-        struct {
-            float xmag; //! The floating-point horizontal magnification of the view. (required)
-            float ymag; //! The floating-point vertical magnification of the view. (required)
-            float zfar; //! The floating-point distance to the far clipping plane. (required)
-            float znear; //! The floating-point distance to the near clipping plane. (required)
-        } ortographic;
+        struct Perspective perspective;
+        struct Ortographic ortographic;
     };
     };
 
 
     Camera() = default;
     Camera() = default;

+ 8 - 2
code/AssetLib/glTF/glTFImporter.cpp

@@ -93,7 +93,10 @@ const aiImporterDesc *glTFImporter::GetInfo() const {
 bool glTFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /* checkSig */) const {
 bool glTFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /* checkSig */) const {
     glTF::Asset asset(pIOHandler);
     glTF::Asset asset(pIOHandler);
     try {
     try {
-        asset.Load(pFile, GetExtension(pFile) == "glb");
+        asset.Load(pFile,
+                   CheckMagicToken(
+                       pIOHandler, pFile, AI_GLB_MAGIC_NUMBER, 1, 0,
+                       static_cast<unsigned int>(strlen(AI_GLB_MAGIC_NUMBER))));
         return asset.asset;
         return asset.asset;
     } catch (...) {
     } catch (...) {
         return false;
         return false;
@@ -697,7 +700,10 @@ void glTFImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOS
 
 
     // read the asset file
     // read the asset file
     glTF::Asset asset(pIOHandler);
     glTF::Asset asset(pIOHandler);
-    asset.Load(pFile, GetExtension(pFile) == "glb");
+    asset.Load(pFile,
+               CheckMagicToken(
+                   pIOHandler, pFile, AI_GLB_MAGIC_NUMBER, 1, 0,
+                   static_cast<unsigned int>(strlen(AI_GLB_MAGIC_NUMBER))));
 
 
     //
     //
     // Copy the data out
     // Copy the data out

+ 36 - 10
code/AssetLib/glTF2/glTF2Asset.h

@@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  *
  * glTF Extensions Support:
  * glTF Extensions Support:
  *   KHR_materials_pbrSpecularGlossiness full
  *   KHR_materials_pbrSpecularGlossiness full
+ *   KHR_materials_specular full
  *   KHR_materials_unlit full
  *   KHR_materials_unlit full
  *   KHR_lights_punctual full
  *   KHR_lights_punctual full
  *   KHR_materials_sheen full
  *   KHR_materials_sheen full
@@ -370,6 +371,15 @@ struct CustomExtension {
     CustomExtension& operator=(const CustomExtension&) = default;
     CustomExtension& operator=(const CustomExtension&) = default;
 };
 };
 
 
+//! Represents metadata in an glTF2 object
+struct Extras {
+    std::vector<CustomExtension> mValues;
+
+    inline bool HasExtras() const {
+        return !mValues.empty();
+    }
+};
+
 //! Base class for all glTF top-level objects
 //! Base class for all glTF top-level objects
 struct Object {
 struct Object {
     int index; //!< The index of this object within its property container
     int index; //!< The index of this object within its property container
@@ -378,7 +388,7 @@ struct Object {
     std::string name; //!< The user-defined name of this object
     std::string name; //!< The user-defined name of this object
 
 
     CustomExtension customExtensions;
     CustomExtension customExtensions;
-    CustomExtension extras;
+    Extras extras;
 
 
     //! Objects marked as special are not exported (used to emulate the binary body buffer)
     //! Objects marked as special are not exported (used to emulate the binary body buffer)
     virtual bool IsSpecial() const { return false; }
     virtual bool IsSpecial() const { return false; }
@@ -483,7 +493,7 @@ private:
 
 
 public:
 public:
     Buffer();
     Buffer();
-    ~Buffer();
+    ~Buffer() override;
 
 
     void Read(Value &obj, Asset &r);
     void Read(Value &obj, Asset &r);
 
 
@@ -565,7 +575,7 @@ struct Accessor : public Object {
     inline size_t GetMaxByteSize();
     inline size_t GetMaxByteSize();
 
 
     template <class T>
     template <class T>
-    void ExtractData(T *&outData);
+    size_t ExtractData(T *&outData, const std::vector<unsigned int> *remappingIndices = nullptr);
 
 
     void WriteData(size_t count, const void *src_buffer, size_t src_stride);
     void WriteData(size_t count, const void *src_buffer, size_t src_stride);
     void WriteSparseValues(size_t count, const void *src_data, size_t src_dataStride);
     void WriteSparseValues(size_t count, const void *src_data, size_t src_dataStride);
@@ -710,6 +720,7 @@ const vec4 defaultBaseColor = { 1, 1, 1, 1 };
 const vec3 defaultEmissiveFactor = { 0, 0, 0 };
 const vec3 defaultEmissiveFactor = { 0, 0, 0 };
 const vec4 defaultDiffuseFactor = { 1, 1, 1, 1 };
 const vec4 defaultDiffuseFactor = { 1, 1, 1, 1 };
 const vec3 defaultSpecularFactor = { 1, 1, 1 };
 const vec3 defaultSpecularFactor = { 1, 1, 1 };
+const vec3 defaultSpecularColorFactor = { 0, 0, 0 };
 const vec3 defaultSheenFactor = { 0, 0, 0 };
 const vec3 defaultSheenFactor = { 0, 0, 0 };
 const vec3 defaultAttenuationColor = { 1, 1, 1 };
 const vec3 defaultAttenuationColor = { 1, 1, 1 };
 
 
@@ -753,6 +764,16 @@ struct PbrSpecularGlossiness {
     void SetDefaults();
     void SetDefaults();
 };
 };
 
 
+struct MaterialSpecular {
+    float specularFactor;
+    vec3 specularColorFactor;
+    TextureInfo specularTexture;
+    TextureInfo specularColorTexture;
+
+    MaterialSpecular() { SetDefaults(); }
+    void SetDefaults();
+};
+
 struct MaterialSheen {
 struct MaterialSheen {
     vec3 sheenColorFactor;
     vec3 sheenColorFactor;
     float sheenRoughnessFactor;
     float sheenRoughnessFactor;
@@ -817,6 +838,9 @@ struct Material : public Object {
     //extension: KHR_materials_pbrSpecularGlossiness
     //extension: KHR_materials_pbrSpecularGlossiness
     Nullable<PbrSpecularGlossiness> pbrSpecularGlossiness;
     Nullable<PbrSpecularGlossiness> pbrSpecularGlossiness;
 
 
+    //extension: KHR_materials_specular
+    Nullable<MaterialSpecular> materialSpecular;
+
     //extension: KHR_materials_sheen
     //extension: KHR_materials_sheen
     Nullable<MaterialSheen> materialSheen;
     Nullable<MaterialSheen> materialSheen;
 
 
@@ -1099,6 +1123,7 @@ public:
     //! Keeps info about the enabled extensions
     //! Keeps info about the enabled extensions
     struct Extensions {
     struct Extensions {
         bool KHR_materials_pbrSpecularGlossiness;
         bool KHR_materials_pbrSpecularGlossiness;
+        bool KHR_materials_specular;
         bool KHR_materials_unlit;
         bool KHR_materials_unlit;
         bool KHR_lights_punctual;
         bool KHR_lights_punctual;
         bool KHR_texture_transform;
         bool KHR_texture_transform;
@@ -1113,13 +1138,14 @@ public:
         bool KHR_texture_basisu;
         bool KHR_texture_basisu;
 
 
         Extensions() :
         Extensions() :
-                KHR_materials_pbrSpecularGlossiness(false),
-                KHR_materials_unlit(false),
-                KHR_lights_punctual(false),
-                KHR_texture_transform(false),
-                KHR_materials_sheen(false),
-                KHR_materials_clearcoat(false),
-                KHR_materials_transmission(false),
+                KHR_materials_pbrSpecularGlossiness(false), 
+                KHR_materials_specular(false), 
+                KHR_materials_unlit(false), 
+                KHR_lights_punctual(false), 
+                KHR_texture_transform(false), 
+                KHR_materials_sheen(false), 
+                KHR_materials_clearcoat(false), 
+                KHR_materials_transmission(false), 
                 KHR_materials_volume(false),
                 KHR_materials_volume(false),
                 KHR_materials_ior(false),
                 KHR_materials_ior(false),
                 KHR_materials_emissive_strength(false),
                 KHR_materials_emissive_strength(false),

+ 61 - 12
code/AssetLib/glTF2/glTF2Asset.inl

@@ -45,6 +45,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/StringUtils.h>
 #include <assimp/StringUtils.h>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/Base64.hpp>
 #include <assimp/Base64.hpp>
+#include <rapidjson/document.h>
+#include <rapidjson/schema.h>
+#include <rapidjson/stringbuffer.h>
 
 
 // clang-format off
 // clang-format off
 #ifdef ASSIMP_ENABLE_DRACO
 #ifdef ASSIMP_ENABLE_DRACO
@@ -139,6 +142,18 @@ inline CustomExtension ReadExtensions(const char *name, Value &obj) {
     return ret;
     return ret;
 }
 }
 
 
+inline Extras ReadExtras(Value &obj) {
+    Extras ret;
+
+    ret.mValues.reserve(obj.MemberCount());
+    for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) {
+        auto &val = it->value;
+        ret.mValues.emplace_back(ReadExtensions(it->name.GetString(), val));
+    }
+
+    return ret;
+}
+
 inline void CopyData(size_t count, const uint8_t *src, size_t src_stride,
 inline void CopyData(size_t count, const uint8_t *src, size_t src_stride,
         uint8_t *dst, size_t dst_stride) {
         uint8_t *dst, size_t dst_stride) {
     if (src_stride == dst_stride) {
     if (src_stride == dst_stride) {
@@ -248,7 +263,7 @@ inline void Object::ReadExtensions(Value &val) {
 
 
 inline void Object::ReadExtras(Value &val) {
 inline void Object::ReadExtras(Value &val) {
     if (Value *curExtras = FindObject(val, "extras")) {
     if (Value *curExtras = FindObject(val, "extras")) {
-        this->extras = glTF2::ReadExtensions("extras", *curExtras);
+        this->extras = glTF2::ReadExtras(*curExtras);
     }
     }
 }
 }
 
 
@@ -962,14 +977,15 @@ inline size_t Accessor::GetMaxByteSize() {
 }
 }
 
 
 template <class T>
 template <class T>
-void Accessor::ExtractData(T *&outData) {
+size_t Accessor::ExtractData(T *&outData, const std::vector<unsigned int> *remappingIndices) {
     uint8_t *data = GetPointer();
     uint8_t *data = GetPointer();
     if (!data) {
     if (!data) {
         throw DeadlyImportError("GLTF2: data is null when extracting data from ", getContextForErrorMessages(id, name));
         throw DeadlyImportError("GLTF2: data is null when extracting data from ", getContextForErrorMessages(id, name));
     }
     }
 
 
+    const size_t usedCount = (remappingIndices != nullptr) ? remappingIndices->size() : count;
     const size_t elemSize = GetElementSize();
     const size_t elemSize = GetElementSize();
-    const size_t totalSize = elemSize * count;
+    const size_t totalSize = elemSize * usedCount;
 
 
     const size_t stride = GetStride();
     const size_t stride = GetStride();
 
 
@@ -980,18 +996,31 @@ void Accessor::ExtractData(T *&outData) {
     }
     }
 
 
     const size_t maxSize = GetMaxByteSize();
     const size_t maxSize = GetMaxByteSize();
-    if (count * stride > maxSize) {
-        throw DeadlyImportError("GLTF: count*stride ", (count * stride), " > maxSize ", maxSize, " in ", getContextForErrorMessages(id, name));
-    }
 
 
-    outData = new T[count];
-    if (stride == elemSize && targetElemSize == elemSize) {
-        memcpy(outData, data, totalSize);
-    } else {
-        for (size_t i = 0; i < count; ++i) {
-            memcpy(outData + i, data + i * stride, elemSize);
+    outData = new T[usedCount];
+
+    if (remappingIndices != nullptr) {
+        const unsigned int maxIndex = static_cast<unsigned int>(maxSize / stride - 1);
+        for (size_t i = 0; i < usedCount; ++i) {
+            size_t srcIdx = (*remappingIndices)[i];
+            if (srcIdx > maxIndex) {
+                throw DeadlyImportError("GLTF: index*stride ", (srcIdx * stride), " > maxSize ", maxSize, " in ", getContextForErrorMessages(id, name));
+            }
+            memcpy(outData + i, data + srcIdx * stride, elemSize);
+        }
+    } else { // non-indexed cases
+        if (usedCount * stride > maxSize) {
+            throw DeadlyImportError("GLTF: count*stride ", (usedCount * stride), " > maxSize ", maxSize, " in ", getContextForErrorMessages(id, name));
+        }
+        if (stride == elemSize && targetElemSize == elemSize) {
+            memcpy(outData, data, totalSize);
+        } else {
+            for (size_t i = 0; i < usedCount; ++i) {
+                memcpy(outData + i, data + i * stride, elemSize);
+            }
         }
         }
     }
     }
+    return usedCount;
 }
 }
 
 
 inline void Accessor::WriteData(size_t _count, const void *src_buffer, size_t src_stride) {
 inline void Accessor::WriteData(size_t _count, const void *src_buffer, size_t src_stride) {
@@ -1249,6 +1278,19 @@ inline void Material::Read(Value &material, Asset &r) {
                 this->pbrSpecularGlossiness = Nullable<PbrSpecularGlossiness>(pbrSG);
                 this->pbrSpecularGlossiness = Nullable<PbrSpecularGlossiness>(pbrSG);
             }
             }
         }
         }
+        
+        if (r.extensionsUsed.KHR_materials_specular) {
+            if (Value *curMatSpecular = FindObject(*extensions, "KHR_materials_specular")) {
+                MaterialSpecular specular;
+
+                ReadMember(*curMatSpecular, "specularFactor", specular.specularFactor);
+                ReadTextureProperty(r, *curMatSpecular, "specularTexture", specular.specularTexture);
+                ReadMember(*curMatSpecular, "specularColorFactor", specular.specularColorFactor);
+                ReadTextureProperty(r, *curMatSpecular, "specularColorTexture", specular.specularColorTexture);
+
+                this->materialSpecular = Nullable<MaterialSpecular>(specular);
+            }
+        }
 
 
         // Extension KHR_texture_transform is handled in ReadTextureProperty
         // Extension KHR_texture_transform is handled in ReadTextureProperty
 
 
@@ -1347,6 +1389,12 @@ inline void PbrSpecularGlossiness::SetDefaults() {
     glossinessFactor = 1.0f;
     glossinessFactor = 1.0f;
 }
 }
 
 
+inline void MaterialSpecular::SetDefaults() {
+    //KHR_materials_specular properties
+    SetVector(specularColorFactor, defaultSpecularColorFactor);
+    specularFactor = 0.f;
+}
+
 inline void MaterialSheen::SetDefaults() {
 inline void MaterialSheen::SetDefaults() {
     //KHR_materials_sheen properties
     //KHR_materials_sheen properties
     SetVector(sheenColorFactor, defaultSheenFactor);
     SetVector(sheenColorFactor, defaultSheenFactor);
@@ -2033,6 +2081,7 @@ inline void Asset::ReadExtensionsUsed(Document &doc) {
     }
     }
 
 
     CHECK_EXT(KHR_materials_pbrSpecularGlossiness);
     CHECK_EXT(KHR_materials_pbrSpecularGlossiness);
+    CHECK_EXT(KHR_materials_specular);
     CHECK_EXT(KHR_materials_unlit);
     CHECK_EXT(KHR_materials_unlit);
     CHECK_EXT(KHR_lights_punctual);
     CHECK_EXT(KHR_lights_punctual);
     CHECK_EXT(KHR_texture_transform);
     CHECK_EXT(KHR_texture_transform);

+ 1 - 0
code/AssetLib/glTF2/glTF2AssetWriter.h

@@ -45,6 +45,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  *
  * glTF Extensions Support:
  * glTF Extensions Support:
  *   KHR_materials_pbrSpecularGlossiness: full
  *   KHR_materials_pbrSpecularGlossiness: full
+ *   KHR_materials_specular: full
  *   KHR_materials_unlit: full
  *   KHR_materials_unlit: full
  *   KHR_materials_sheen: full
  *   KHR_materials_sheen: full
  *   KHR_materials_clearcoat: full
  *   KHR_materials_clearcoat: full

+ 68 - 5
code/AssetLib/glTF2/glTF2AssetWriter.inl

@@ -418,6 +418,26 @@ namespace glTF2 {
           exts.AddMember("KHR_materials_unlit", unlit, w.mAl);
           exts.AddMember("KHR_materials_unlit", unlit, w.mAl);
         }
         }
 
 
+        if (m.materialSpecular.isPresent) {
+            Value materialSpecular(rapidjson::Type::kObjectType);
+            materialSpecular.SetObject();
+
+            MaterialSpecular &specular = m.materialSpecular.value;
+
+            if (specular.specularFactor != 0.0f) {
+                WriteFloat(materialSpecular, specular.specularFactor, "specularFactor", w.mAl);
+                WriteTex(materialSpecular, specular.specularTexture, "specularTexture", w.mAl);
+            }
+            if (specular.specularColorFactor[0] != defaultSpecularColorFactor[0] && specular.specularColorFactor[1] != defaultSpecularColorFactor[1] && specular.specularColorFactor[2] != defaultSpecularColorFactor[2]) {
+                WriteVec(materialSpecular, specular.specularColorFactor, "specularColorFactor", w.mAl);
+                WriteTex(materialSpecular, specular.specularColorTexture, "specularColorTexture", w.mAl);
+            }
+
+            if (!materialSpecular.ObjectEmpty()) {
+                exts.AddMember("KHR_materials_specular", materialSpecular, w.mAl);
+            }
+        }
+
         if (m.materialSheen.isPresent) {
         if (m.materialSheen.isPresent) {
             Value materialSheen(rapidjson::Type::kObjectType);
             Value materialSheen(rapidjson::Type::kObjectType);
 
 
@@ -550,7 +570,7 @@ namespace glTF2 {
 
 
     inline void Write(Value& obj, Mesh& m, AssetWriter& w)
     inline void Write(Value& obj, Mesh& m, AssetWriter& w)
     {
     {
-		/****************** Primitives *******************/
+        /****************** Primitives *******************/
         Value primitives;
         Value primitives;
         primitives.SetArray();
         primitives.SetArray();
         primitives.Reserve(unsigned(m.primitives.size()), w.mAl);
         primitives.Reserve(unsigned(m.primitives.size()), w.mAl);
@@ -634,6 +654,44 @@ namespace glTF2 {
         }
         }
     }
     }
 
 
+    inline void WriteExtrasValue(Value &parent, const CustomExtension &value, AssetWriter &w) {
+        Value valueNode;
+
+        if (value.mStringValue.isPresent) {
+            MakeValue(valueNode, value.mStringValue.value.c_str(), w.mAl);
+        } else if (value.mDoubleValue.isPresent) {
+            MakeValue(valueNode, value.mDoubleValue.value, w.mAl);
+        } else if (value.mUint64Value.isPresent) {
+            MakeValue(valueNode, value.mUint64Value.value, w.mAl);
+        } else if (value.mInt64Value.isPresent) {
+            MakeValue(valueNode, value.mInt64Value.value, w.mAl);
+        } else if (value.mBoolValue.isPresent) {
+            MakeValue(valueNode, value.mBoolValue.value, w.mAl);
+        } else if (value.mValues.isPresent) {
+            valueNode.SetObject();
+            for (auto const &subvalue : value.mValues.value) {
+                WriteExtrasValue(valueNode, subvalue, w);
+            }
+        }
+
+        parent.AddMember(StringRef(value.name), valueNode, w.mAl);
+    }
+
+    inline void WriteExtras(Value &obj, const Extras &extras, AssetWriter &w) {
+        if (!extras.HasExtras()) {
+            return;
+        }
+
+        Value extrasNode;
+        extrasNode.SetObject();
+
+        for (auto const &value : extras.mValues) {
+            WriteExtrasValue(extrasNode, value, w);
+        }
+        
+        obj.AddMember("extras", extrasNode, w.mAl);
+    }
+
     inline void Write(Value& obj, Node& n, AssetWriter& w)
     inline void Write(Value& obj, Node& n, AssetWriter& w)
     {
     {
         if (n.matrix.isPresent) {
         if (n.matrix.isPresent) {
@@ -669,6 +727,8 @@ namespace glTF2 {
         if(n.skeletons.size()) {
         if(n.skeletons.size()) {
             AddRefsVector(obj, "skeletons", n.skeletons, w.mAl);
             AddRefsVector(obj, "skeletons", n.skeletons, w.mAl);
         }
         }
+
+        WriteExtras(obj, n.extras, w);
     }
     }
 
 
     inline void Write(Value& /*obj*/, Program& /*b*/, AssetWriter& /*w*/)
     inline void Write(Value& /*obj*/, Program& /*b*/, AssetWriter& /*w*/)
@@ -742,7 +802,6 @@ namespace glTF2 {
         }
         }
     }
     }
 
 
-
     inline AssetWriter::AssetWriter(Asset& a)
     inline AssetWriter::AssetWriter(Asset& a)
         : mDoc()
         : mDoc()
         , mAsset(a)
         , mAsset(a)
@@ -836,7 +895,7 @@ namespace glTF2 {
             throw DeadlyExportError("Failed to write scene data!");
             throw DeadlyExportError("Failed to write scene data!");
         }
         }
 
 
-        uint32_t jsonChunkLength = (docBuffer.GetSize() + 3) & ~3; // Round up to next multiple of 4
+        uint32_t jsonChunkLength = static_cast<uint32_t>((docBuffer.GetSize() + 3) & ~3); // Round up to next multiple of 4
         auto paddingLength = jsonChunkLength - docBuffer.GetSize();
         auto paddingLength = jsonChunkLength - docBuffer.GetSize();
 
 
         GLB_Chunk jsonChunk;
         GLB_Chunk jsonChunk;
@@ -862,7 +921,7 @@ namespace glTF2 {
         int GLB_Chunk_count = 1;
         int GLB_Chunk_count = 1;
         uint32_t binaryChunkLength = 0;
         uint32_t binaryChunkLength = 0;
         if (bodyBuffer->byteLength > 0) {
         if (bodyBuffer->byteLength > 0) {
-            binaryChunkLength = (bodyBuffer->byteLength + 3) & ~3; // Round up to next multiple of 4
+            binaryChunkLength = static_cast<uint32_t>((bodyBuffer->byteLength + 3) & ~3); // Round up to next multiple of 4
 
 
             auto curPaddingLength = binaryChunkLength - bodyBuffer->byteLength;
             auto curPaddingLength = binaryChunkLength - bodyBuffer->byteLength;
             ++GLB_Chunk_count;
             ++GLB_Chunk_count;
@@ -929,6 +988,10 @@ namespace glTF2 {
               exts.PushBack(StringRef("KHR_materials_unlit"), mAl);
               exts.PushBack(StringRef("KHR_materials_unlit"), mAl);
             }
             }
 
 
+            if (this->mAsset.extensionsUsed.KHR_materials_specular) {
+                exts.PushBack(StringRef("KHR_materials_specular"), mAl);
+            }
+
             if (this->mAsset.extensionsUsed.KHR_materials_sheen) {
             if (this->mAsset.extensionsUsed.KHR_materials_sheen) {
                 exts.PushBack(StringRef("KHR_materials_sheen"), mAl);
                 exts.PushBack(StringRef("KHR_materials_sheen"), mAl);
             }
             }
@@ -980,7 +1043,7 @@ namespace glTF2 {
         if (d.mObjs.empty()) return;
         if (d.mObjs.empty()) return;
 
 
         Value* container = &mDoc;
         Value* container = &mDoc;
-		const char* context = "Document";
+        const char* context = "Document";
 
 
         if (d.mExtId) {
         if (d.mExtId) {
             Value* exts = FindObject(mDoc, "extensions");
             Value* exts = FindObject(mDoc, "extensions");

+ 139 - 49
code/AssetLib/glTF2/glTF2Exporter.cpp

@@ -263,7 +263,7 @@ size_t NZDiff(void *data, void *dataBase, size_t count, unsigned int numCompsIn,
     for (short idx = 0; bufferData_ptr < bufferData_end; idx += 1, bufferData_ptr += numCompsIn) {
     for (short idx = 0; bufferData_ptr < bufferData_end; idx += 1, bufferData_ptr += numCompsIn) {
         bool bNonZero = false;
         bool bNonZero = false;
 
 
-        //for the data, check any component Non Zero
+        // for the data, check any component Non Zero
         for (unsigned int j = 0; j < numCompsOut; j++) {
         for (unsigned int j = 0; j < numCompsOut; j++) {
             double valueData = bufferData_ptr[j];
             double valueData = bufferData_ptr[j];
             double valueBase = bufferBase_ptr ? bufferBase_ptr[j] : 0;
             double valueBase = bufferBase_ptr ? bufferBase_ptr[j] : 0;
@@ -273,11 +273,11 @@ size_t NZDiff(void *data, void *dataBase, size_t count, unsigned int numCompsIn,
             }
             }
         }
         }
 
 
-        //all zeros, continue
+        // all zeros, continue
         if (!bNonZero)
         if (!bNonZero)
             continue;
             continue;
 
 
-        //non zero, store the data
+        // non zero, store the data
         for (unsigned int j = 0; j < numCompsOut; j++) {
         for (unsigned int j = 0; j < numCompsOut; j++) {
             T valueData = bufferData_ptr[j];
             T valueData = bufferData_ptr[j];
             T valueBase = bufferBase_ptr ? bufferBase_ptr[j] : 0;
             T valueBase = bufferBase_ptr ? bufferBase_ptr[j] : 0;
@@ -286,14 +286,14 @@ size_t NZDiff(void *data, void *dataBase, size_t count, unsigned int numCompsIn,
         vNZIdx.push_back(idx);
         vNZIdx.push_back(idx);
     }
     }
 
 
-    //avoid all-0, put 1 item
+    // avoid all-0, put 1 item
     if (vNZDiff.size() == 0) {
     if (vNZDiff.size() == 0) {
         for (unsigned int j = 0; j < numCompsOut; j++)
         for (unsigned int j = 0; j < numCompsOut; j++)
             vNZDiff.push_back(0);
             vNZDiff.push_back(0);
         vNZIdx.push_back(0);
         vNZIdx.push_back(0);
     }
     }
 
 
-    //process data
+    // process data
     outputNZDiff = new T[vNZDiff.size()];
     outputNZDiff = new T[vNZDiff.size()];
     memcpy(outputNZDiff, vNZDiff.data(), vNZDiff.size() * sizeof(T));
     memcpy(outputNZDiff, vNZDiff.data(), vNZDiff.size() * sizeof(T));
 
 
@@ -361,7 +361,7 @@ inline Ref<Accessor> ExportDataSparse(Asset &a, std::string &meshName, Ref<Buffe
         acc->sparse.reset(new Accessor::Sparse);
         acc->sparse.reset(new Accessor::Sparse);
         acc->sparse->count = nzCount;
         acc->sparse->count = nzCount;
 
 
-        //indices
+        // indices
         unsigned int bytesPerIdx = sizeof(unsigned short);
         unsigned int bytesPerIdx = sizeof(unsigned short);
         size_t indices_offset = buffer->byteLength;
         size_t indices_offset = buffer->byteLength;
         size_t indices_padding = indices_offset % bytesPerIdx;
         size_t indices_padding = indices_offset % bytesPerIdx;
@@ -379,7 +379,7 @@ inline Ref<Accessor> ExportDataSparse(Asset &a, std::string &meshName, Ref<Buffe
         acc->sparse->indicesByteOffset = 0;
         acc->sparse->indicesByteOffset = 0;
         acc->WriteSparseIndices(nzCount, nzIdx, 1 * bytesPerIdx);
         acc->WriteSparseIndices(nzCount, nzIdx, 1 * bytesPerIdx);
 
 
-        //values
+        // values
         size_t values_offset = buffer->byteLength;
         size_t values_offset = buffer->byteLength;
         size_t values_padding = values_offset % bytesPerComp;
         size_t values_padding = values_offset % bytesPerComp;
         values_offset += values_padding;
         values_offset += values_padding;
@@ -395,9 +395,9 @@ inline Ref<Accessor> ExportDataSparse(Asset &a, std::string &meshName, Ref<Buffe
         acc->sparse->valuesByteOffset = 0;
         acc->sparse->valuesByteOffset = 0;
         acc->WriteSparseValues(nzCount, nzDiff, numCompsIn * bytesPerComp);
         acc->WriteSparseValues(nzCount, nzDiff, numCompsIn * bytesPerComp);
 
 
-        //clear
-        delete[](char *) nzDiff;
-        delete[](char *) nzIdx;
+        // clear
+        delete[] (char *)nzDiff;
+        delete[] (char *)nzIdx;
     }
     }
     return acc;
     return acc;
 }
 }
@@ -443,6 +443,61 @@ inline Ref<Accessor> ExportData(Asset &a, std::string &meshName, Ref<Buffer> &bu
     return acc;
     return acc;
 }
 }
 
 
+inline void ExportNodeExtras(const aiMetadataEntry &metadataEntry, aiString name, CustomExtension &value) {
+
+    value.name = name.C_Str();
+    switch (metadataEntry.mType) {
+    case AI_BOOL:
+        value.mBoolValue.value = *static_cast<bool *>(metadataEntry.mData);
+        value.mBoolValue.isPresent = true;
+        break;
+    case AI_INT32:
+        value.mInt64Value.value = *static_cast<int32_t *>(metadataEntry.mData);
+        value.mInt64Value.isPresent = true;
+        break;
+    case AI_UINT64:
+        value.mUint64Value.value = *static_cast<uint64_t *>(metadataEntry.mData);
+        value.mUint64Value.isPresent = true;
+        break;
+    case AI_FLOAT:
+        value.mDoubleValue.value = *static_cast<float *>(metadataEntry.mData);
+        value.mDoubleValue.isPresent = true;
+        break;
+    case AI_DOUBLE:
+        value.mDoubleValue.value = *static_cast<double *>(metadataEntry.mData);
+        value.mDoubleValue.isPresent = true;
+        break;
+    case AI_AISTRING:
+        value.mStringValue.value = static_cast<aiString *>(metadataEntry.mData)->C_Str();
+        value.mStringValue.isPresent = true;
+        break;
+    case AI_AIMETADATA: {
+        const aiMetadata *subMetadata = static_cast<aiMetadata *>(metadataEntry.mData);
+        value.mValues.value.resize(subMetadata->mNumProperties);
+        value.mValues.isPresent = true;
+
+        for (unsigned i = 0; i < subMetadata->mNumProperties; ++i) {
+            ExportNodeExtras(subMetadata->mValues[i], subMetadata->mKeys[i], value.mValues.value.at(i));
+        }
+        break;
+    }
+    default:
+        // AI_AIVECTOR3D not handled
+        break;
+    }
+}
+
+inline void ExportNodeExtras(const aiMetadata *metadata, Extras &extras) {
+    if (metadata == nullptr || metadata->mNumProperties == 0) {
+        return;
+    }
+
+    extras.mValues.resize(metadata->mNumProperties);
+    for (unsigned int i = 0; i < metadata->mNumProperties; ++i) {
+        ExportNodeExtras(metadata->mValues[i], metadata->mKeys[i], extras.mValues.at(i));
+    }
+}
+
 inline void SetSamplerWrap(SamplerWrap &wrap, aiTextureMapMode map) {
 inline void SetSamplerWrap(SamplerWrap &wrap, aiTextureMapMode map) {
     switch (map) {
     switch (map) {
     case aiTextureMapMode_Clamp:
     case aiTextureMapMode_Clamp:
@@ -544,7 +599,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial &mat, Ref<Texture> &texture, unsi
                 if (curTex != nullptr) { // embedded
                 if (curTex != nullptr) { // embedded
                     texture->source->name = curTex->mFilename.C_Str();
                     texture->source->name = curTex->mFilename.C_Str();
 
 
-                    //basisu: embedded ktx2, bu
+                    // basisu: embedded ktx2, bu
                     if (curTex->achFormatHint[0]) {
                     if (curTex->achFormatHint[0]) {
                         std::string mimeType = "image/";
                         std::string mimeType = "image/";
                         if (memcmp(curTex->achFormatHint, "jpg", 3) == 0)
                         if (memcmp(curTex->achFormatHint, "jpg", 3) == 0)
@@ -564,7 +619,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial &mat, Ref<Texture> &texture, unsi
                     }
                     }
 
 
                     // The asset has its own buffer, see Image::SetData
                     // The asset has its own buffer, see Image::SetData
-                    //basisu: "image/ktx2", "image/basis" as is
+                    // basisu: "image/ktx2", "image/basis" as is
                     texture->source->SetData(reinterpret_cast<uint8_t *>(curTex->pcData), curTex->mWidth, *mAsset);
                     texture->source->SetData(reinterpret_cast<uint8_t *>(curTex->pcData), curTex->mWidth, *mAsset);
                 } else {
                 } else {
                     texture->source->uri = path;
                     texture->source->uri = path;
@@ -574,7 +629,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial &mat, Ref<Texture> &texture, unsi
                     }
                     }
                 }
                 }
 
 
-                //basisu
+                // basisu
                 if (useBasisUniversal) {
                 if (useBasisUniversal) {
                     mAsset->extensionsUsed.KHR_texture_basisu = true;
                     mAsset->extensionsUsed.KHR_texture_basisu = true;
                     mAsset->extensionsRequired.KHR_texture_basisu = true;
                     mAsset->extensionsRequired.KHR_texture_basisu = true;
@@ -597,7 +652,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial &mat, NormalTextureInfo &prop, ai
     GetMatTex(mat, texture, prop.texCoord, tt, slot);
     GetMatTex(mat, texture, prop.texCoord, tt, slot);
 
 
     if (texture) {
     if (texture) {
-        //GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot);
+        // GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot);
         GetMatTexProp(mat, prop.scale, "scale", tt, slot);
         GetMatTexProp(mat, prop.scale, "scale", tt, slot);
     }
     }
 }
 }
@@ -608,7 +663,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial &mat, OcclusionTextureInfo &prop,
     GetMatTex(mat, texture, prop.texCoord, tt, slot);
     GetMatTex(mat, texture, prop.texCoord, tt, slot);
 
 
     if (texture) {
     if (texture) {
-        //GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot);
+        // GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot);
         GetMatTexProp(mat, prop.strength, "strength", tt, slot);
         GetMatTexProp(mat, prop.strength, "strength", tt, slot);
     }
     }
 }
 }
@@ -640,11 +695,10 @@ aiReturn glTF2Exporter::GetMatColor(const aiMaterial &mat, vec3 &prop, const cha
     return result;
     return result;
 }
 }
 
 
+// This extension has been deprecated, only export with the specific flag enabled, defaults to false. Uses KHR_material_specular default.
 bool glTF2Exporter::GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlossiness &pbrSG) {
 bool glTF2Exporter::GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlossiness &pbrSG) {
     bool result = false;
     bool result = false;
     // If has Glossiness, a Specular Color or Specular Texture, use the KHR_materials_pbrSpecularGlossiness extension
     // If has Glossiness, a Specular Color or Specular Texture, use the KHR_materials_pbrSpecularGlossiness extension
-    // NOTE: This extension is being considered for deprecation (Dec 2020), may be replaced by KHR_material_specular
-
     if (mat.Get(AI_MATKEY_GLOSSINESS_FACTOR, pbrSG.glossinessFactor) == AI_SUCCESS) {
     if (mat.Get(AI_MATKEY_GLOSSINESS_FACTOR, pbrSG.glossinessFactor) == AI_SUCCESS) {
         result = true;
         result = true;
     } else {
     } else {
@@ -674,6 +728,25 @@ bool glTF2Exporter::GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlo
     return result;
     return result;
 }
 }
 
 
+bool glTF2Exporter::GetMatSpecular(const aiMaterial &mat, glTF2::MaterialSpecular &specular) {
+    // Specular requires either/or, default factors of zero disables specular, so do not export
+    if (GetMatColor(mat, specular.specularColorFactor, AI_MATKEY_COLOR_SPECULAR) != AI_SUCCESS && mat.Get(AI_MATKEY_SPECULAR_FACTOR, specular.specularFactor) != AI_SUCCESS) {
+        return false;
+    }
+    // The spec states that the default is 1.0 and [1.0, 1.0, 1.0]. We if both are 0, which should disable specular. Otherwise, if one is 0, set to 1.0
+    const bool colorFactorIsZero = specular.specularColorFactor[0] == defaultSpecularColorFactor[0] && specular.specularColorFactor[1] == defaultSpecularColorFactor[1] && specular.specularColorFactor[2] == defaultSpecularColorFactor[2];
+    if (specular.specularFactor == 0.0f && colorFactorIsZero) {
+        return false;
+    } else if (specular.specularFactor == 0.0f) {
+        specular.specularFactor = 1.0f;
+    } else if (colorFactorIsZero) {
+        specular.specularColorFactor[0] = specular.specularColorFactor[1] = specular.specularColorFactor[2] = 1.0f;
+    }
+    GetMatTex(mat, specular.specularColorTexture, aiTextureType_SPECULAR);
+    GetMatTex(mat, specular.specularTexture, aiTextureType_SPECULAR);
+    return true;
+}
+
 bool glTF2Exporter::GetMatSheen(const aiMaterial &mat, glTF2::MaterialSheen &sheen) {
 bool glTF2Exporter::GetMatSheen(const aiMaterial &mat, glTF2::MaterialSheen &sheen) {
     // Return true if got any valid Sheen properties or textures
     // Return true if got any valid Sheen properties or textures
     if (GetMatColor(mat, sheen.sheenColorFactor, AI_MATKEY_SHEEN_COLOR_FACTOR) != aiReturn_SUCCESS) {
     if (GetMatColor(mat, sheen.sheenColorFactor, AI_MATKEY_SHEEN_COLOR_FACTOR) != aiReturn_SUCCESS) {
@@ -759,20 +832,30 @@ void glTF2Exporter::ExportMaterials() {
         GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, aiTextureType_BASE_COLOR);
         GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, aiTextureType_BASE_COLOR);
 
 
         if (!m->pbrMetallicRoughness.baseColorTexture.texture) {
         if (!m->pbrMetallicRoughness.baseColorTexture.texture) {
-            //if there wasn't a baseColorTexture defined in the source, fallback to any diffuse texture
+            // if there wasn't a baseColorTexture defined in the source, fallback to any diffuse texture
             GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, aiTextureType_DIFFUSE);
             GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, aiTextureType_DIFFUSE);
         }
         }
 
 
-        GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE);
+        GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, aiTextureType_DIFFUSE_ROUGHNESS);
+
+        if (!m->pbrMetallicRoughness.metallicRoughnessTexture.texture) {
+            // if there wasn't a aiTextureType_DIFFUSE_ROUGHNESS defined in the source, fallback to aiTextureType_METALNESS
+            GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, aiTextureType_METALNESS);
+        }
+
+        if (!m->pbrMetallicRoughness.metallicRoughnessTexture.texture) {
+            // if there still wasn't a aiTextureType_METALNESS defined in the source, fallback to AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE
+            GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE);
+        }
 
 
         if (GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_BASE_COLOR) != AI_SUCCESS) {
         if (GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_BASE_COLOR) != AI_SUCCESS) {
             // if baseColorFactor wasn't defined, then the source is likely not a metallic roughness material.
             // if baseColorFactor wasn't defined, then the source is likely not a metallic roughness material.
-            //a fallback to any diffuse color should be used instead
+            // a fallback to any diffuse color should be used instead
             GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_COLOR_DIFFUSE);
             GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_COLOR_DIFFUSE);
         }
         }
 
 
         if (mat.Get(AI_MATKEY_METALLIC_FACTOR, m->pbrMetallicRoughness.metallicFactor) != AI_SUCCESS) {
         if (mat.Get(AI_MATKEY_METALLIC_FACTOR, m->pbrMetallicRoughness.metallicFactor) != AI_SUCCESS) {
-            //if metallicFactor wasn't defined, then the source is likely not a PBR file, and the metallicFactor should be 0
+            // if metallicFactor wasn't defined, then the source is likely not a PBR file, and the metallicFactor should be 0
             m->pbrMetallicRoughness.metallicFactor = 0;
             m->pbrMetallicRoughness.metallicFactor = 0;
         }
         }
 
 
@@ -785,10 +868,10 @@ void glTF2Exporter::ExportMaterials() {
             if (mat.Get(AI_MATKEY_COLOR_SPECULAR, specularColor) == AI_SUCCESS && mat.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) {
             if (mat.Get(AI_MATKEY_COLOR_SPECULAR, specularColor) == AI_SUCCESS && mat.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) {
                 // convert specular color to luminance
                 // convert specular color to luminance
                 float specularIntensity = specularColor[0] * 0.2125f + specularColor[1] * 0.7154f + specularColor[2] * 0.0721f;
                 float specularIntensity = specularColor[0] * 0.2125f + specularColor[1] * 0.7154f + specularColor[2] * 0.0721f;
-                //normalize shininess (assuming max is 1000) with an inverse exponentional curve
+                // normalize shininess (assuming max is 1000) with an inverse exponentional curve
                 float normalizedShininess = std::sqrt(shininess / 1000);
                 float normalizedShininess = std::sqrt(shininess / 1000);
 
 
-                //clamp the shininess value between 0 and 1
+                // clamp the shininess value between 0 and 1
                 normalizedShininess = std::min(std::max(normalizedShininess, 0.0f), 1.0f);
                 normalizedShininess = std::min(std::max(normalizedShininess, 0.0f), 1.0f);
                 // low specular intensity values should produce a rough material even if shininess is high.
                 // low specular intensity values should produce a rough material even if shininess is high.
                 normalizedShininess = normalizedShininess * specularIntensity;
                 normalizedShininess = normalizedShininess * specularIntensity;
@@ -818,9 +901,9 @@ void glTF2Exporter::ExportMaterials() {
             m->alphaMode = alphaMode.C_Str();
             m->alphaMode = alphaMode.C_Str();
         }
         }
 
 
-        {
+        // This extension has been deprecated, only export with the specific flag enabled, defaults to false. Uses KHR_material_specular default.
+        if (mProperties->GetPropertyBool(AI_CONFIG_USE_GLTF_PBR_SPECULAR_GLOSSINESS)) {
             // KHR_materials_pbrSpecularGlossiness extension
             // KHR_materials_pbrSpecularGlossiness extension
-            // NOTE: This extension is being considered for deprecation (Dec 2020)
             PbrSpecularGlossiness pbrSG;
             PbrSpecularGlossiness pbrSG;
             if (GetMatSpecGloss(mat, pbrSG)) {
             if (GetMatSpecGloss(mat, pbrSG)) {
                 mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true;
                 mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true;
@@ -837,7 +920,12 @@ void glTF2Exporter::ExportMaterials() {
         } else {
         } else {
             // These extensions are not compatible with KHR_materials_unlit or KHR_materials_pbrSpecularGlossiness
             // These extensions are not compatible with KHR_materials_unlit or KHR_materials_pbrSpecularGlossiness
             if (!m->pbrSpecularGlossiness.isPresent) {
             if (!m->pbrSpecularGlossiness.isPresent) {
-                // Sheen
+                MaterialSpecular specular;
+                if (GetMatSpecular(mat, specular)) {
+                    mAsset->extensionsUsed.KHR_materials_specular = true;
+                    m->materialSpecular = Nullable<MaterialSpecular>(specular);
+                }
+
                 MaterialSheen sheen;
                 MaterialSheen sheen;
                 if (GetMatSheen(mat, sheen)) {
                 if (GetMatSheen(mat, sheen)) {
                     mAsset->extensionsUsed.KHR_materials_sheen = true;
                     mAsset->extensionsUsed.KHR_materials_sheen = true;
@@ -981,7 +1069,7 @@ void ExportSkin(Asset &mAsset, const aiMesh *aimesh, Ref<Mesh> &meshRef, Ref<Buf
                 if (boneIndexFitted != -1) {
                 if (boneIndexFitted != -1) {
                     vertexJointData[vertexId][boneIndexFitted] = static_cast<float>(jointNamesIndex);
                     vertexJointData[vertexId][boneIndexFitted] = static_cast<float>(jointNamesIndex);
                 }
                 }
-            }else {
+            } else {
                 vertexJointData[vertexId][jointsPerVertex[vertexId]] = static_cast<float>(jointNamesIndex);
                 vertexJointData[vertexId][jointsPerVertex[vertexId]] = static_cast<float>(jointNamesIndex);
                 vertexWeightData[vertexId][jointsPerVertex[vertexId]] = vertWeight;
                 vertexWeightData[vertexId][jointsPerVertex[vertexId]] = vertWeight;
 
 
@@ -993,7 +1081,7 @@ void ExportSkin(Asset &mAsset, const aiMesh *aimesh, Ref<Mesh> &meshRef, Ref<Buf
 
 
     Mesh::Primitive &p = meshRef->primitives.back();
     Mesh::Primitive &p = meshRef->primitives.back();
     Ref<Accessor> vertexJointAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices,
     Ref<Accessor> vertexJointAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices,
-        vertexJointData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
+            vertexJointData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
     if (vertexJointAccessor) {
     if (vertexJointAccessor) {
         size_t offset = vertexJointAccessor->bufferView->byteOffset;
         size_t offset = vertexJointAccessor->bufferView->byteOffset;
         size_t bytesLen = vertexJointAccessor->bufferView->byteLength;
         size_t bytesLen = vertexJointAccessor->bufferView->byteLength;
@@ -1077,7 +1165,7 @@ void glTF2Exporter::ExportMeshes() {
 
 
         /******************* Vertices ********************/
         /******************* Vertices ********************/
         Ref<Accessor> v = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mVertices, AttribType::VEC3,
         Ref<Accessor> v = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mVertices, AttribType::VEC3,
-            AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
+                AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
         if (v) {
         if (v) {
             p.attributes.position.push_back(v);
             p.attributes.position.push_back(v);
         }
         }
@@ -1091,7 +1179,7 @@ void glTF2Exporter::ExportMeshes() {
         }
         }
 
 
         Ref<Accessor> n = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mNormals, AttribType::VEC3,
         Ref<Accessor> n = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mNormals, AttribType::VEC3,
-            AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
+                AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
         if (n) {
         if (n) {
             p.attributes.normal.push_back(n);
             p.attributes.normal.push_back(n);
         }
         }
@@ -1113,7 +1201,7 @@ void glTF2Exporter::ExportMeshes() {
                 AttribType::Value type = (aim->mNumUVComponents[i] == 2) ? AttribType::VEC2 : AttribType::VEC3;
                 AttribType::Value type = (aim->mNumUVComponents[i] == 2) ? AttribType::VEC2 : AttribType::VEC3;
 
 
                 Ref<Accessor> tc = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mTextureCoords[i],
                 Ref<Accessor> tc = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mTextureCoords[i],
-                    AttribType::VEC3, type, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
+                        AttribType::VEC3, type, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
                 if (tc) {
                 if (tc) {
                     p.attributes.texcoord.push_back(tc);
                     p.attributes.texcoord.push_back(tc);
                 }
                 }
@@ -1123,7 +1211,7 @@ void glTF2Exporter::ExportMeshes() {
         /*************** Vertex colors ****************/
         /*************** Vertex colors ****************/
         for (unsigned int indexColorChannel = 0; indexColorChannel < aim->GetNumColorChannels(); ++indexColorChannel) {
         for (unsigned int indexColorChannel = 0; indexColorChannel < aim->GetNumColorChannels(); ++indexColorChannel) {
             Ref<Accessor> c = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mColors[indexColorChannel],
             Ref<Accessor> c = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mColors[indexColorChannel],
-                AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
+                    AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
             if (c) {
             if (c) {
                 p.attributes.color.push_back(c);
                 p.attributes.color.push_back(c);
             }
             }
@@ -1141,7 +1229,7 @@ void glTF2Exporter::ExportMeshes() {
             }
             }
 
 
             p.indices = ExportData(*mAsset, meshId, b, indices.size(), &indices[0], AttribType::SCALAR, AttribType::SCALAR,
             p.indices = ExportData(*mAsset, meshId, b, indices.size(), &indices[0], AttribType::SCALAR, AttribType::SCALAR,
-                ComponentType_UNSIGNED_INT, BufferViewTarget_ELEMENT_ARRAY_BUFFER);
+                    ComponentType_UNSIGNED_INT, BufferViewTarget_ELEMENT_ARRAY_BUFFER);
         }
         }
 
 
         switch (aim->mPrimitiveTypes) {
         switch (aim->mPrimitiveTypes) {
@@ -1284,24 +1372,24 @@ void glTF2Exporter::MergeMeshes() {
 
 
         unsigned int nMeshes = static_cast<unsigned int>(node->meshes.size());
         unsigned int nMeshes = static_cast<unsigned int>(node->meshes.size());
 
 
-        //skip if it's 1 or less meshes per node
+        // skip if it's 1 or less meshes per node
         if (nMeshes > 1) {
         if (nMeshes > 1) {
             Ref<Mesh> firstMesh = node->meshes.at(0);
             Ref<Mesh> firstMesh = node->meshes.at(0);
 
 
-            //loop backwards to allow easy removal of a mesh from a node once it's merged
+            // loop backwards to allow easy removal of a mesh from a node once it's merged
             for (unsigned int m = nMeshes - 1; m >= 1; --m) {
             for (unsigned int m = nMeshes - 1; m >= 1; --m) {
                 Ref<Mesh> mesh = node->meshes.at(m);
                 Ref<Mesh> mesh = node->meshes.at(m);
 
 
-                //append this mesh's primitives to the first mesh's primitives
+                // append this mesh's primitives to the first mesh's primitives
                 firstMesh->primitives.insert(
                 firstMesh->primitives.insert(
                         firstMesh->primitives.end(),
                         firstMesh->primitives.end(),
                         mesh->primitives.begin(),
                         mesh->primitives.begin(),
                         mesh->primitives.end());
                         mesh->primitives.end());
 
 
-                //remove the mesh from the list of meshes
+                // remove the mesh from the list of meshes
                 unsigned int removedIndex = mAsset->meshes.Remove(mesh->id.c_str());
                 unsigned int removedIndex = mAsset->meshes.Remove(mesh->id.c_str());
 
 
-                //find the presence of the removed mesh in other nodes
+                // find the presence of the removed mesh in other nodes
                 for (unsigned int nn = 0; nn < mAsset->nodes.Size(); ++nn) {
                 for (unsigned int nn = 0; nn < mAsset->nodes.Size(); ++nn) {
                     Ref<Node> curNode = mAsset->nodes.Get(nn);
                     Ref<Node> curNode = mAsset->nodes.Get(nn);
 
 
@@ -1320,7 +1408,7 @@ void glTF2Exporter::MergeMeshes() {
                 }
                 }
             }
             }
 
 
-            //since we were looping backwards, reverse the order of merged primitives to their original order
+            // since we were looping backwards, reverse the order of merged primitives to their original order
             std::reverse(firstMesh->primitives.begin() + 1, firstMesh->primitives.end());
             std::reverse(firstMesh->primitives.begin() + 1, firstMesh->primitives.end());
         }
         }
     }
     }
@@ -1363,6 +1451,8 @@ unsigned int glTF2Exporter::ExportNode(const aiNode *n, Ref<Node> &parent) {
     node->parent = parent;
     node->parent = parent;
     node->name = name;
     node->name = name;
 
 
+    ExportNodeExtras(n->mMetaData, node->extras);
+
     if (!n->mTransformation.IsIdentity()) {
     if (!n->mTransformation.IsIdentity()) {
         if (mScene->mNumAnimations > 0 || (mProperties && mProperties->HasPropertyBool("GLTF2_NODE_IN_TRS"))) {
         if (mScene->mNumAnimations > 0 || (mProperties && mProperties->HasPropertyBool("GLTF2_NODE_IN_TRS"))) {
             aiQuaternion quaternion;
             aiQuaternion quaternion;
@@ -1445,9 +1535,9 @@ inline void ExtractTranslationSampler(Asset &asset, std::string &animId, Ref<Buf
         const aiVectorKey &key = nodeChannel->mPositionKeys[i];
         const aiVectorKey &key = nodeChannel->mPositionKeys[i];
         // mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
         // mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
         times[i] = static_cast<float>(key.mTime / ticksPerSecond);
         times[i] = static_cast<float>(key.mTime / ticksPerSecond);
-        values[(i * 3) + 0] = (ai_real) key.mValue.x;
-        values[(i * 3) + 1] = (ai_real) key.mValue.y;
-        values[(i * 3) + 2] = (ai_real) key.mValue.z;
+        values[(i * 3) + 0] = (ai_real)key.mValue.x;
+        values[(i * 3) + 1] = (ai_real)key.mValue.y;
+        values[(i * 3) + 2] = (ai_real)key.mValue.z;
     }
     }
 
 
     sampler.input = GetSamplerInputRef(asset, animId, buffer, times);
     sampler.input = GetSamplerInputRef(asset, animId, buffer, times);
@@ -1464,9 +1554,9 @@ inline void ExtractScaleSampler(Asset &asset, std::string &animId, Ref<Buffer> &
         const aiVectorKey &key = nodeChannel->mScalingKeys[i];
         const aiVectorKey &key = nodeChannel->mScalingKeys[i];
         // mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
         // mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
         times[i] = static_cast<float>(key.mTime / ticksPerSecond);
         times[i] = static_cast<float>(key.mTime / ticksPerSecond);
-        values[(i * 3) + 0] = (ai_real) key.mValue.x;
-        values[(i * 3) + 1] = (ai_real) key.mValue.y;
-        values[(i * 3) + 2] = (ai_real) key.mValue.z;
+        values[(i * 3) + 0] = (ai_real)key.mValue.x;
+        values[(i * 3) + 1] = (ai_real)key.mValue.y;
+        values[(i * 3) + 2] = (ai_real)key.mValue.z;
     }
     }
 
 
     sampler.input = GetSamplerInputRef(asset, animId, buffer, times);
     sampler.input = GetSamplerInputRef(asset, animId, buffer, times);
@@ -1483,10 +1573,10 @@ inline void ExtractRotationSampler(Asset &asset, std::string &animId, Ref<Buffer
         const aiQuatKey &key = nodeChannel->mRotationKeys[i];
         const aiQuatKey &key = nodeChannel->mRotationKeys[i];
         // mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
         // mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
         times[i] = static_cast<float>(key.mTime / ticksPerSecond);
         times[i] = static_cast<float>(key.mTime / ticksPerSecond);
-        values[(i * 4) + 0] = (ai_real) key.mValue.x;
-        values[(i * 4) + 1] = (ai_real) key.mValue.y;
-        values[(i * 4) + 2] = (ai_real) key.mValue.z;
-        values[(i * 4) + 3] = (ai_real) key.mValue.w;
+        values[(i * 4) + 0] = (ai_real)key.mValue.x;
+        values[(i * 4) + 1] = (ai_real)key.mValue.y;
+        values[(i * 4) + 2] = (ai_real)key.mValue.z;
+        values[(i * 4) + 3] = (ai_real)key.mValue.w;
     }
     }
 
 
     sampler.input = GetSamplerInputRef(asset, animId, buffer, times);
     sampler.input = GetSamplerInputRef(asset, animId, buffer, times);

+ 2 - 0
code/AssetLib/glTF2/glTF2Exporter.h

@@ -76,6 +76,7 @@ struct OcclusionTextureInfo;
 struct Node;
 struct Node;
 struct Texture;
 struct Texture;
 struct PbrSpecularGlossiness;
 struct PbrSpecularGlossiness;
+struct MaterialSpecular;
 struct MaterialSheen;
 struct MaterialSheen;
 struct MaterialClearcoat;
 struct MaterialClearcoat;
 struct MaterialTransmission;
 struct MaterialTransmission;
@@ -117,6 +118,7 @@ protected:
     aiReturn GetMatColor(const aiMaterial &mat, glTF2::vec4 &prop, const char *propName, int type, int idx) const;
     aiReturn GetMatColor(const aiMaterial &mat, glTF2::vec4 &prop, const char *propName, int type, int idx) const;
     aiReturn GetMatColor(const aiMaterial &mat, glTF2::vec3 &prop, const char *propName, int type, int idx) const;
     aiReturn GetMatColor(const aiMaterial &mat, glTF2::vec3 &prop, const char *propName, int type, int idx) const;
     bool GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlossiness &pbrSG);
     bool GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlossiness &pbrSG);
+    bool GetMatSpecular(const aiMaterial &mat, glTF2::MaterialSpecular &specular);
     bool GetMatSheen(const aiMaterial &mat, glTF2::MaterialSheen &sheen);
     bool GetMatSheen(const aiMaterial &mat, glTF2::MaterialSheen &sheen);
     bool GetMatClearcoat(const aiMaterial &mat, glTF2::MaterialClearcoat &clearcoat);
     bool GetMatClearcoat(const aiMaterial &mat, glTF2::MaterialClearcoat &clearcoat);
     bool GetMatTransmission(const aiMaterial &mat, glTF2::MaterialTransmission &transmission);
     bool GetMatTransmission(const aiMaterial &mat, glTF2::MaterialTransmission &transmission);

+ 137 - 78
code/AssetLib/glTF2/glTF2Importer.cpp

@@ -100,8 +100,6 @@ glTF2Importer::glTF2Importer() :
     // empty
     // empty
 }
 }
 
 
-glTF2Importer::~glTF2Importer() = default;
-
 const aiImporterDesc *glTF2Importer::GetInfo() const {
 const aiImporterDesc *glTF2Importer::GetInfo() const {
     return &desc;
     return &desc;
 }
 }
@@ -114,7 +112,11 @@ bool glTF2Importer::CanRead(const std::string &filename, IOSystem *pIOHandler, b
 
 
     if (pIOHandler) {
     if (pIOHandler) {
         glTF2::Asset asset(pIOHandler);
         glTF2::Asset asset(pIOHandler);
-        return asset.CanRead(filename, extension == "glb");
+        return asset.CanRead(
+            filename,
+            CheckMagicToken(
+                pIOHandler, filename, AI_GLB_MAGIC_NUMBER, 1, 0,
+                static_cast<unsigned int>(strlen(AI_GLB_MAGIC_NUMBER))));
     }
     }
 
 
     return false;
     return false;
@@ -232,7 +234,8 @@ inline void SetMaterialTextureProperty(std::vector<int> &embeddedTexIdxs, Asset
     SetMaterialTextureProperty(embeddedTexIdxs, r, (glTF2::TextureInfo)prop, mat, texType, texSlot);
     SetMaterialTextureProperty(embeddedTexIdxs, r, (glTF2::TextureInfo)prop, mat, texType, texSlot);
 
 
     if (prop.texture && prop.texture->source) {
     if (prop.texture && prop.texture->source) {
-        mat->AddProperty(&prop.strength, 1, AI_MATKEY_GLTF_TEXTURE_STRENGTH(texType, texSlot));
+        std::string textureStrengthKey = std::string(_AI_MATKEY_TEXTURE_BASE) + "." + "strength";
+        mat->AddProperty(&prop.strength, 1, textureStrengthKey.c_str(), texType, texSlot);
     }
     }
 }
 }
 
 
@@ -278,8 +281,19 @@ static aiMaterial *ImportMaterial(std::vector<int> &embeddedTexIdxs, Asset &r, M
         aimat->AddProperty(&alphaMode, AI_MATKEY_GLTF_ALPHAMODE);
         aimat->AddProperty(&alphaMode, AI_MATKEY_GLTF_ALPHAMODE);
         aimat->AddProperty(&mat.alphaCutoff, 1, AI_MATKEY_GLTF_ALPHACUTOFF);
         aimat->AddProperty(&mat.alphaCutoff, 1, AI_MATKEY_GLTF_ALPHACUTOFF);
 
 
+        // KHR_materials_specular
+        if (mat.materialSpecular.isPresent) {
+            MaterialSpecular &specular = mat.materialSpecular.value;
+            // Default values of zero disables Specular
+            if (std::memcmp(specular.specularColorFactor, defaultSpecularColorFactor, sizeof(glTFCommon::vec3)) != 0 || specular.specularFactor != 0.0f) {
+                SetMaterialColorProperty(r, specular.specularColorFactor, aimat, AI_MATKEY_COLOR_SPECULAR);
+                aimat->AddProperty(&specular.specularFactor, 1, AI_MATKEY_SPECULAR_FACTOR);
+                SetMaterialTextureProperty(embeddedTexIdxs, r, specular.specularTexture, aimat, aiTextureType_SPECULAR);
+                SetMaterialTextureProperty(embeddedTexIdxs, r, specular.specularColorTexture, aimat, aiTextureType_SPECULAR);
+            }
+        }
         // pbrSpecularGlossiness
         // pbrSpecularGlossiness
-        if (mat.pbrSpecularGlossiness.isPresent) {
+        else if (mat.pbrSpecularGlossiness.isPresent) {
             PbrSpecularGlossiness &pbrSG = mat.pbrSpecularGlossiness.value;
             PbrSpecularGlossiness &pbrSG = mat.pbrSpecularGlossiness.value;
 
 
             SetMaterialColorProperty(r, pbrSG.diffuseFactor, aimat, AI_MATKEY_COLOR_DIFFUSE);
             SetMaterialColorProperty(r, pbrSG.diffuseFactor, aimat, AI_MATKEY_COLOR_DIFFUSE);
@@ -432,10 +446,10 @@ static inline bool CheckValidFacesIndices(aiFace *faces, unsigned nFaces, unsign
 #endif // ASSIMP_BUILD_DEBUG
 #endif // ASSIMP_BUILD_DEBUG
 
 
 template <typename T>
 template <typename T>
-aiColor4D *GetVertexColorsForType(Ref<Accessor> input) {
+aiColor4D *GetVertexColorsForType(Ref<Accessor> input, std::vector<unsigned int> *vertexRemappingTable) {
     constexpr float max = std::numeric_limits<T>::max();
     constexpr float max = std::numeric_limits<T>::max();
     aiColor4t<T> *colors;
     aiColor4t<T> *colors;
-    input->ExtractData(colors);
+    input->ExtractData(colors, vertexRemappingTable);
     auto output = new aiColor4D[input->count];
     auto output = new aiColor4D[input->count];
     for (size_t i = 0; i < input->count; i++) {
     for (size_t i = 0; i < input->count; i++) {
         output[i] = aiColor4D(
         output[i] = aiColor4D(
@@ -450,18 +464,73 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
     ASSIMP_LOG_DEBUG("Importing ", r.meshes.Size(), " meshes");
     ASSIMP_LOG_DEBUG("Importing ", r.meshes.Size(), " meshes");
     std::vector<std::unique_ptr<aiMesh>> meshes;
     std::vector<std::unique_ptr<aiMesh>> meshes;
 
 
-    unsigned int k = 0;
     meshOffsets.clear();
     meshOffsets.clear();
+    meshOffsets.reserve(r.meshes.Size() + 1);
+    mVertexRemappingTables.clear();
 
 
+    // Count the number of aiMeshes
+    unsigned int num_aiMeshes = 0;
     for (unsigned int m = 0; m < r.meshes.Size(); ++m) {
     for (unsigned int m = 0; m < r.meshes.Size(); ++m) {
-        Mesh &mesh = r.meshes[m];
+        meshOffsets.push_back(num_aiMeshes);
+        num_aiMeshes += unsigned(r.meshes[m].primitives.size());
+    }
+    meshOffsets.push_back(num_aiMeshes); // add a last element so we can always do meshOffsets[n+1] - meshOffsets[n]
 
 
-        meshOffsets.push_back(k);
-        k += unsigned(mesh.primitives.size());
+    std::vector<unsigned int> reverseMappingIndices;
+    std::vector<unsigned int> indexBuffer;
+    meshes.reserve(num_aiMeshes);
+    mVertexRemappingTables.resize(num_aiMeshes);
+
+    for (unsigned int m = 0; m < r.meshes.Size(); ++m) {
+        Mesh &mesh = r.meshes[m];
 
 
         for (unsigned int p = 0; p < mesh.primitives.size(); ++p) {
         for (unsigned int p = 0; p < mesh.primitives.size(); ++p) {
             Mesh::Primitive &prim = mesh.primitives[p];
             Mesh::Primitive &prim = mesh.primitives[p];
 
 
+            Mesh::Primitive::Attributes &attr = prim.attributes;
+
+            // Find out the maximum number of vertices:
+            size_t numAllVertices = 0;
+            if (!attr.position.empty() && attr.position[0]) {
+                numAllVertices = attr.position[0]->count;
+            }
+
+            // Extract used vertices:
+            bool useIndexBuffer = prim.indices;
+            std::vector<unsigned int> *vertexRemappingTable = nullptr;
+            
+            if (useIndexBuffer) {
+                size_t count = prim.indices->count;
+                indexBuffer.resize(count);
+                reverseMappingIndices.clear();
+                vertexRemappingTable = &mVertexRemappingTables[meshes.size()];
+                vertexRemappingTable->reserve(count / 3); // this is a very rough heuristic to reduce re-allocations
+                Accessor::Indexer data = prim.indices->GetIndexer();
+                if (!data.IsValid()) {
+                    throw DeadlyImportError("GLTF: Invalid accessor without data in mesh ", getContextForErrorMessages(mesh.id, mesh.name));
+                }
+
+                // Build the vertex remapping table and the modified index buffer (used later instead of the original one)
+                // In case no index buffer is used, the original vertex arrays are being used so no remapping is required in the first place.
+                const unsigned int unusedIndex = ~0u;
+                for (unsigned int i = 0; i < count; ++i) {
+                    unsigned int index = data.GetUInt(i);
+                    if (index >= numAllVertices) {
+                        // Out-of-range indices will be filtered out when adding the faces and then lead to a warning. At this stage, we just keep them.
+                        indexBuffer[i] = index;
+                        continue; 
+                    }
+                    if (index >= reverseMappingIndices.size()) {
+                        reverseMappingIndices.resize(index + 1, unusedIndex);
+                    }
+                    if (reverseMappingIndices[index] == unusedIndex) {
+                        reverseMappingIndices[index] = static_cast<unsigned int>(vertexRemappingTable->size());
+                        vertexRemappingTable->push_back(index);
+                    }
+                    indexBuffer[i] = reverseMappingIndices[index];
+                }
+            }
+
             aiMesh *aim = new aiMesh();
             aiMesh *aim = new aiMesh();
             meshes.push_back(std::unique_ptr<aiMesh>(aim));
             meshes.push_back(std::unique_ptr<aiMesh>(aim));
 
 
@@ -491,28 +560,25 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                 break;
                 break;
             }
             }
 
 
-            Mesh::Primitive::Attributes &attr = prim.attributes;
-
             if (!attr.position.empty() && attr.position[0]) {
             if (!attr.position.empty() && attr.position[0]) {
-                aim->mNumVertices = static_cast<unsigned int>(attr.position[0]->count);
-                attr.position[0]->ExtractData(aim->mVertices);
+                aim->mNumVertices = static_cast<unsigned int>(attr.position[0]->ExtractData(aim->mVertices, vertexRemappingTable));
             }
             }
 
 
             if (!attr.normal.empty() && attr.normal[0]) {
             if (!attr.normal.empty() && attr.normal[0]) {
-                if (attr.normal[0]->count != aim->mNumVertices) {
+                    if (attr.normal[0]->count != numAllVertices) {
                     DefaultLogger::get()->warn("Normal count in mesh \"", mesh.name, "\" does not match the vertex count, normals ignored.");
                     DefaultLogger::get()->warn("Normal count in mesh \"", mesh.name, "\" does not match the vertex count, normals ignored.");
                 } else {
                 } else {
-                    attr.normal[0]->ExtractData(aim->mNormals);
+                    attr.normal[0]->ExtractData(aim->mNormals, vertexRemappingTable);
 
 
                     // only extract tangents if normals are present
                     // only extract tangents if normals are present
                     if (!attr.tangent.empty() && attr.tangent[0]) {
                     if (!attr.tangent.empty() && attr.tangent[0]) {
-                        if (attr.tangent[0]->count != aim->mNumVertices) {
+                        if (attr.tangent[0]->count != numAllVertices) {
                             DefaultLogger::get()->warn("Tangent count in mesh \"", mesh.name, "\" does not match the vertex count, tangents ignored.");
                             DefaultLogger::get()->warn("Tangent count in mesh \"", mesh.name, "\" does not match the vertex count, tangents ignored.");
                         } else {
                         } else {
                             // generate bitangents from normals and tangents according to spec
                             // generate bitangents from normals and tangents according to spec
                             Tangent *tangents = nullptr;
                             Tangent *tangents = nullptr;
 
 
-                            attr.tangent[0]->ExtractData(tangents);
+                            attr.tangent[0]->ExtractData(tangents, vertexRemappingTable);
 
 
                             aim->mTangents = new aiVector3D[aim->mNumVertices];
                             aim->mTangents = new aiVector3D[aim->mNumVertices];
                             aim->mBitangents = new aiVector3D[aim->mNumVertices];
                             aim->mBitangents = new aiVector3D[aim->mNumVertices];
@@ -529,7 +595,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
             }
             }
 
 
             for (size_t c = 0; c < attr.color.size() && c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) {
             for (size_t c = 0; c < attr.color.size() && c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) {
-                if (attr.color[c]->count != aim->mNumVertices) {
+                if (attr.color[c]->count != numAllVertices) {
                     DefaultLogger::get()->warn("Color stream size in mesh \"", mesh.name,
                     DefaultLogger::get()->warn("Color stream size in mesh \"", mesh.name,
                             "\" does not match the vertex count");
                             "\" does not match the vertex count");
                     continue;
                     continue;
@@ -537,12 +603,12 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
 
 
                 auto componentType = attr.color[c]->componentType;
                 auto componentType = attr.color[c]->componentType;
                 if (componentType == glTF2::ComponentType_FLOAT) {
                 if (componentType == glTF2::ComponentType_FLOAT) {
-                    attr.color[c]->ExtractData(aim->mColors[c]);
+                    attr.color[c]->ExtractData(aim->mColors[c], vertexRemappingTable);
                 } else {
                 } else {
                     if (componentType == glTF2::ComponentType_UNSIGNED_BYTE) {
                     if (componentType == glTF2::ComponentType_UNSIGNED_BYTE) {
-                        aim->mColors[c] = GetVertexColorsForType<unsigned char>(attr.color[c]);
+                        aim->mColors[c] = GetVertexColorsForType<unsigned char>(attr.color[c], vertexRemappingTable);
                     } else if (componentType == glTF2::ComponentType_UNSIGNED_SHORT) {
                     } else if (componentType == glTF2::ComponentType_UNSIGNED_SHORT) {
-                        aim->mColors[c] = GetVertexColorsForType<unsigned short>(attr.color[c]);
+                        aim->mColors[c] = GetVertexColorsForType<unsigned short>(attr.color[c], vertexRemappingTable);
                     }
                     }
                 }
                 }
             }
             }
@@ -552,13 +618,13 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                     continue;
                     continue;
                 }
                 }
 
 
-                if (attr.texcoord[tc]->count != aim->mNumVertices) {
+                if (attr.texcoord[tc]->count != numAllVertices) {
                     DefaultLogger::get()->warn("Texcoord stream size in mesh \"", mesh.name,
                     DefaultLogger::get()->warn("Texcoord stream size in mesh \"", mesh.name,
                             "\" does not match the vertex count");
                             "\" does not match the vertex count");
                     continue;
                     continue;
                 }
                 }
 
 
-                attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]);
+                attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc], vertexRemappingTable);
                 aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents();
                 aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents();
 
 
                 aiVector3D *values = aim->mTextureCoords[tc];
                 aiVector3D *values = aim->mTextureCoords[tc];
@@ -583,11 +649,11 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                     Mesh::Primitive::Target &target = targets[i];
                     Mesh::Primitive::Target &target = targets[i];
 
 
                     if (needPositions) {
                     if (needPositions) {
-                        if (target.position[0]->count != aim->mNumVertices) {
+                        if (target.position[0]->count != numAllVertices) {
                             ASSIMP_LOG_WARN("Positions of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count");
                             ASSIMP_LOG_WARN("Positions of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count");
                         } else {
                         } else {
                             aiVector3D *positionDiff = nullptr;
                             aiVector3D *positionDiff = nullptr;
-                            target.position[0]->ExtractData(positionDiff);
+                            target.position[0]->ExtractData(positionDiff, vertexRemappingTable);
                             for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) {
                             for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) {
                                 aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId];
                                 aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId];
                             }
                             }
@@ -595,11 +661,11 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                         }
                         }
                     }
                     }
                     if (needNormals) {
                     if (needNormals) {
-                        if (target.normal[0]->count != aim->mNumVertices) {
+                        if (target.normal[0]->count != numAllVertices) {
                             ASSIMP_LOG_WARN("Normals of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count");
                             ASSIMP_LOG_WARN("Normals of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count");
                         } else {
                         } else {
                             aiVector3D *normalDiff = nullptr;
                             aiVector3D *normalDiff = nullptr;
-                            target.normal[0]->ExtractData(normalDiff);
+                            target.normal[0]->ExtractData(normalDiff, vertexRemappingTable);
                             for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) {
                             for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) {
                                 aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId];
                                 aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId];
                             }
                             }
@@ -610,14 +676,14 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                         if (!aiAnimMesh.HasNormals()) {
                         if (!aiAnimMesh.HasNormals()) {
                             // prevent nullptr access to aiAnimMesh.mNormals below when no normals are available
                             // prevent nullptr access to aiAnimMesh.mNormals below when no normals are available
                             ASSIMP_LOG_WARN("Bitangents of target ", i, " in mesh \"", mesh.name, "\" can't be computed, because mesh has no normals.");
                             ASSIMP_LOG_WARN("Bitangents of target ", i, " in mesh \"", mesh.name, "\" can't be computed, because mesh has no normals.");
-                        } else if (target.tangent[0]->count != aim->mNumVertices) {
+                        } else if (target.tangent[0]->count != numAllVertices) {
                             ASSIMP_LOG_WARN("Tangents of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count");
                             ASSIMP_LOG_WARN("Tangents of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count");
                         } else {
                         } else {
                             Tangent *tangent = nullptr;
                             Tangent *tangent = nullptr;
-                            attr.tangent[0]->ExtractData(tangent);
+                            attr.tangent[0]->ExtractData(tangent, vertexRemappingTable);
 
 
                             aiVector3D *tangentDiff = nullptr;
                             aiVector3D *tangentDiff = nullptr;
-                            target.tangent[0]->ExtractData(tangentDiff);
+                            target.tangent[0]->ExtractData(tangentDiff, vertexRemappingTable);
 
 
                             for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) {
                             for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) {
                                 tangent[vertexId].xyz += tangentDiff[vertexId];
                                 tangent[vertexId].xyz += tangentDiff[vertexId];
@@ -641,20 +707,15 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
             aiFace *facePtr = nullptr;
             aiFace *facePtr = nullptr;
             size_t nFaces = 0;
             size_t nFaces = 0;
 
 
-            if (prim.indices) {
-                size_t count = prim.indices->count;
-
-                Accessor::Indexer data = prim.indices->GetIndexer();
-                if (!data.IsValid()) {
-                    throw DeadlyImportError("GLTF: Invalid accessor without data in mesh ", getContextForErrorMessages(mesh.id, mesh.name));
-                }
+            if (useIndexBuffer) {
+                size_t count = indexBuffer.size();
 
 
                 switch (prim.mode) {
                 switch (prim.mode) {
                 case PrimitiveMode_POINTS: {
                 case PrimitiveMode_POINTS: {
                     nFaces = count;
                     nFaces = count;
                     facePtr = faces = new aiFace[nFaces];
                     facePtr = faces = new aiFace[nFaces];
                     for (unsigned int i = 0; i < count; ++i) {
                     for (unsigned int i = 0; i < count; ++i) {
-                        SetFaceAndAdvance1(facePtr, aim->mNumVertices, data.GetUInt(i));
+                        SetFaceAndAdvance1(facePtr, aim->mNumVertices, indexBuffer[i]);
                     }
                     }
                     break;
                     break;
                 }
                 }
@@ -667,7 +728,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                     }
                     }
                     facePtr = faces = new aiFace[nFaces];
                     facePtr = faces = new aiFace[nFaces];
                     for (unsigned int i = 0; i < count; i += 2) {
                     for (unsigned int i = 0; i < count; i += 2) {
-                        SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(i), data.GetUInt(i + 1));
+                        SetFaceAndAdvance2(facePtr, aim->mNumVertices, indexBuffer[i], indexBuffer[i + 1]);
                     }
                     }
                     break;
                     break;
                 }
                 }
@@ -676,12 +737,12 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                 case PrimitiveMode_LINE_STRIP: {
                 case PrimitiveMode_LINE_STRIP: {
                     nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0);
                     nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0);
                     facePtr = faces = new aiFace[nFaces];
                     facePtr = faces = new aiFace[nFaces];
-                    SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(0), data.GetUInt(1));
+                    SetFaceAndAdvance2(facePtr, aim->mNumVertices, indexBuffer[0], indexBuffer[1]);
                     for (unsigned int i = 2; i < count; ++i) {
                     for (unsigned int i = 2; i < count; ++i) {
-                        SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(i - 1), data.GetUInt(i));
+                        SetFaceAndAdvance2(facePtr, aim->mNumVertices, indexBuffer[i - 1], indexBuffer[i]);
                     }
                     }
                     if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop
                     if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop
-                        SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(static_cast<int>(count) - 1), faces[0].mIndices[0]);
+                        SetFaceAndAdvance2(facePtr, aim->mNumVertices, indexBuffer[static_cast<int>(count) - 1], faces[0].mIndices[0]);
                     }
                     }
                     break;
                     break;
                 }
                 }
@@ -694,7 +755,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                     }
                     }
                     facePtr = faces = new aiFace[nFaces];
                     facePtr = faces = new aiFace[nFaces];
                     for (unsigned int i = 0; i < count; i += 3) {
                     for (unsigned int i = 0; i < count; i += 3) {
-                        SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2));
+                        SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[i], indexBuffer[i + 1], indexBuffer[i + 2]);
                     }
                     }
                     break;
                     break;
                 }
                 }
@@ -705,10 +766,10 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                         // The ordering is to ensure that the triangles are all drawn with the same orientation
                         // The ordering is to ensure that the triangles are all drawn with the same orientation
                         if ((i + 1) % 2 == 0) {
                         if ((i + 1) % 2 == 0) {
                             // For even n, vertices n + 1, n, and n + 2 define triangle n
                             // For even n, vertices n + 1, n, and n + 2 define triangle n
-                            SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(i + 1), data.GetUInt(i), data.GetUInt(i + 2));
+                            SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[i + 1], indexBuffer[i], indexBuffer[i + 2]);
                         } else {
                         } else {
                             // For odd n, vertices n, n+1, and n+2 define triangle n
                             // For odd n, vertices n, n+1, and n+2 define triangle n
-                            SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2));
+                            SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[i], indexBuffer[i + 1], indexBuffer[i + 2]);
                         }
                         }
                     }
                     }
                     break;
                     break;
@@ -716,9 +777,9 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                 case PrimitiveMode_TRIANGLE_FAN:
                 case PrimitiveMode_TRIANGLE_FAN:
                     nFaces = count - 2;
                     nFaces = count - 2;
                     facePtr = faces = new aiFace[nFaces];
                     facePtr = faces = new aiFace[nFaces];
-                    SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(0), data.GetUInt(1), data.GetUInt(2));
+                    SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[0], indexBuffer[1], indexBuffer[2]);
                     for (unsigned int i = 1; i < nFaces; ++i) {
                     for (unsigned int i = 1; i < nFaces; ++i) {
-                        SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(0), data.GetUInt(i + 1), data.GetUInt(i + 2));
+                        SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[0], indexBuffer[i + 1], indexBuffer[i + 2]);
                     }
                     }
                     break;
                     break;
                 }
                 }
@@ -823,8 +884,6 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
         }
         }
     }
     }
 
 
-    meshOffsets.push_back(k);
-
     CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes);
     CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes);
 }
 }
 
 
@@ -957,7 +1016,8 @@ static void GetNodeTransform(aiMatrix4x4 &matrix, const glTF2::Node &node) {
     }
     }
 }
 }
 
 
-static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vector<std::vector<aiVertexWeight>> &map) {
+static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vector<std::vector<aiVertexWeight>> &map, std::vector<unsigned int>* vertexRemappingTablePtr) {
+
     Mesh::Primitive::Attributes &attr = primitive.attributes;
     Mesh::Primitive::Attributes &attr = primitive.attributes;
     if (attr.weight.empty() || attr.joint.empty()) {
     if (attr.weight.empty() || attr.joint.empty()) {
         return;
         return;
@@ -966,14 +1026,14 @@ static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vector<std
         return;
         return;
     }
     }
 
 
-    size_t num_vertices = attr.weight[0]->count;
+    size_t num_vertices = 0;
 
 
     struct Weights {
     struct Weights {
         float values[4];
         float values[4];
     };
     };
     Weights **weights = new Weights*[attr.weight.size()];
     Weights **weights = new Weights*[attr.weight.size()];
     for (size_t w = 0; w < attr.weight.size(); ++w) {
     for (size_t w = 0; w < attr.weight.size(); ++w) {
-        attr.weight[w]->ExtractData(weights[w]);
+        num_vertices = attr.weight[w]->ExtractData(weights[w], vertexRemappingTablePtr);
     }
     }
 
 
     struct Indices8 {
     struct Indices8 {
@@ -987,12 +1047,12 @@ static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vector<std
     if (attr.joint[0]->GetElementSize() == 4) {
     if (attr.joint[0]->GetElementSize() == 4) {
         indices8 = new Indices8*[attr.joint.size()];
         indices8 = new Indices8*[attr.joint.size()];
         for (size_t j = 0; j < attr.joint.size(); ++j) {
         for (size_t j = 0; j < attr.joint.size(); ++j) {
-            attr.joint[j]->ExtractData(indices8[j]);
+            attr.joint[j]->ExtractData(indices8[j], vertexRemappingTablePtr);
         }
         }
     } else {
     } else {
         indices16 = new Indices16 *[attr.joint.size()];
         indices16 = new Indices16 *[attr.joint.size()];
         for (size_t j = 0; j < attr.joint.size(); ++j) {
         for (size_t j = 0; j < attr.joint.size(); ++j) {
-            attr.joint[j]->ExtractData(indices16[j]);
+            attr.joint[j]->ExtractData(indices16[j], vertexRemappingTablePtr);
         }
         }
     }
     }
     //
     //
@@ -1051,15 +1111,13 @@ void ParseExtensions(aiMetadata *metadata, const CustomExtension &extension) {
     }
     }
 }
 }
 
 
-void ParseExtras(aiMetadata *metadata, const CustomExtension &extension) {
-    if (extension.mValues.isPresent) {
-        for (auto const &subExtension : extension.mValues.value) {
-            ParseExtensions(metadata, subExtension);
-        }
+void ParseExtras(aiMetadata* metadata, const Extras& extras) {
+    for (auto const &value : extras.mValues) {
+        ParseExtensions(metadata, value);
     }
     }
 }
 }
 
 
-aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector<unsigned int> &meshOffsets, glTF2::Ref<glTF2::Node> &ptr) {
+aiNode *glTF2Importer::ImportNode(glTF2::Asset &r, glTF2::Ref<glTF2::Node> &ptr) {
     Node &node = *ptr;
     Node &node = *ptr;
 
 
     aiNode *ainode = new aiNode(GetNodeName(node));
     aiNode *ainode = new aiNode(GetNodeName(node));
@@ -1071,18 +1129,18 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector<unsigned int> &
             std::fill(ainode->mChildren, ainode->mChildren + ainode->mNumChildren, nullptr);
             std::fill(ainode->mChildren, ainode->mChildren + ainode->mNumChildren, nullptr);
 
 
             for (unsigned int i = 0; i < ainode->mNumChildren; ++i) {
             for (unsigned int i = 0; i < ainode->mNumChildren; ++i) {
-                aiNode *child = ImportNode(pScene, r, meshOffsets, node.children[i]);
+                aiNode *child = ImportNode(r, node.children[i]);
                 child->mParent = ainode;
                 child->mParent = ainode;
                 ainode->mChildren[i] = child;
                 ainode->mChildren[i] = child;
             }
             }
         }
         }
 
 
-        if (node.customExtensions || node.extras) {
+        if (node.customExtensions || node.extras.HasExtras()) {
             ainode->mMetaData = new aiMetadata;
             ainode->mMetaData = new aiMetadata;
             if (node.customExtensions) {
             if (node.customExtensions) {
                 ParseExtensions(ainode->mMetaData, node.customExtensions);
                 ParseExtensions(ainode->mMetaData, node.customExtensions);
             }
             }
-            if (node.extras) {
+            if (node.extras.HasExtras()) {
                 ParseExtras(ainode->mMetaData, node.extras);
                 ParseExtras(ainode->mMetaData, node.extras);
             }
             }
         }
         }
@@ -1104,11 +1162,13 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector<unsigned int> &
 
 
             if (node.skin) {
             if (node.skin) {
                 for (int primitiveNo = 0; primitiveNo < count; ++primitiveNo) {
                 for (int primitiveNo = 0; primitiveNo < count; ++primitiveNo) {
-                    aiMesh *mesh = pScene->mMeshes[meshOffsets[mesh_idx] + primitiveNo];
+                    unsigned int aiMeshIdx = meshOffsets[mesh_idx] + primitiveNo;
+                    aiMesh *mesh = mScene->mMeshes[aiMeshIdx];
                     unsigned int numBones = static_cast<unsigned int>(node.skin->jointNames.size());
                     unsigned int numBones = static_cast<unsigned int>(node.skin->jointNames.size());
+                    std::vector<unsigned int> *vertexRemappingTablePtr = mVertexRemappingTables[aiMeshIdx].empty() ? nullptr : &mVertexRemappingTables[aiMeshIdx];
 
 
                     std::vector<std::vector<aiVertexWeight>> weighting(numBones);
                     std::vector<std::vector<aiVertexWeight>> weighting(numBones);
-                    BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting);
+                    BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting, vertexRemappingTablePtr);
 
 
                     mesh->mNumBones = static_cast<unsigned int>(numBones);
                     mesh->mNumBones = static_cast<unsigned int>(numBones);
                     mesh->mBones = new aiBone *[mesh->mNumBones];
                     mesh->mBones = new aiBone *[mesh->mNumBones];
@@ -1125,7 +1185,7 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector<unsigned int> &
                     // mapping which makes things doubly-slow.
                     // mapping which makes things doubly-slow.
 
 
                     mat4 *pbindMatrices = nullptr;
                     mat4 *pbindMatrices = nullptr;
-                    node.skin->inverseBindMatrices->ExtractData(pbindMatrices);
+                    node.skin->inverseBindMatrices->ExtractData(pbindMatrices, nullptr);
 
 
                     for (uint32_t i = 0; i < numBones; ++i) {
                     for (uint32_t i = 0; i < numBones; ++i) {
                         const std::vector<aiVertexWeight> &weights = weighting[i];
                         const std::vector<aiVertexWeight> &weights = weighting[i];
@@ -1171,16 +1231,11 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector<unsigned int> &
         }
         }
 
 
         if (node.camera) {
         if (node.camera) {
-            pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName;
-            if (node.translation.isPresent) {
-                aiVector3D trans;
-                CopyValue(node.translation.value, trans);
-                pScene->mCameras[node.camera.GetIndex()]->mPosition = trans;
-            }
+            mScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName;
         }
         }
 
 
         if (node.light) {
         if (node.light) {
-            pScene->mLights[node.light.GetIndex()]->mName = ainode->mName;
+            mScene->mLights[node.light.GetIndex()]->mName = ainode->mName;
 
 
             // range is optional - see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
             // range is optional - see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
             // it is added to meta data of parent node, because there is no other place to put it
             // it is added to meta data of parent node, because there is no other place to put it
@@ -1212,7 +1267,7 @@ void glTF2Importer::ImportNodes(glTF2::Asset &r) {
     // The root nodes
     // The root nodes
     unsigned int numRootNodes = unsigned(rootNodes.size());
     unsigned int numRootNodes = unsigned(rootNodes.size());
     if (numRootNodes == 1) { // a single root node: use it
     if (numRootNodes == 1) { // a single root node: use it
-        mScene->mRootNode = ImportNode(mScene, r, meshOffsets, rootNodes[0]);
+        mScene->mRootNode = ImportNode(r, rootNodes[0]);
     } else if (numRootNodes > 1) { // more than one root node: create a fake root
     } else if (numRootNodes > 1) { // more than one root node: create a fake root
         aiNode *root = mScene->mRootNode = new aiNode("ROOT");
         aiNode *root = mScene->mRootNode = new aiNode("ROOT");
 
 
@@ -1220,7 +1275,7 @@ void glTF2Importer::ImportNodes(glTF2::Asset &r) {
         std::fill(root->mChildren, root->mChildren + numRootNodes, nullptr);
         std::fill(root->mChildren, root->mChildren + numRootNodes, nullptr);
 
 
         for (unsigned int i = 0; i < numRootNodes; ++i) {
         for (unsigned int i = 0; i < numRootNodes; ++i) {
-            aiNode *node = ImportNode(mScene, r, meshOffsets, rootNodes[i]);
+            aiNode *node = ImportNode(r, rootNodes[i]);
             node->mParent = root;
             node->mParent = root;
             root->mChildren[root->mNumChildren++] = node;
             root->mChildren[root->mNumChildren++] = node;
         }
         }
@@ -1621,13 +1676,17 @@ void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IO
 
 
     // clean all member arrays
     // clean all member arrays
     meshOffsets.clear();
     meshOffsets.clear();
+    mVertexRemappingTables.clear();
     mEmbeddedTexIdxs.clear();
     mEmbeddedTexIdxs.clear();
 
 
     this->mScene = pScene;
     this->mScene = pScene;
 
 
     // read the asset file
     // read the asset file
     glTF2::Asset asset(pIOHandler, static_cast<rapidjson::IRemoteSchemaDocumentProvider *>(mSchemaDocumentProvider));
     glTF2::Asset asset(pIOHandler, static_cast<rapidjson::IRemoteSchemaDocumentProvider *>(mSchemaDocumentProvider));
-    asset.Load(pFile, GetExtension(pFile) == "glb");
+    asset.Load(pFile,
+               CheckMagicToken(
+                   pIOHandler, pFile, AI_GLB_MAGIC_NUMBER, 1, 0,
+                   static_cast<unsigned int>(strlen(AI_GLB_MAGIC_NUMBER))));
     if (asset.scene) {
     if (asset.scene) {
         pScene->mName = asset.scene->name;
         pScene->mName = asset.scene->name;
     }
     }

+ 4 - 1
code/AssetLib/glTF2/glTF2Importer.h

@@ -43,6 +43,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define AI_GLTF2IMPORTER_H_INC
 #define AI_GLTF2IMPORTER_H_INC
 
 
 #include <assimp/BaseImporter.h>
 #include <assimp/BaseImporter.h>
+#include <AssetLib/glTF2/glTF2Asset.h>
 
 
 struct aiNode;
 struct aiNode;
 
 
@@ -59,7 +60,7 @@ namespace Assimp {
 class glTF2Importer : public BaseImporter {
 class glTF2Importer : public BaseImporter {
 public:
 public:
     glTF2Importer();
     glTF2Importer();
-    ~glTF2Importer() override;
+    ~glTF2Importer() override = default;
     bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override;
     bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override;
 
 
 protected:
 protected:
@@ -76,10 +77,12 @@ private:
     void ImportNodes(glTF2::Asset &a);
     void ImportNodes(glTF2::Asset &a);
     void ImportAnimations(glTF2::Asset &a);
     void ImportAnimations(glTF2::Asset &a);
     void ImportCommonMetadata(glTF2::Asset &a);
     void ImportCommonMetadata(glTF2::Asset &a);
+    aiNode *ImportNode(glTF2::Asset &r, glTF2::Ref<glTF2::Node> &ptr);
 
 
 private:
 private:
     std::vector<unsigned int> meshOffsets;
     std::vector<unsigned int> meshOffsets;
     std::vector<int> mEmbeddedTexIdxs;
     std::vector<int> mEmbeddedTexIdxs;
+    std::vector<std::vector<unsigned int>> mVertexRemappingTables; // for each converted aiMesh in the scene, it stores a list of vertices that are actually used
     aiScene *mScene;
     aiScene *mScene;
 
 
     /// An instance of rapidjson::IRemoteSchemaDocumentProvider
     /// An instance of rapidjson::IRemoteSchemaDocumentProvider

+ 0 - 2
code/CApi/AssimpCExport.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2022, assimp team
 Copyright (c) 2006-2022, assimp team
 
 
-
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,

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