瀏覽代碼

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

Kim Kulling 2 年之前
父節點
當前提交
d65049a657
共有 100 個文件被更改,包括 3626 次插入3186 次删除
  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
         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
       id: dxcache
       if: contains(matrix.name, 'windows')

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

@@ -14,7 +14,7 @@ jobs:
     name: adress-sanitizer
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: lukka/get-cmake@latest    
     - uses: lukka/set-shell-env@v1
       with:
@@ -38,7 +38,7 @@ jobs:
     name: undefined-behavior-sanitizer
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: lukka/get-cmake@latest    
     - uses: lukka/set-shell-env@v1
       with:
@@ -46,7 +46,7 @@ jobs:
         CC: clang
     
     - name: configure and build
-      uses: lukka/run-cmake@v2
+      uses: lukka/run-cmake@v3
       with:
         cmakeListsOrSettingsJson: CMakeListsTxtAdvanced
         cmakeListsTxtPath: '${{ github.workspace }}/CMakeLists.txt'
@@ -57,3 +57,13 @@ jobs:
     - name: test
       run: cd build/bin && ./unit
       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)
   include("cmake-modules/HunterGate.cmake")
   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)
 ENDIF()
 
@@ -84,10 +83,6 @@ OPTION( ASSIMP_NO_EXPORT
   "Disable Assimp's export functionality."
   OFF
 )
-OPTION( ASSIMP_BUILD_ZLIB
-  "Build your own zlib"
-  OFF
-)
 OPTION( ASSIMP_BUILD_ASSIMP_TOOLS
   "If the supplementary tools for Assimp are built in addition to the library."
   OFF
@@ -134,6 +129,18 @@ OPTION ( ASSIMP_IGNORE_GIT_HASH
    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)
   # Use subset of Windows.h
   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_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)
   # Get the current working branch
@@ -246,8 +250,7 @@ IF( UNIX )
   # Use GNUInstallDirs for Unix predefined directories
   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
-  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
       ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 )
     ENDIF()
@@ -257,9 +260,13 @@ ENDIF()
 # Grouped compiler settings ########################################
 IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT MINGW)
   IF(NOT ASSIMP_HUNTER_ENABLED)
-    SET(CMAKE_CXX_STANDARD 17)
     SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
   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
   IF(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "mips64" )
     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)
   # enable multi-core compilation with MSVC
   IF(CMAKE_CXX_COMPILER_ID MATCHES "Clang" ) # clang-cl
-    ADD_COMPILE_OPTIONS(/bigobj /W4 /WX )
+    ADD_COMPILE_OPTIONS(/bigobj)
   ELSE() # msvc
-    ADD_COMPILE_OPTIONS(/MP /bigobj /W4 /WX)
+    ADD_COMPILE_OPTIONS(/MP /bigobj)
   ENDIF()
   
   # 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")
 ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang" )
   IF(NOT ASSIMP_HUNTER_ENABLED)
-    SET(CMAKE_CXX_STANDARD 17)
     SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
   ENDIF()
   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 (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")
   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")
-    # Experimental for pdb generation
   ENDIF()
 ENDIF()
 
 IF (ASSIMP_COVERALLS)
   MESSAGE(STATUS "Coveralls enabled")
+  
   INCLUDE(Coveralls)
   SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
   SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
@@ -332,14 +338,16 @@ ENDIF()
 
 IF (ASSIMP_ASAN)
   MESSAGE(STATUS "AddressSanitizer enabled")
+  
   SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
-  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
+  SET(CMAKE_C_FLAGS   "${CMAKE_C_FLAGS} -fsanitize=address")
 ENDIF()
 
 IF (ASSIMP_UBSAN)
   MESSAGE(STATUS "Undefined Behavior sanitizer enabled")
+
   SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,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()
 
 INCLUDE (FindPkgMacros)
@@ -660,13 +668,13 @@ ELSE()
       set_target_properties(draco_encoder draco_decoder PROPERTIES
         EXCLUDE_FROM_ALL TRUE
         EXCLUDE_FROM_DEFAULT_BUILD TRUE
-        )
+      )
 
       # Do build the draco shared library
       set_target_properties(${draco_LIBRARIES} PROPERTIES
         EXCLUDE_FROM_ALL FALSE
         EXCLUDE_FROM_DEFAULT_BUILD FALSE
-        )
+      )
 
       TARGET_USE_COMMON_OUTPUT_DIRECTORY(${draco_LIBRARIES})
       TARGET_USE_COMMON_OUTPUT_DIRECTORY(draco_encoder)
@@ -683,8 +691,7 @@ ELSE()
         FRAMEWORK DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
         COMPONENT ${LIBASSIMP_COMPONENT}
         INCLUDES DESTINATION include
-    )
-
+      )
     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
 
-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
 
@@ -19,7 +14,8 @@ WORKDIR /opt/assimp
 
 RUN git checkout master \
     && mkdir build && cd build && \
-    cmake \
+    cmake -G 'Ninja' \
     -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)
 ==================================
-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 ###
 [![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)
@@ -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)
 [![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")
-[![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>
 
 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 ###
 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
 
+### 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 ####
 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.
 	/test		Unit- and regression tests, test suite of models
 	/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:
 
 	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/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 ###
 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.

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

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

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

@@ -266,8 +266,15 @@ void Discreet3DSImporter::ParseMainChunk() {
     };
 
     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
     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 {
 public:
     Discreet3DSImporter();
-    ~Discreet3DSImporter();
+    ~Discreet3DSImporter() override;
 
     // -------------------------------------------------------------------
     /** 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
     }
 
-    ~EmbeddedTexture() = default;
+    ~EmbeddedTexture() override = default;
 
     ResourceType getType() const override {
         return ResourceType::RT_EmbeddedTexture2D;
@@ -110,7 +110,7 @@ public:
         // empty
     }
 
-    ~Texture2DGroup() = default;
+    ~Texture2DGroup() override = default;
 
     ResourceType getType() const override {
         return ResourceType::RT_Texture2DGroup;
@@ -127,7 +127,7 @@ public:
         // empty
     }
 
-    ~BaseMaterials() = default;
+    ~BaseMaterials() override = default;
 
     ResourceType getType() const override {
         return ResourceType::RT_BaseMaterials;
@@ -152,7 +152,7 @@ public:
         // empty
     }
 
-    ~Object() = default;
+    ~Object() override = default;
 
     ResourceType getType() const override {
         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_ConvertedNode(const std::string &pID, NodeArray &nodeArray, aiNode **pNode) 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);
     bool XML_SearchNode(const std::string &nodeName);
     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) {
                 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.
+                    // FIXME: this leaks memory on test models test8.amf and test9.amf
                     nodeArray.erase(nl_it);
 
                     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_3DS_IMPORTER
 
 // internal headers
@@ -322,21 +321,6 @@ void ASEImporter::BuildAnimations(const std::vector<BaseNode *> &nodes) {
                 aiNodeAnim *nd = pcAnim->mChannels[iNum++] = new aiNodeAnim();
                 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
                 nd->mNumPositionKeys = (unsigned int)me->mTargetAnim.akeyPositions.size();
                 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();
     }
-    return;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -480,6 +479,11 @@ void Parser::ParseLV1MaterialListBlock() {
             if (TokenMatch(filePtr, "MATERIAL_COUNT", 14)) {
                 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
                 m_vMaterials.resize(iOldMaterialCount + iMaterialCount, Material("INVALID"));
                 continue;
@@ -734,7 +738,6 @@ void Parser::ParseLV3MapBlock(Texture &map) {
         }
         AI_ASE_HANDLE_SECTION("3", "*MAP_XXXXXX");
     }
-    return;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -859,7 +862,6 @@ void Parser::ParseLV1ObjectBlock(ASE::BaseNode &node) {
         }
         AI_ASE_HANDLE_TOP_LEVEL_SECTION();
     }
-    return;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -883,7 +885,6 @@ void Parser::ParseLV2CameraSettingsBlock(ASE::Camera &camera) {
         }
         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");
     }
-    return;
 }
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV2MeshBlock(ASE::Mesh &mesh) {
@@ -1310,7 +1310,6 @@ void Parser::ParseLV2MeshBlock(ASE::Mesh &mesh) {
         }
         AI_ASE_HANDLE_SECTION("2", "*MESH");
     }
-    return;
 }
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MeshWeightsBlock(ASE::Mesh &mesh) {
@@ -1344,7 +1343,6 @@ void Parser::ParseLV3MeshWeightsBlock(ASE::Mesh &mesh) {
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_WEIGHTS");
     }
-    return;
 }
 // ------------------------------------------------------------------------------------------------
 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");
     }
-    return;
 }
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MeshVertexListBlock(
@@ -1443,7 +1440,6 @@ void Parser::ParseLV3MeshVertexListBlock(
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_VERTEX_LIST");
     }
-    return;
 }
 // ------------------------------------------------------------------------------------------------
 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");
     }
-    return;
 }
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MeshTListBlock(unsigned int iNumVertices,
@@ -1503,7 +1498,6 @@ void Parser::ParseLV3MeshTListBlock(unsigned int iNumVertices,
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_TVERT_LIST");
     }
-    return;
 }
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MeshTFaceListBlock(unsigned int iNumFaces,
@@ -1532,7 +1526,6 @@ void Parser::ParseLV3MeshTFaceListBlock(unsigned int iNumFaces,
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_TFACE_LIST");
     }
-    return;
 }
 // ------------------------------------------------------------------------------------------------
 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");
     }
-    return;
 }
 // ------------------------------------------------------------------------------------------------
 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");
     }
-    return;
 }
 // ------------------------------------------------------------------------------------------------
 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");
     }
-    return;
 }
 // ------------------------------------------------------------------------------------------------
 void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) {
@@ -1681,7 +1671,6 @@ void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) {
         }
         AI_ASE_HANDLE_SECTION("3", "*MESH_NORMALS");
     }
-    return;
 }
 // ------------------------------------------------------------------------------------------------
 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>
 
-const int CHARS_PER_LINE = 72;
+static const int CHARS_PER_LINE = 72;
 
 #ifdef _MSC_VER
 #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() {
-    if (_pos > _buf.size()) {
+    if (_pos >= _buf.size()) {
         Fail("EOF");
     }
 
@@ -418,7 +418,6 @@ void B3DImporter::ReadTRIS(int v0) {
             ASSIMP_LOG_ERROR("Bad triangle index: i0=", i0, ", i1=", i1, ", i2=", i2);
 #endif
             Fail("Bad triangle index");
-            continue;
         }
         face->mNumIndices = 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
         *           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_STRUCT_CUSTOMDATATYPEDESCRIPTION(MEdge),

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

@@ -115,15 +115,12 @@ BlenderImporter::~BlenderImporter() {
     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.
 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.
 void BlenderImporter::InternReadFile(const std::string &pFile,
         aiScene *pScene, IOSystem *pIOHandler) {
-#ifndef ASSIMP_BUILD_NO_COMPRESSED_BLEND
-    std::vector<char> uncompressed;
-#endif
-
     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",
             ", little endian: ", file.little ? "true" : "false", ")");
 
@@ -1338,4 +1293,55 @@ aiNode *BlenderImporter::ConvertNode(const Scene &in, const Object *obj, Convers
     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

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

@@ -180,6 +180,19 @@ private:
             const Blender::MTex *tex,
             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.
     // --------------------
     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);
     {
-        //std::shared_ptr<CollectionObject> prev;
-        //ReadFieldPtr<ErrorPolicy_Fail>(prev, "*prev", db);
-        //dest.prev = prev.get();
-
         std::shared_ptr<Object> ob;
         ReadFieldPtr<ErrorPolicy_Igno>(ob, "*ob", db);
         dest.ob = ob.get();
@@ -569,7 +565,7 @@ void Structure ::Convert<MVert>(
         const FileDatabase &db) const {
 
     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_Warn>(dest.mat_nr,"mat_nr",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
 
-
 All rights reserved.
 
 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
 

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

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2022, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -144,11 +143,7 @@ namespace Assimp
 
 #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
 {

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

@@ -95,6 +95,7 @@ ColladaLoader::ColladaLoader() :
         noSkeletonMesh(false),
         removeEmptyBones(false),
         ignoreUpDirection(false),
+        ignoreUnitSize(false),
         useColladaName(false),
         mNodeNameCounter(0) {
     // empty
@@ -122,6 +123,7 @@ void ColladaLoader::SetupProperties(const Importer *pImp) {
     noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, 0) != 0;
     removeEmptyBones = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true) != 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;
 }
 
@@ -170,12 +172,15 @@ void ColladaLoader::InternReadFile(const std::string &pFile, aiScene *pScene, IO
     // ... then fill the materials with the now adjusted settings
     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) {
         // Convert to Y_UP, if different orientation
         if (parser.mUpDirection == ColladaParser::UP_X) {

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

@@ -239,6 +239,7 @@ protected:
     bool noSkeletonMesh;
     bool removeEmptyBones;
     bool ignoreUpDirection;
+    bool ignoreUnitSize;
     bool useColladaName;
 
     /** 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:
             // LineStrip is not supported due to expected index unmangling
             throw DeadlyImportError("Unsupported primitive type.");
-            break;
         }
 
         // 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
 // taken directly from the DXF spec.
 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 (0.0f, 1.0f, 0.0f, 1.0f), // green
     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 (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_ENTITIES_MAGIC_BLOCK "$ASSIMP_ENTITIES_MAGIC"
 
@@ -109,14 +110,6 @@ static const aiImporterDesc desc = {
     "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.
 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);
     }
 
-    if (! output.blocks.size()  ) {
+    if (output.blocks.empty()) {
         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) {
@@ -639,12 +633,6 @@ void DXFImporter::ParsePolyLine(DXF::LineReader& reader, DXF::FileData& output)
         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) {
         ASSIMP_LOG_WARN("DXF: unexpected vertex count in polymesh: ",
             line.positions.size(),", expected ", vguess );
@@ -734,12 +722,18 @@ void DXFImporter::ParsePolyLineVertex(DXF::LineReader& reader, DXF::PolyLine& li
         case 71:
         case 72:
         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;
 
         // 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
     // 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) {
             break;
         }
-        switch (reader.GroupCode())
-        {
+        switch (reader.GroupCode()) {
 
         // 8 specifies the layer
         case 8:

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

@@ -68,8 +68,8 @@ namespace DXF {
  */
 class DXFImporter : public BaseImporter {
 public:
-    DXFImporter();
-    ~DXFImporter() override;
+    DXFImporter() = default;
+    ~DXFImporter() override = default;
 
     // -------------------------------------------------------------------
     /** 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) {
     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
 	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 ..
         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) ));
 
@@ -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
-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);
 	ASSIMP_LOG_DEBUG("Tokenizing binary FBX file");
 
@@ -465,7 +464,7 @@ void TokenizeBinary(TokenList& output_tokens, const char* input, size_t length)
     try
     {
         while (cursor < end ) {
-		    if (!ReadScope(output_tokens, input, cursor, input + length, is64bits)) {
+            if (!ReadScope(output_tokens, token_allocator, input, cursor, input + length, is64bits)) {
                 break;
             }
         }

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

@@ -93,6 +93,8 @@ FBXConverter::FBXConverter(aiScene *out, const Document &doc, bool removeEmptyBo
         mSceneOut(out),
         doc(doc),
         mRemoveEmptyBones(removeEmptyBones) {
+
+
     // animations need to be converted first since this will
     // populate the node_anim_chain_bits map, which is needed
     // to determine which nodes need to be generated.
@@ -421,16 +423,32 @@ void FBXConverter::ConvertCamera(const Camera &cam, const std::string &orig_name
 
     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->mLookAt = aiVector3D(1.0f, 0.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->mClipPlaneFar = cam.FarPlane();
 }
@@ -640,7 +658,7 @@ void FBXConverter::GetRotationMatrix(Model::RotOrder mode, const aiVector3D &rot
 bool FBXConverter::NeedsComplexTransformationChain(const Model &model) {
     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);
     for (size_t i = 0; i < TransformationComp_MAXIMUM; ++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());
         } else if (const TypedProperty<int> *interpretedInt = prop.second->As<TypedProperty<int>>()) {
             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>>()) {
             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>>()) {
             data->Set(index++, prop.first, interpretedFloat->Value());
         } 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;
     for (const BlendShape *blendShape : mesh.GetBlendShapes()) {
         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);
-                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()
-                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++) {
                     const unsigned int curIndex = curIndices.at(j);
                     aiVector3D vertex = curVertices.at(j);
@@ -1406,13 +1436,12 @@ unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, co
     std::vector<aiAnimMesh *> animMeshes;
     for (const BlendShape *blendShape : mesh.GetBlendShapes()) {
         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);
-                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()));
                 for (size_t j = 0; j < curIndices.size(); 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) {
         const BlendShapeChannel* const bspc = ProcessSimpleConnection<BlendShapeChannel>(*con, false, "BlendShapeChannel -> BlendShape", element);
         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) {
         const ShapeGeometry* const sg = ProcessSimpleConnection<ShapeGeometry>(*con, false, "Shape -> BlendShapeChannel", element);
         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) {
 	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|
 }
@@ -356,9 +360,11 @@ void Document::ReadObjects() {
         DOMError("no Objects dictionary found");
     }
 
+    StackAllocator &allocator = parser.GetAllocator();
+
     // add a dummy entry to represent the Model::RootNode object (id 0),
     // 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();
     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);
         }
 
-        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);
+            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
         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
     const Element* const econns = sc["Connections"];
     if(!econns || !econns->Compound()) {
@@ -492,7 +502,7 @@ void Document::ReadConnections() {
         }
 
         // 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));
         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
 
 #include <numeric>
+#include <unordered_set>
 #include <stdint.h>
 #include <assimp/mesh.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)  _AI_CONCAT(a,b)
 
+
 namespace Assimp {
 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 Object;
 struct ImportSettings;
@@ -80,6 +86,10 @@ class BlendShape;
 class Skin;
 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
  *  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(ApertureMode, int, 0)
 
-    fbx_simple_property(FieldOfView, float, 1.0f)
+    fbx_simple_property(FieldOfView, float, kFovUnknown)
     fbx_simple_property(FocalLength, float, 1.0f)
 };
 
@@ -855,14 +865,14 @@ public:
         return fullWeights;
     }
 
-    const std::vector<const ShapeGeometry*>& GetShapeGeometries() const {
+    const std::unordered_set<const ShapeGeometry*>& GetShapeGeometries() const {
         return shapeGeometries;
     }
 
 private:
     float percent;
     WeightArray fullWeights;
-    std::vector<const ShapeGeometry*> shapeGeometries;
+    std::unordered_set<const ShapeGeometry*> shapeGeometries;
 };
 
 /** DOM class for BlendShape deformers */
@@ -872,12 +882,12 @@ public:
 
     virtual ~BlendShape();
 
-    const std::vector<const BlendShapeChannel*>& BlendShapeChannels() const {
+    const std::unordered_set<const BlendShapeChannel*>& BlendShapeChannels() const {
         return blendShapeChannels;
     }
 
 private:
-    std::vector<const BlendShapeChannel*> blendShapeChannels;
+    std::unordered_set<const BlendShapeChannel*> blendShapeChannels;
 };
 
 /** DOM class for skin deformer clusters (aka sub-deformers) */
@@ -1072,7 +1082,7 @@ private:
 /** DOM root for a FBX file */
 class Document {
 public:
-    Document(const Parser& parser, const ImportSettings& settings);
+    Document(Parser& parser, const ImportSettings& settings);
 
     ~Document();
 
@@ -1156,7 +1166,7 @@ private:
     const ImportSettings& settings;
 
     ObjectMap objects;
-    const Parser& parser;
+    Parser& parser;
 
     PropertyTemplateMap templates;
     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
 	// syntax elements of FBX (brackets, commas, key:value mappings)
 	TokenList tokens;
-	try {
-
+    Assimp::StackAllocator tempAllocator;
+    try {
 		bool is_binary = false;
 		if (!strncmp(begin, "Kaydara FBX Binary", 18)) {
 			is_binary = true;
-			TokenizeBinary(tokens, begin, contents.size());
+            TokenizeBinary(tokens, begin, contents.size(), tempAllocator);
 		} else {
-			Tokenize(tokens, begin);
+            Tokenize(tokens, begin, tempAllocator);
 		}
 
 		// use this information to construct a very rudimentary
 		// 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
 		Document doc(parser, mSettings);
@@ -183,10 +183,12 @@ void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
 		// assimp universal format (M)
 		SetFileScale(size_relative_to_cm * 0.01f);
 
-		std::for_each(tokens.begin(), tokens.end(), Util::delete_fun<Token>());
-	} catch (std::exception &) {
-		std::for_each(tokens.begin(), tokens.end(), Util::delete_fun<Token>());
-		throw;
+		// 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;
 
-    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) :
         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);
         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;
 }
 

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

@@ -62,7 +62,7 @@ public:
     /// @param name     The name instance
     /// @param doc      The document instance
     Geometry( uint64_t id, const Element& element, const std::string& name, const Document& doc );
-    
+
     /// @brief The class destructor, default.
     virtual ~Geometry() = default;
 
@@ -72,11 +72,12 @@ public:
 
     /// @brief Get the BlendShape attached to this geometry or nullptr
     /// @return The blendshape arrays.
-    const std::vector<const BlendShape*>& GetBlendShapes() const;
+    const std::unordered_set<const BlendShape*>& GetBlendShapes() const;
 
 private:
     const Skin* skin;
-    std::vector<const BlendShape*> blendShapes;
+    std::unordered_set<const BlendShape*> blendShapes;
+
 };
 
 typedef std::vector<int> MatIndexArray;
@@ -112,7 +113,7 @@ public:
     /// @return The binomal vector.
     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.
     /// @return The face indices vector.
     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)
     {
         if(token) {
@@ -115,8 +116,11 @@ namespace Assimp {
 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;
+    StackAllocator &allocator = parser.GetAllocator();
     do {
         n = parser.AdvanceToNextToken();
         if(!n) {
@@ -145,7 +149,7 @@ Element::Element(const Token& key_token, Parser& parser) : key_token(key_token)
         }
 
         if (n->Type() == TokenType_OPEN_BRACKET) {
-            compound.reset(new Scope(parser));
+            compound = new_Scope(parser);
 
             // current token should be a TOK_CLOSE_BRACKET
             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)
 {
     if(!topLevel) {
@@ -172,6 +185,7 @@ Scope::Scope(Parser& parser,bool topLevel)
         }
     }
 
+    StackAllocator &allocator = parser.GetAllocator();
     TokenPtr n = parser.AdvanceToNextToken();
     if (n == nullptr) {
         ParseError("unexpected end of file");
@@ -188,36 +202,45 @@ Scope::Scope(Parser& parser,bool topLevel)
             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)
         n = parser.CurrentToken();
         if (n == nullptr) {
             if (topLevel) {
+                elements.insert(ElementMap::value_type(str, element));
                 return;
             }
+            delete_Element(element);
             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");
-    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/fast_atof.h>
 
+#include "Common/StackAllocator.h"
 #include "FBXCompileConfig.h"
 #include "FBXTokenizer.h"
 
@@ -63,14 +64,14 @@ class Parser;
 class Element;
 
 // 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.
  *
@@ -82,15 +83,16 @@ typedef std::pair<ElementMap::const_iterator,ElementMap::const_iterator> Element
  *  @endverbatim
  *
  *  As can be seen in this sample, elements can contain nested #Scope
- *  as their trailing member.  **/
+ *  as their trailing member.  
+**/
 class Element
 {
 public:
     Element(const Token& key_token, Parser& parser);
-    ~Element() = default;
+    ~Element();
 
     const Scope* Compound() const {
-        return compound.get();
+        return compound;
     }
 
     const Token& KeyToken() const {
@@ -104,7 +106,7 @@ public:
 private:
     const Token& key_token;
     TokenList tokens;
-    std::unique_ptr<Scope> compound;
+    Scope* compound;
 };
 
 /** FBX data entity that consists of a 'scope', a collection
@@ -159,8 +161,8 @@ class Parser
 public:
     /** Parse given a token list. Does not take ownership of the tokens -
      *  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 {
         return *root;
@@ -170,6 +172,10 @@ public:
         return is_binary;
     }
 
+    StackAllocator &GetAllocator() {
+        return allocator;
+    }
+
 private:
     friend class Scope;
     friend class Element;
@@ -180,10 +186,10 @@ private:
 
 private:
     const TokenList& tokens;
-
+    StackAllocator &allocator;
     TokenPtr last, current;
     TokenList::const_iterator cursor;
-    std::unique_ptr<Scope> root;
+    Scope *root;
 
     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'.
 // ------------------------------------------------------------------------------------------------
-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 column,
                       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);
 	ASSIMP_LOG_DEBUG("Tokenizing ASCII FBX file");
 
@@ -164,7 +164,7 @@ void Tokenize(TokenList& output_tokens, const char* input)
                 in_double_quotes = false;
                 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;
             }
             continue;
@@ -181,30 +181,30 @@ void Tokenize(TokenList& output_tokens, const char* input)
             continue;
 
         case ';':
-            ProcessDataToken(output_tokens,token_begin,token_end,line,column);
+            ProcessDataToken(output_tokens, token_allocator, token_begin, token_end, line, column);
             comment = true;
             continue;
 
         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));
             continue;
 
         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));
             continue;
 
         case ',':
             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));
             continue;
 
         case ':':
             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 {
                 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;

+ 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
 
 #include "FBXCompileConfig.h"
+#include "Common/StackAllocator.h"
 #include <assimp/ai_assert.h>
 #include <assimp/defs.h>
 #include <vector>
@@ -157,7 +158,8 @@ private:
 typedef const Token* TokenPtr;
 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.
@@ -167,7 +169,7 @@ typedef std::vector< TokenPtr > TokenList;
  * @param output_tokens Receives a list of all tokens in the input data.
  * @param input_buffer Textual input buffer to be processed, 0-terminated.
  * @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.
@@ -178,7 +180,7 @@ void Tokenize(TokenList& output_tokens, const char* input);
  * @param input_buffer Binary input buffer to be processed.
  * @param length Length of input buffer, in bytes. There is no 0-terminal.
  * @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

+ 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. */
 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.");
 
     // 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);
     iFileSize = (unsigned int)fileSize;
 
@@ -143,9 +145,6 @@ void HMPImporter::InternReadFile(const std::string &pFile,
         // Print the magic word to the logger
         std::string szBuffer = ai_str_toprintable((const char *)&iMagic, sizeof(iMagic));
 
-        delete[] mBuffer;
-        mBuffer = nullptr;
-
         // We're definitely unable to load this file
         throw DeadlyImportError("Unknown HMP subformat ", pFile,
                                 ". Magic word (", szBuffer, ") is not known");
@@ -153,9 +152,6 @@ void HMPImporter::InternReadFile(const std::string &pFile,
 
     // Set the AI_SCENE_FLAGS_TERRAIN bit
     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);
 
     // allocate an output material
-    aiMaterial *pcMat = new aiMaterial();
+    std::unique_ptr<aiMaterial> pcMat(new aiMaterial());
 
     // read the skin, this works exactly as for MDL7
     ParseSkinLump_3DGS_MDL7(szCursor, &szCursor,
-            pcMat, iType, iWidth, iHeight);
+            pcMat.get(), iType, iWidth, iHeight);
 
     // now we need to skip any other skins ...
     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 ...
     pScene->mNumMaterials = 1;
     pScene->mMaterials = new aiMaterial *[1];
-    pScene->mMaterials[0] = pcMat;
+    pScene->mMaterials[0] = pcMat.release();
 
     *szCursorOut = szCursor;
 }

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

@@ -86,7 +86,7 @@ protected:
     // -------------------------------------------------------------------
     /** Import a HMP4 file
     */
-    void InternReadFile_HMP4();
+    AI_WONT_RETURN void InternReadFile_HMP4() AI_WONT_RETURN_SUFFIX;
 
     // -------------------------------------------------------------------
     /** 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
 
@@ -48,7 +47,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "Common/PolyTools.h"
 #include "PostProcessing/ProcessHelper.h"
 
-
 #include <iterator>
 #include <tuple>
 #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
     // point leaves the plane through the other side
-    if (std::abs(dotOne + dotTwo) < ai_epsilon)
+    if (std::abs(dotOne + dotTwo) < ai_epsilon) {
         return false;
+    }
 
     // if segment starts on the plane, report a hit only if the end lies on the *other* side
     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
     // 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;
+    }
 
     // t must be in [0..1] if the intersection point is within the given segment
     const IfcFloat t = dotTwo / dotOne;
-    if (t > 1.0 || t < 0.0)
+    if (t > 1.0 || t < 0.0) {
         return false;
+    }
 
     out = e0 + t * seg;
     return true;
@@ -110,11 +111,13 @@ void FilterPolygon(std::vector<IfcVector3> &resultpoly) {
     FuzzyVectorCompare fz(epsilon);
     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());
+    }
 
-    if (!resultpoly.empty() && fz(resultpoly.front(), resultpoly.back()))
+    if (!resultpoly.empty() && fz(resultpoly.front(), resultpoly.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
-        if (endsAtSegment && !halfOpen)
+        if (endsAtSegment && !halfOpen) {
             continue;
+        }
 
         // 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,
@@ -301,15 +305,17 @@ bool IntersectsBoundaryProfile(const IfcVector3 &e0, const IfcVector3 &e1, const
         if (startsAtSegment) {
             IfcVector3 inside_dir = IfcVector3(b.y, -b.x, 0.0) * windingOrder;
             bool isGoingInside = (inside_dir * e) > 0.0;
-            if (isGoingInside == isStartAssumedInside)
+            if (isGoingInside == isStartAssumedInside) {
                 continue;
+            }
 
             // 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.
             if (!intersect_results.empty() && intersect_results.back().first == i - 1) {
                 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;
+                }
             }
             intersect_results.emplace_back(i, e0);
             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.
             if (!intersect_results.empty() && intersect_results.back().first == i - 1) {
                 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;
+                }
             }
             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,
         ConversionData &conv) {
     ai_assert(as != nullptr);
@@ -763,4 +771,4 @@ void ProcessBoolean(const Schema_2x3::IfcBooleanResult &boolean, TempMesh &resul
 } // namespace IFC
 } // 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
 #include "IFCUtil.h"
 
 namespace Assimp {
 namespace IFC {
+
 namespace {
 
 // --------------------------------------------------------------------------------
@@ -56,8 +56,7 @@ namespace {
 class Conic : public Curve {
 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;
         ConvertAxisPlacement(trafo,*entity.Position,conv);
 
@@ -69,12 +68,12 @@ public:
     }
 
     // --------------------------------------------------
-    bool IsClosed() const {
+    bool IsClosed() const override {
         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( 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 ));
     }
 
@@ -102,14 +101,13 @@ protected:
 class Circle : public Conic {
 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;
         return location + static_cast<IfcFloat>(entity.Radius)*(static_cast<IfcFloat>(std::cos(u))*p[0] +
             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;
         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];
@@ -155,17 +153,17 @@ public:
     }
 
     // --------------------------------------------------
-    bool IsClosed() const {
+    bool IsClosed() const override {
         return false;
     }
 
     // --------------------------------------------------
-    IfcVector3 Eval(IfcFloat u) const {
+    IfcVector3 Eval(IfcFloat u) const override {
         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( b ) );
         // 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( b ) );
 
@@ -188,7 +186,7 @@ public:
     }
 
     // --------------------------------------------------
-    ParamRange GetParametricRange() const {
+    ParamRange GetParametricRange() const override {
         const IfcFloat inf = std::numeric_limits<IfcFloat>::infinity();
 
         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()) {
             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( b ) );
         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( b ) );
 
@@ -293,7 +291,7 @@ public:
     }
 
     // --------------------------------------------------
-    ParamRange GetParametricRange() const {
+    ParamRange GetParametricRange() const override {
         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));
         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( 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(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);
     }
 
@@ -431,7 +429,7 @@ public:
     }
 
     // --------------------------------------------------
-    IfcVector3 Eval(IfcFloat p) const {
+    IfcVector3 Eval(IfcFloat p) const override {
         ai_assert(InRange(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(b));
         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));
     }
 
@@ -516,7 +514,7 @@ size_t Curve::EstimateSampleCount(IfcFloat a, IfcFloat b) const {
     ai_assert( InRange( a ) );
     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;
 }
 

+ 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
 #include "IFCUtil.h"
 #include "Common/PolyTools.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 <memory>
@@ -65,8 +56,7 @@ namespace Assimp {
 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;
     for(const Schema_2x3::IfcCartesianPoint& c : loop.Polygon) {
         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
     if(inmesh.mVertcnt.empty()) {
         return;
@@ -127,8 +116,7 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m
     if (master_bounds != (size_t)-1) {
         ai_assert(master_bounds < inmesh.mVertcnt.size());
         outer_polygon_it = begin + master_bounds;
-    }
-    else {
+    } else {
         for(iit = begin; iit != end; ++iit) {
             // find the polygon with the largest area and take it as the outer bound.
             IfcVector3& n = normals[std::distance(begin,iit)];
@@ -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;
 	}
 
@@ -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(ProcessPolyloop(*polyloop, meshout,conv)) {
-
                     // The outer boundary is better determined by checking which
                     // 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());
                 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);
     }
 }
 
 // ------------------------------------------------------------------------------------------------
-void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv)
-{
+void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv) {
     TempMesh meshout;
 
     // first read the profile description
@@ -265,7 +234,8 @@ void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, Tem
         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;
 
     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);
     if(!curve) {
         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;
     IfcMatrix3 m;
 
@@ -504,10 +474,6 @@ IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVect
     IfcVector3 r = (out[idx]-any_point);
     r.Normalize();
 
-    //if(d) {
-    //  *d = -any_point * nor;
-    //}
-
     // Reconstruct orthonormal basis
     // XXX use Gram Schmidt for increased robustness
     IfcVector3 u = r ^ nor;
@@ -531,8 +497,7 @@ IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVect
 const auto closeDistance = ai_epsilon;
 
 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");
         return false;
     }
@@ -540,10 +505,10 @@ bool areClose(Schema_2x3::IfcCartesianPoint pt1,Schema_2x3::IfcCartesianPoint pt
     auto coord2 = pt2.Coordinates.begin();
     // we're just testing each dimension separately rather than doing euclidean distance, as we're
     // 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 true;
 }
@@ -553,6 +518,7 @@ bool areClose(IfcVector3 pt1,IfcVector3 pt2) {
         std::fabs(pt1.y - pt2.y) < 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.
 void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const TempMesh& curve,
     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
     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::vector<IfcVector3> nors;
     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) {
                 for(size_t i = 0; i < in.size(); ++i)
                     out.push_back(in[i] + dir);
-            }
-            else {
+            } else {
                 for(size_t i = in.size(); 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;
 
     // 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>()) {
         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);
-    }
-    else {
+    } else {
         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;
     std::shared_ptr< TempMesh > meshtmp = std::make_shared<TempMesh>();
     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>();
 
                 ProcessConnectedFaceSet(fs, *meshtmp, conv);
-            }
-            catch(std::bad_cast&) {
+            } catch(std::bad_cast&) {
                 IFCImporter::LogWarn("unexpected type error, IfcShell ought to inherit from IfcConnectedFaceSet");
             }
         }
         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);
         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);
-    }
-    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);
-    }
-    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);
         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) {
             ProcessConnectedFaceSet(fc, *meshtmp, conv);
         }
         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);
-    }
-    else if(geo.ToPtr<Schema_2x3::IfcBoundingBox>()) {
+    } else if(geo.ToPtr<Schema_2x3::IfcBoundingBox>()) {
         // silently skip over bounding boxes
         return false;
-    }
-    else {
+    } else {
         std::stringstream toLog;
         toLog << "skipping unknown IfcGeometricRepresentationItem entity, type is " << geo.GetClassName() << " id is " << geo.GetID();
         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()) {
 		std::set<unsigned int>::const_iterator it = mesh_indices.cbegin();
 		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,
-    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::MeshCache::const_iterator it = conv.cached_meshes.find(idx);
     if (it != conv.cached_meshes.end()) {
@@ -900,18 +855,18 @@ bool TryQueryMeshCache(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);
     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
     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()) {
                 PopulateMeshCache(item,mesh_indices,localmatid,conv);
             }
+        } else {
+            return false;
         }
-        else return false;
     }
     return true;
 }
@@ -930,4 +886,4 @@ bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, un
 } // ! IFC
 } // ! 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
 
-
 All rights reserved.
 
 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
 
@@ -92,7 +90,6 @@ using namespace Assimp::IFC;
   IfcUnitAssignment
   IfcClosedShell
   IfcDoor
-
  */
 
 namespace {
@@ -119,14 +116,6 @@ static const aiImporterDesc desc = {
     "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.
 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
     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
@@ -928,4 +922,4 @@ void MakeTreeRelative(ConversionData &conv) {
 
 } // namespace
 
-#endif
+#endif // ASSIMP_BUILD_NO_IFC_IMPORTER

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

@@ -87,8 +87,8 @@ public:
         int cylindricalTessellation;
     };
 
-    IFCImporter();
-    ~IFCImporter() override;
+    IFCImporter() = default;
+    ~IFCImporter() override = default;
 
     // --------------------
     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
 
-
 All rights reserved.
 
 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
 
@@ -174,7 +172,6 @@ unsigned int ProcessMaterials(uint64_t id, unsigned int prevMatId, ConversionDat
 
     aiString name;
     name.Set("<IFCDefault>");
-    //  ConvertColorToString( color, name);
 
     // look if there's already a default material with this base color
     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
 
-
 All rights reserved.
 
 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
 
@@ -52,8 +50,9 @@ namespace Assimp {
 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
     IfcVector3 t;
     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));
     if (!cv) {
         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);
 }
 
 // ------------------------------------------------------------------------------------------------
-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);
 }
 
 // ------------------------------------------------------------------------------------------------
-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>()) {
         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.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>()) {
             // TODO
         }
@@ -129,8 +131,7 @@ void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& de
         }
 
         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
         const IfcFloat offset = (ishape->OverallWidth - ishape->WebThickness) / 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.mVertcnt.push_back(12);
-    }
-    else {
+    } else {
         IFCImporter::LogWarn("skipping unknown IfcParameterizedProfileDef entity, type is ", def.GetClassName());
         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>()) {
         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);
-    }
-    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);
-    }
-    else {
+    } else {
         IFCImporter::LogWarn("skipping unknown IfcProfileDef entity, type is ", prof.GetClassName());
         return false;
     }

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

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2022, assimp team
 
-
 All rights reserved.
 
 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
 
 #include "AssetLib/IFC/IFCUtil.h"
 #include "Common/PolyTools.h"
+#include "Geometry/GeometryUtils.h"
 #include "PostProcessing/ProcessHelper.h"
 
 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)));
 
     if (mVerts.empty()) {
@@ -104,36 +102,31 @@ aiMesh* TempMesh::ToMesh()
 }
 
 // ------------------------------------------------------------------------------------------------
-void TempMesh::Clear()
-{
+void TempMesh::Clear() {
     mVerts.clear();
     mVertcnt.clear();
 }
 
 // ------------------------------------------------------------------------------------------------
-void TempMesh::Transform(const IfcMatrix4& mat)
-{
+void TempMesh::Transform(const IfcMatrix4& mat) {
     for(IfcVector3& v : mVerts) {
         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());
     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
     // Newell's algorithm. The length of the normals gives the area
     // 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);
-    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];
         temp[i++] = v.x;
         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,
-    bool normalize,
-    size_t ofs) const
-{
+        bool normalize,
+        size_t ofs) const {
     size_t max_vcount = 0;
     std::vector<unsigned int>::const_iterator begin = mVertcnt.begin()+ofs, end = mVertcnt.end(),  iit;
     for(iit = begin; iit != end; ++iit) {
@@ -235,7 +225,7 @@ IfcVector3 TempMesh::ComputeLastPolygonNormal(bool normalize) const {
 struct CompareVector {
     bool operator () (const IfcVector3& a, const IfcVector3& b) const {
         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);
     }
 };
@@ -249,29 +239,27 @@ struct FindVector {
 };
 
 // ------------------------------------------------------------------------------------------------
-void TempMesh::FixupFaceOrientation()
-{
+void TempMesh::FixupFaceOrientation() {
     const IfcVector3 vavg = Center();
 
     // create a list of start indices for all faces to allow random access to faces
     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;
+    }
 
     // list all faces on a vertex
     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);
+        }
     }
     // determine neighbourhood for all polys
     std::vector<size_t> neighbour(mVerts.size(), SIZE_MAX);
     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];
             const std::vector<size_t>& facesOnB = facesByVertex[mVerts[ib]];
             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(
                 facesOnB.begin(), facesOnB.end(), facesOnNB.begin(), facesOnNB.end(), sectstart);
 
-            if( std::distance(sectstart, sectend) != 2 )
+            if( std::distance(sectstart, sectend) != 2 ) {
                 continue;
-            if( *sectstart == a )
+            }
+            if( *sectstart == a ) {
                 ++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
     // faces to have the same winding until all faces have been tested.
     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
         size_t farthestIndex = SIZE_MAX;
         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;
+            }
             IfcVector3 faceCenter = std::accumulate(mVerts.begin() + faceStartIndices[a],
                 mVerts.begin() + faceStartIndices[a] + mVertcnt[a], IfcVector3(0.0)) / IfcFloat(mVertcnt[a]);
             IfcFloat dst = (faceCenter - vavg).SquareLength();
@@ -314,8 +303,7 @@ void TempMesh::FixupFaceOrientation()
             / IfcFloat(mVertcnt[farthestIndex]);
         // We accept a bit of negative orientation without reversing. In case of doubt, prefer the orientation given in
         // the file.
-        if( (farthestNormal * (farthestCenter - vavg).Normalize()) < -0.4 )
-        {
+        if( (farthestNormal * (farthestCenter - vavg).Normalize()) < -0.4 ) {
             size_t fsi = faceStartIndices[farthestIndex], fvc = mVertcnt[farthestIndex];
             std::reverse(mVerts.begin() + fsi, mVerts.begin() + fsi + fvc);
             std::reverse(neighbour.begin() + fsi, neighbour.begin() + fsi + fvc);
@@ -332,19 +320,18 @@ void TempMesh::FixupFaceOrientation()
         todo.push_back(farthestIndex);
 
         // 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 vsi = faceStartIndices[tdf], vc = mVertcnt[tdf];
             todo.pop_back();
 
             // 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
                 size_t nbi = neighbour[vsi + a];
-                if( nbi == SIZE_MAX || faceDone[nbi] )
+                if( nbi == SIZE_MAX || faceDone[nbi] ) {
                     continue;
+                }
 
                 const IfcVector3& vp = mVerts[vsi + a];
                 size_t nbvsi = faceStartIndices[nbi], nbvc = mVertcnt[nbi];
@@ -387,32 +374,8 @@ void TempMesh::RemoveAdjacentDuplicates() {
         IfcVector3 vmin,vmax;
         ArrayBounds(&*base, cnt ,vmin,vmax);
 
-
         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
         // of the previous pass as a side-effect.
         FuzzyVectorCompare fz(epsilon);
@@ -439,78 +402,58 @@ void TempMesh::RemoveAdjacentDuplicates() {
 }
 
 // ------------------------------------------------------------------------------------------------
-void TempMesh::Swap(TempMesh& other)
-{
+void TempMesh::Swap(TempMesh& other) {
     mVertcnt.swap(other.mVertcnt);
     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";
 }
 
 // ------------------------------------------------------------------------------------------------
-IfcFloat ConvertSIPrefix(const std::string& prefix)
-{
+IfcFloat ConvertSIPrefix(const std::string& prefix) {
     if (prefix == "EXA") {
         return 1e18f;
-    }
-    else if (prefix == "PETA") {
+    } else if (prefix == "PETA") {
         return 1e15f;
-    }
-    else if (prefix == "TERA") {
+    } else if (prefix == "TERA") {
         return 1e12f;
-    }
-    else if (prefix == "GIGA") {
+    } else if (prefix == "GIGA") {
         return 1e9f;
-    }
-    else if (prefix == "MEGA") {
+    } else if (prefix == "MEGA") {
         return 1e6f;
-    }
-    else if (prefix == "KILO") {
+    } else if (prefix == "KILO") {
         return 1e3f;
-    }
-    else if (prefix == "HECTO") {
+    } else if (prefix == "HECTO") {
         return 1e2f;
-    }
-    else if (prefix == "DECA") {
+    } else if (prefix == "DECA") {
         return 1e-0f;
-    }
-    else if (prefix == "DECI") {
+    } else if (prefix == "DECI") {
         return 1e-1f;
-    }
-    else if (prefix == "CENTI") {
+    } else if (prefix == "CENTI") {
         return 1e-2f;
-    }
-    else if (prefix == "MILLI") {
+    } else if (prefix == "MILLI") {
         return 1e-3f;
-    }
-    else if (prefix == "MICRO") {
+    } else if (prefix == "MICRO") {
         return 1e-6f;
-    }
-    else if (prefix == "NANO") {
+    } else if (prefix == "NANO") {
         return 1e-9f;
-    }
-    else if (prefix == "PICO") {
+    } else if (prefix == "PICO") {
         return 1e-12f;
-    }
-    else if (prefix == "FEMTO") {
+    } else if (prefix == "FEMTO") {
         return 1e-15f;
-    }
-    else if (prefix == "ATTO") {
+    } else if (prefix == "ATTO") {
         return 1e-18f;
-    }
-    else {
+    } else {
         IFCImporter::LogError("Unrecognized SI prefix: ", prefix);
         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.g = static_cast<float>( in.Green );
     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>()) {
         out.r = out.g = out.b = static_cast<float>(*r);
         if(base) {
@@ -527,20 +472,18 @@ void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourOrFactor& in,Conver
             out.g *= static_cast<float>( base->g );
             out.b *= static_cast<float>( base->b );
             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);
-    }
-    else {
+    } else {
         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();
     for(size_t i = 0; i < in.Coordinates.size(); ++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);
     out *= in.Magnitude;
 }
 
 // ------------------------------------------------------------------------------------------------
-void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in)
-{
+void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in) {
     out = IfcVector3();
     for(size_t i = 0; i < in.DirectionRatios.size(); ++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.b1 = x.y;
     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;
     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;
     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);
     if (in.Axis) {
         ConvertDirection(axis,in.Axis.Get());
-    }
-    else {
+    } else {
         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)) {
         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);
-    }
-    else {
+    } else {
         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;
     ConvertCartesianPoint(loc,op.LocalOrigin);
 
@@ -676,14 +608,12 @@ void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTra
     IfcMatrix4::Translation(loc,locm);
     AssignMatrixAxes(out,x,y,z);
 
-
     IfcVector3 vscale;
     if (const Schema_2x3::IfcCartesianTransformationOperator3DnonUniform* nuni = op.ToPtr<Schema_2x3::IfcCartesianTransformationOperator3DnonUniform>()) {
         vscale.x = nuni->Scale?op.Scale.Get():1.f;
         vscale.y = nuni->Scale2?nuni->Scale2.Get():1.f;
         vscale.z = nuni->Scale3?nuni->Scale3.Get():1.f;
-    }
-    else {
+    } else {
         const IfcFloat sc = op.Scale?op.Scale.Get():1.f;
         vscale = IfcVector3(sc,sc,sc);
     }
@@ -694,8 +624,7 @@ void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTra
     out = locm * out * s;
 }
 
-
 } // ! IFC
 } // ! 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
  */
 
+#include "assimp/Exceptional.h"
+#include "assimp/StringComparison.h"
 #ifndef ASSIMP_BUILD_NO_IRR_IMPORTER
 
 #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/IOSystem.hpp>
 
-#include <memory>
-
 using namespace Assimp;
 
 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
 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.
 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 {
-	return &desc;
+    return &desc;
 }
 
 // ------------------------------------------------------------------------------------------------
 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)
 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) {
-	// 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,
-		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;
             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);
             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) {
-	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) {
-	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) {
-	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
 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
-	// 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,
-		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.
 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) {
         throw DeadlyImportError("Failed to open IRR file ", pFile);
     }
 
     // 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);
     }
-    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

+ 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/anim.h>
 
-namespace Assimp    {
+namespace Assimp {
 
 // ---------------------------------------------------------------------------
 /** Irr importer class.
@@ -71,13 +71,13 @@ public:
     /** Returns whether the class can handle the format of the given file.
      *  See BaseImporter::CanRead() for details.
      */
-    bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
-        bool checkSig) const override;
+    bool CanRead(const std::string &pFile, IOSystem *pIOHandler,
+            bool checkSig) const override;
 
 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:
     /** Data structure for a scene-graph node animator
@@ -85,27 +85,19 @@ private:
     struct Animator {
         // Type of the animator
         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,
-            OTHER         = 0x5
+            OTHER = 0x5
 
         } 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
         ai_real speed;
         aiVector3D direction;
@@ -128,11 +120,9 @@ private:
 
     /** Data structure for a scene-graph node in an IRR file
      */
-    struct Node
-    {
+    struct Node {
         // Type of the node
-        enum ET
-        {
+        enum ET {
             LIGHT,
             CUBE,
             MESH,
@@ -144,21 +134,20 @@ private:
             ANIMMESH
         } 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
             char buffer[128];
             static int cnt;
-            ai_snprintf(buffer, 128, "IrrNode_%i",cnt++);
+            ai_snprintf(buffer, 128, "IrrNode_%i", cnt++);
             name = std::string(buffer);
 
             // reserve space for up to 5 materials
@@ -175,10 +164,10 @@ private:
         std::string name;
 
         // List of all child nodes
-        std::vector<Node*> children;
+        std::vector<Node *> children;
 
         // Parent node
-        Node* parent;
+        Node *parent;
 
         // Animated meshes: frames per second
         // 0.f if not specified
@@ -190,13 +179,13 @@ private:
 
         // Meshes: List of materials to be assigned
         // 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
         ai_real sphereRadius;
 
         // 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
         std::list<Animator> animators;
@@ -204,40 +193,54 @@ private:
 
     /** Data structure for a vertex in an IRR skybox
      */
-    struct SkyboxVertex
-    {
+    struct SkyboxVertex {
         SkyboxVertex() = default;
 
         //! Construction from single vertex components
         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;
     };
 
+    // -------------------------------------------------------------------
+    // 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
-    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
-    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
@@ -245,8 +248,8 @@ private:
     /// @param meshes Receives 6 output meshes
     /// @param materials The last 6 materials are assigned to the newly
     ///                  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
@@ -256,10 +259,10 @@ private:
      *  @param defMatIdx Default material index - UINT_MAX if not present
      *  @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
@@ -267,8 +270,8 @@ private:
      *  @param root Node to be processed
      *  @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:
     /// Configuration option: desired output FPS
@@ -276,6 +279,12 @@ private:
 
     /// Configuration option: speed flag was set?
     bool configSpeedFlag;
+
+    std::vector<aiCamera*> cameras;
+    std::vector<aiLight*> lights;
+    unsigned int guessedMeshCnt;
+    unsigned int guessedMatCnt;
+    unsigned int guessedAnimCnt;
 };
 
 } // 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;
 
 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.
 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
 const aiImporterDesc *IRRMeshImporter::GetInfo() const {
-	return &desc;
+    return &desc;
 }
 
 static void releaseMaterial(aiMaterial **mat) {
-	if (*mat != nullptr) {
-		delete *mat;
-		*mat = nullptr;
-	}
+    if (*mat != nullptr) {
+        delete *mat;
+        *mat = nullptr;
+    }
 }
 
 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.
 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

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

@@ -85,6 +85,19 @@ protected:
      */
     void InternReadFile(const std::string &pFile, aiScene *pScene,
             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

+ 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
  */
 
-//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))
 
 #include "IRRShared.h"
 #include <assimp/ParsingUtils.h>
 #include <assimp/fast_atof.h>
-#include <assimp/DefaultLogger.hpp>
 #include <assimp/material.h>
+#include <assimp/DefaultLogger.hpp>
 
 using namespace Assimp;
 
 // 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)
-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")) {
-            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
-			out.value = strtoul16(attrib.name());
+            out.value = strtoul16(attrib.value());
         }
     }
 }
 
 // ------------------------------------------------------------------------------------------------
 // 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
-			out.value = strtol10(attrib.name());
+            out.value = strtol10(attrib.value());
         }
     }
 }
 
 // ------------------------------------------------------------------------------------------------
 // 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
-			out.value = std::string(attrib.value());
+            out.value = std::string(attrib.value());
         }
     }
 }
 
 // ------------------------------------------------------------------------------------------------
 // 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
-			out.value = (ASSIMP_stricmp(attrib.value(), "true") ? false : true);
+            out.value = (ASSIMP_stricmp(attrib.value(), "true") ? false : true);
         }
     }
 }
 
 // ------------------------------------------------------------------------------------------------
 // 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
-			out.value = fast_atof(attrib.value());
+            out.value = fast_atof(attrib.value());
         }
     }
 }
 
 // ------------------------------------------------------------------------------------------------
 // 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
             const char *ptr = attrib.value();
 
             SkipSpaces(&ptr);
-            ptr = fast_atoreal_move<float>( ptr,(float&)out.value.x );
+            ptr = fast_atoreal_move<float>(ptr, (float &)out.value.x);
             SkipSpaces(&ptr);
             if (',' != *ptr) {
                 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);
             if (',' != *ptr) {
                 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
-int ConvertMappingMode(const std::string& mode) {
+int ConvertMappingMode(const std::string &mode) {
     if (mode == "texture_clamp_repeat") {
         return aiTextureMapMode_Wrap;
-	} else if (mode == "texture_clamp_mirror") {
-		return aiTextureMapMode_Mirror;
-	}
+    } else if (mode == "texture_clamp_mirror") {
+        return aiTextureMapMode_Mirror;
+    }
 
     return aiTextureMapMode_Clamp;
 }
 
 // ------------------------------------------------------------------------------------------------
 // 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;
     aiString s;
 
     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;
 
-    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
             else if (prop.name == "Emissive") {
                 ColorFromARGBPacked(prop.value,clr);
                 mat->AddProperty(&clr,1,AI_MATKEY_COLOR_EMISSIVE);
             }
 #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
                 if ( !ASSIMP_stricmp(reader->getNodeName(),"material") ||
@@ -378,8 +378,8 @@ aiMaterial* IrrlichtBase::ParseMaterial(unsigned int& matFlags) {
                 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;
 }

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

@@ -1,8 +1,8 @@
 
 
 /** @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
 #define INCLUDED_AI_IRRSHARED_H
@@ -58,8 +58,7 @@ extern const aiMatrix4x4 AI_TO_IRR_MATRIX;
  */
 class IrrlichtBase {
 protected:
-    IrrlichtBase() :
-            mNode(nullptr) {
+    IrrlichtBase() {
         // empty
     }
 
@@ -82,25 +81,25 @@ protected:
 
     /// XML reader instance
     XmlParser mParser;
-    pugi::xml_node *mNode;
 
     // -------------------------------------------------------------------
     /** Parse a material description from the XML
      *  @return The created material
      *  @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.
      *  @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)
         {
             throw DeadlyImportError("LWOB: Invalid chunk length");
-            break;
         }
         uint8_t* const next = mFileBuffer+head.length;
         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
 
-
-
 All rights reserved.
 
 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 "PostProcessing/ConvertToLHProcess.h"
 #include "PostProcessing/ProcessHelper.h"
+#include "Geometry/GeometryUtils.h"
 
 #include <assimp/ByteSwapper.h>
 #include <assimp/SGSpatialSort.h>
@@ -178,7 +177,7 @@ void LWOImporter::InternReadFile(const std::string &pFile,
     mLayers->push_back(Layer());
     mCurLayer = &mLayers->back();
     mCurLayer->mName = "<LWODefault>";
-    mCurLayer->mIndex = (uint16_t) -1;
+    mCurLayer->mIndex = 1;
 
     // old lightwave file format (prior to v6)
     mIsLWO2 = false;
@@ -398,14 +397,6 @@ void LWOImporter::InternReadFile(const std::string &pFile,
                             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;
                     }
                     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
         unsigned int num = static_cast<unsigned int>(apcMeshes.size() - meshStart);
         if (layer.mName != "<LWODefault>" || num > 0) {
-            aiNode *pcNode = new aiNode();
+            std::unique_ptr<aiNode> pcNode(new aiNode());
             pcNode->mName.Set(layer.mName);
             pcNode->mParent = (aiNode *)&layer;
             pcNode->mNumMeshes = num;
@@ -439,7 +430,8 @@ void LWOImporter::InternReadFile(const std::string &pFile,
                 for (unsigned int p = 0; p < pcNode->mNumMeshes; ++p)
                     pcNode->mMeshes[p] = p + meshStart;
             }
-            apcNodes[layer.mIndex] = pcNode;
+            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;
                     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];
                     vNormals += v;
                 }
-                vNormals.Normalize();
                 for (std::vector<unsigned int>::const_iterator a = poResult.begin(); a != poResult.end(); ++a) {
                     mesh->mNormals[*a] = vNormals;
                     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();
     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
@@ -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");
+    }
 
     // Remove a single root node with no meshes assigned to it ...
     if (1 == mScene->mRootNode->mNumChildren) {
@@ -1462,7 +1484,6 @@ void LWOImporter::LoadLWO2File() {
 
         if (mFileBuffer + head.length > end) {
             throw DeadlyImportError("LWO2: Chunk length points behind the file");
-            break;
         }
         uint8_t *const next = mFileBuffer + head.length;
         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)
     // 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.g *= 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);
                 }
                 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
         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;
     while (!IsSpaceOrNewLine(*buffer)) {
         ++buffer;
+        if (buffer == bufferEnd)
+            return false;
     }
     out.mName = std::string(sz, (uintptr_t)(buffer - sz));
-    SkipSpaces();
+    while (IsSpace(*buffer)) {
+        ++buffer;
+        if (buffer == bufferEnd)
+            return false;
+    }
 
     bool running = true;
     while (running) {
         if ('{' == *buffer) {
             // it is a normal section so read all lines
             ++buffer;
+            if (buffer == bufferEnd)
+                return false;
             bool run = true;
             while (run) {
-                if (!SkipSpacesAndLineEnd()) {
+                while (IsSpaceOrNewLine(*buffer)) {
+                    ++buffer;
+                    if (buffer == bufferEnd)
+                        return false;
+                }
+                if ('\0' == *buffer) {
                     return false; // seems this was the last section
                 }
                 if ('}' == *buffer) {
@@ -164,25 +177,39 @@ bool MD5Parser::ParseSection(Section &out) {
                 elem.szStart = buffer;
 
                 // terminate the line with zero
-                while (!IsLineEnd(*buffer))
+                while (!IsLineEnd(*buffer)) {
                     ++buffer;
+                    if (buffer == bufferEnd)
+                        return false;
+                }
                 if (*buffer) {
                     ++lineNumber;
                     *buffer++ = '\0';
+                    if (buffer == bufferEnd)
+                        return false;
                 }
             }
             break;
         } else if (!IsSpaceOrNewLine(*buffer)) {
             // it is an element at global scope. Parse its value and go on
             sz = buffer;
-            while (!IsSpaceOrNewLine(*buffer++))
-                ;
+            while (!IsSpaceOrNewLine(*buffer++)) {
+                if (buffer == bufferEnd)
+                    return false;
+            }
             out.mGlobalValue = std::string(sz, (uintptr_t)(buffer - sz));
             continue;
         }
         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';
 
 // 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';
 // ------------------------------------------------------------------------------------------------
 // .MD5MESH parsing function

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

@@ -365,9 +365,7 @@ public:
     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) {
         return ReportWarning(warn, lineNumber);
@@ -404,6 +402,9 @@ private:
     unsigned int lineNumber;
 };
 
+inline void MD5Parser::ReportError(const char* error) {
+    ReportError(error, lineNumber);
+}
 // -------------------------------------------------------------------
 inline bool MD5Parser::SkipLine(const char* in, const char** out) {
     ++lineNumber;

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

@@ -470,14 +470,16 @@ void HL1MDLLoader::read_bones() {
 
     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);
     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.
     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]);
         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]));
 
         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 {
-            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_[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.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);
 
+    /**
+     *  \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 */
     aiScene *scene_;
 
@@ -198,11 +206,13 @@ private:
         TempBone() :
             node(nullptr),
             absolute_transform(),
-            offset_matrix() {}
+            offset_matrix(),
+            children() {}
 
         aiNode *node;
         aiMatrix4x4 absolute_transform;
         aiMatrix4x4 offset_matrix;
+        std::vector<int> children; // Bone children
     };
 
     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
 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 "
                                 "or contains invalid data.");
     }
@@ -284,7 +290,7 @@ void MDLImporter::SizeCheck(const void *szPos) {
 // Just for debugging purposes
 void MDLImporter::SizeCheck(const void *szPos, const char *szFile, unsigned int iLine) {
     ai_assert(nullptr != szFile);
-    if (!szPos || (const unsigned char *)szPos > mBuffer + iFileSize) {
+    if (!IsPosValid(szPos)) {
         // remove a directory if there is one
         const char *szFilePtr = ::strrchr(szFile, '\\');
         if (!szFilePtr) {
@@ -975,7 +981,7 @@ void MDLImporter::CalcAbsBoneMatrices_3DGS_MDL7(MDL::IntBone_MDL7 **apcOutBones)
                     }
 
                     // 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);
                     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)
     */
-    void InternReadFile_HL2( );
+    AI_WONT_RETURN void InternReadFile_HL2( ) AI_WONT_RETURN_SUFFIX;
 
     // -------------------------------------------------------------------
     /** 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, const char* szFile, unsigned int iLine);
+    bool IsPosValid(const void* szPos) const;
 
     // -------------------------------------------------------------------
     /** 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[3] = '\0';
 
+        SizeCheck(szCurrent + pcNew->mWidth);
+
         pcNew->pcData = (aiTexel *)new unsigned char[pcNew->mWidth];
         memcpy(pcNew->pcData, szCurrent, pcNew->mWidth);
         szCurrent += iWidth;
@@ -493,12 +495,12 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7(
 
         aiString szFile;
         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);
+        szFile.data[iLen2] = '\0';
         szFile.length = static_cast<ai_uint32>(iLen2);
 
-        szCurrent += iLen2;
+        szCurrent += iLen2 + 1;
 
         // place this as diffuse texture
         pcMatOut->AddProperty(&szFile, AI_MATKEY_TEXTURE_DIFFUSE(0));
@@ -703,7 +705,14 @@ void MDLImporter::SkipSkinLump_3DGS_MDL7(
             tex.pcData = bad_texel;
             tex.mHeight = iHeight;
             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
             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 <fstream>
 #include <memory>
+#include <assimp/types.h>
 #include "MMDCpp14.h"
 
 namespace pmx
@@ -730,7 +731,7 @@ namespace pmx
 		std::unique_ptr<PmxAncherRigidBody []> anchers;
 		int pin_vertex_count;
 		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

+ 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) {
             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");
             }
 
@@ -494,7 +494,7 @@ void MS3DImporter::InternReadFile( const std::string& pFile,
             f.mIndices = new unsigned int[f.mNumIndices=3];
 
             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");
                 }
 

+ 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/StreamReader.h>
 #include <map>
+#include <limits>
 
 using namespace Assimp;
 
@@ -160,6 +161,9 @@ void NDOImporter::InternReadFile( const std::string& pFile,
 
         temp = file_format >= 12 ? reader.GetU4() : reader.GetU2();
         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 */
 
         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; ) {
         if(!GetNextLine(buffer,line)) {
             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;
         sz = line; SkipSpaces(&sz);

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

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

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

@@ -3,7 +3,7 @@
 Open Asset Import Library (assimp)
 ---------------------------------------------------------------------------
 
-Copyright (c) 2006-2020, assimp team
+Copyright (c) 2006-2023, assimp team
 
 All rights reserved.
 
@@ -84,7 +84,6 @@ ObjFileImporter::ObjFileImporter() :
 //  Destructor.
 ObjFileImporter::~ObjFileImporter() {
     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) {
         unsigned int meshId = pObject->m_Meshes[i];
         aiMesh *pMesh = createTopology(pModel, pObject, meshId);
-        if (pMesh) {
+        if (pMesh != nullptr) {
             if (pMesh->mNumFaces > 0) {
                 MeshArray.push_back(pMesh);
             } 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++) {
         const ObjFile::Face *inp = pObjMesh->m_Faces[index];
-        //ai_assert(nullptr != inp);
 
         if (inp->mPrimitiveType == aiPrimitiveType_LINE) {
             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 (!last) {
+                        if (pMesh->mNumVertices <= newIndex + 1) {
+                            throw DeadlyImportError("OBJ: bad vertex index");
+                        }
+
                         pMesh->mVertices[newIndex + 1] = pMesh->mVertices[newIndex];
                         if (!sourceFace->m_normals.empty() && !pModel->mNormals.empty()) {
                             pMesh->mNormals[newIndex + 1] = pMesh->mNormals[newIndex];

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

@@ -252,9 +252,9 @@ void ObjFileMtlImporter::load() {
             case 'a': // Anisotropy
             {
                 ++m_DataIt;
-                getFloatValue(m_pModel->mCurrentMaterial->anisotropy);
                 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;
 
             default: {
@@ -371,6 +371,7 @@ void ObjFileMtlImporter::getTexture() {
     if (m_pModel->mCurrentMaterial == nullptr) {
         m_pModel->mCurrentMaterial = new ObjFile::Material();
         m_pModel->mCurrentMaterial->MaterialName.Set("Empty_Material");
+        m_pModel->mMaterialMap["Empty_Material"] = m_pModel->mCurrentMaterial;
     }
 
     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)
                     getHomogeneousVector3(m_pModel->mVertices);
                 } 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
                     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') {
                 // read in texture coordinate ( 2D or 3D )
                 ++m_DataIt;
@@ -236,7 +244,7 @@ void ObjFileParser::parseFile(IOStreamBuffer<char> &streamBuffer) {
             getNameNoSpace(m_DataIt, m_DataItEnd, name);
             insideCstype = name == "cstype";
             goto pf_skip_line;
-        } break;
+        }
 
         default: {
         pf_skip_line:
@@ -456,8 +464,19 @@ void ObjFileParser::getFace(aiPrimitiveType type) {
             iPos = 0;
         } else {
             //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
             int tmp = iVal;

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

@@ -57,7 +57,7 @@ namespace Assimp {
 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 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) {
     if (!error.empty()) {
         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 + "'");
-    return false;
 }
 
 // Mesh XML constants
@@ -490,7 +489,7 @@ bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *me
     OgreXmlSerializer serializer(xmlParser.get());
     XmlNode root = xmlParser->getRootNode();
     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()) {
             if (std::string(a.name()) == nnSkeleton) {
                 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*/) {
     if (nullptr == m_currentNode) {
         throw DeadlyImportError("No current node for name.");
-        return;
     }
 
     Value *val(node->getValue());
     if (nullptr != val) {
         if (Value::ValueType::ddl_string != val->m_type) {
             throw DeadlyImportError("OpenGEX: invalid data type for value in node name.");
-            return;
         }
 
         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*/) {
     if (nullptr == m_currentNode) {
         throw DeadlyImportError("No parent node for name.");
-        return;
     }
 
     std::vector<std::string> objRefNames;
@@ -532,7 +529,6 @@ void OpenGEXImporter::handleObjectRefNode(DDLNode *node, aiScene * /*pScene*/) {
 void OpenGEXImporter::handleMaterialRefNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) {
     if (nullptr == m_currentNode) {
         throw DeadlyImportError("No parent node for name.");
-        return;
     }
 
     std::vector<std::string> matRefNames;
@@ -672,14 +668,12 @@ static void setMatrix(aiNode *node, DataArrayList *transformData) {
 void OpenGEXImporter::handleTransformNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) {
     if (nullptr == m_currentNode) {
         throw DeadlyImportError("No parent node for name.");
-        return;
     }
 
     DataArrayList *transformData(node->getDataArrayList());
     if (nullptr != transformData) {
         if (transformData->m_numItems != 16) {
             throw DeadlyImportError("Invalid number of data for transform matrix.");
-            return;
         }
         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*/) {
     if (nullptr == node) {
         throw DeadlyImportError("No parent node for name.");
-        return;
     }
 
     Property *prop = node->getProperties();
@@ -876,12 +869,10 @@ void OpenGEXImporter::handleVertexArrayNode(ODDLParser::DDLNode *node, aiScene *
 void OpenGEXImporter::handleIndexArrayNode(ODDLParser::DDLNode *node, aiScene * /*pScene*/) {
     if (nullptr == node) {
         throw DeadlyImportError("No parent node for name.");
-        return;
     }
 
     if (nullptr == m_currentMesh) {
         throw DeadlyImportError("No current mesh for index data found.");
-        return;
     }
 
     DataArrayList *vaList = node->getDataArrayList();

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

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

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

@@ -58,7 +58,7 @@ namespace Assimp {
 class RAWImporter : public BaseImporter {
 public:
     RAWImporter();
-    ~RAWImporter();
+    ~RAWImporter() override;
 
     // -------------------------------------------------------------------
     /** 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 {
     uint32_t Tag;
     uint32_t Size;
-} PACK_STRUCT;
+};
 
 enum {
     POS,

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

@@ -590,7 +590,7 @@ void SMDImporter::CreateOutputMaterials() {
         pScene->mMaterials[iMat] = pcMat;
 
         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);
 
         if (aszTextures[iMat].length())
@@ -837,7 +837,10 @@ void SMDImporter::ParseNodeInfo(const char* szCurrent, const char** szCurrentOut
     unsigned int iBone  = 0;
     SkipSpacesAndLineEnd(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;
     }
     // add our bone to the list

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

@@ -56,7 +56,7 @@ namespace Assimp {
 class UnrealImporter : public BaseImporter {
 public:
     UnrealImporter();
-    ~UnrealImporter();
+    ~UnrealImporter() override;
 
     // -------------------------------------------------------------------
     /** @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;
                 pScene->mMaterials[b]->Get( AI_MATKEY_NAME, name);
                 if( strcmp( name.C_Str(), oldMat.mName.data()) == 0 ) {
-                    oldMat.sceneIndex = a;
+                    oldMat.sceneIndex = b;
                     break;
                 }
             }

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

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

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

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

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

@@ -513,21 +513,22 @@ struct Camera : public Object {
     };
 
     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 {
-        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;

+ 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 {
     glTF::Asset asset(pIOHandler);
     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;
     } catch (...) {
         return false;
@@ -697,7 +700,10 @@ void glTFImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOS
 
     // read the asset file
     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

+ 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:
  *   KHR_materials_pbrSpecularGlossiness full
+ *   KHR_materials_specular full
  *   KHR_materials_unlit full
  *   KHR_lights_punctual full
  *   KHR_materials_sheen full
@@ -370,6 +371,15 @@ struct CustomExtension {
     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
 struct Object {
     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
 
     CustomExtension customExtensions;
-    CustomExtension extras;
+    Extras extras;
 
     //! Objects marked as special are not exported (used to emulate the binary body buffer)
     virtual bool IsSpecial() const { return false; }
@@ -483,7 +493,7 @@ private:
 
 public:
     Buffer();
-    ~Buffer();
+    ~Buffer() override;
 
     void Read(Value &obj, Asset &r);
 
@@ -565,7 +575,7 @@ struct Accessor : public Object {
     inline size_t GetMaxByteSize();
 
     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 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 vec4 defaultDiffuseFactor = { 1, 1, 1, 1 };
 const vec3 defaultSpecularFactor = { 1, 1, 1 };
+const vec3 defaultSpecularColorFactor = { 0, 0, 0 };
 const vec3 defaultSheenFactor = { 0, 0, 0 };
 const vec3 defaultAttenuationColor = { 1, 1, 1 };
 
@@ -753,6 +764,16 @@ struct PbrSpecularGlossiness {
     void SetDefaults();
 };
 
+struct MaterialSpecular {
+    float specularFactor;
+    vec3 specularColorFactor;
+    TextureInfo specularTexture;
+    TextureInfo specularColorTexture;
+
+    MaterialSpecular() { SetDefaults(); }
+    void SetDefaults();
+};
+
 struct MaterialSheen {
     vec3 sheenColorFactor;
     float sheenRoughnessFactor;
@@ -817,6 +838,9 @@ struct Material : public Object {
     //extension: KHR_materials_pbrSpecularGlossiness
     Nullable<PbrSpecularGlossiness> pbrSpecularGlossiness;
 
+    //extension: KHR_materials_specular
+    Nullable<MaterialSpecular> materialSpecular;
+
     //extension: KHR_materials_sheen
     Nullable<MaterialSheen> materialSheen;
 
@@ -1099,6 +1123,7 @@ public:
     //! Keeps info about the enabled extensions
     struct Extensions {
         bool KHR_materials_pbrSpecularGlossiness;
+        bool KHR_materials_specular;
         bool KHR_materials_unlit;
         bool KHR_lights_punctual;
         bool KHR_texture_transform;
@@ -1113,13 +1138,14 @@ public:
         bool KHR_texture_basisu;
 
         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_ior(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/DefaultLogger.hpp>
 #include <assimp/Base64.hpp>
+#include <rapidjson/document.h>
+#include <rapidjson/schema.h>
+#include <rapidjson/stringbuffer.h>
 
 // clang-format off
 #ifdef ASSIMP_ENABLE_DRACO
@@ -139,6 +142,18 @@ inline CustomExtension ReadExtensions(const char *name, Value &obj) {
     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,
         uint8_t *dst, size_t dst_stride) {
     if (src_stride == dst_stride) {
@@ -248,7 +263,7 @@ inline void Object::ReadExtensions(Value &val) {
 
 inline void Object::ReadExtras(Value &val) {
     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>
-void Accessor::ExtractData(T *&outData) {
+size_t Accessor::ExtractData(T *&outData, const std::vector<unsigned int> *remappingIndices) {
     uint8_t *data = GetPointer();
     if (!data) {
         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 totalSize = elemSize * count;
+    const size_t totalSize = elemSize * usedCount;
 
     const size_t stride = GetStride();
 
@@ -980,18 +996,31 @@ void Accessor::ExtractData(T *&outData) {
     }
 
     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) {
@@ -1249,6 +1278,19 @@ inline void Material::Read(Value &material, Asset &r) {
                 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
 
@@ -1347,6 +1389,12 @@ inline void PbrSpecularGlossiness::SetDefaults() {
     glossinessFactor = 1.0f;
 }
 
+inline void MaterialSpecular::SetDefaults() {
+    //KHR_materials_specular properties
+    SetVector(specularColorFactor, defaultSpecularColorFactor);
+    specularFactor = 0.f;
+}
+
 inline void MaterialSheen::SetDefaults() {
     //KHR_materials_sheen properties
     SetVector(sheenColorFactor, defaultSheenFactor);
@@ -2033,6 +2081,7 @@ inline void Asset::ReadExtensionsUsed(Document &doc) {
     }
 
     CHECK_EXT(KHR_materials_pbrSpecularGlossiness);
+    CHECK_EXT(KHR_materials_specular);
     CHECK_EXT(KHR_materials_unlit);
     CHECK_EXT(KHR_lights_punctual);
     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:
  *   KHR_materials_pbrSpecularGlossiness: full
+ *   KHR_materials_specular: full
  *   KHR_materials_unlit: full
  *   KHR_materials_sheen: 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);
         }
 
+        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) {
             Value materialSheen(rapidjson::Type::kObjectType);
 
@@ -550,7 +570,7 @@ namespace glTF2 {
 
     inline void Write(Value& obj, Mesh& m, AssetWriter& w)
     {
-		/****************** Primitives *******************/
+        /****************** Primitives *******************/
         Value primitives;
         primitives.SetArray();
         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)
     {
         if (n.matrix.isPresent) {
@@ -669,6 +727,8 @@ namespace glTF2 {
         if(n.skeletons.size()) {
             AddRefsVector(obj, "skeletons", n.skeletons, w.mAl);
         }
+
+        WriteExtras(obj, n.extras, w);
     }
 
     inline void Write(Value& /*obj*/, Program& /*b*/, AssetWriter& /*w*/)
@@ -742,7 +802,6 @@ namespace glTF2 {
         }
     }
 
-
     inline AssetWriter::AssetWriter(Asset& a)
         : mDoc()
         , mAsset(a)
@@ -836,7 +895,7 @@ namespace glTF2 {
             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();
 
         GLB_Chunk jsonChunk;
@@ -862,7 +921,7 @@ namespace glTF2 {
         int GLB_Chunk_count = 1;
         uint32_t binaryChunkLength = 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;
             ++GLB_Chunk_count;
@@ -929,6 +988,10 @@ namespace glTF2 {
               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) {
                 exts.PushBack(StringRef("KHR_materials_sheen"), mAl);
             }
@@ -980,7 +1043,7 @@ namespace glTF2 {
         if (d.mObjs.empty()) return;
 
         Value* container = &mDoc;
-		const char* context = "Document";
+        const char* context = "Document";
 
         if (d.mExtId) {
             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) {
         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++) {
             double valueData = bufferData_ptr[j];
             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)
             continue;
 
-        //non zero, store the data
+        // non zero, store the data
         for (unsigned int j = 0; j < numCompsOut; j++) {
             T valueData = bufferData_ptr[j];
             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);
     }
 
-    //avoid all-0, put 1 item
+    // avoid all-0, put 1 item
     if (vNZDiff.size() == 0) {
         for (unsigned int j = 0; j < numCompsOut; j++)
             vNZDiff.push_back(0);
         vNZIdx.push_back(0);
     }
 
-    //process data
+    // process data
     outputNZDiff = new T[vNZDiff.size()];
     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->count = nzCount;
 
-        //indices
+        // indices
         unsigned int bytesPerIdx = sizeof(unsigned short);
         size_t indices_offset = buffer->byteLength;
         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->WriteSparseIndices(nzCount, nzIdx, 1 * bytesPerIdx);
 
-        //values
+        // values
         size_t values_offset = buffer->byteLength;
         size_t values_padding = values_offset % bytesPerComp;
         values_offset += values_padding;
@@ -395,9 +395,9 @@ inline Ref<Accessor> ExportDataSparse(Asset &a, std::string &meshName, Ref<Buffe
         acc->sparse->valuesByteOffset = 0;
         acc->WriteSparseValues(nzCount, nzDiff, numCompsIn * bytesPerComp);
 
-        //clear
-        delete[](char *) nzDiff;
-        delete[](char *) nzIdx;
+        // clear
+        delete[] (char *)nzDiff;
+        delete[] (char *)nzIdx;
     }
     return acc;
 }
@@ -443,6 +443,61 @@ inline Ref<Accessor> ExportData(Asset &a, std::string &meshName, Ref<Buffer> &bu
     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) {
     switch (map) {
     case aiTextureMapMode_Clamp:
@@ -544,7 +599,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial &mat, Ref<Texture> &texture, unsi
                 if (curTex != nullptr) { // embedded
                     texture->source->name = curTex->mFilename.C_Str();
 
-                    //basisu: embedded ktx2, bu
+                    // basisu: embedded ktx2, bu
                     if (curTex->achFormatHint[0]) {
                         std::string mimeType = "image/";
                         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
-                    //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);
                 } else {
                     texture->source->uri = path;
@@ -574,7 +629,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial &mat, Ref<Texture> &texture, unsi
                     }
                 }
 
-                //basisu
+                // basisu
                 if (useBasisUniversal) {
                     mAsset->extensionsUsed.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);
 
     if (texture) {
-        //GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot);
+        // GetMatTexProp(mat, prop.texCoord, "texCoord", 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);
 
     if (texture) {
-        //GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot);
+        // GetMatTexProp(mat, prop.texCoord, "texCoord", 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;
 }
 
+// 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 result = false;
     // 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) {
         result = true;
     } else {
@@ -674,6 +728,25 @@ bool glTF2Exporter::GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlo
     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) {
     // Return true if got any valid Sheen properties or textures
     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);
 
         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.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 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);
         }
 
         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;
         }
 
@@ -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) {
                 // convert specular color to luminance
                 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);
 
-                //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);
                 // low specular intensity values should produce a rough material even if shininess is high.
                 normalizedShininess = normalizedShininess * specularIntensity;
@@ -818,9 +901,9 @@ void glTF2Exporter::ExportMaterials() {
             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
-            // NOTE: This extension is being considered for deprecation (Dec 2020)
             PbrSpecularGlossiness pbrSG;
             if (GetMatSpecGloss(mat, pbrSG)) {
                 mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true;
@@ -837,7 +920,12 @@ void glTF2Exporter::ExportMaterials() {
         } else {
             // These extensions are not compatible with KHR_materials_unlit or KHR_materials_pbrSpecularGlossiness
             if (!m->pbrSpecularGlossiness.isPresent) {
-                // Sheen
+                MaterialSpecular specular;
+                if (GetMatSpecular(mat, specular)) {
+                    mAsset->extensionsUsed.KHR_materials_specular = true;
+                    m->materialSpecular = Nullable<MaterialSpecular>(specular);
+                }
+
                 MaterialSheen sheen;
                 if (GetMatSheen(mat, sheen)) {
                     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) {
                     vertexJointData[vertexId][boneIndexFitted] = static_cast<float>(jointNamesIndex);
                 }
-            }else {
+            } else {
                 vertexJointData[vertexId][jointsPerVertex[vertexId]] = static_cast<float>(jointNamesIndex);
                 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();
     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) {
         size_t offset = vertexJointAccessor->bufferView->byteOffset;
         size_t bytesLen = vertexJointAccessor->bufferView->byteLength;
@@ -1077,7 +1165,7 @@ void glTF2Exporter::ExportMeshes() {
 
         /******************* Vertices ********************/
         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) {
             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,
-            AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
+                AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);
         if (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;
 
                 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) {
                     p.attributes.texcoord.push_back(tc);
                 }
@@ -1123,7 +1211,7 @@ void glTF2Exporter::ExportMeshes() {
         /*************** Vertex colors ****************/
         for (unsigned int indexColorChannel = 0; indexColorChannel < aim->GetNumColorChannels(); ++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) {
                 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,
-                ComponentType_UNSIGNED_INT, BufferViewTarget_ELEMENT_ARRAY_BUFFER);
+                    ComponentType_UNSIGNED_INT, BufferViewTarget_ELEMENT_ARRAY_BUFFER);
         }
 
         switch (aim->mPrimitiveTypes) {
@@ -1284,24 +1372,24 @@ void glTF2Exporter::MergeMeshes() {
 
         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) {
             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) {
                 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.end(),
                         mesh->primitives.begin(),
                         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());
 
-                //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) {
                     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());
         }
     }
@@ -1363,6 +1451,8 @@ unsigned int glTF2Exporter::ExportNode(const aiNode *n, Ref<Node> &parent) {
     node->parent = parent;
     node->name = name;
 
+    ExportNodeExtras(n->mMetaData, node->extras);
+
     if (!n->mTransformation.IsIdentity()) {
         if (mScene->mNumAnimations > 0 || (mProperties && mProperties->HasPropertyBool("GLTF2_NODE_IN_TRS"))) {
             aiQuaternion quaternion;
@@ -1445,9 +1535,9 @@ inline void ExtractTranslationSampler(Asset &asset, std::string &animId, Ref<Buf
         const aiVectorKey &key = nodeChannel->mPositionKeys[i];
         // mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
         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);
@@ -1464,9 +1554,9 @@ inline void ExtractScaleSampler(Asset &asset, std::string &animId, Ref<Buffer> &
         const aiVectorKey &key = nodeChannel->mScalingKeys[i];
         // mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
         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);
@@ -1483,10 +1573,10 @@ inline void ExtractRotationSampler(Asset &asset, std::string &animId, Ref<Buffer
         const aiQuatKey &key = nodeChannel->mRotationKeys[i];
         // mTime is measured in ticks, but GLTF time is measured in seconds, so convert.
         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);

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

@@ -76,6 +76,7 @@ struct OcclusionTextureInfo;
 struct Node;
 struct Texture;
 struct PbrSpecularGlossiness;
+struct MaterialSpecular;
 struct MaterialSheen;
 struct MaterialClearcoat;
 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::vec3 &prop, const char *propName, int type, int idx) const;
     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 GetMatClearcoat(const aiMaterial &mat, glTF2::MaterialClearcoat &clearcoat);
     bool GetMatTransmission(const aiMaterial &mat, glTF2::MaterialTransmission &transmission);

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

@@ -100,8 +100,6 @@ glTF2Importer::glTF2Importer() :
     // empty
 }
 
-glTF2Importer::~glTF2Importer() = default;
-
 const aiImporterDesc *glTF2Importer::GetInfo() const {
     return &desc;
 }
@@ -114,7 +112,11 @@ bool glTF2Importer::CanRead(const std::string &filename, IOSystem *pIOHandler, b
 
     if (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;
@@ -232,7 +234,8 @@ inline void SetMaterialTextureProperty(std::vector<int> &embeddedTexIdxs, Asset
     SetMaterialTextureProperty(embeddedTexIdxs, r, (glTF2::TextureInfo)prop, mat, texType, texSlot);
 
     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(&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
-        if (mat.pbrSpecularGlossiness.isPresent) {
+        else if (mat.pbrSpecularGlossiness.isPresent) {
             PbrSpecularGlossiness &pbrSG = mat.pbrSpecularGlossiness.value;
 
             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
 
 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();
     aiColor4t<T> *colors;
-    input->ExtractData(colors);
+    input->ExtractData(colors, vertexRemappingTable);
     auto output = new aiColor4D[input->count];
     for (size_t i = 0; i < input->count; i++) {
         output[i] = aiColor4D(
@@ -450,18 +464,73 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
     ASSIMP_LOG_DEBUG("Importing ", r.meshes.Size(), " meshes");
     std::vector<std::unique_ptr<aiMesh>> meshes;
 
-    unsigned int k = 0;
     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) {
-        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) {
             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();
             meshes.push_back(std::unique_ptr<aiMesh>(aim));
 
@@ -491,28 +560,25 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                 break;
             }
 
-            Mesh::Primitive::Attributes &attr = prim.attributes;
-
             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[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.");
                 } else {
-                    attr.normal[0]->ExtractData(aim->mNormals);
+                    attr.normal[0]->ExtractData(aim->mNormals, vertexRemappingTable);
 
                     // only extract tangents if normals are present
                     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.");
                         } else {
                             // generate bitangents from normals and tangents according to spec
                             Tangent *tangents = nullptr;
 
-                            attr.tangent[0]->ExtractData(tangents);
+                            attr.tangent[0]->ExtractData(tangents, vertexRemappingTable);
 
                             aim->mTangents = 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) {
-                if (attr.color[c]->count != aim->mNumVertices) {
+                if (attr.color[c]->count != numAllVertices) {
                     DefaultLogger::get()->warn("Color stream size in mesh \"", mesh.name,
                             "\" does not match the vertex count");
                     continue;
@@ -537,12 +603,12 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
 
                 auto componentType = attr.color[c]->componentType;
                 if (componentType == glTF2::ComponentType_FLOAT) {
-                    attr.color[c]->ExtractData(aim->mColors[c]);
+                    attr.color[c]->ExtractData(aim->mColors[c], vertexRemappingTable);
                 } else {
                     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) {
-                        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;
                 }
 
-                if (attr.texcoord[tc]->count != aim->mNumVertices) {
+                if (attr.texcoord[tc]->count != numAllVertices) {
                     DefaultLogger::get()->warn("Texcoord stream size in mesh \"", mesh.name,
                             "\" does not match the vertex count");
                     continue;
                 }
 
-                attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]);
+                attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc], vertexRemappingTable);
                 aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents();
 
                 aiVector3D *values = aim->mTextureCoords[tc];
@@ -583,11 +649,11 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                     Mesh::Primitive::Target &target = targets[i];
 
                     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");
                         } else {
                             aiVector3D *positionDiff = nullptr;
-                            target.position[0]->ExtractData(positionDiff);
+                            target.position[0]->ExtractData(positionDiff, vertexRemappingTable);
                             for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) {
                                 aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId];
                             }
@@ -595,11 +661,11 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                         }
                     }
                     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");
                         } else {
                             aiVector3D *normalDiff = nullptr;
-                            target.normal[0]->ExtractData(normalDiff);
+                            target.normal[0]->ExtractData(normalDiff, vertexRemappingTable);
                             for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) {
                                 aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId];
                             }
@@ -610,14 +676,14 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                         if (!aiAnimMesh.HasNormals()) {
                             // 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.");
-                        } 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");
                         } else {
                             Tangent *tangent = nullptr;
-                            attr.tangent[0]->ExtractData(tangent);
+                            attr.tangent[0]->ExtractData(tangent, vertexRemappingTable);
 
                             aiVector3D *tangentDiff = nullptr;
-                            target.tangent[0]->ExtractData(tangentDiff);
+                            target.tangent[0]->ExtractData(tangentDiff, vertexRemappingTable);
 
                             for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) {
                                 tangent[vertexId].xyz += tangentDiff[vertexId];
@@ -641,20 +707,15 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
             aiFace *facePtr = nullptr;
             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) {
                 case PrimitiveMode_POINTS: {
                     nFaces = count;
                     facePtr = faces = new aiFace[nFaces];
                     for (unsigned int i = 0; i < count; ++i) {
-                        SetFaceAndAdvance1(facePtr, aim->mNumVertices, data.GetUInt(i));
+                        SetFaceAndAdvance1(facePtr, aim->mNumVertices, indexBuffer[i]);
                     }
                     break;
                 }
@@ -667,7 +728,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                     }
                     facePtr = faces = new aiFace[nFaces];
                     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;
                 }
@@ -676,12 +737,12 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                 case PrimitiveMode_LINE_STRIP: {
                     nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0);
                     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) {
-                        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
-                        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;
                 }
@@ -694,7 +755,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                     }
                     facePtr = faces = new aiFace[nFaces];
                     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;
                 }
@@ -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
                         if ((i + 1) % 2 == 0) {
                             // 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 {
                             // 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;
@@ -716,9 +777,9 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                 case PrimitiveMode_TRIANGLE_FAN:
                     nFaces = count - 2;
                     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) {
-                        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;
                 }
@@ -823,8 +884,6 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
         }
     }
 
-    meshOffsets.push_back(k);
-
     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;
     if (attr.weight.empty() || attr.joint.empty()) {
         return;
@@ -966,14 +1026,14 @@ static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vector<std
         return;
     }
 
-    size_t num_vertices = attr.weight[0]->count;
+    size_t num_vertices = 0;
 
     struct Weights {
         float values[4];
     };
     Weights **weights = new Weights*[attr.weight.size()];
     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 {
@@ -987,12 +1047,12 @@ static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vector<std
     if (attr.joint[0]->GetElementSize() == 4) {
         indices8 = new Indices8*[attr.joint.size()];
         for (size_t j = 0; j < attr.joint.size(); ++j) {
-            attr.joint[j]->ExtractData(indices8[j]);
+            attr.joint[j]->ExtractData(indices8[j], vertexRemappingTablePtr);
         }
     } else {
         indices16 = new Indices16 *[attr.joint.size()];
         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;
 
     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);
 
             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;
                 ainode->mChildren[i] = child;
             }
         }
 
-        if (node.customExtensions || node.extras) {
+        if (node.customExtensions || node.extras.HasExtras()) {
             ainode->mMetaData = new aiMetadata;
             if (node.customExtensions) {
                 ParseExtensions(ainode->mMetaData, node.customExtensions);
             }
-            if (node.extras) {
+            if (node.extras.HasExtras()) {
                 ParseExtras(ainode->mMetaData, node.extras);
             }
         }
@@ -1104,11 +1162,13 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector<unsigned int> &
 
             if (node.skin) {
                 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());
+                    std::vector<unsigned int> *vertexRemappingTablePtr = mVertexRemappingTables[aiMeshIdx].empty() ? nullptr : &mVertexRemappingTables[aiMeshIdx];
 
                     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->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.
 
                     mat4 *pbindMatrices = nullptr;
-                    node.skin->inverseBindMatrices->ExtractData(pbindMatrices);
+                    node.skin->inverseBindMatrices->ExtractData(pbindMatrices, nullptr);
 
                     for (uint32_t i = 0; i < numBones; ++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) {
-            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) {
-            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
             // 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
     unsigned int numRootNodes = unsigned(rootNodes.size());
     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
         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);
 
         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;
             root->mChildren[root->mNumChildren++] = node;
         }
@@ -1621,13 +1676,17 @@ void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IO
 
     // clean all member arrays
     meshOffsets.clear();
+    mVertexRemappingTables.clear();
     mEmbeddedTexIdxs.clear();
 
     this->mScene = pScene;
 
     // read the asset file
     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) {
         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
 
 #include <assimp/BaseImporter.h>
+#include <AssetLib/glTF2/glTF2Asset.h>
 
 struct aiNode;
 
@@ -59,7 +60,7 @@ namespace Assimp {
 class glTF2Importer : public BaseImporter {
 public:
     glTF2Importer();
-    ~glTF2Importer() override;
+    ~glTF2Importer() override = default;
     bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override;
 
 protected:
@@ -76,10 +77,12 @@ private:
     void ImportNodes(glTF2::Asset &a);
     void ImportAnimations(glTF2::Asset &a);
     void ImportCommonMetadata(glTF2::Asset &a);
+    aiNode *ImportNode(glTF2::Asset &r, glTF2::Ref<glTF2::Node> &ptr);
 
 private:
     std::vector<unsigned int> meshOffsets;
     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;
 
     /// 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
 
-
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,

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