Browse Source

Merge branch 'master' into bugfix/fix_gltf2_animation

Kim Kulling 7 months ago
parent
commit
aa329fb24d
100 changed files with 2110 additions and 1395 deletions
  1. 94 7
      .github/workflows/ccpp.yml
  2. 52 0
      .github/workflows/cd.yml
  3. 1 1
      .github/workflows/inno_setup
  4. 2 0
      .gitignore
  5. 41 43
      Build.md
  6. 76 28
      CMakeLists.txt
  7. 7 12
      Dockerfile
  8. 16 15
      Readme.md
  9. 0 8
      code/.editorconfig
  10. 8 2
      code/AssetLib/3DS/3DSConverter.cpp
  11. 2 5
      code/AssetLib/3DS/3DSExporter.cpp
  12. 2 3
      code/AssetLib/3DS/3DSExporter.h
  13. 1 2
      code/AssetLib/3DS/3DSLoader.h
  14. 4 4
      code/AssetLib/3MF/D3MFExporter.cpp
  15. 1 1
      code/AssetLib/3MF/D3MFImporter.cpp
  16. 3 3
      code/AssetLib/3MF/XmlSerializer.cpp
  17. 3 3
      code/AssetLib/3MF/XmlSerializer.h
  18. 6 7
      code/AssetLib/AC/ACLoader.cpp
  19. 2 2
      code/AssetLib/AC/ACLoader.h
  20. 1 1
      code/AssetLib/AMF/AMFImporter.cpp
  21. 1 1
      code/AssetLib/AMF/AMFImporter_Geometry.cpp
  22. 6 6
      code/AssetLib/AMF/AMFImporter_Material.cpp
  23. 8 7
      code/AssetLib/AMF/AMFImporter_Node.hpp
  24. 2 2
      code/AssetLib/AMF/AMFImporter_Postprocess.cpp
  25. 9 2
      code/AssetLib/Assbin/AssbinLoader.cpp
  26. 1 3
      code/AssetLib/Assxml/AssxmlFileWriter.cpp
  27. 3 7
      code/AssetLib/BVH/BVHLoader.cpp
  28. 7 12
      code/AssetLib/BVH/BVHLoader.h
  29. 0 1
      code/AssetLib/Blender/BlenderDNA.h
  30. 3 1
      code/AssetLib/Blender/BlenderDNA.inl
  31. 0 1
      code/AssetLib/Blender/BlenderIntermediate.h
  32. 3 4
      code/AssetLib/Blender/BlenderLoader.cpp
  33. 2 10
      code/AssetLib/C4D/C4DImporter.cpp
  34. 2 2
      code/AssetLib/C4D/C4DImporter.h
  35. 6 4
      code/AssetLib/COB/COBLoader.cpp
  36. 4 8
      code/AssetLib/CSM/CSMLoader.cpp
  37. 309 256
      code/AssetLib/Collada/ColladaExporter.cpp
  38. 1 2
      code/AssetLib/Collada/ColladaExporter.h
  39. 1 0
      code/AssetLib/Collada/ColladaLoader.cpp
  40. 21 18
      code/AssetLib/Collada/ColladaLoader.h
  41. 8 19
      code/AssetLib/DXF/DXFHelper.h
  42. 22 20
      code/AssetLib/FBX/FBXAnimation.cpp
  43. 9 52
      code/AssetLib/FBX/FBXBinaryTokenizer.cpp
  44. 2 1
      code/AssetLib/FBX/FBXCommon.h
  45. 36 27
      code/AssetLib/FBX/FBXConverter.cpp
  46. 2 3
      code/AssetLib/FBX/FBXConverter.h
  47. 5 18
      code/AssetLib/FBX/FBXDeformer.cpp
  48. 8 0
      code/AssetLib/FBX/FBXDocument.cpp
  49. 7 7
      code/AssetLib/FBX/FBXDocument.h
  50. 10 15
      code/AssetLib/FBX/FBXDocumentUtil.cpp
  51. 1 1
      code/AssetLib/FBX/FBXDocumentUtil.h
  52. 13 20
      code/AssetLib/FBX/FBXExportNode.cpp
  53. 26 35
      code/AssetLib/FBX/FBXExportNode.h
  54. 327 302
      code/AssetLib/FBX/FBXExporter.cpp
  55. 2 2
      code/AssetLib/FBX/FBXExporter.h
  56. 3 0
      code/AssetLib/FBX/FBXImportSettings.h
  57. 1 0
      code/AssetLib/FBX/FBXImporter.cpp
  58. 0 4
      code/AssetLib/FBX/FBXMaterial.cpp
  59. 5 2
      code/AssetLib/FBX/FBXMeshGeometry.cpp
  60. 1 2
      code/AssetLib/FBX/FBXParser.h
  61. 4 16
      code/AssetLib/FBX/FBXProperties.cpp
  62. 20 25
      code/AssetLib/FBX/FBXProperties.h
  63. 0 5
      code/AssetLib/FBX/FBXUtil.h
  64. 7 1
      code/AssetLib/HMP/HMPLoader.cpp
  65. 2 0
      code/AssetLib/LWO/LWOLoader.cpp
  66. 28 2
      code/AssetLib/LWS/LWSLoader.cpp
  67. 1 1
      code/AssetLib/LWS/LWSLoader.h
  68. 11 0
      code/AssetLib/MD3/MD3Loader.cpp
  69. 9 5
      code/AssetLib/MD5/MD5Parser.cpp
  70. 3 3
      code/AssetLib/MDL/MDLLoader.cpp
  71. 2 2
      code/AssetLib/NFF/NFFLoader.cpp
  72. 0 1
      code/AssetLib/NFF/NFFLoader.h
  73. 5 2
      code/AssetLib/Obj/ObjExporter.cpp
  74. 2 2
      code/AssetLib/Obj/ObjExporter.h
  75. 24 19
      code/AssetLib/Obj/ObjFileImporter.cpp
  76. 1 1
      code/AssetLib/Obj/ObjFileImporter.h
  77. 19 7
      code/AssetLib/Obj/ObjFileMtlImporter.cpp
  78. 2 5
      code/AssetLib/Obj/ObjFileMtlImporter.h
  79. 6 6
      code/AssetLib/Obj/ObjFileParser.cpp
  80. 7 4
      code/AssetLib/Obj/ObjFileParser.h
  81. 66 77
      code/AssetLib/Ply/PlyLoader.cpp
  82. 3 7
      code/AssetLib/Ply/PlyLoader.h
  83. 29 0
      code/AssetLib/Ply/PlyParser.cpp
  84. 8 5
      code/AssetLib/SMD/SMDLoader.cpp
  85. 7 15
      code/AssetLib/STEPParser/STEPFileReader.cpp
  86. 17 16
      code/AssetLib/STL/STLExporter.cpp
  87. 280 58
      code/AssetLib/USD/USDLoaderImplTinyusdz.cpp
  88. 9 13
      code/AssetLib/USD/USDLoaderImplTinyusdz.h
  89. 0 37
      code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp
  90. 23 1
      code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h
  91. 21 0
      code/AssetLib/VRML/README.md
  92. 103 0
      code/AssetLib/VRML/VrmlConverter.cpp
  93. 57 0
      code/AssetLib/VRML/VrmlConverter.hpp
  94. 37 8
      code/AssetLib/X3D/X3DImporter.cpp
  95. 3 1
      code/AssetLib/X3D/X3DImporter.hpp
  96. 16 16
      code/AssetLib/glTF/glTFImporter.cpp
  97. 16 0
      code/AssetLib/glTF2/glTF2Asset.h
  98. 36 9
      code/AssetLib/glTF2/glTF2Asset.inl
  99. 1 0
      code/AssetLib/glTF2/glTF2AssetWriter.h
  100. 26 1
      code/AssetLib/glTF2/glTF2AssetWriter.inl

+ 94 - 7
.github/workflows/ccpp.yml

@@ -7,10 +7,10 @@ on:
     branches: [ master ]
 
 permissions:
-  contents: read # to fetch code (actions/checkout)
+  contents: write # to fetch code (actions/checkout),and release
 
 jobs:
-  job:
+  build:
     name: ${{ matrix.name }}-build-and-test
     runs-on: ${{ matrix.os }}
     strategy:
@@ -37,6 +37,9 @@ jobs:
             cc: gcc
 
     steps:
+    - name: ccache
+      uses: hendrikmuhs/[email protected]
+      
     - uses: actions/checkout@v4
       with:
           submodules: true
@@ -70,12 +73,12 @@ jobs:
     - name: Set Windows specific CMake arguments
       if: contains(matrix.name, 'windows')
       id: windows_extra_cmake_args
-      run: echo "::set-output name=args::-DASSIMP_BUILD_ASSIMP_TOOLS=1 -DASSIMP_BUILD_ASSIMP_VIEW=1 -DASSIMP_BUILD_ZLIB=1"
+      run: echo ":set-output name=args::=-DASSIMP_BUILD_ASSIMP_TOOLS=1 -DASSIMP_BUILD_ASSIMP_VIEW=1" >> $GITHUB_OUTPUT
     
     - name: Set Hunter specific CMake arguments
       if: contains(matrix.name, 'hunter')
       id: hunter_extra_cmake_args
-      run: echo "::set-output name=args::-DBUILD_SHARED_LIBS=OFF -DASSIMP_HUNTER_ENABLED=ON -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/cmake/polly/${{ matrix.toolchain }}.cmake"
+      run: echo "args=-DBUILD_SHARED_LIBS=OFF -DASSIMP_HUNTER_ENABLED=ON -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/cmake/polly/${{ matrix.toolchain }}.cmake" >> $GITHUB_OUTPUT
 
     - name: configure and build
       uses: lukka/run-cmake@v3
@@ -92,7 +95,7 @@ jobs:
     - name: Exclude certain tests in Hunter specific builds
       if: contains(matrix.name, 'hunter')
       id: hunter_extra_test_args
-      run: echo "::set-output name=args::--gtest_filter=-utOpenGEXImportExport.Importissue1340_EmptyCameraObject:utColladaZaeImportExport.importBlenFromFileTest"
+      run: echo "args=--gtest_filter=-utOpenGEXImportExport.Importissue1340_EmptyCameraObject:utColladaZaeImportExport.importBlenFromFileTest" >> $GITHUB_OUTPUT
 
     - name: test
       run: cd build/bin && ./unit ${{ steps.hunter_extra_test_args.outputs.args }}
@@ -101,5 +104,89 @@ jobs:
     - uses: actions/upload-artifact@v4
       if: matrix.name == 'windows-msvc'
       with:
-        name: 'assimp-bins-${{ matrix.name }}-${{ github.sha }}'
-        path: build/bin
+        name: 'assimp-bins-${{ matrix.name }}'
+        path: build/bin/assimp*.exe
+        
+    - uses: marvinpinto/action-automatic-releases@latest
+      if: contains(matrix.name, 'windows-msvc-hunter')
+      with:
+        repo_token: "${{ secrets.GITHUB_TOKEN }}"
+        automatic_release_tag: "master"
+        prerelease: true
+        title: "AutoRelease"
+        files: |
+          build/bin/assimp*.exe
+
+  create-release:
+    needs: [build]
+    runs-on: ubuntu-latest
+    if: startsWith(github.ref, 'refs/tags/')
+    steps:
+      - id: create-release
+        uses: actions/create-release@v1
+        env:
+          GITHUB_TOKEN: '${{secrets.GITHUB_TOKEN}}'
+        with:
+          tag_name: '${{github.ref}}'
+          release_name: 'Release ${{github.ref}}'
+          draft: false
+          prerelease: true
+      - run: |
+          echo '${{steps.create-release.outputs.upload_url}}' > release_upload_url.txt
+      - uses: actions/upload-artifact@v4
+        with:
+          name: create-release
+          path: release_upload_url.txt
+
+  upload-release:
+    strategy:
+      matrix:
+        name: [ubuntu-latest-g++, macos-latest-clang++, windows-latest-cl.exe, ubuntu-latest-clang++, ubuntu-gcc-hunter, macos-clang-hunter, windows-msvc-hunter]
+        # For Windows msvc, for Linux and macOS let's use the clang compiler, use gcc for Linux.
+        include:
+          - name: windows-latest-cl.exe
+            os: windows-latest
+            cxx: cl.exe
+            cc: cl.exe
+          - name: ubuntu-latest-clang++
+            os: ubuntu-latest
+            cxx: clang++
+            cc: clang
+          - name: macos-latest-clang++
+            os: macos-latest
+            cxx: clang++
+            cc: clang
+          - name: ubuntu-latest-g++
+            os: ubuntu-latest
+            cxx: g++
+            cc: gcc
+          - name: ubuntu-gcc-hunter
+            os: ubuntu-latest
+            toolchain: ninja-gcc-cxx17-fpic
+          - name: macos-clang-hunter
+            os: macos-latest
+            toolchain: ninja-clang-cxx17-fpic
+          - name: windows-msvc-hunter
+            os: windows-latest
+            toolchain: ninja-vs-win64-cxx17
+            
+    needs: [create-release]
+    runs-on: ubuntu-latest
+    if: startsWith(github.ref, 'refs/tags/')
+    steps:
+      - uses: softprops/action-gh-release@v2
+        with:
+          name: create-release
+      - id: upload-url
+        run: |
+          echo "url=$(cat create-release/release_upload_url.txt)" >> $GITHUB_OUTPUT
+      - uses: actions/download-artifact@v4
+        with:
+          name: 'assimp-bins-${{ matrix.name }}-${{ github.sha }}'
+      - uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: '${{secrets.GITHUB_TOKEN}}'
+        with:
+          files: |
+            *.zip
+ 

+ 52 - 0
.github/workflows/cd.yml

@@ -0,0 +1,52 @@
+name: Build and Publish Prebuilt Binaries
+
+on:
+  release:
+    types: [created]
+    
+jobs:
+  build:
+    name: ${{ matrix.name }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - name: windows-x64
+            os: windows-latest
+            arch: x64
+          - name: windows-x86
+            os: windows-latest
+            arch: x86
+            cmake_args: -A Win32
+          - name: macos-x64
+            os: macos-13
+          - name: macos-arm64
+            os: macos-latest
+          - name: linux-x64
+            os: ubuntu-latest
+
+    steps:
+      - uses: actions/[email protected]
+
+      - uses: lukka/get-cmake@latest
+
+      - uses: ilammy/msvc-dev-cmd@v1
+        with:
+          arch: ${{ matrix.arch }}
+      
+      - name: Build
+        run: |
+          cmake -B build -S . ${{ matrix.cmake_args }} -DCMAKE_BUILD_TYPE=Release -DASSIMP_BUILD_TESTS=OFF
+          cmake --build build --config Release
+
+      - uses: TheDoctor0/[email protected]
+        with:
+          filename: ${{ matrix.name }}-${{ github.event.release.tag_name }}.zip
+          directory: build/bin/
+
+      - uses: softprops/action-gh-release@v2
+        with:
+          files: build/bin/${{ matrix.name }}-${{ github.event.release.tag_name }}.zip
+          append_body: true
+          fail_on_unmatched_files: true

+ 1 - 1
.github/workflows/inno_setup

@@ -45,7 +45,7 @@ jobs:
           buildDirectory: '${{ github.workspace }}/build/'
 
       - name: Compile .ISS to .EXE Installer
-        uses: Minionguyjpro/[email protected].2
+        uses: Minionguyjpro/[email protected].5
         with:
           path: packaging/windows-innosetup/script_x64.iss
           options: /O+

+ 2 - 0
.gitignore

@@ -122,5 +122,7 @@ tools/assimp_qt_viewer/ui_mainwindow.h
 generated/*
 
 # 3rd party cloned repos/tarballs etc
+# meshlab repo, automatically cloned via CMake (to gain 2 source files for VRML file format conversion)
+contrib/meshlab/autoclone
 # tinyusdz repo, automatically cloned via CMake
 contrib/tinyusdz/autoclone

+ 41 - 43
Build.md

@@ -1,36 +1,10 @@
 # Build / Install Instructions
 
-## Install on all platforms using vcpkg
-You can download and install assimp using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager:
-```bash
-    git clone https://github.com/Microsoft/vcpkg.git
-    cd vcpkg
-    ./bootstrap-vcpkg.sh
-    ./vcpkg integrate install
-    ./vcpkg install assimp
-```
-The assimp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
-
-## Install on Ubuntu
-You can install the Asset-Importer-Lib via apt:
-```
-sudo apt-get update
-sudo apt-get install libassimp-dev
-```
-
-## Install pyassimp
-You need to have pip installed:
-```
-pip install pyassimp
-```
-
 ## Manual build instructions
-
-### Install CMake
-Asset-Importer-Lib can be built for a lot of different platforms. We are using cmake to generate the build environment for these via cmake. So you have to make sure that you have a working cmake-installation on your system. You can download it at https://cmake.org/ or for linux install it via
-```bash
-sudo apt-get install cmake
-```
+### Install prerequisites
+You need to install
+* cmake
+* Your compiler
 
 ### Get the source
 Make sure you have a working git-installation. Open a command prompt and clone the Asset-Importer-Lib via:
@@ -38,15 +12,22 @@ Make sure you have a working git-installation. Open a command prompt and clone t
 git clone https://github.com/assimp/assimp.git
 ```
 ### Build from source:
+* For *assimp.lib* without any tools:
+```bash
+cd assimp
+cmake CMakeLists.txt
+cmake --build .
+```
+
+* For assimp with the common tools like *assimp-cmd*
 ```bash
 cd assimp
-cmake CMakeLists.txt 
+cmake CMakeLists.txt -DASSIMP_BUILD_ASSIMP_TOOLS=ON
 cmake --build .
 ```
 Note that by default this builds a shared library into the `bin` directory. If you want to build it as a static library see the build options at the bottom of this file.
 
 ### Build instructions for Windows with Visual-Studio
-
 First, you have to install Visual-Studio on your windows-system. You can get the Community-Version for free here: https://visualstudio.microsoft.com/de/downloads/
 To generate the build environment for your IDE open a command prompt, navigate to your repo and type:
 ```bash
@@ -57,17 +38,6 @@ This will generate the project files for the visual studio. All dependencies use
 ### Build instructions for Windows with UWP
 See <https://stackoverflow.com/questions/40803170/cmake-uwp-using-cmake-to-build-universal-windows-app>
 
-### Build instructions for Linux / Unix
-Open a terminal and got to your repository. You can generate the makefiles and build the library via:
-
-```bash
-cmake CMakeLists.txt
-make -j4
-```
-The option -j describes the number of parallel processes for the build. In this case make will try to use 4 cores for the build.
-
-If you want to use an IDE for linux you can try QTCreator for instance. 
-
 ### Build instructions for MinGW
  Older versions of MinGW's compiler (e.g. 5.1.0) do not support the -mbig_obj flag 
 required to compile some of assimp's files, especially for debug builds.
@@ -111,3 +81,31 @@ The cmake-build-environment provides options to configure the build. The followi
 - **USE_STATIC_CRT (default OFF)**: Link against the static MSVC runtime libraries.
 - **ASSIMP_BUILD_DRACO (default OFF)**: Build Draco libraries. Primarily for glTF.
 - **ASSIMP_BUILD_ASSIMP_VIEW (default ON, if DirectX found, OFF otherwise)**: Build Assimp view tool (requires DirectX).
+
+### Install prebuild binaries
+## Install on all platforms using vcpkg
+You can download and install assimp using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager:
+```bash
+    git clone https://github.com/Microsoft/vcpkg.git
+    cd vcpkg
+    ./bootstrap-vcpkg.sh
+    ./vcpkg integrate install
+    ./vcpkg install assimp
+```
+The assimp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
+
+### Install on Ubuntu
+You can install the Asset-Importer-Lib via apt:
+```
+sudo apt-get update
+sudo apt-get install libassimp-dev
+```
+
+### Install pyassimp
+You need to have pip installed:
+```
+pip install pyassimp
+```
+
+### Get the SDK from itchi.io
+Just check [itchi.io](https://kimkulling.itch.io/the-asset-importer-lib)

+ 76 - 28
CMakeLists.txt

@@ -40,6 +40,19 @@ SET(CMAKE_POLICY_DEFAULT_CMP0092 NEW)
 
 CMAKE_MINIMUM_REQUIRED( VERSION 3.22 )
 
+#================================================================================#
+#                    Model formats not enabled by default
+#
+#    3rd party projects may not adhere to strict standards enforced by assimp,
+#    in which case those formats must be opt-in; otherwise the 3rd party code
+#    would fail assimp CI checks
+#================================================================================#
+# M3D format import support (assimp integration no longer supported by M3D format author)
+# User may override these in their CMake script to provide M3D import/export support
+# (M3D importer/exporter was disabled for assimp release 5.1 or later)
+option(ASSIMP_BUILD_M3D_IMPORTER "Enable M3D file import" off)
+option(ASSIMP_BUILD_M3D_EXPORTER "Enable M3D file export" off)
+
 # Experimental USD importer: disabled, need to opt-in
 # Note: assimp github PR automatic checks will fail the PR due to compiler warnings in
 # the external, 3rd party tinyusdz code which isn't technically part of the PR since it's
@@ -47,22 +60,50 @@ CMAKE_MINIMUM_REQUIRED( VERSION 3.22 )
 option(ASSIMP_BUILD_USD_IMPORTER "Enable USD file import" off)
 option(ASSIMP_BUILD_USD_VERBOSE_LOGS "Enable verbose USD import debug logging" off)
 
-# Disabled importers: m3d for 5.1 or later
-ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_IMPORTER)
-ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_EXPORTER)
+# VRML (.wrl/.x3dv) file import support by leveraging X3D importer and 3rd party file
+# format converter to convert .wrl/.x3dv files to X3D-compatible .xml
+# (Need to make this opt-in because 3rd party code triggers lots of CI code quality warnings)
+option(ASSIMP_BUILD_VRML_IMPORTER "Enable VRML (.wrl/.x3dv) file import" off)
+#--------------------------------------------------------------------------------#
+#                  Internal impl for optional model formats
+#--------------------------------------------------------------------------------#
+# Internal/private M3D logic
+if (NOT ASSIMP_BUILD_M3D_IMPORTER)
+    ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_IMPORTER)
+endif () # if (not ASSIMP_BUILD_M3D_IMPORTER)
+if (NOT ASSIMP_BUILD_M3D_EXPORTER)
+    ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_EXPORTER)
+endif () # if (not ASSIMP_BUILD_M3D_EXPORTER)
+
+# Internal/private VRML logic
+if (NOT ASSIMP_BUILD_VRML_IMPORTER)
+    ADD_DEFINITIONS( -DASSIMP_BUILD_NO_VRML_IMPORTER)
+endif () # if (not ASSIMP_BUILD_VRML_IMPORTER)
+#================================================================================#
+
+option(ASSIMP_BUILD_USE_CCACHE "Use ccache to speed up compilation." on)
+
+if(ASSIMP_BUILD_USE_CCACHE)
+  find_program(CCACHE_PATH ccache)
+  if (CCACHE_PATH)
+    set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PATH})
+    set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_PATH})
+  endif()
+endif()
+
 # Toggles the use of the hunter package manager
 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.25.5.tar.gz"
-    SHA1 "a20151e4c0740ee7d0f9994476856d813cdead29"
+    URL "https://github.com/cpp-pm/hunter/archive/v0.25.8.tar.gz"
+    SHA1 "26c79d587883ec910bce168e25f6ac4595f97033"
   )
   add_definitions(-DASSIMP_USE_HUNTER)
 ENDIF()
 
-PROJECT(Assimp VERSION 5.4.1)
+PROJECT(Assimp VERSION 5.4.3)
 
 # All supported options ###############################################
 
@@ -154,7 +195,7 @@ IF (WIN32)
 
   IF(MSVC)
     OPTION( ASSIMP_INSTALL_PDB
-      "Install MSVC debug files."
+      "Create MSVC debug symbol files and add to Install target."
       ON )
     IF(NOT (MSVC_VERSION LESS 1900))
       # Multibyte character set has been deprecated since at least MSVC2015 (possibly earlier)
@@ -270,7 +311,7 @@ IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT MINGW AND NOT HAIKU)
     SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
   ENDIF()
 
-  IF(CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 13)
+  IF(CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 13 AND CMAKE_CXX_COMPILER_ID MATCHES "GNU")
     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()
@@ -299,8 +340,15 @@ ELSEIF(MSVC)
   # supress warning for double to float conversion if Double precision is activated
   ADD_COMPILE_OPTIONS(/wd4244)
   SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG /Zi /Od")
-  SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
-  SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG:FULL /PDBALTPATH:%_PDB% /OPT:REF /OPT:ICF")
+  # Allow user to disable PDBs
+  if(ASSIMP_INSTALL_PDB)
+    SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
+    SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG:FULL /PDBALTPATH:%_PDB% /OPT:REF /OPT:ICF")
+  elseif((GENERATOR_IS_MULTI_CONFIG) OR (CMAKE_BUILD_TYPE MATCHES Release))
+    message("-- MSVC PDB generation disabled. Release binary will not be debuggable.")
+  endif()
+  # Source code is encoded in UTF-8
+  ADD_COMPILE_OPTIONS(/source-charset:utf-8)
 ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang" )
   IF(NOT ASSIMP_HUNTER_ENABLED)
     SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
@@ -643,7 +691,7 @@ ELSE()
   IF ( ASSIMP_BUILD_DRACO )
     # Primarily for glTF v2
     # Enable Draco glTF feature set
-    set(DRACO_GLTF ON CACHE BOOL "" FORCE)
+    set(DRACO_GLTF_BITSTREAM ON CACHE BOOL "" FORCE)
     # Disable unnecessary or omitted components
     set(DRACO_JS_GLUE OFF CACHE BOOL "" FORCE)
     set(DRACO_WASM OFF CACHE BOOL "" FORCE)
@@ -857,24 +905,24 @@ if(WIN32)
   IF(MSVC12 OR MSVC14 OR MSVC15 )
     ADD_CUSTOM_TARGET(UpdateAssimpLibsDebugSymbolsAndDLLs COMMENT "Copying Assimp Libraries ..." VERBATIM)
     IF(CMAKE_GENERATOR MATCHES "^Visual Studio")
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.dll	${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.dll VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.exp	${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.exp VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.lib	${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.lib VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.dll		${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.dll  VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.exp		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.exp VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.lib		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.lib VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.dll	${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.dll VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.exp	${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.exp VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.lib	${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.lib VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.dll		${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.dll  VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.exp		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.exp VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.lib		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.lib VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM)
     ELSE()
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mt.dll	${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.dll VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mt.exp	${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.exp VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mt.lib	${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.lib VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.dll		${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.dll  VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.exp		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.exp VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.lib		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.lib VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM)
-      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mt.dll	${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.dll VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mt.exp	${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.exp VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mt.lib	${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.lib VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.dll		${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.dll  VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.exp		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.exp VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.lib		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.lib VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM)
+      ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb		${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM)
     ENDIF()
   ENDIF()
 ENDIF ()

+ 7 - 12
Dockerfile

@@ -1,22 +1,17 @@
-FROM ubuntu:22.04
+FROM gcc:latest
 
-RUN apt-get update && apt-get install --no-install-recommends -y ninja-build \
-    git cmake build-essential software-properties-common
+RUN apt-get update && apt-get install --no-install-recommends -y ninja-build cmake 
 
-RUN add-apt-repository ppa:ubuntu-toolchain-r/test && apt-get update 
-
-WORKDIR /opt
+WORKDIR /app
 RUN apt install zlib1g-dev
 
-# Build Assimp
-RUN git clone https://github.com/assimp/assimp.git /opt/assimp
-
-WORKDIR /opt/assimp
+COPY . .
 
-RUN git checkout master \
-    && mkdir build && cd build && \
+RUN mkdir build && cd build && \
     cmake -G 'Ninja' \
     -DCMAKE_BUILD_TYPE=Release \
     -DASSIMP_BUILD_ASSIMP_TOOLS=ON \
     .. && \
     ninja -j4 && ninja install
+
+CMD ["/app/build/bin/unit"]

+ 16 - 15
Readme.md

@@ -1,20 +1,24 @@
 Open Asset Import Library (assimp)
 ==================================
 
-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.
+Open Asset Import Library is a library that loads 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) 
+### Current project status ### 
 ![C/C++ CI](https://github.com/assimp/assimp/workflows/C/C++%20CI/badge.svg)
 [![Codacy Badge](https://app.codacy.com/project/badge/Grade/9973693b7bdd4543b07084d5d9cf4745)](https://www.codacy.com/gh/assimp/assimp/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=assimp/assimp&amp;utm_campaign=Badge_Grade)
-[![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)
+[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=assimp_assimp&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=assimp_assimp)
 [![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")
+[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20assimp%20Guru-006BFF)](https://gurubase.io/g/assimp)
+[![Financial Contributors on Open Collective](https://opencollective.com/assimp/all/badge.svg?label=financial+contributors)](https://opencollective.com/assimp)
 <br>
 
-APIs are provided for C and C++. There are various bindings to other languages (C#, Java, Python, Delphi, D). Assimp also runs on Android and iOS.
+APIs are provided for C and C++. Various bindings exist to other languages (C#, Java, Python, Delphi, D). Assimp also runs on Android and iOS.
 Additionally, assimp features various __mesh post-processing tools__: normals and tangent space generation, triangulation, vertex cache locality optimization, removal of degenerate primitives and duplicate vertices, sorting by primitive type, merging of redundant materials and many more.
 
+## Project activity ##
+![Alt](https://repobeats.axiom.co/api/embed/997f84e5f9fcf772da1e687f3a4f3a8afdbf4cf0.svg "Repobeats analytics image")
+
 ### Documentation ###
 Read [our latest documentation](https://assimp-docs.readthedocs.io/en/latest/).
 
@@ -29,7 +33,7 @@ Clone [our model database](https://github.com/assimp/assimp-mdb).
 - Find us on [https://discord.gg/s9KJfaem](https://discord.gg/kKazXMXDy2)
 - Ask [the 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)
+- Nothing has worked? File a question or an issue report at [The Assimp-Issue Tracker](https://github.com/assimp/assimp/issues)
 
 #### Supported file formats ####
 See [the complete list of supported formats](https://github.com/assimp/assimp/blob/master/doc/Fileformats.md).
@@ -40,18 +44,18 @@ Start by reading [our build instructions](https://github.com/assimp/assimp/blob/
 ### Ports ###
 * [Android](port/AndroidJNI/README.md)
 * [Python](port/PyAssimp/README.md)
-* [.NET](https://bitbucket.org/Starnick/assimpnet/src/master/)
+* [.NET](https://github.com/Saalvage/AssimpNetter)
 * [Pascal](port/AssimpPascal/Readme.md)
 * [Javascript (Alpha)](https://github.com/makc/assimp2json)
 * [Javascript/Node.js Interface](https://github.com/kovacsv/assimpjs)
 * [Unity 3d Plugin](https://ricardoreis.net/trilib-2/)
 * [Unreal Engine Plugin](https://github.com/irajsb/UE4_Assimp/)
-* [JVM](https://github.com/kotlin-graphics/assimp) Full jvm port (current [status](https://github.com/kotlin-graphics/assimp/wiki/Status))
+* [JVM](https://github.com/kotlin-graphics/assimp) Full JVM port (current [status](https://github.com/kotlin-graphics/assimp/wiki/Status))
 * [HAXE-Port](https://github.com/longde123/assimp-haxe) The Assimp-HAXE-port.
 * [Rust](https://github.com/jkvargas/russimp)
 
 ### Other tools ###
-[open3mod](https://github.com/acgessler/open3mod) is a powerful 3D model viewer based on Assimp's import and export abilities.
+[Qt5-ModelViewer](https://github.com/sharjith/ModelViewer-Qt5) is a powerful viewer based on Qt5 and Assimp's import and export abilities.<br>
 [Assimp-Viewer](https://github.com/assimp/assimp_view) is an experimental implementation for an Asset-Viewer based on ImGUI and Assimp (experimental).
 
 #### Repository structure ####
@@ -59,7 +63,7 @@ Open Asset Import Library is implemented in C++. The directory structure looks l
 
 	/code		Source code
 	/contrib	Third-party libraries
-	/doc		Documentation (doxysource and pre-compiled docs)
+	/doc		Documentation (Doxygen source and pre-compiled docs)
 	/fuzz           Contains the test code for the Google Fuzzer project
 	/include	Public header C and C++ header files
 	/scripts 	Scripts are used to generate the loading code for some formats
@@ -79,7 +83,7 @@ The source code is organized in the following way:
 	code/AssetLib/<FormatName>	Implementation for import and export of the format
 
 ### Contributing ###
-Contributions to assimp are highly appreciated. The easiest way to get involved is to submit
+I would greatly appreciate contributing to assimp. The easiest way to get involved is to submit
 a pull request with your changes against the main repository's `master` branch.
 
 ## Contributors
@@ -101,7 +105,7 @@ Become a financial contributor and help us sustain our community. [[Contribute](
 
 #### Organizations
 
-Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/assimp/contribute)]
+You can support the project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/assimp/contribute)]
 
 <a href="https://opencollective.com/assimp/organization/0/website"><img src="https://opencollective.com/assimp/organization/0/avatar.svg"></a>
 
@@ -111,6 +115,3 @@ Our license is based on the modified, __3-clause BSD__-License.
 An _informal_ summary is: do whatever you want, but include Assimp's license text with your product -
 and don't sue us if our code doesn't work. Note that, unlike LGPLed code, you may link statically to Assimp.
 For the legal details, see the `LICENSE` file.
-
-### Why this name ###
-Sorry, we're germans :-), no English native speakers ...

+ 0 - 8
code/.editorconfig

@@ -1,8 +0,0 @@
-# See <http://EditorConfig.org> for details
-
-[*.{h,hpp,c,cpp}]
-end_of_line = lf
-insert_final_newline = true
-trim_trailing_whitespace = true
-indent_size = 4
-indent_style = space

+ 8 - 2
code/AssetLib/3DS/3DSConverter.cpp

@@ -643,11 +643,17 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene *pcSOut, aiNode *pcOut,
     }
 
     // Allocate storage for children
-    pcOut->mNumChildren = (unsigned int)pcIn->mChildren.size();
+    const unsigned int size = static_cast<unsigned int>(pcIn->mChildren.size());
+
+    pcOut->mNumChildren = size;
+    if (size == 0) {
+        return;
+    }
+
     pcOut->mChildren = new aiNode *[pcIn->mChildren.size()];
 
     // Recursively process all children
-    const unsigned int size = static_cast<unsigned int>(pcIn->mChildren.size());
+    
     for (unsigned int i = 0; i < size; ++i) {
         pcOut->mChildren[i] = new aiNode();
         pcOut->mChildren[i]->mParent = pcOut;

+ 2 - 5
code/AssetLib/3DS/3DSExporter.cpp

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -52,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/StringComparison.h>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/Exporter.hpp>
+#include <assimp/Exceptional.h>
 #include <assimp/IOSystem.hpp>
 
 #include <memory>
@@ -102,7 +102,7 @@ private:
 // preserves the mesh's given name if it has one. |index| is the index
 // of the mesh in |aiScene::mMeshes|.
 std::string GetMeshName(const aiMesh &mesh, unsigned int index, const aiNode &node) {
-    static const char underscore = '_';
+    static constexpr char underscore = '_';
     char postfix[10] = { 0 };
     ASSIMP_itoa10(postfix, index);
 
@@ -208,9 +208,6 @@ Discreet3DSExporter::Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, con
     }
 }
 
-// ------------------------------------------------------------------------------------------------
-Discreet3DSExporter::~Discreet3DSExporter() = default;
-
 // ------------------------------------------------------------------------------------------------
 int Discreet3DSExporter::WriteHierarchy(const aiNode &node, int seq, int sibling_level) {
     // 3DS scene hierarchy is serialized as in http://www.martinreddy.net/gfx/3d/3DS.spec

+ 2 - 3
code/AssetLib/3DS/3DSExporter.h

@@ -63,10 +63,10 @@ namespace Assimp {
  *  @brief  Helper class to export a given scene to a 3DS file.
  */
 // ------------------------------------------------------------------------------------------------
-class Discreet3DSExporter {
+class Discreet3DSExporter final {
 public:
     Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, const aiScene* pScene);
-    ~Discreet3DSExporter();
+    ~Discreet3DSExporter() = default;
 
 private:
     void WriteMeshes();
@@ -88,7 +88,6 @@ private:
 
     using MeshesByNodeMap = std::multimap<const aiNode*, unsigned int>;
     MeshesByNodeMap meshes;
-
 };
 
 } // Namespace Assimp

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

@@ -5,7 +5,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -64,7 +63,7 @@ using namespace D3DS;
 // ---------------------------------------------------------------------------------
 /** Importer class for 3D Studio r3 and r4 3DS files
  */
-class Discreet3DSImporter : public BaseImporter {
+class Discreet3DSImporter final : public BaseImporter {
 public:
     Discreet3DSImporter();
     ~Discreet3DSImporter() override = default;

+ 4 - 4
code/AssetLib/3MF/D3MFExporter.cpp

@@ -249,10 +249,10 @@ void D3MFExporter::writeBaseMaterials() {
             if (color.r <= 1 && color.g <= 1 && color.b <= 1 && color.a <= 1) {
 
                 hexDiffuseColor = ai_rgba2hex(
-                        (int)((ai_real)color.r) * 255,
-                        (int)((ai_real)color.g) * 255,
-                        (int)((ai_real)color.b) * 255,
-                        (int)((ai_real)color.a) * 255,
+                        (int)(((ai_real)color.r) * 255),
+                        (int)(((ai_real)color.g) * 255),
+                        (int)(((ai_real)color.b) * 255),
+                        (int)(((ai_real)color.a) * 255),
                         true);
 
             } else {

+ 1 - 1
code/AssetLib/3MF/D3MFImporter.cpp

@@ -107,7 +107,7 @@ void D3MFImporter::InternReadFile(const std::string &filename, aiScene *pScene,
 
     XmlParser xmlParser;
     if (xmlParser.parse(opcPackage.RootStream())) {
-        XmlSerializer xmlSerializer(&xmlParser);
+        XmlSerializer xmlSerializer(xmlParser);
         xmlSerializer.ImportXml(pScene);
 
         const std::vector<aiTexture*> &tex =  opcPackage.GetEmbeddedTextures();

+ 3 - 3
code/AssetLib/3MF/XmlSerializer.cpp

@@ -199,11 +199,11 @@ void assignDiffuseColor(XmlNode &node, aiMaterial *mat) {
 
 } // namespace
 
-XmlSerializer::XmlSerializer(XmlParser *xmlParser) :
+XmlSerializer::XmlSerializer(XmlParser &xmlParser) :
         mResourcesDictionnary(),
         mMeshCount(0),
         mXmlParser(xmlParser) {
-    ai_assert(nullptr != xmlParser);
+    // empty
 }
 
 XmlSerializer::~XmlSerializer() {
@@ -218,7 +218,7 @@ void XmlSerializer::ImportXml(aiScene *scene) {
     }
 
     scene->mRootNode = new aiNode(XmlTag::RootTag);
-    XmlNode node = mXmlParser->getRootNode().child(XmlTag::model);
+    XmlNode node = mXmlParser.getRootNode().child(XmlTag::model);
     if (node.empty()) {
         return;
     }

+ 3 - 3
code/AssetLib/3MF/XmlSerializer.h

@@ -59,9 +59,9 @@ class Texture2DGroup;
 class EmbeddedTexture;
 class ColorGroup;
 
-class XmlSerializer {
+class XmlSerializer final {
 public:
-    XmlSerializer(XmlParser *xmlParser);
+    XmlSerializer(XmlParser &xmlParser);
     ~XmlSerializer();
     void ImportXml(aiScene *scene);
 
@@ -92,7 +92,7 @@ private:
     std::vector<aiMaterial *> mMaterials;
     std::map<unsigned int, Resource *> mResourcesDictionnary;
     unsigned int mMeshCount;
-    XmlParser *mXmlParser;
+    XmlParser &mXmlParser;
 };
 
 } // namespace D3MF

+ 6 - 7
code/AssetLib/AC/ACLoader.cpp

@@ -144,10 +144,6 @@ AC3DImporter::AC3DImporter() :
     // nothing to be done here
 }
 
-// ------------------------------------------------------------------------------------------------
-// Destructor, private as well
-AC3DImporter::~AC3DImporter() = default;
-
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 bool AC3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
@@ -171,8 +167,9 @@ bool AC3DImporter::GetNextLine() {
 // ------------------------------------------------------------------------------------------------
 // Parse an object section in an AC file
 bool AC3DImporter::LoadObjectSection(std::vector<Object> &objects) {
-    if (!TokenMatch(mBuffer.data, "OBJECT", 6))
+    if (!TokenMatch(mBuffer.data, "OBJECT", 6)) {
         return false;
+    }
 
     SkipSpaces(&mBuffer.data, mBuffer.end);
 
@@ -192,7 +189,6 @@ bool AC3DImporter::LoadObjectSection(std::vector<Object> &objects) {
         light->mAttenuationConstant = 1.f;
 
         // Generate a default name for both the light source and the node
-        // FIXME - what's the right way to print a size_t? Is 'zu' universally available? stick with the safe version.
         light->mName.length = ::ai_snprintf(light->mName.data, AI_MAXLEN, "ACLight_%i", static_cast<unsigned int>(mLights->size()) - 1);
         obj.name = std::string(light->mName.data);
 
@@ -202,8 +198,10 @@ bool AC3DImporter::LoadObjectSection(std::vector<Object> &objects) {
         obj.type = Object::Group;
     } else if (!ASSIMP_strincmp(mBuffer.data, "world", 5)) {
         obj.type = Object::World;
-    } else
+    } else {
         obj.type = Object::Poly;
+    }
+
     while (GetNextLine()) {
         if (TokenMatch(mBuffer.data, "kids", 4)) {
             SkipSpaces(&mBuffer.data, mBuffer.end);
@@ -344,6 +342,7 @@ bool AC3DImporter::LoadObjectSection(std::vector<Object> &objects) {
         }
     }
     ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: \'kids\' line was expected");
+
     return false;
 }
 

+ 2 - 2
code/AssetLib/AC/ACLoader.h

@@ -63,7 +63,7 @@ namespace Assimp {
 class AC3DImporter : public BaseImporter {
 public:
     AC3DImporter();
-    ~AC3DImporter() override;
+    ~AC3DImporter() override = default;
 
     // Represents an AC3D material
     struct Material {
@@ -103,7 +103,7 @@ public:
 
         unsigned int mat, flags;
 
-        typedef std::pair<unsigned int, aiVector2D> SurfaceEntry;
+        using SurfaceEntry = std::pair<unsigned int, aiVector2D>;
         std::vector<SurfaceEntry> entries;
 
         // Type is low nibble of flags

+ 1 - 1
code/AssetLib/AMF/AMFImporter.cpp

@@ -474,7 +474,7 @@ void AMFImporter::ParseNode_Metadata(XmlNode &node) {
 
     // read attribute
     ne = new AMFMetadata(mNodeElement_Cur);
-    ((AMFMetadata *)ne)->Type = type;
+    ((AMFMetadata *)ne)->MetaType = type;
     ((AMFMetadata *)ne)->Value = value;
     mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element
     mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.

+ 1 - 1
code/AssetLib/AMF/AMFImporter_Geometry.cpp

@@ -202,7 +202,7 @@ void AMFImporter::ParseNode_Volume(XmlNode &node) {
 
     ((AMFVolume *)ne)->MaterialID = node.attribute("materialid").as_string();
 
-    ((AMFVolume *)ne)->Type = type;
+    ((AMFVolume *)ne)->VolumeType = type;
     // Check for child nodes
     bool col_read = false;
     if (!node.empty()) {

+ 6 - 6
code/AssetLib/AMF/AMFImporter_Material.cpp

@@ -263,22 +263,22 @@ void AMFImporter::ParseNode_TexMap(XmlNode &node, const bool pUseOldName) {
             const std::string &name = currentNode.name();
             if (name == "utex1") {
 				read_flag[0] = true;
-                XmlParser::getValueAsReal(node, als.TextureCoordinate[0].x);
+                XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[0].x);
             } else if (name == "utex2") {
 				read_flag[1] = true;
-                XmlParser::getValueAsReal(node, als.TextureCoordinate[1].x);
+                XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[1].x);
             } else if (name == "utex3") {
 				read_flag[2] = true;
-                XmlParser::getValueAsReal(node, als.TextureCoordinate[2].x);
+                XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[2].x);
             } else if (name == "vtex1") {
 				read_flag[3] = true;
-                XmlParser::getValueAsReal(node, als.TextureCoordinate[0].y);
+                XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[0].y);
             } else if (name == "vtex2") {
 				read_flag[4] = true;
-                XmlParser::getValueAsReal(node, als.TextureCoordinate[1].y);
+                XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[1].y);
             } else if (name == "vtex3") {
 				read_flag[5] = true;
-                XmlParser::getValueAsReal(node, als.TextureCoordinate[2].y);
+                XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[2].y);
 			}
 		}
         ParseHelper_Node_Exit();

+ 8 - 7
code/AssetLib/AMF/AMFImporter_Node.hpp

@@ -86,7 +86,8 @@ public:
 	AMFNodeElementBase *Parent; ///< Parent element. If nullptr then this node is root.
 	std::list<AMFNodeElementBase *> Child; ///< Child elements.
 
-public: /// Destructor, virtual..
+public: 
+	/// Destructor, virtual..
 	virtual ~AMFNodeElementBase() = default;
 
 	/// Disabled copy constructor and co.
@@ -97,10 +98,10 @@ public: /// Destructor, virtual..
 
 protected:
 	/// In constructor inheritor must set element type.
-	/// \param [in] pType - element type.
+	/// \param [in] type - element type.
 	/// \param [in] pParent - parent element.
-	AMFNodeElementBase(const EType pType, AMFNodeElementBase *pParent) :
-			Type(pType), Parent(pParent) {
+	AMFNodeElementBase(EType type, AMFNodeElementBase *pParent) :
+			Type(type), Parent(pParent) {
 		// empty
 	}
 }; // class IAMFImporter_NodeElement
@@ -135,8 +136,8 @@ struct AMFInstance : public AMFNodeElementBase {
 /// Structure that define metadata node.
 struct AMFMetadata : public AMFNodeElementBase {
 
-	std::string Type; ///< Type of "Value".
-	std::string Value; ///< Value.
+	std::string MetaType; ///< Type of "Value".
+	std::string Value;    ///< Value.
 
 	/// Constructor.
 	/// \param [in] pParent - pointer to parent node.
@@ -225,7 +226,7 @@ struct AMFVertices : public AMFNodeElementBase {
 /// Structure that define volume node.
 struct AMFVolume : public AMFNodeElementBase {
 	std::string MaterialID; ///< Which material to use.
-	std::string Type; ///< What this volume describes can be "region" or "support". If none specified, "object" is assumed.
+	std::string VolumeType; ///< What this volume describes can be "region" or "support". If none specified, "object" is assumed.
 
 	/// Constructor.
 	/// \param [in] pParent - pointer to parent node.

+ 2 - 2
code/AssetLib/AMF/AMFImporter_Postprocess.cpp

@@ -1,4 +1,4 @@
-/*
+/*
 ---------------------------------------------------------------------------
 Open Asset Import Library (assimp)
 ---------------------------------------------------------------------------
@@ -333,7 +333,7 @@ void AMFImporter::Postprocess_AddMetadata(const AMFMetaDataArray &metadataList,
     size_t meta_idx(0);
 
     for (const AMFMetadata *metadata : metadataList) {
-        sceneNode.mMetaData->Set(static_cast<unsigned int>(meta_idx++), metadata->Type, aiString(metadata->Value));
+        sceneNode.mMetaData->Set(static_cast<unsigned int>(meta_idx++), metadata->MetaType, aiString(metadata->Value));
     }
 }
 

+ 9 - 2
code/AssetLib/Assbin/AssbinLoader.cpp

@@ -91,9 +91,13 @@ bool AssbinImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, boo
     }
 
     char s[32];
-    in->Read(s, sizeof(char), 32);
+    const size_t read = in->Read(s, sizeof(char), 32);
 
     pIOHandler->Close(in);
+    
+    if (read < 19) {
+      return false;
+    }
 
     return strncmp(s, "ASSIMP.binary-dump.", 19) == 0;
 }
@@ -684,6 +688,7 @@ void AssbinImporter::InternReadFile(const std::string &pFile, aiScene *pScene, I
     unsigned int versionMajor = Read<unsigned int>(stream);
     unsigned int versionMinor = Read<unsigned int>(stream);
     if (versionMinor != ASSBIN_VERSION_MINOR || versionMajor != ASSBIN_VERSION_MAJOR) {
+        pIOHandler->Close(stream);
         throw DeadlyImportError("Invalid version, data format not compatible!");
     }
 
@@ -693,8 +698,10 @@ void AssbinImporter::InternReadFile(const std::string &pFile, aiScene *pScene, I
     shortened = Read<uint16_t>(stream) > 0;
     compressed = Read<uint16_t>(stream) > 0;
 
-    if (shortened)
+    if (shortened) {
+        pIOHandler->Close(stream);
         throw DeadlyImportError("Shortened binaries are not supported!");
+    }
 
     stream->Seek(256, aiOrigin_CUR); // original filename
     stream->Seek(128, aiOrigin_CUR); // options

+ 1 - 3
code/AssetLib/Assxml/AssxmlFileWriter.cpp

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -36,7 +35,6 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
 ----------------------------------------------------------------------
 */
 
@@ -223,7 +221,7 @@ static void WriteDump(const char *pFile, const char *cmd, const aiScene *scene,
     const unsigned int majorVersion(aiGetVersionMajor());
     const unsigned int minorVersion(aiGetVersionMinor());
     const unsigned int rev(aiGetVersionRevision());
-    const char *curtime(asctime(p));
+    const char *curtime = asctime(p);
     ioprintf(io, header.c_str(), majorVersion, minorVersion, rev, pFile, c.c_str(), curtime, scene->mFlags, 0u);
 
     // write the node graph

+ 3 - 7
code/AssetLib/BVH/BVHLoader.cpp

@@ -6,8 +6,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -85,11 +83,9 @@ BVHLoader::BVHLoader() :
         mLine(),
         mAnimTickDuration(),
         mAnimNumFrames(),
-        noSkeletonMesh() {}
-
-// ------------------------------------------------------------------------------------------------
-// Destructor, private as well
-BVHLoader::~BVHLoader() = default;
+        noSkeletonMesh() {
+    // empty
+}
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.

+ 7 - 12
code/AssetLib/BVH/BVHLoader.h

@@ -80,32 +80,27 @@ class BVHLoader : public BaseImporter {
         std::vector<ChannelType> mChannels;
         std::vector<float> mChannelValues; // motion data values for that node. Of size NumChannels * NumFrames
 
-        Node() :
-                mNode(nullptr) {}
-
-        explicit Node(const aiNode *pNode) :
-                mNode(pNode) {}
+        Node() : mNode(nullptr) {}
+        explicit Node(const aiNode *pNode) :mNode(pNode) {}
     };
 
 public:
     BVHLoader();
-    ~BVHLoader();
+    ~BVHLoader() override = default;
 
-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 cs) const;
+    bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool cs) const override;
 
-    void SetupProperties(const Importer *pImp);
-    const aiImporterDesc *GetInfo() const;
+    void SetupProperties(const Importer *pImp) override;
+    const aiImporterDesc *GetInfo() const override;
 
 protected:
     /** Imports the given file into the given scene structure.
      * See BaseImporter::InternReadFile() for details
      */
-    void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler);
+    void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
 
-protected:
     /** Reads the file */
     void ReadStructure(aiScene *pScene);
 

+ 0 - 1
code/AssetLib/Blender/BlenderDNA.h

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

+ 3 - 1
code/AssetLib/Blender/BlenderDNA.inl

@@ -841,5 +841,7 @@ template <template <typename> class TOUT> template <typename T> void ObjectCache
 #endif
 }
 
-}}
+}
+}
+
 #endif

+ 0 - 1
code/AssetLib/Blender/BlenderIntermediate.h

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

+ 3 - 4
code/AssetLib/Blender/BlenderLoader.cpp

@@ -1,11 +1,9 @@
-
 /*
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -495,8 +493,9 @@ void BlenderImporter::BuildDefaultMaterial(Blender::ConversionData &conv_data) {
             if (index == static_cast<unsigned int>(-1)) {
                 // Setup a default material.
                 std::shared_ptr<Material> p(new Material());
-                ai_assert(::strlen(AI_DEFAULT_MATERIAL_NAME) < sizeof(p->id.name) - 2);
-                strcpy(p->id.name + 2, AI_DEFAULT_MATERIAL_NAME);
+                const size_t len = ::strlen(AI_DEFAULT_MATERIAL_NAME);
+                ai_assert(len < sizeof(p->id.name) - 2);
+                memcpy(p->id.name + 2, AI_DEFAULT_MATERIAL_NAME, len);
 
                 // Note: MSVC11 does not zero-initialize Material here, although it should.
                 // Thus all relevant fields should be explicitly initialized. We cannot add

+ 2 - 10
code/AssetLib/C4D/C4DImporter.cpp

@@ -2,7 +2,7 @@
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 
-Copyright (c) 2006-2021, assimp team
+Copyright (c) 2006-2024, assimp team
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -86,7 +86,7 @@ namespace Assimp {
     }
 }
 
-static const aiImporterDesc desc = {
+static constexpr aiImporterDesc desc = {
     "Cinema4D Importer",
     "",
     "",
@@ -99,13 +99,6 @@ static const aiImporterDesc desc = {
     "c4d"
 };
 
-
-// ------------------------------------------------------------------------------------------------
-C4DImporter::C4DImporter() = default;
-
-// ------------------------------------------------------------------------------------------------
-C4DImporter::~C4DImporter() = default;
-
 // ------------------------------------------------------------------------------------------------
 bool C4DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
     const std::string& extension = GetExtension(pFile);
@@ -196,7 +189,6 @@ void C4DImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS
     std::copy(materials.begin(), materials.end(), pScene->mMaterials);
 }
 
-
 // ------------------------------------------------------------------------------------------------
 bool C4DImporter::ReadShader(aiMaterial* out, BaseShader* shader) {
     // based on Cineware sample code (C4DImportExport.cpp)

+ 2 - 2
code/AssetLib/C4D/C4DImporter.h

@@ -78,8 +78,8 @@ namespace Assimp  {
 // -------------------------------------------------------------------------------------------
 class C4DImporter : public BaseImporter, public LogFunctions<C4DImporter> {
 public:
-    C4DImporter();
-    ~C4DImporter() override;
+    C4DImporter() = default;
+    ~C4DImporter() override = default;
     bool CanRead( const std::string& pFile, IOSystem*, bool checkSig) const override;
 
 protected:

+ 6 - 4
code/AssetLib/COB/COBLoader.cpp

@@ -228,7 +228,7 @@ aiNode *COBImporter::BuildNodes(const Node &root, const Scene &scin, aiScene *fi
         const Mesh &ndmesh = (const Mesh &)(root);
         if (ndmesh.vertex_positions.size() && ndmesh.texture_coords.size()) {
 
-            typedef std::pair<const unsigned int, Mesh::FaceRefList> Entry;
+            using Entry = std::pair<const unsigned int, Mesh::FaceRefList>;
             for (const Entry &reflist : ndmesh.temp_map) {
                 { // create mesh
                     size_t n = 0;
@@ -372,9 +372,11 @@ aiNode *COBImporter::BuildNodes(const Node &root, const Scene &scin, aiScene *fi
     }
 
     // add children recursively
-    nd->mChildren = new aiNode *[root.temp_children.size()]();
-    for (const Node *n : root.temp_children) {
-        (nd->mChildren[nd->mNumChildren++] = BuildNodes(*n, scin, fill))->mParent = nd;
+    if (!root.temp_children.empty()) {
+        nd->mChildren = new aiNode *[root.temp_children.size()]();
+        for (const Node *n : root.temp_children) {
+            (nd->mChildren[nd->mNumChildren++] = BuildNodes(*n, scin, fill))->mParent = nd;
+        }
     }
 
     return nd;

+ 4 - 8
code/AssetLib/CSM/CSMLoader.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -73,10 +71,9 @@ static constexpr aiImporterDesc desc = {
     "csm"
 };
 
-
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
-CSMImporter::CSMImporter() : noSkeletonMesh(){
+CSMImporter::CSMImporter() : noSkeletonMesh() {
     // empty
 }
 
@@ -102,8 +99,7 @@ void CSMImporter::SetupProperties(const Importer* pImp) {
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure.
 void CSMImporter::InternReadFile( const std::string& pFile,
-    aiScene* pScene, IOSystem* pIOHandler)
-{
+        aiScene* pScene, IOSystem* pIOHandler) {
     std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
 
     // Check whether we can read from the file
@@ -127,11 +123,11 @@ void CSMImporter::InternReadFile( const std::string& pFile,
 
         if ('$'  == *buffer)    {
             ++buffer;
-            if (TokenMatchI(buffer,"firstframe",10))    {
+            if (TokenMatchI(buffer,"firstframe",10)) {
                 SkipSpaces(&buffer, end);
                 first = strtol10(buffer,&buffer);
             }
-            else if (TokenMatchI(buffer,"lastframe",9))     {
+            else if (TokenMatchI(buffer,"lastframe",9)) {
                 SkipSpaces(&buffer, end);
                 last = strtol10(buffer,&buffer);
             }

+ 309 - 256
code/AssetLib/Collada/ColladaExporter.cpp

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -36,7 +35,6 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
 ----------------------------------------------------------------------
 */
 
@@ -64,6 +62,37 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 namespace Assimp {
 
+static const aiNode *findSkeletonRootNode(const aiScene *scene, const aiMesh *mesh) {
+    std::set<const aiNode *> topParentBoneNodes;
+    if (mesh && mesh->mNumBones > 0) {
+        for (unsigned int i = 0; i < mesh->mNumBones; ++i) {
+            aiBone *bone = mesh->mBones[i];
+
+            const aiNode *node = scene->mRootNode->findBoneNode(bone);
+            if (node) {
+                while (node->mParent && scene->findBone(node->mParent->mName) != nullptr) {
+                    node = node->mParent;
+                }
+                topParentBoneNodes.insert(node);
+            }
+        }
+    }
+
+    if (!topParentBoneNodes.empty()) {
+        const aiNode *parentBoneNode = *topParentBoneNodes.begin();
+        if (topParentBoneNodes.size() == 1) {
+            return parentBoneNode;
+        } else {
+            for (auto it : topParentBoneNodes) {
+                if (it->mParent) return it->mParent;
+            }
+            return parentBoneNode;
+        }
+    }
+
+    return nullptr;
+}
+
 // ------------------------------------------------------------------------------------------------
 // Worker function for exporting a scene to Collada. Prototyped and registered in Exporter.cpp
 void ExportSceneCollada(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) {
@@ -152,10 +181,6 @@ ColladaExporter::ColladaExporter(const aiScene *pScene, IOSystem *pIOSystem, con
     WriteFile();
 }
 
-// ------------------------------------------------------------------------------------------------
-// Destructor
-ColladaExporter::~ColladaExporter() = default;
-
 // ------------------------------------------------------------------------------------------------
 // Starts writing the contents
 void ColladaExporter::WriteFile() {
@@ -331,60 +356,68 @@ void ColladaExporter::WriteHeader() {
 // ------------------------------------------------------------------------------------------------
 // Write the embedded textures
 void ColladaExporter::WriteTextures() {
-    static const unsigned int buffer_size = 1024;
-    char str[buffer_size];
-
-    if (mScene->HasTextures()) {
-        for (unsigned int i = 0; i < mScene->mNumTextures; i++) {
-            // It would be great to be able to create a directory in portable standard C++, but it's not the case,
-            // so we just write the textures in the current directory.
+    static constexpr unsigned int buffer_size = 1024;
+    char str[buffer_size] = {'\0'};
 
-            aiTexture *texture = mScene->mTextures[i];
-            if (nullptr == texture) {
-                continue;
-            }
+    if (!mScene->HasTextures()) {
+        return;
+    }
 
-            ASSIMP_itoa10(str, buffer_size, i + 1);
+    for (unsigned int i = 0; i < mScene->mNumTextures; i++) {
+        // It would be great to be able to create a directory in portable standard C++, but it's not the case,
+        // so we just write the textures in the current directory.
 
-            std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char *)texture->achFormatHint);
+        aiTexture *texture = mScene->mTextures[i];
+        if (nullptr == texture) {
+            continue;
+        }
 
-            std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + mIOSystem->getOsSeparator() + name, "wb"));
-            if (outfile == nullptr) {
-                throw DeadlyExportError("could not open output texture file: " + mPath + name);
-            }
+        ASSIMP_itoa10(str, buffer_size, i + 1);
 
-            if (texture->mHeight == 0) {
-                outfile->Write((void *)texture->pcData, texture->mWidth, 1);
-            } else {
-                Bitmap::Save(texture, outfile.get());
-            }
+        std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char *)texture->achFormatHint);
 
-            outfile->Flush();
+        std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + mIOSystem->getOsSeparator() + name, "wb"));
+        if (outfile == nullptr) {
+            throw DeadlyExportError("could not open output texture file: " + mPath + name);
+        }
 
-            textures.insert(std::make_pair(i, name));
+        if (texture->mHeight == 0) {
+            outfile->Write((void *)texture->pcData, texture->mWidth, 1);
+        } else {
+            Bitmap::Save(texture, outfile.get());
         }
+
+        outfile->Flush();
+
+        textures.insert(std::make_pair(i, name));
     }
 }
 
 // ------------------------------------------------------------------------------------------------
 // Write the embedded textures
 void ColladaExporter::WriteCamerasLibrary() {
-    if (mScene->HasCameras()) {
-
-        mOutput << startstr << "<library_cameras>" << endstr;
-        PushTag();
+    if (!mScene->HasCameras()) {
+        return;
+    }
 
-        for (size_t a = 0; a < mScene->mNumCameras; ++a)
-            WriteCamera(a);
+    mOutput << startstr << "<library_cameras>" << endstr;
+    PushTag();
 
-        PopTag();
-        mOutput << startstr << "</library_cameras>" << endstr;
+    for (size_t a = 0; a < mScene->mNumCameras; ++a) {
+        WriteCamera(a);
     }
+
+    PopTag();
+    mOutput << startstr << "</library_cameras>" << endstr;
 }
 
 void ColladaExporter::WriteCamera(size_t pIndex) {
 
     const aiCamera *cam = mScene->mCameras[pIndex];
+    if (cam == nullptr) {
+        return;
+    }
+
     const std::string cameraId = GetObjectUniqueId(AiObjectType::Camera, pIndex);
     const std::string cameraName = GetObjectName(AiObjectType::Camera, pIndex);
 
@@ -422,22 +455,27 @@ void ColladaExporter::WriteCamera(size_t pIndex) {
 // ------------------------------------------------------------------------------------------------
 // Write the embedded textures
 void ColladaExporter::WriteLightsLibrary() {
-    if (mScene->HasLights()) {
-
-        mOutput << startstr << "<library_lights>" << endstr;
-        PushTag();
+    if (!mScene->HasLights()) {
+        return;
+    }
 
-        for (size_t a = 0; a < mScene->mNumLights; ++a)
-            WriteLight(a);
+    mOutput << startstr << "<library_lights>" << endstr;
+    PushTag();
 
-        PopTag();
-        mOutput << startstr << "</library_lights>" << endstr;
+    for (size_t a = 0; a < mScene->mNumLights; ++a) {
+        WriteLight(a);
     }
+
+    PopTag();
+    mOutput << startstr << "</library_lights>" << endstr;
 }
 
 void ColladaExporter::WriteLight(size_t pIndex) {
 
     const aiLight *light = mScene->mLights[pIndex];
+    if (light == nullptr) {
+        return;
+    }
     const std::string lightId = GetObjectUniqueId(AiObjectType::Light, pIndex);
     const std::string lightName = GetObjectName(AiObjectType::Light, pIndex);
 
@@ -462,6 +500,7 @@ void ColladaExporter::WriteLight(size_t pIndex) {
     case aiLightSource_AREA:
     case aiLightSource_UNDEFINED:
     case _aiLightSource_Force32Bit:
+    default:
         break;
     }
     PopTag();
@@ -521,10 +560,6 @@ void ColladaExporter::WriteSpotLight(const aiLight *const light) {
     mOutput << startstr << "<quadratic_attenuation>"
             << light->mAttenuationQuadratic
             << "</quadratic_attenuation>" << endstr;
-    /*
-    out->mAngleOuterCone = AI_DEG_TO_RAD (std::acos(std::pow(0.1f,1.f/srcLight->mFalloffExponent))+
-                            srcLight->mFalloffAngle);
-    */
 
     const ai_real fallOffAngle = AI_RAD_TO_DEG(light->mAngleInnerCone);
     mOutput << startstr << "<falloff_angle sid=\"fall_off_angle\">"
@@ -559,41 +594,43 @@ void ColladaExporter::WriteAmbientLight(const aiLight *const light) {
 // ------------------------------------------------------------------------------------------------
 // Reads a single surface entry from the given material keys
 bool ColladaExporter::ReadMaterialSurface(Surface &poSurface, const aiMaterial &pSrcMat, aiTextureType pTexture, const char *pKey, size_t pType, size_t pIndex) {
-    if (pSrcMat.GetTextureCount(pTexture) > 0) {
-        aiString texfile;
-        unsigned int uvChannel = 0;
-        pSrcMat.GetTexture(pTexture, 0, &texfile, nullptr, &uvChannel);
+    if (pSrcMat.GetTextureCount(pTexture) == 0) {
+        if (pKey)
+            poSurface.exist = pSrcMat.Get(pKey, static_cast<unsigned int>(pType), static_cast<unsigned int>(pIndex), poSurface.color) == aiReturn_SUCCESS;
+        return poSurface.exist;
+    }
 
-        std::string index_str(texfile.C_Str());
+    aiString texfile;
+    unsigned int uvChannel = 0;
+    pSrcMat.GetTexture(pTexture, 0, &texfile, nullptr, &uvChannel);
 
-        if (index_str.size() != 0 && index_str[0] == '*') {
-            unsigned int index;
+    std::string index_str(texfile.C_Str());
 
-            index_str = index_str.substr(1, std::string::npos);
+    if (index_str.size() != 0 && index_str[0] == '*') {
+        unsigned int index;
 
-            try {
-                index = (unsigned int)strtoul10_64<DeadlyExportError>(index_str.c_str());
-            } catch (std::exception &error) {
-                throw DeadlyExportError(error.what());
-            }
+        index_str = index_str.substr(1, std::string::npos);
 
-            std::map<unsigned int, std::string>::const_iterator name = textures.find(index);
+        try {
+            index = (unsigned int)strtoul10_64<DeadlyExportError>(index_str.c_str());
+        } catch (std::exception &error) {
+            throw DeadlyExportError(error.what());
+        }
 
-            if (name != textures.end()) {
-                poSurface.texture = name->second;
-            } else {
-                throw DeadlyExportError("could not find embedded texture at index " + index_str);
-            }
+        std::map<unsigned int, std::string>::const_iterator name = textures.find(index);
+
+        if (name != textures.end()) {
+            poSurface.texture = name->second;
         } else {
-            poSurface.texture = texfile.C_Str();
+            throw DeadlyExportError("could not find embedded texture at index " + index_str);
         }
-
-        poSurface.channel = uvChannel;
-        poSurface.exist = true;
     } else {
-        if (pKey)
-            poSurface.exist = pSrcMat.Get(pKey, static_cast<unsigned int>(pType), static_cast<unsigned int>(pIndex), poSurface.color) == aiReturn_SUCCESS;
+        poSurface.texture = texfile.C_Str();
     }
+
+    poSurface.channel = uvChannel;
+    poSurface.exist = true;
+
     return poSurface.exist;
 }
 
@@ -606,79 +643,87 @@ static bool isalnum_C(char c) {
 // ------------------------------------------------------------------------------------------------
 // Writes an image entry for the given surface
 void ColladaExporter::WriteImageEntry(const Surface &pSurface, const std::string &imageId) {
-    if (!pSurface.texture.empty()) {
-        mOutput << startstr << "<image id=\"" << imageId << "\">" << endstr;
-        PushTag();
-        mOutput << startstr << "<init_from>";
+    if (pSurface.texture.empty()) {
+        return;
+    }
 
-        // URL encode image file name first, then XML encode on top
-        std::stringstream imageUrlEncoded;
-        for (std::string::const_iterator it = pSurface.texture.begin(); it != pSurface.texture.end(); ++it) {
-            if (isalnum_C((unsigned char)*it) || *it == ':' || *it == '_' || *it == '-' || *it == '.' || *it == '/' || *it == '\\')
-                imageUrlEncoded << *it;
-            else
-                imageUrlEncoded << '%' << std::hex << size_t((unsigned char)*it) << std::dec;
-        }
-        mOutput << XMLEscape(imageUrlEncoded.str());
-        mOutput << "</init_from>" << endstr;
-        PopTag();
-        mOutput << startstr << "</image>" << endstr;
+    mOutput << startstr << "<image id=\"" << imageId << "\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<init_from>";
+
+    // URL encode image file name first, then XML encode on top
+    std::stringstream imageUrlEncoded;
+    for (std::string::const_iterator it = pSurface.texture.begin(); it != pSurface.texture.end(); ++it) {
+        if (isalnum_C((unsigned char)*it) || *it == ':' || *it == '_' || *it == '-' || *it == '.' || *it == '/' || *it == '\\')
+            imageUrlEncoded << *it;
+        else
+            imageUrlEncoded << '%' << std::hex << size_t((unsigned char)*it) << std::dec;
     }
+    mOutput << XMLEscape(imageUrlEncoded.str());
+    mOutput << "</init_from>" << endstr;
+    PopTag();
+    mOutput << startstr << "</image>" << endstr;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Writes a color-or-texture entry into an effect definition
 void ColladaExporter::WriteTextureColorEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &imageId) {
-    if (pSurface.exist) {
-        mOutput << startstr << "<" << pTypeName << ">" << endstr;
-        PushTag();
-        if (pSurface.texture.empty()) {
-            mOutput << startstr << "<color sid=\"" << pTypeName << "\">" << pSurface.color.r << "   " << pSurface.color.g << "   " << pSurface.color.b << "   " << pSurface.color.a << "</color>" << endstr;
-        } else {
-            mOutput << startstr << "<texture texture=\"" << imageId << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
-        }
-        PopTag();
-        mOutput << startstr << "</" << pTypeName << ">" << endstr;
+    if (!pSurface.exist) {
+        return;
     }
+
+    mOutput << startstr << "<" << pTypeName << ">" << endstr;
+    PushTag();
+    if (pSurface.texture.empty()) {
+        mOutput << startstr << "<color sid=\"" << pTypeName << "\">" << pSurface.color.r << "   " << pSurface.color.g << "   " << pSurface.color.b << "   " << pSurface.color.a << "</color>" << endstr;
+    } else {
+        mOutput << startstr << "<texture texture=\"" << imageId << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
+    }
+    PopTag();
+    mOutput << startstr << "</" << pTypeName << ">" << endstr;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Writes the two parameters necessary for referencing a texture in an effect entry
 void ColladaExporter::WriteTextureParamEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &materialId) {
     // if surface is a texture, write out the sampler and the surface parameters necessary to reference the texture
-    if (!pSurface.texture.empty()) {
-        mOutput << startstr << "<newparam sid=\"" << materialId << "-" << pTypeName << "-surface\">" << endstr;
-        PushTag();
-        mOutput << startstr << "<surface type=\"2D\">" << endstr;
-        PushTag();
-        mOutput << startstr << "<init_from>" << materialId << "-" << pTypeName << "-image</init_from>" << endstr;
-        PopTag();
-        mOutput << startstr << "</surface>" << endstr;
-        PopTag();
-        mOutput << startstr << "</newparam>" << endstr;
-
-        mOutput << startstr << "<newparam sid=\"" << materialId << "-" << pTypeName << "-sampler\">" << endstr;
-        PushTag();
-        mOutput << startstr << "<sampler2D>" << endstr;
-        PushTag();
-        mOutput << startstr << "<source>" << materialId << "-" << pTypeName << "-surface</source>" << endstr;
-        PopTag();
-        mOutput << startstr << "</sampler2D>" << endstr;
-        PopTag();
-        mOutput << startstr << "</newparam>" << endstr;
+    if (pSurface.texture.empty()) {
+        return;
     }
+
+    mOutput << startstr << "<newparam sid=\"" << materialId << "-" << pTypeName << "-surface\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<surface type=\"2D\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<init_from>" << materialId << "-" << pTypeName << "-image</init_from>" << endstr;
+    PopTag();
+    mOutput << startstr << "</surface>" << endstr;
+    PopTag();
+    mOutput << startstr << "</newparam>" << endstr;
+
+    mOutput << startstr << "<newparam sid=\"" << materialId << "-" << pTypeName << "-sampler\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<sampler2D>" << endstr;
+    PushTag();
+    mOutput << startstr << "<source>" << materialId << "-" << pTypeName << "-surface</source>" << endstr;
+    PopTag();
+    mOutput << startstr << "</sampler2D>" << endstr;
+    PopTag();
+    mOutput << startstr << "</newparam>" << endstr;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Writes a scalar property
 void ColladaExporter::WriteFloatEntry(const Property &pProperty, const std::string &pTypeName) {
-    if (pProperty.exist) {
-        mOutput << startstr << "<" << pTypeName << ">" << endstr;
-        PushTag();
-        mOutput << startstr << "<float sid=\"" << pTypeName << "\">" << pProperty.value << "</float>" << endstr;
-        PopTag();
-        mOutput << startstr << "</" << pTypeName << ">" << endstr;
+    if (!pProperty.exist) {
+        return;
     }
+
+    mOutput << startstr << "<" << pTypeName << ">" << endstr;
+    PushTag();
+    mOutput << startstr << "<float sid=\"" << pTypeName << "\">" << pProperty.value << "</float>" << endstr;
+    PopTag();
+    mOutput << startstr << "</" << pTypeName << ">" << endstr;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -832,8 +877,9 @@ void ColladaExporter::WriteControllerLibrary() {
 void ColladaExporter::WriteController(size_t pIndex) {
     const aiMesh *mesh = mScene->mMeshes[pIndex];
     // Is there a skin controller?
-    if (mesh->mNumBones == 0 || mesh->mNumFaces == 0 || mesh->mNumVertices == 0)
+    if (mesh->mNumBones == 0 || mesh->mNumFaces == 0 || mesh->mNumVertices == 0) {
         return;
+    }
 
     const std::string idstr = GetObjectUniqueId(AiObjectType::Mesh, pIndex);
     const std::string namestr = GetObjectName(AiObjectType::Mesh, pIndex);
@@ -864,8 +910,9 @@ void ColladaExporter::WriteController(size_t pIndex) {
 
     mOutput << startstr << "<Name_array id=\"" << idstr << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">";
 
-    for (size_t i = 0; i < mesh->mNumBones; ++i)
+    for (size_t i = 0; i < mesh->mNumBones; ++i) {
         mOutput << GetBoneUniqueId(mesh->mBones[i]) << ' ';
+    }
 
     mOutput << "</Name_array>" << endstr;
 
@@ -888,9 +935,11 @@ void ColladaExporter::WriteController(size_t pIndex) {
 
     std::vector<ai_real> bind_poses;
     bind_poses.reserve(mesh->mNumBones * 16);
-    for (unsigned int i = 0; i < mesh->mNumBones; ++i)
-        for (unsigned int j = 0; j < 4; ++j)
+    for (unsigned int i = 0; i < mesh->mNumBones; ++i) {
+        for (unsigned int j = 0; j < 4; ++j) {
             bind_poses.insert(bind_poses.end(), mesh->mBones[i]->mOffsetMatrix[j], mesh->mBones[i]->mOffsetMatrix[j] + 4);
+        }
+    }
 
     WriteFloatArray(idstr + "-skin-bind_poses", FloatType_Mat4x4, (const ai_real *)bind_poses.data(), bind_poses.size() / 16);
 
@@ -898,9 +947,11 @@ void ColladaExporter::WriteController(size_t pIndex) {
 
     std::vector<ai_real> skin_weights;
     skin_weights.reserve(mesh->mNumVertices * mesh->mNumBones);
-    for (size_t i = 0; i < mesh->mNumBones; ++i)
-        for (size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j)
+    for (size_t i = 0; i < mesh->mNumBones; ++i) {
+        for (size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j) {
             skin_weights.push_back(mesh->mBones[i]->mWeights[j].mWeight);
+        }
+    }
 
     WriteFloatArray(idstr + "-skin-weights", FloatType_Weight, (const ai_real *)skin_weights.data(), skin_weights.size());
 
@@ -924,12 +975,15 @@ void ColladaExporter::WriteController(size_t pIndex) {
     mOutput << startstr << "<vcount>";
 
     std::vector<ai_uint> num_influences(mesh->mNumVertices, (ai_uint)0);
-    for (size_t i = 0; i < mesh->mNumBones; ++i)
-        for (size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j)
+    for (size_t i = 0; i < mesh->mNumBones; ++i) {
+        for (size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j) {
             ++num_influences[mesh->mBones[i]->mWeights[j].mVertexId];
+        }
+    }
 
-    for (size_t i = 0; i < mesh->mNumVertices; ++i)
+    for (size_t i = 0; i < mesh->mNumVertices; ++i) {
         mOutput << num_influences[i] << " ";
+    }
 
     mOutput << "</vcount>" << endstr;
 
@@ -945,7 +999,7 @@ void ColladaExporter::WriteController(size_t pIndex) {
 
     ai_uint weight_index = 0;
     std::vector<ai_int> joint_weight_indices(2 * joint_weight_indices_length, (ai_int)-1);
-    for (unsigned int i = 0; i < mesh->mNumBones; ++i)
+    for (unsigned int i = 0; i < mesh->mNumBones; ++i) {
         for (unsigned j = 0; j < mesh->mBones[i]->mNumWeights; ++j) {
             unsigned int vId = mesh->mBones[i]->mWeights[j].mVertexId;
             for (ai_uint k = 0; k < num_influences[vId]; ++k) {
@@ -957,9 +1011,11 @@ void ColladaExporter::WriteController(size_t pIndex) {
             }
             ++weight_index;
         }
+    }
 
-    for (size_t i = 0; i < joint_weight_indices.size(); ++i)
+    for (size_t i = 0; i < joint_weight_indices.size(); ++i) {
         mOutput << joint_weight_indices[i] << " ";
+    }
 
     num_influences.clear();
     accum_influences.clear();
@@ -983,8 +1039,9 @@ void ColladaExporter::WriteGeometryLibrary() {
     mOutput << startstr << "<library_geometries>" << endstr;
     PushTag();
 
-    for (size_t a = 0; a < mScene->mNumMeshes; ++a)
+    for (size_t a = 0; a < mScene->mNumMeshes; ++a) {
         WriteGeometry(a);
+    }
 
     PopTag();
     mOutput << startstr << "</library_geometries>" << endstr;
@@ -997,8 +1054,9 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
     const std::string geometryId = GetObjectUniqueId(AiObjectType::Mesh, pIndex);
     const std::string geometryName = GetObjectName(AiObjectType::Mesh, pIndex);
 
-    if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0)
+    if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0) {
         return;
+    }
 
     // opening tag
     mOutput << startstr << "<geometry id=\"" << geometryId << "\" name=\"" << geometryName << "\" >" << endstr;
@@ -1010,8 +1068,9 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
     // Positions
     WriteFloatArray(geometryId + "-positions", FloatType_Vector, (ai_real *)mesh->mVertices, mesh->mNumVertices);
     // Normals, if any
-    if (mesh->HasNormals())
+    if (mesh->HasNormals()) {
         WriteFloatArray(geometryId + "-normals", FloatType_Vector, (ai_real *)mesh->mNormals, mesh->mNumVertices);
+    }
 
     // texture coords
     for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
@@ -1040,10 +1099,11 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
     int countLines = 0;
     int countPoly = 0;
     for (size_t a = 0; a < mesh->mNumFaces; ++a) {
-        if (mesh->mFaces[a].mNumIndices == 2)
+        if (mesh->mFaces[a].mNumIndices == 2) {
             countLines++;
-        else if (mesh->mFaces[a].mNumIndices >= 3)
+        } else if (mesh->mFaces[a].mNumIndices >= 3) {
             countPoly++;
+        }
     }
 
     // lines
@@ -1051,13 +1111,18 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
         mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr;
         PushTag();
         mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
-        if (mesh->HasNormals())
+        if (mesh->HasNormals()) {
             mOutput << startstr << "<input semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
+        }
         for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
-            if (mesh->HasTextureCoords(static_cast<unsigned int>(a)))
-                mOutput << startstr << "<input semantic=\"TEXCOORD\" source=\"#" << geometryId << "-tex" << a << "\" "
+            if (mesh->HasTextureCoords(static_cast<unsigned int>(a))) {
+                mOutput << startstr 
+                        << "<input semantic=\"TEXCOORD\" source=\"#" 
+                        << geometryId 
+                        << "-tex" << a << "\" "
                         << "set=\"" << a << "\""
                         << " />" << endstr;
+            }
         }
         for (size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) {
             if (mesh->HasVertexColors(static_cast<unsigned int>(a)))
@@ -1070,8 +1135,9 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
         for (size_t a = 0; a < mesh->mNumFaces; ++a) {
             const aiFace &face = mesh->mFaces[a];
             if (face.mNumIndices != 2) continue;
-            for (size_t b = 0; b < face.mNumIndices; ++b)
+            for (size_t b = 0; b < face.mNumIndices; ++b) {
                 mOutput << face.mIndices[b] << " ";
+            }
         }
         mOutput << "</p>" << endstr;
         PopTag();
@@ -1085,8 +1151,9 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
         mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr;
         PushTag();
         mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
-        if (mesh->HasNormals())
+        if (mesh->HasNormals()) {
             mOutput << startstr << "<input offset=\"0\" semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
+        }
         for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
             if (mesh->HasTextureCoords(static_cast<unsigned int>(a)))
                 mOutput << startstr << "<input offset=\"0\" semantic=\"TEXCOORD\" source=\"#" << geometryId << "-tex" << a << "\" "
@@ -1111,8 +1178,9 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
         for (size_t a = 0; a < mesh->mNumFaces; ++a) {
             const aiFace &face = mesh->mFaces[a];
             if (face.mNumIndices < 3) continue;
-            for (size_t b = 0; b < face.mNumIndices; ++b)
+            for (size_t b = 0; b < face.mNumIndices; ++b) {
                 mOutput << face.mIndices[b] << " ";
+            }
         }
         mOutput << "</p>" << endstr;
         PopTag();
@@ -1131,13 +1199,27 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
 void ColladaExporter::WriteFloatArray(const std::string &pIdString, FloatDataType pType, const ai_real *pData, size_t pElementCount) {
     size_t floatsPerElement = 0;
     switch (pType) {
-    case FloatType_Vector: floatsPerElement = 3; break;
-    case FloatType_TexCoord2: floatsPerElement = 2; break;
-    case FloatType_TexCoord3: floatsPerElement = 3; break;
-    case FloatType_Color: floatsPerElement = 3; break;
-    case FloatType_Mat4x4: floatsPerElement = 16; break;
-    case FloatType_Weight: floatsPerElement = 1; break;
-    case FloatType_Time: floatsPerElement = 1; break;
+    case FloatType_Vector: 
+        floatsPerElement = 3; 
+        break;
+    case FloatType_TexCoord2: 
+        floatsPerElement = 2; 
+        break;
+    case FloatType_TexCoord3: 
+        floatsPerElement = 3; 
+        break;
+    case FloatType_Color: 
+        floatsPerElement = 3; 
+        break;
+    case FloatType_Mat4x4: 
+        floatsPerElement = 16; 
+        break;
+    case FloatType_Weight: 
+        floatsPerElement = 1; 
+        break;
+    case FloatType_Time: 
+        floatsPerElement = 1; 
+        break;
     default:
         return;
     }
@@ -1163,8 +1245,9 @@ void ColladaExporter::WriteFloatArray(const std::string &pIdString, FloatDataTyp
             mOutput << pData[a * 4 + 2] << " ";
         }
     } else {
-        for (size_t a = 0; a < pElementCount * floatsPerElement; ++a)
+        for (size_t a = 0; a < pElementCount * floatsPerElement; ++a) {
             mOutput << pData[a] << " ";
+        }
     }
     mOutput << "</float_array>" << endstr;
     PopTag();
@@ -1256,9 +1339,13 @@ void ColladaExporter::WriteSceneLibrary() {
 // ------------------------------------------------------------------------------------------------
 void ColladaExporter::WriteAnimationLibrary(size_t pIndex) {
     const aiAnimation *anim = mScene->mAnimations[pIndex];
-
-    if (anim->mNumChannels == 0 && anim->mNumMeshChannels == 0 && anim->mNumMorphMeshChannels == 0)
+    if (anim == nullptr) {
+        return;
+    }
+    
+    if (anim->mNumChannels == 0 && anim->mNumMeshChannels == 0 && anim->mNumMorphMeshChannels == 0) {
         return;
+    }
 
     const std::string animationNameEscaped = GetObjectName(AiObjectType::Animation, pIndex);
     const std::string idstrEscaped = GetObjectUniqueId(AiObjectType::Animation, pIndex);
@@ -1269,8 +1356,11 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex) {
     std::string cur_node_idstr;
     for (size_t a = 0; a < anim->mNumChannels; ++a) {
         const aiNodeAnim *nodeAnim = anim->mChannels[a];
+        if (nodeAnim == nullptr) {
+            continue;
+        }
 
-        // sanity check
+        // sanity checks
         if (nodeAnim->mNumPositionKeys != nodeAnim->mNumScalingKeys || nodeAnim->mNumPositionKeys != nodeAnim->mNumRotationKeys) {
             continue;
         }
@@ -1369,6 +1459,9 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex) {
 
     for (size_t a = 0; a < anim->mNumChannels; ++a) {
         const aiNodeAnim *nodeAnim = anim->mChannels[a];
+        if (nodeAnim == nullptr) {
+            continue;
+        }
 
         {
             // samplers
@@ -1387,97 +1480,42 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex) {
 
     for (size_t a = 0; a < anim->mNumChannels; ++a) {
         const aiNodeAnim *nodeAnim = anim->mChannels[a];
+        if (nodeAnim == nullptr) {
+            continue;
+        }
 
         {
             // channels
-            mOutput << startstr << "<channel source=\"#" << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-sampler")) << "\" target=\"" << XMLIDEncode(nodeAnim->mNodeName.data) << "/matrix\"/>" << endstr;
+            mOutput << startstr 
+                    << "<channel source=\"#" 
+                    << XMLIDEncode(nodeAnim->mNodeName.data + std::string("_matrix-sampler")) 
+                    << "\" target=\"" 
+                    << XMLIDEncode(nodeAnim->mNodeName.data) 
+                    << "/matrix\"/>" 
+                    << endstr;
         }
     }
 
     PopTag();
     mOutput << startstr << "</animation>" << endstr;
 }
-// ------------------------------------------------------------------------------------------------
-void ColladaExporter::WriteAnimationsLibrary() {
-    if (mScene->mNumAnimations > 0) {
-        mOutput << startstr << "<library_animations>" << endstr;
-        PushTag();
-
-        // start recursive write at the root node
-        for (size_t a = 0; a < mScene->mNumAnimations; ++a)
-            WriteAnimationLibrary(a);
-
-        PopTag();
-        mOutput << startstr << "</library_animations>" << endstr;
-    }
-}
-// ------------------------------------------------------------------------------------------------
-// Helper to find a bone by name in the scene
-aiBone *findBone(const aiScene *scene, const aiString &name) {
-    for (size_t m = 0; m < scene->mNumMeshes; m++) {
-        aiMesh *mesh = scene->mMeshes[m];
-        for (size_t b = 0; b < mesh->mNumBones; b++) {
-            aiBone *bone = mesh->mBones[b];
-            if (name == bone->mName) {
-                return bone;
-            }
-        }
-    }
-    return nullptr;
-}
 
 // ------------------------------------------------------------------------------------------------
-// Helper to find the node associated with a bone in the scene
-const aiNode *findBoneNode(const aiNode *aNode, const aiBone *bone) {
-    if (aNode && bone && aNode->mName == bone->mName) {
-        return aNode;
-    }
-
-    if (aNode && bone) {
-        for (unsigned int i = 0; i < aNode->mNumChildren; ++i) {
-            aiNode *aChild = aNode->mChildren[i];
-            const aiNode *foundFromChild = nullptr;
-            if (aChild) {
-                foundFromChild = findBoneNode(aChild, bone);
-                if (foundFromChild) {
-                    return foundFromChild;
-                }
-            }
-        }
-    }
-
-    return nullptr;
-}
-
-const aiNode *findSkeletonRootNode(const aiScene *scene, const aiMesh *mesh) {
-    std::set<const aiNode *> topParentBoneNodes;
-    if (mesh && mesh->mNumBones > 0) {
-        for (unsigned int i = 0; i < mesh->mNumBones; ++i) {
-            aiBone *bone = mesh->mBones[i];
-
-            const aiNode *node = findBoneNode(scene->mRootNode, bone);
-            if (node) {
-                while (node->mParent && findBone(scene, node->mParent->mName) != nullptr) {
-                    node = node->mParent;
-                }
-                topParentBoneNodes.insert(node);
-            }
-        }
+void ColladaExporter::WriteAnimationsLibrary() {
+    if (mScene->mNumAnimations == 0) {
+        return;
     }
+    
+    mOutput << startstr << "<library_animations>" << endstr;
+    PushTag();
 
-    if (!topParentBoneNodes.empty()) {
-        const aiNode *parentBoneNode = *topParentBoneNodes.begin();
-        if (topParentBoneNodes.size() == 1) {
-            return parentBoneNode;
-        } else {
-            for (auto it : topParentBoneNodes) {
-                if (it->mParent) return it->mParent;
-            }
-            return parentBoneNode;
-        }
+    // start recursive write at the root node
+    for (size_t a = 0; a < mScene->mNumAnimations; ++a) {
+        WriteAnimationLibrary(a);
     }
 
-    return nullptr;
+    PopTag();
+    mOutput << startstr << "</library_animations>" << endstr;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -1488,13 +1526,13 @@ void ColladaExporter::WriteNode(const aiNode *pNode) {
     // Assimp-specific: nodes with no name cannot be associated with bones
     const char *node_type;
     bool is_joint, is_skeleton_root = false;
-    if (pNode->mName.length == 0 || nullptr == findBone(mScene, pNode->mName)) {
+    if (pNode->mName.length == 0 || nullptr == mScene->findBone(pNode->mName)) {
         node_type = "NODE";
         is_joint = false;
     } else {
         node_type = "JOINT";
         is_joint = true;
-        if (!pNode->mParent || nullptr == findBone(mScene, pNode->mParent->mName)) {
+        if (!pNode->mParent || nullptr == mScene->findBone(pNode->mParent->mName)) {
             is_skeleton_root = true;
         }
     }
@@ -1532,7 +1570,6 @@ void ColladaExporter::WriteNode(const aiNode *pNode) {
     }
 
     // customized, sid should be 'matrix' to match with loader code.
-    //mOutput << startstr << "<matrix sid=\"transform\">";
     mOutput << startstr << "<matrix sid=\"matrix\">";
 
     mOutput << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << " ";
@@ -1556,7 +1593,6 @@ void ColladaExporter::WriteNode(const aiNode *pNode) {
                 break;
             }
         }
-
     } else
         // instance every geometry
         for (size_t a = 0; a < pNode->mNumMeshes; ++a) {
@@ -1612,8 +1648,9 @@ void ColladaExporter::WriteNode(const aiNode *pNode) {
         }
 
     // recurse into subnodes
-    for (size_t a = 0; a < pNode->mNumChildren; ++a)
+    for (size_t a = 0; a < pNode->mNumChildren; ++a) {
         WriteNode(pNode->mChildren[a]);
+    }
 
     PopTag();
     mOutput << startstr << "</node>" << endstr;
@@ -1628,8 +1665,9 @@ void ColladaExporter::CreateNodeIds(const aiNode *node) {
 std::string ColladaExporter::GetNodeUniqueId(const aiNode *node) {
     // Use the pointer as the key. This is safe because the scene is immutable.
     auto idIt = mNodeIdMap.find(node);
-    if (idIt != mNodeIdMap.cend())
+    if (idIt != mNodeIdMap.cend()) {
         return idIt->second;
+    }
 
     // Prefer the requested Collada Id if extant
     std::string idStr;
@@ -1640,36 +1678,42 @@ std::string ColladaExporter::GetNodeUniqueId(const aiNode *node) {
         idStr = node->mName.C_Str();
     }
     // Make sure the requested id is valid
-    if (idStr.empty())
+    if (idStr.empty()) {
         idStr = "node";
-    else
+    } else {
         idStr = XMLIDEncode(idStr);
+    }
 
     // Ensure it's unique
     idStr = MakeUniqueId(mUniqueIds, idStr, std::string());
     mUniqueIds.insert(idStr);
     mNodeIdMap.insert(std::make_pair(node, idStr));
+    
     return idStr;
 }
 
 std::string ColladaExporter::GetNodeName(const aiNode *node) {
-
+    if (node == nullptr) {
+        return std::string();
+    }
     return XMLEscape(node->mName.C_Str());
 }
 
 std::string ColladaExporter::GetBoneUniqueId(const aiBone *bone) {
     // Find the Node that is this Bone
-    const aiNode *boneNode = findBoneNode(mScene->mRootNode, bone);
-    if (boneNode == nullptr)
+    const aiNode *boneNode = mScene->mRootNode->findBoneNode(bone);
+    if (boneNode == nullptr) {
         return std::string();
+    }
 
     return GetNodeUniqueId(boneNode);
 }
 
 std::string ColladaExporter::GetObjectUniqueId(AiObjectType type, size_t pIndex) {
     auto idIt = GetObjectIdMap(type).find(pIndex);
-    if (idIt != GetObjectIdMap(type).cend())
+    if (idIt != GetObjectIdMap(type).cend()) {
         return idIt->second;
+    }
 
     // Not seen this object before, create and add
     NameIdPair result = AddObjectIndexToMaps(type, pIndex);
@@ -1678,8 +1722,9 @@ std::string ColladaExporter::GetObjectUniqueId(AiObjectType type, size_t pIndex)
 
 std::string ColladaExporter::GetObjectName(AiObjectType type, size_t pIndex) {
     auto objectName = GetObjectNameMap(type).find(pIndex);
-    if (objectName != GetObjectNameMap(type).cend())
+    if (objectName != GetObjectNameMap(type).cend()) {
         return objectName->second;
+    }
 
     // Not seen this object before, create and add
     NameIdPair result = AddObjectIndexToMaps(type, pIndex);
@@ -1699,9 +1744,15 @@ ColladaExporter::NameIdPair ColladaExporter::AddObjectIndexToMaps(AiObjectType t
 
     // Get the name and id postfix
     switch (type) {
-    case AiObjectType::Mesh: name = mScene->mMeshes[index]->mName.C_Str(); break;
-    case AiObjectType::Material: name = mScene->mMaterials[index]->GetName().C_Str(); break;
-    case AiObjectType::Animation: name = mScene->mAnimations[index]->mName.C_Str(); break;
+    case AiObjectType::Mesh: 
+        name = mScene->mMeshes[index]->mName.C_Str(); 
+        break;
+    case AiObjectType::Material: 
+        name = mScene->mMaterials[index]->GetName().C_Str(); 
+        break;
+    case AiObjectType::Animation: 
+        name = mScene->mAnimations[index]->mName.C_Str(); 
+        break;
     case AiObjectType::Light:
         name = mScene->mLights[index]->mName.C_Str();
         idPostfix = "-light";
@@ -1710,7 +1761,8 @@ ColladaExporter::NameIdPair ColladaExporter::AddObjectIndexToMaps(AiObjectType t
         name = mScene->mCameras[index]->mName.C_Str();
         idPostfix = "-camera";
         break;
-    case AiObjectType::Count: throw std::logic_error("ColladaExporter::AiObjectType::Count is not an object type");
+    case AiObjectType::Count: 
+        throw std::logic_error("ColladaExporter::AiObjectType::Count is not an object type");
     }
 
     if (name.empty()) {
@@ -1728,8 +1780,9 @@ ColladaExporter::NameIdPair ColladaExporter::AddObjectIndexToMaps(AiObjectType t
         idStr = XMLIDEncode(name);
     }
 
-    if (!name.empty())
+    if (!name.empty()) {
         name = XMLEscape(name);
+    }
 
     idStr = MakeUniqueId(mUniqueIds, idStr, idPostfix);
 
@@ -1743,5 +1796,5 @@ ColladaExporter::NameIdPair ColladaExporter::AddObjectIndexToMaps(AiObjectType t
 
 } // end of namespace Assimp
 
-#endif
-#endif
+#endif // ASSIMP_BUILD_NO_COLLADA_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 1 - 2
code/AssetLib/Collada/ColladaExporter.h

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -72,7 +71,7 @@ public:
     ColladaExporter(const aiScene *pScene, IOSystem *pIOSystem, const std::string &path, const std::string &file);
 
     /// Destructor
-    virtual ~ColladaExporter();
+    virtual ~ColladaExporter() = default;
 
 protected:
     /// Starts writing the contents

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

@@ -1077,6 +1077,7 @@ static float getWeightAtKey(const std::vector<MorphTimeValues> &values, int key,
             return mKey.mWeight;
         }
     }
+    
     // no value at key found, try to interpolate if present at other keys. if not, return zero
     // TODO: interpolation
     return 0.0f;

+ 21 - 18
code/AssetLib/Collada/ColladaLoader.h

@@ -6,7 +6,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -77,8 +76,11 @@ struct ColladaMeshIndex {
     }
 };
 
-/** Loader class to read Collada scenes. Collada is over-engineered to death, with every new iteration bringing
- * more useless stuff, so I limited the data to what I think is useful for games.
+/** 
+ * @brief Loader class to read Collada scenes. 
+ *
+ * Collada is over-engineered to death, with every new iteration bringing  more useless stuff, 
+ * so I limited the data to what I think is useful for games.
 */
 class ColladaLoader : public BaseImporter {
 public:
@@ -102,50 +104,51 @@ protected:
     /// See #BaseImporter::InternReadFile for the details
     void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
 
-    /** Recursively constructs a scene node for the given parser node and returns it. */
+    /// Recursively constructs a scene node for the given parser node and returns it.
     aiNode *BuildHierarchy(const ColladaParser &pParser, const Collada::Node *pNode);
 
-    /** Resolve node instances */
+    /// Resolve node instances
     void ResolveNodeInstances(const ColladaParser &pParser, const Collada::Node *pNode,
             std::vector<const Collada::Node *> &resolved);
 
-    /** Builds meshes for the given node and references them */
+    /// Builds meshes for the given node and references them 
     void BuildMeshesForNode(const ColladaParser &pParser, const Collada::Node *pNode,
             aiNode *pTarget);
 
+    /// Lookup for meshes by their name
     aiMesh *findMesh(const std::string &meshid);
 
-    /** Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh */
+    /// Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh
     aiMesh *CreateMesh(const ColladaParser &pParser, const Collada::Mesh *pSrcMesh, const Collada::SubMesh &pSubMesh,
             const Collada::Controller *pSrcController, size_t pStartVertex, size_t pStartFace);
 
-    /** Builds cameras for the given node and references them */
+    /// Builds cameras for the given node and references them
     void BuildCamerasForNode(const ColladaParser &pParser, const Collada::Node *pNode,
             aiNode *pTarget);
 
-    /** Builds lights for the given node and references them */
+    /// Builds lights for the given node and references them
     void BuildLightsForNode(const ColladaParser &pParser, const Collada::Node *pNode,
             aiNode *pTarget);
 
-    /** Stores all meshes in the given scene */
+    /// Stores all meshes in the given scene
     void StoreSceneMeshes(aiScene *pScene);
 
-    /** Stores all materials in the given scene */
+    /// Stores all materials in the given scene
     void StoreSceneMaterials(aiScene *pScene);
 
-    /** Stores all lights in the given scene */
+    /// Stores all lights in the given scene
     void StoreSceneLights(aiScene *pScene);
 
-    /** Stores all cameras in the given scene */
+    /// Stores all cameras in the given scene
     void StoreSceneCameras(aiScene *pScene);
 
-    /** Stores all textures in the given scene */
+    /// Stores all textures in the given scene
     void StoreSceneTextures(aiScene *pScene);
 
-    /** Stores all animations
-     * @param pScene target scene to store the anims
-     */
-    void StoreAnimations(aiScene *pScene, const ColladaParser &pParser);
+    /// Stores all animations
+    /// @param pScene   Target scene to store the anims
+    /// @param parser   The collada parser
+    void StoreAnimations(aiScene *pScene, const ColladaParser &parser);
 
     /** Stores all animations for the given source anim and its nested child animations
      * @param pScene target scene to store the anims

+ 8 - 19
code/AssetLib/DXF/DXFHelper.h

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -62,10 +61,7 @@ namespace DXF {
 // do NOT skip empty lines. In DXF files, they count as valid data.
 class LineReader {
 public:
-    LineReader(StreamReaderLE& reader)
-    : splitter(reader,false,true)
-    , groupcode( 0 )
-    , end() {
+    LineReader(StreamReaderLE& reader) : splitter(reader,false,true), groupcode( 0 ), end() {
         // empty
     }
 
@@ -165,8 +161,7 @@ private:
 
 // represents a POLYLINE or a LWPOLYLINE. or even a 3DFACE The data is converted as needed.
 struct PolyLine {
-    PolyLine()
-    : flags() {
+    PolyLine() : flags() {
         // empty
     }
 
@@ -182,10 +177,7 @@ struct PolyLine {
 
 // reference to a BLOCK. Specifies its own coordinate system.
 struct InsertBlock {
-    InsertBlock()
-    : pos()
-    , scale(1.f,1.f,1.f)
-    , angle() {
+    InsertBlock() : pos(0.f, 0.f, 0.f), scale(1.f,1.f,1.f), angle(0.0f) {
         // empty
     }
 
@@ -198,8 +190,7 @@ struct InsertBlock {
 
 
 // keeps track of all geometry in a single BLOCK.
-struct Block
-{
+struct Block {
     std::vector< std::shared_ptr<PolyLine> > lines;
     std::vector<InsertBlock> insertions;
 
@@ -207,14 +198,12 @@ struct Block
     aiVector3D base;
 };
 
-
-struct FileData
-{
+struct FileData {
     // note: the LAST block always contains the stuff from ENTITIES.
     std::vector<Block> blocks;
 };
 
-}
-} // Namespace Assimp
+} // namespace DXF
+} // namespace Assimp
 
-#endif
+#endif // INCLUDED_DXFHELPER_H

+ 22 - 20
code/AssetLib/FBX/FBXAnimation.cpp

@@ -143,31 +143,33 @@ AnimationCurveNode::AnimationCurveNode(uint64_t id, const Element &element, cons
 
 // ------------------------------------------------------------------------------------------------
 const AnimationCurveMap &AnimationCurveNode::Curves() const {
-    if (curves.empty()) {
-        // resolve attached animation curves
-        const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationCurve");
-
-        for (const Connection *con : conns) {
+    if (!curves.empty()) {
+        return curves;
+    }
+        
+    // resolve attached animation curves
+    const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationCurve");
 
-            // link should go for a property
-            if (!con->PropertyName().length()) {
-                continue;
-            }
+    for (const Connection *con : conns) {
 
-            const Object *const ob = con->SourceObject();
-            if (nullptr == ob) {
-                DOMWarning("failed to read source object for AnimationCurve->AnimationCurveNode link, ignoring", &element);
-                continue;
-            }
+        // link should go for a property
+        if (!con->PropertyName().length()) {
+            continue;
+        }
 
-            const AnimationCurve *const anim = dynamic_cast<const AnimationCurve *>(ob);
-            if (nullptr == anim) {
-                DOMWarning("source object for ->AnimationCurveNode link is not an AnimationCurve", &element);
-                continue;
-            }
+        const Object *const ob = con->SourceObject();
+        if (nullptr == ob) {
+            DOMWarning("failed to read source object for AnimationCurve->AnimationCurveNode link, ignoring", &element);
+            continue;
+        }
 
-            curves[con->PropertyName()] = anim;
+        const AnimationCurve *const anim = dynamic_cast<const AnimationCurve *>(ob);
+        if (nullptr == anim) {
+            DOMWarning("source object for ->AnimationCurveNode link is not an AnimationCurve", &element);
+            continue;
         }
+
+        curves[con->PropertyName()] = anim;
     }
 
     return curves;

+ 9 - 52
code/AssetLib/FBX/FBXBinaryTokenizer.cpp

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -60,58 +59,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp {
 namespace FBX {
 
-//enum Flag
-//{
-//   e_unknown_0 = 1 << 0,
-//   e_unknown_1 = 1 << 1,
-//   e_unknown_2 = 1 << 2,
-//   e_unknown_3 = 1 << 3,
-//   e_unknown_4 = 1 << 4,
-//   e_unknown_5 = 1 << 5,
-//   e_unknown_6 = 1 << 6,
-//   e_unknown_7 = 1 << 7,
-//   e_unknown_8 = 1 << 8,
-//   e_unknown_9 = 1 << 9,
-//   e_unknown_10 = 1 << 10,
-//   e_unknown_11 = 1 << 11,
-//   e_unknown_12 = 1 << 12,
-//   e_unknown_13 = 1 << 13,
-//   e_unknown_14 = 1 << 14,
-//   e_unknown_15 = 1 << 15,
-//   e_unknown_16 = 1 << 16,
-//   e_unknown_17 = 1 << 17,
-//   e_unknown_18 = 1 << 18,
-//   e_unknown_19 = 1 << 19,
-//   e_unknown_20 = 1 << 20,
-//   e_unknown_21 = 1 << 21,
-//   e_unknown_22 = 1 << 22,
-//   e_unknown_23 = 1 << 23,
-//   e_flag_field_size_64_bit = 1 << 24, // Not sure what is
-//   e_unknown_25 = 1 << 25,
-//   e_unknown_26 = 1 << 26,
-//   e_unknown_27 = 1 << 27,
-//   e_unknown_28 = 1 << 28,
-//   e_unknown_29 = 1 << 29,
-//   e_unknown_30 = 1 << 30,
-//   e_unknown_31 = 1 << 31
-//};
-//
-//bool check_flag(uint32_t flags, Flag to_check)
-//{
-//	return (flags & to_check) != 0;
-//}
 // ------------------------------------------------------------------------------------------------
-Token::Token(const char* sbegin, const char* send, TokenType type, size_t offset)
-    :
-    #ifdef DEBUG
-    contents(sbegin, static_cast<size_t>(send-sbegin)),
-    #endif
-    sbegin(sbegin)
-    , send(send)
-    , type(type)
-    , line(offset)
-    , column(BINARY_MARKER)
-{
+Token::Token(const char* sbegin, const char* send, TokenType type, size_t offset) :
+        #ifdef DEBUG
+        contents(sbegin, static_cast<size_t>(send-sbegin)),
+        #endif
+        sbegin(sbegin),
+        send(send),
+        type(type),
+        line(offset),
+        column(BINARY_MARKER) {
     ai_assert(sbegin);
     ai_assert(send);
 

+ 2 - 1
code/AssetLib/FBX/FBXCommon.h

@@ -51,7 +51,8 @@ namespace Assimp {
 namespace FBX {
 
 static constexpr size_t NumNullRecords = 25;
-const char NULL_RECORD[NumNullRecords] = { // 25 null bytes in 64-bit and 13 null bytes in 32-bit
+
+constexpr char NULL_RECORD[NumNullRecords] = { // 25 null bytes in 64-bit and 13 null bytes in 32-bit
     '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
     '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'
 }; // who knows why, it looks like two integers 32/64 bit (compressed and uncompressed sizes?) + 1 byte (might be compression type?)

+ 36 - 27
code/AssetLib/FBX/FBXConverter.cpp

@@ -181,7 +181,9 @@ FBXConverter::FBXConverter(aiScene *out, const Document &doc, bool removeEmptyBo
     if (out->mNumMeshes == 0) {
         out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
     } else {
-        correctRootTransform(mSceneOut);
+        // Apply the FBX axis metadata unless requested not to
+        if (!doc.Settings().ignoreUpDirection)
+            correctRootTransform(mSceneOut);
     }
 }
 
@@ -245,7 +247,7 @@ struct FBXConverter::PotentialNode {
 /// todo: get bone from stack
 /// todo: make map of aiBone* to aiNode*
 /// then update convert clusters to the new format
-void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) {
+void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node, const aiMatrix4x4& parent_transform) {
     const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(id, "Model");
 
     std::vector<PotentialNode> nodes;
@@ -276,7 +278,7 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node)
         if (nullptr != model) {
             nodes_chain.clear();
             post_nodes_chain.clear();
-            aiMatrix4x4 new_abs_transform = parent->mTransformation;
+            aiMatrix4x4 new_abs_transform = parent_transform;
             std::string node_name = FixNodeName(model->Name());
             // even though there is only a single input node, the design of
             // assimp (or rather: the complicated transformation chain that
@@ -310,6 +312,8 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node)
 
                 child->mParent = last_parent;
                 last_parent = child.mNode;
+
+                new_abs_transform *= child->mTransformation;
             }
 
             // attach geometry
@@ -332,6 +336,8 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node)
 
                     postnode->mParent = last_parent;
                     last_parent = postnode.mNode;
+
+                    new_abs_transform *= postnode->mTransformation;
                 }
             } else {
                 // free the nodes we allocated as we don't need them
@@ -339,7 +345,7 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node)
             }
 
             // recursion call - child nodes
-            ConvertNodes(model->ID(), last_parent, root_node);
+            ConvertNodes(model->ID(), last_parent, root_node, new_abs_transform);
 
             if (doc.Settings().readLights) {
                 ConvertLights(*model, node_name);
@@ -357,12 +363,12 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node)
     if (nodes.empty()) {
         parent->mNumChildren = 0;
         parent->mChildren = nullptr;
-    }
-
-    parent->mChildren = new aiNode *[nodes.size()]();
-    parent->mNumChildren = static_cast<unsigned int>(nodes.size());
-    for (unsigned int i = 0; i < nodes.size(); ++i) {
-        parent->mChildren[i] = nodes[i].mOwnership.release();
+    } else {
+        parent->mChildren = new aiNode *[nodes.size()]();
+        parent->mNumChildren = static_cast<unsigned int>(nodes.size());
+        for (unsigned int i = 0; i < nodes.size(); ++i) {
+            parent->mChildren[i] = nodes[i].mOwnership.release();
+        }
     }
 }
 
@@ -432,7 +438,8 @@ void FBXConverter::ConvertLight(const Light &light, const std::string &orig_name
             out_light->mType = aiLightSource_UNDEFINED;
             break;
         default:
-            ai_assert(false);
+            FBXImporter::LogError("Not handled light type: ", light.LightType());
+            break;
     }
 
     float decay = light.DecayStart();
@@ -457,7 +464,7 @@ void FBXConverter::ConvertLight(const Light &light, const std::string &orig_name
             out_light->mAttenuationQuadratic = 1.0f;
             break;
         default:
-            ai_assert(false);
+            FBXImporter::LogError("Not handled light decay type: ", light.DecayType());
             break;
     }
 }
@@ -595,7 +602,7 @@ const char *FBXConverter::NameTransformationCompProperty(TransformationComp comp
             return "GeometricRotationInverse";
         case TransformationComp_GeometricTranslationInverse:
             return "GeometricTranslationInverse";
-        case TransformationComp_MAXIMUM: // this is to silence compiler warnings
+        case TransformationComp_MAXIMUM:
             break;
     }
 
@@ -711,8 +718,7 @@ bool FBXConverter::NeedsComplexTransformationChain(const Model &model) {
     for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
         const TransformationComp comp = static_cast<TransformationComp>(i);
 
-        if (comp == TransformationComp_Rotation || comp == TransformationComp_Scaling || comp == TransformationComp_Translation ||
-            comp == TransformationComp_PreRotation || comp == TransformationComp_PostRotation) {
+        if (comp == TransformationComp_Rotation || comp == TransformationComp_Scaling || comp == TransformationComp_Translation) {
             continue;
         }
 
@@ -1248,9 +1254,9 @@ unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, c
         for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) {
             const auto& shapeGeometries = blendShapeChannel->GetShapeGeometries();
             for (const ShapeGeometry *shapeGeometry : shapeGeometries) {
-                aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh);
-                const auto &curVertices = shapeGeometry->GetVertices();
                 const auto &curNormals = shapeGeometry->GetNormals();
+                aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh, true, !curNormals.empty());
+                const auto &curVertices = shapeGeometry->GetVertices();
                 const auto &curIndices = shapeGeometry->GetIndices();
                 //losing channel name if using shapeGeometry->Name()
                 // if blendShapeChannel Name is empty or doesn't have a ".", add geoMetryName;
@@ -1266,7 +1272,7 @@ unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, c
                 for (size_t j = 0; j < curIndices.size(); j++) {
                     const unsigned int curIndex = curIndices.at(j);
                     aiVector3D vertex = curVertices.at(j);
-                    aiVector3D normal = curNormals.at(j);
+                    aiVector3D normal = curNormals.empty() ? aiVector3D() : curNormals.at(j);
                     unsigned int count = 0;
                     const unsigned int *outIndices = mesh.ToOutputVertexIndex(curIndex, count);
                     for (unsigned int k = 0; k < count; k++) {
@@ -1486,15 +1492,15 @@ unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, co
         for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) {
             const auto& shapeGeometries = blendShapeChannel->GetShapeGeometries();
             for (const ShapeGeometry *shapeGeometry : shapeGeometries) {
-                aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh);
-                const auto& curVertices = shapeGeometry->GetVertices();
                 const auto& curNormals = shapeGeometry->GetNormals();
+                aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh, true, !curNormals.empty());
+                const auto& curVertices = shapeGeometry->GetVertices();
                 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);
                     aiVector3D vertex = curVertices.at(j);
-                    aiVector3D normal = curNormals.at(j);
+                    aiVector3D normal = curNormals.empty() ? aiVector3D() : curNormals.at(j);
                     unsigned int count = 0;
                     const unsigned int *outIndices = mesh.ToOutputVertexIndex(curIndex, count);
                     for (unsigned int k = 0; k < count; k++) {
@@ -1670,14 +1676,14 @@ void FBXConverter::ConvertCluster(std::vector<aiBone*> &local_mesh_bones, const
 
         //bone->mOffsetMatrix = cluster->Transform();
         // store local transform link for post processing
-        
+
         bone->mOffsetMatrix = cluster->TransformLink();
         bone->mOffsetMatrix.Inverse();
 
         const aiMatrix4x4 matrix = (aiMatrix4x4)absolute_transform;
 
         bone->mOffsetMatrix = bone->mOffsetMatrix * matrix; // * mesh_offset
-        
+
         //
         // Now calculate the aiVertexWeights
         //
@@ -2128,6 +2134,10 @@ void FBXConverter::SetTextureProperties(aiMaterial *out_mat, const TextureMap &_
     TrySetTextureProperties(out_mat, _textures, "Maya|emissionColor", aiTextureType_EMISSION_COLOR, mesh);
     TrySetTextureProperties(out_mat, _textures, "Maya|metalness", aiTextureType_METALNESS, mesh);
     TrySetTextureProperties(out_mat, _textures, "Maya|diffuseRoughness", aiTextureType_DIFFUSE_ROUGHNESS, mesh);
+    TrySetTextureProperties(out_mat, _textures, "Maya|base", aiTextureType_MAYA_BASE, mesh);
+    TrySetTextureProperties(out_mat, _textures, "Maya|specular", aiTextureType_MAYA_SPECULAR, mesh);
+    TrySetTextureProperties(out_mat, _textures, "Maya|specularColor", aiTextureType_MAYA_SPECULAR_COLOR, mesh);
+    TrySetTextureProperties(out_mat, _textures, "Maya|specularRoughness", aiTextureType_MAYA_SPECULAR_ROUGHNESS, mesh);
 
     // Maya stingray
     TrySetTextureProperties(out_mat, _textures, "Maya|TEX_color_map", aiTextureType_BASE_COLOR, mesh);
@@ -3406,7 +3416,7 @@ FBXConverter::KeyFrameListList FBXConverter::GetRotationKeyframeList(const std::
     KeyFrameListList inputs;
     inputs.reserve(nodes.size() * 3);
 
-    //give some breathing room for rounding errors
+    // give some breathing room for rounding errors
     const int64_t adj_start = start - 10000;
     const int64_t adj_stop = stop + 10000;
 
@@ -3432,7 +3442,7 @@ FBXConverter::KeyFrameListList FBXConverter::GetRotationKeyframeList(const std::
             ai_assert(curve->GetKeys().size() == curve->GetValues().size());
             ai_assert(curve->GetKeys().size());
 
-            //get values within the start/stop time window
+            // get values within the start/stop time window
             std::shared_ptr<KeyTimeList> Keys(new KeyTimeList());
             std::shared_ptr<KeyValueList> Values(new KeyValueList());
             const size_t count = curve->GetKeys().size();
@@ -3452,8 +3462,7 @@ FBXConverter::KeyFrameListList FBXConverter::GetRotationKeyframeList(const std::
                         if (tnew >= adj_start && tnew <= adj_stop) {
                             Keys->push_back(tnew);
                             Values->push_back(vnew);
-                        }
-                        else {
+                        } else {
                             // Something broke
                             break;
                         }

+ 2 - 3
code/AssetLib/FBX/FBXConverter.h

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -70,7 +69,7 @@ struct morphKeyData {
     std::vector<unsigned int> values;
     std::vector<float> weights;
 };
-typedef std::map<int64_t, morphKeyData*> morphAnimData;
+using morphAnimData = std::map<int64_t, morphKeyData*> ;
 
 namespace Assimp {
 namespace FBX {
@@ -134,7 +133,7 @@ private:
 
     // ------------------------------------------------------------------------------------------------
     // collect and assign child nodes
-    void ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node);
+    void ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node, const aiMatrix4x4& parent_transform = aiMatrix4x4());
 
     // ------------------------------------------------------------------------------------------------
     void ConvertLights(const Model& model, const std::string &orig_name );

+ 5 - 18
code/AssetLib/FBX/FBXDeformer.cpp

@@ -65,9 +65,6 @@ Deformer::Deformer(uint64_t id, const Element& element, const Document& doc, con
     props = GetPropertyTable(doc,"Deformer.Fbx" + classname,element,sc,true);
 }
 
-// ------------------------------------------------------------------------------------------------
-Deformer::~Deformer() = default;
-
 // ------------------------------------------------------------------------------------------------
 Cluster::Cluster(uint64_t id, const Element& element, const Document& doc, const std::string& name)
 : Deformer(id,element,doc,name)
@@ -113,10 +110,6 @@ Cluster::Cluster(uint64_t id, const Element& element, const Document& doc, const
     }
 }
 
-
-// ------------------------------------------------------------------------------------------------
-Cluster::~Cluster() = default;
-
 // ------------------------------------------------------------------------------------------------
 Skin::Skin(uint64_t id, const Element& element, const Document& doc, const std::string& name)
 : Deformer(id,element,doc,name)
@@ -142,9 +135,6 @@ Skin::Skin(uint64_t id, const Element& element, const Document& doc, const std::
     }
 }
 
-
-// ------------------------------------------------------------------------------------------------
-Skin::~Skin() = default;
 // ------------------------------------------------------------------------------------------------
 BlendShape::BlendShape(uint64_t id, const Element& element, const Document& doc, const std::string& name)
     : Deformer(id, element, doc, name)
@@ -161,8 +151,7 @@ BlendShape::BlendShape(uint64_t id, const Element& element, const Document& doc,
         }
     }
 }
-// ------------------------------------------------------------------------------------------------
-BlendShape::~BlendShape() = default;
+
 // ------------------------------------------------------------------------------------------------
 BlendShapeChannel::BlendShapeChannel(uint64_t id, const Element& element, const Document& doc, const std::string& name)
     : Deformer(id, element, doc, name)
@@ -188,10 +177,8 @@ BlendShapeChannel::BlendShapeChannel(uint64_t id, const Element& element, const
         }
     }
 }
-// ------------------------------------------------------------------------------------------------
-BlendShapeChannel::~BlendShapeChannel() = default;
-// ------------------------------------------------------------------------------------------------
-}
-}
-#endif
 
+} // namespace FBX
+} // Namespace Assimp
+
+#endif // ASSIMP_BUILD_NO_FBX_IMPORTER

+ 8 - 0
code/AssetLib/FBX/FBXDocument.cpp

@@ -663,6 +663,10 @@ LazyObject& Connection::LazyDestinationObject() const {
 const Object* Connection::SourceObject() const {
     LazyObject* const lazy = doc.GetObject(src);
     ai_assert(lazy);
+    if (lazy == nullptr) {
+        return nullptr;
+    }
+
     return lazy->Get();
 }
 
@@ -670,6 +674,10 @@ const Object* Connection::SourceObject() const {
 const Object* Connection::DestinationObject() const {
     LazyObject* const lazy = doc.GetObject(dest);
     ai_assert(lazy);
+    if (lazy == nullptr) {
+        return nullptr;
+    }
+
     return lazy->Get();
 }
 

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

@@ -657,11 +657,11 @@ private:
 };
 
 /** DOM class for generic FBX materials */
-class Material : public Object {
+class Material final : public Object {
 public:
     Material(uint64_t id, const Element& element, const Document& doc, const std::string& name);
 
-    virtual ~Material();
+    ~Material() override = default;
 
     const std::string& GetShadingModel() const {
         return shading;
@@ -835,7 +835,7 @@ private:
 class Deformer : public Object {
 public:
     Deformer(uint64_t id, const Element& element, const Document& doc, const std::string& name);
-    virtual ~Deformer();
+    virtual ~Deformer() = default;
 
     const PropertyTable& Props() const {
         ai_assert(props.get());
@@ -855,7 +855,7 @@ class BlendShapeChannel : public Deformer {
 public:
     BlendShapeChannel(uint64_t id, const Element& element, const Document& doc, const std::string& name);
 
-    virtual ~BlendShapeChannel();
+    virtual ~BlendShapeChannel() = default;
 
     float DeformPercent() const {
         return percent;
@@ -880,7 +880,7 @@ class BlendShape : public Deformer {
 public:
     BlendShape(uint64_t id, const Element& element, const Document& doc, const std::string& name);
 
-    virtual ~BlendShape();
+    virtual ~BlendShape() = default;
 
     const std::unordered_set<const BlendShapeChannel*>& BlendShapeChannels() const {
         return blendShapeChannels;
@@ -895,7 +895,7 @@ class Cluster : public Deformer {
 public:
     Cluster(uint64_t id, const Element& element, const Document& doc, const std::string& name);
 
-    virtual ~Cluster();
+    virtual ~Cluster() = default;
 
     /** get the list of deformer weights associated with this cluster.
      *  Use #GetIndices() to get the associated vertices. Both arrays
@@ -939,7 +939,7 @@ class Skin : public Deformer {
 public:
     Skin(uint64_t id, const Element& element, const Document& doc, const std::string& name);
 
-    virtual ~Skin();
+    virtual ~Skin() = default;
 
     float DeformAccuracy() const {
         return accuracy;

+ 10 - 15
code/AssetLib/FBX/FBXDocumentUtil.cpp

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -36,7 +35,6 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
 ----------------------------------------------------------------------
 */
 
@@ -81,8 +79,7 @@ void DOMWarning(const std::string& message, const Token& token) {
 }
 
 // ------------------------------------------------------------------------------------------------
-void DOMWarning(const std::string& message, const Element* element /*= nullptr*/)
-{
+void DOMWarning(const std::string& message, const Element* element /*= nullptr*/) {
     if(element) {
         DOMWarning(message,element->KeyToken());
         return;
@@ -92,41 +89,39 @@ void DOMWarning(const std::string& message, const Element* element /*= nullptr*/
     }
 }
 
-
 // ------------------------------------------------------------------------------------------------
 // fetch a property table and the corresponding property template
 std::shared_ptr<const PropertyTable> GetPropertyTable(const Document& doc,
-    const std::string& templateName,
-    const Element &element,
-    const Scope& sc,
-    bool no_warn /*= false*/)
-{
+        const std::string& templateName,
+        const Element &element,
+        const Scope& sc,
+        bool no_warn /*= false*/) {
     const Element* const Properties70 = sc["Properties70"];
     std::shared_ptr<const PropertyTable> templateProps = std::shared_ptr<const PropertyTable>(
             static_cast<const PropertyTable *>(nullptr));
 
-    if(templateName.length()) {
+    if (templateName.length()) {
         PropertyTemplateMap::const_iterator it = doc.Templates().find(templateName);
         if(it != doc.Templates().end()) {
             templateProps = (*it).second;
         }
     }
 
-    if(!Properties70 || !Properties70->Compound()) {
+    if (!Properties70 || !Properties70->Compound()) {
         if(!no_warn) {
             DOMWarning("property table (Properties70) not found",&element);
         }
         if(templateProps) {
             return templateProps;
-        }
-        else {
+        } else {
             return std::make_shared<const PropertyTable>();
         }
     }
     return std::make_shared<const PropertyTable>(*Properties70,templateProps);
 }
+
 } // !Util
 } // !FBX
 } // !Assimp
 
-#endif
+#endif // ASSIMP_BUILD_NO_FBX_IMPORTER

+ 1 - 1
code/AssetLib/FBX/FBXDocumentUtil.h

@@ -114,4 +114,4 @@ inline const T* ProcessSimpleConnection(const Connection& con,
 } //!FBX
 } //!Assimp
 
-#endif
+#endif // ASSIMP_BUILD_NO_FBX_IMPORTER

+ 13 - 20
code/AssetLib/FBX/FBXExportNode.cpp

@@ -35,7 +35,6 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
 ----------------------------------------------------------------------
 */
 #ifndef ASSIMP_BUILD_NO_EXPORT
@@ -55,37 +54,31 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <memory> // shared_ptr
 
 namespace Assimp {
+
 // AddP70<type> helpers... there's no usable pattern here,
 // so all are defined as separate functions.
 // Even "animatable" properties are often completely different
 // from the standard (nonanimated) property definition,
 // so they are specified with an 'A' suffix.
 
-void FBX::Node::AddP70int(
-    const std::string& cur_name, int32_t value
-) {
+void FBX::Node::AddP70int(const std::string& cur_name, int32_t value) {
     FBX::Node n("P");
     n.AddProperties(cur_name, "int", "Integer", "", value);
     AddChild(n);
 }
 
-void FBX::Node::AddP70bool(
-    const std::string& cur_name, bool value
-) {
+void FBX::Node::AddP70bool(const std::string& cur_name, bool value) {
     FBX::Node n("P");
     n.AddProperties(cur_name, "bool", "", "", int32_t(value));
     AddChild(n);
 }
 
-void FBX::Node::AddP70double(
-        const std::string &cur_name, double value) {
-    FBX::Node n("P");
+void FBX::Node::AddP70double(const std::string &cur_name, double value) {    FBX::Node n("P");
     n.AddProperties(cur_name, "double", "Number", "", value);
     AddChild(n);
 }
 
-void FBX::Node::AddP70numberA(
-        const std::string &cur_name, double value) {
+void FBX::Node::AddP70numberA(const std::string &cur_name, double value) {
     FBX::Node n("P");
     n.AddProperties(cur_name, "Number", "", "A", value);
     AddChild(n);
@@ -405,8 +398,7 @@ void FBX::Node::DumpChildrenAscii(std::ostream& s, int indent)
     }
 }
 
-void FBX::Node::EndAscii(std::ostream& s, int indent, bool has_children)
-{
+void FBX::Node::EndAscii(std::ostream& s, int indent, bool has_children) {
     if (!has_children) { return; } // nothing to do
     s << '\n';
     for (int i = 0; i < indent; ++i) { s << '\t'; }
@@ -417,11 +409,10 @@ void FBX::Node::EndAscii(std::ostream& s, int indent, bool has_children)
 
 // ascii property node from vector of doubles
 void FBX::Node::WritePropertyNodeAscii(
-    const std::string& name,
-    const std::vector<double>& v,
-    Assimp::StreamWriterLE& s,
-    int indent
-){
+        const std::string& name,
+        const std::vector<double>& v,
+        Assimp::StreamWriterLE& s,
+        int indent){
     char buffer[32];
     FBX::Node node(name);
     node.Begin(s, false, indent);
@@ -556,6 +547,8 @@ void FBX::Node::WritePropertyNode(
         FBX::Node::WritePropertyNodeAscii(name, v, s, indent);
     }
 }
-}
+
+} // namespace Assimp
+
 #endif // ASSIMP_BUILD_NO_FBX_EXPORTER
 #endif // ASSIMP_BUILD_NO_EXPORT

+ 26 - 35
code/AssetLib/FBX/FBXExportNode.h

@@ -35,7 +35,6 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
 ----------------------------------------------------------------------
 */
 
@@ -70,7 +69,6 @@ public:
     // some nodes always pretend they have children...
     bool force_has_children = false;
 
-public: // constructors
     /// The default class constructor.
     Node() = default;
 
@@ -89,7 +87,6 @@ public: // constructors
         AddProperties(std::forward<More>(more)...);
     }
 
-public: // functions to add properties or children
     // add a single property to the node
     template <typename T>
     void AddProperty(T&& value) {
@@ -118,8 +115,6 @@ public: // functions to add properties or children
         children.push_back(std::move(c));
     }
 
-public: // support specifically for dealing with Properties70 nodes
-
     // it really is simpler to make these all separate functions.
     // the versions with 'A' suffixes are for animatable properties.
     // those often follow a completely different format internally in FBX.
@@ -150,8 +145,6 @@ public: // support specifically for dealing with Properties70 nodes
         AddChild(n);
     }
 
-public: // member functions for writing data to a file or stream
-
     // write the full node to the given file or stream
     void Dump(
             const std::shared_ptr<Assimp::IOStream> &outfile,
@@ -175,31 +168,6 @@ public: // member functions for writing data to a file or stream
         bool has_children
     );
 
-private: // internal functions used for writing
-
-    void DumpBinary(Assimp::StreamWriterLE &s);
-    void DumpAscii(Assimp::StreamWriterLE &s, int indent);
-    void DumpAscii(std::ostream &s, int indent);
-
-    void BeginBinary(Assimp::StreamWriterLE &s);
-    void DumpPropertiesBinary(Assimp::StreamWriterLE& s);
-    void EndPropertiesBinary(Assimp::StreamWriterLE &s);
-    void EndPropertiesBinary(Assimp::StreamWriterLE &s, size_t num_properties);
-    void DumpChildrenBinary(Assimp::StreamWriterLE& s);
-    void EndBinary(Assimp::StreamWriterLE &s, bool has_children);
-
-    void BeginAscii(std::ostream &s, int indent);
-    void DumpPropertiesAscii(std::ostream &s, int indent);
-    void BeginChildrenAscii(std::ostream &s, int indent);
-    void DumpChildrenAscii(std::ostream &s, int indent);
-    void EndAscii(std::ostream &s, int indent, bool has_children);
-
-private: // data used for binary dumps
-    size_t start_pos; // starting position in stream
-    size_t end_pos; // ending position in stream
-    size_t property_start; // starting position of property section
-
-public: // static member functions
 
     // convenience function to create a node with a single property,
     // and write it to the stream.
@@ -235,7 +203,26 @@ public: // static member functions
         bool binary, int indent
     );
 
-private: // static helper functions
+private: // internal functions used for writing
+
+    void DumpBinary(Assimp::StreamWriterLE &s);
+    void DumpAscii(Assimp::StreamWriterLE &s, int indent);
+    void DumpAscii(std::ostream &s, int indent);
+
+    void BeginBinary(Assimp::StreamWriterLE &s);
+    void DumpPropertiesBinary(Assimp::StreamWriterLE& s);
+    void EndPropertiesBinary(Assimp::StreamWriterLE &s);
+    void EndPropertiesBinary(Assimp::StreamWriterLE &s, size_t num_properties);
+    void DumpChildrenBinary(Assimp::StreamWriterLE& s);
+    void EndBinary(Assimp::StreamWriterLE &s, bool has_children);
+
+    void BeginAscii(std::ostream &s, int indent);
+    void DumpPropertiesAscii(std::ostream &s, int indent);
+    void BeginChildrenAscii(std::ostream &s, int indent);
+    void DumpChildrenAscii(std::ostream &s, int indent);
+    void EndAscii(std::ostream &s, int indent, bool has_children);
+    
+    // static helper functions
     static void WritePropertyNodeAscii(
         const std::string& name,
         const std::vector<double>& v,
@@ -259,9 +246,13 @@ private: // static helper functions
         Assimp::StreamWriterLE& s
     );
 
+private: // data used for binary dumps
+    size_t start_pos; // starting position in stream
+    size_t end_pos; // ending position in stream
+    size_t property_start; // starting position of property section
 };
-}
 
-#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+} // Namespace Assimp
 
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
 #endif // AI_FBXEXPORTNODE_H_INC

+ 327 - 302
code/AssetLib/FBX/FBXExporter.cpp

@@ -370,12 +370,6 @@ void FBXExporter::WriteHeaderExtension ()
         "Creator", creator.str(), outstream, binary, indent
     );
 
-    //FBX::Node sceneinfo("SceneInfo");
-    //sceneinfo.AddProperty("GlobalInfo" + FBX::SEPARATOR + "SceneInfo");
-    // not sure if any of this is actually needed,
-    // so just write an empty node for now.
-    //sceneinfo.Dump(outstream, binary, indent);
-
     indent = 0;
 
     // finish node
@@ -459,11 +453,7 @@ void WritePropString(const aiScene* scene, FBX::Node& p, const std::string& key,
     }
 }
 
-void FBXExporter::WriteGlobalSettings ()
-{
-    if (!binary) {
-        // no title, follows directly from the header extension
-    }
+void FBXExporter::WriteGlobalSettings () {
     FBX::Node gs("GlobalSettings");
     gs.AddChild("Version", int32_t(1000));
 
@@ -493,8 +483,7 @@ void FBXExporter::WriteGlobalSettings ()
     gs.Dump(outfile, binary, 0);
 }
 
-void FBXExporter::WriteDocuments ()
-{
+void FBXExporter::WriteDocuments() {
     if (!binary) {
         WriteAsciiSectionHeader("Documents Description");
     }
@@ -523,8 +512,7 @@ void FBXExporter::WriteDocuments ()
     docs.Dump(outfile, binary, 0);
 }
 
-void FBXExporter::WriteReferences ()
-{
+void FBXExporter::WriteReferences() {
     if (!binary) {
         WriteAsciiSectionHeader("Document References");
     }
@@ -540,7 +528,6 @@ void FBXExporter::WriteReferences ()
 // some internal helper functions used for writing the definitions
 // (before any actual data is written)
 // ---------------------------------------------------------------
-
 size_t count_nodes(const aiNode* n, const aiNode* root) {
     size_t count;
     if (n == root) {
@@ -556,8 +543,7 @@ size_t count_nodes(const aiNode* n, const aiNode* root) {
     return count;
 }
 
-bool has_phong_mat(const aiScene* scene)
-{
+static bool has_phong_mat(const aiScene* scene) {
     // just search for any material with a shininess exponent
     for (size_t i = 0; i < scene->mNumMaterials; ++i) {
         aiMaterial* mat = scene->mMaterials[i];
@@ -570,16 +556,12 @@ bool has_phong_mat(const aiScene* scene)
     return false;
 }
 
-size_t count_images(const aiScene* scene) {
+static size_t count_images(const aiScene* scene) {
     std::unordered_set<std::string> images;
     aiString texpath;
     for (size_t i = 0; i < scene->mNumMaterials; ++i) {
-        aiMaterial* mat = scene->mMaterials[i];
-        for (
-            size_t tt = aiTextureType_DIFFUSE;
-            tt < aiTextureType_UNKNOWN;
-            ++tt
-        ){
+        aiMaterial *mat = scene->mMaterials[i];
+        for (size_t tt = aiTextureType_DIFFUSE; tt < aiTextureType_UNKNOWN; ++tt) {
             const aiTextureType textype = static_cast<aiTextureType>(tt);
             const size_t texcount = mat->GetTextureCount(textype);
             for (unsigned int j = 0; j < texcount; ++j) {
@@ -588,10 +570,11 @@ size_t count_images(const aiScene* scene) {
             }
         }
     }
+
     return images.size();
 }
 
-size_t count_textures(const aiScene* scene) {
+static size_t count_textures(const aiScene* scene) {
     size_t count = 0;
     for (size_t i = 0; i < scene->mNumMaterials; ++i) {
         aiMaterial* mat = scene->mMaterials[i];
@@ -609,7 +592,7 @@ size_t count_textures(const aiScene* scene) {
     return count;
 }
 
-size_t count_deformers(const aiScene* scene) {
+static size_t count_deformers(const aiScene* scene) {
     size_t count = 0;
     for (size_t i = 0; i < scene->mNumMeshes; ++i) {
         const size_t n = scene->mMeshes[i]->mNumBones;
@@ -621,8 +604,7 @@ size_t count_deformers(const aiScene* scene) {
     return count;
 }
 
-void FBXExporter::WriteDefinitions ()
-{
+void FBXExporter::WriteDefinitions () {
     // basically this is just bookkeeping:
     // determining how many of each type of object there are
     // and specifying the base properties to use when otherwise unspecified.
@@ -1033,9 +1015,7 @@ void FBXExporter::WriteDefinitions ()
 // some internal helper functions used for writing the objects section
 // (which holds the actual data)
 // -------------------------------------------------------------------
-
-aiNode* get_node_for_mesh(unsigned int meshIndex, aiNode* node)
-{
+static aiNode* get_node_for_mesh(unsigned int meshIndex, aiNode* node) {
     for (size_t i = 0; i < node->mNumMeshes; ++i) {
         if (node->mMeshes[i] == meshIndex) {
             return node;
@@ -1048,8 +1028,7 @@ aiNode* get_node_for_mesh(unsigned int meshIndex, aiNode* node)
     return nullptr;
 }
 
-aiMatrix4x4 get_world_transform(const aiNode* node, const aiScene* scene)
-{
+aiMatrix4x4 get_world_transform(const aiNode* node, const aiScene* scene) {
     std::vector<const aiNode*> node_chain;
     while (node != scene->mRootNode && node != nullptr) {
         node_chain.push_back(node);
@@ -1073,8 +1052,7 @@ inline int64_t to_ktime(double time) {
     return (static_cast<int64_t>(time * FBX::SECOND));
 }
 
-void FBXExporter::WriteObjects ()
-{
+void FBXExporter::WriteObjects () {
     if (!binary) {
         WriteAsciiSectionHeader("Object properties");
     }
@@ -1087,21 +1065,28 @@ void FBXExporter::WriteObjects ()
     object_node.BeginChildren(outstream, binary, indent);
 
     bool bJoinIdenticalVertices = mProperties->GetPropertyBool("bJoinIdenticalVertices", true);
-    std::vector<std::vector<int32_t>> vVertexIndice;//save vertex_indices as it is needed later
+    // save vertex_indices as it is needed later
+    std::vector<std::vector<int32_t>> vVertexIndice(mScene->mNumMeshes);
+
+    std::vector<uint32_t> uniq_v_before_mi;
 
     const auto bTransparencyFactorReferencedToOpacity = mProperties->GetPropertyBool(AI_CONFIG_EXPORT_FBX_TRANSPARENCY_FACTOR_REFER_TO_OPACITY, false);
 
     // geometry (aiMesh)
     mesh_uids.clear();
     indent = 1;
-    for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
-        // it's all about this mesh
-        aiMesh* m = mScene->mMeshes[mi];
+    std::function<void(const aiNode*)> visit_node_geo = [&](const aiNode *node) {
+        if (node->mNumMeshes == 0) {
+          for (uint32_t ni = 0; ni < node->mNumChildren; ni++) {
+            visit_node_geo(node->mChildren[ni]);
+          }
+          return;
+        }
 
         // start the node record
         FBX::Node n("Geometry");
         int64_t uid = generate_uid();
-        mesh_uids.push_back(uid);
+        mesh_uids[node] = uid;
         n.AddProperty(uid);
         n.AddProperty(FBX::SEPARATOR + "Geometry");
         n.AddProperty("Mesh");
@@ -1109,160 +1094,112 @@ void FBXExporter::WriteObjects ()
         n.DumpProperties(outstream, binary, indent);
         n.EndProperties(outstream, binary, indent);
         n.BeginChildren(outstream, binary, indent);
-        indent = 2;
 
         // output vertex data - each vertex should be unique (probably)
         std::vector<double> flattened_vertices;
         // index of original vertex in vertex data vector
         std::vector<int32_t> vertex_indices;
-        // map of vertex value to its index in the data vector
-        std::map<aiVector3D,size_t> index_by_vertex_value;
-        if(bJoinIdenticalVertices){
-            int32_t index = 0;
-            for (size_t vi = 0; vi < m->mNumVertices; ++vi) {
-                aiVector3D vtx = m->mVertices[vi];
-                auto elem = index_by_vertex_value.find(vtx);
-                if (elem == index_by_vertex_value.end()) {
-                    vertex_indices.push_back(index);
-                    index_by_vertex_value[vtx] = index;
-                    flattened_vertices.push_back(vtx[0]);
-                    flattened_vertices.push_back(vtx[1]);
-                    flattened_vertices.push_back(vtx[2]);
-                    ++index;
-                } else {
-                    vertex_indices.push_back(int32_t(elem->second));
-                }
-            }
-        }
-        else { // do not join vertex, respect the export flag
-            vertex_indices.resize(m->mNumVertices);
-            std::iota(vertex_indices.begin(), vertex_indices.end(), 0);
-            for(unsigned int v = 0; v < m->mNumVertices; ++ v) {
-                aiVector3D vtx = m->mVertices[v];
-                flattened_vertices.push_back(vtx.x);
-                flattened_vertices.push_back(vtx.y);
-                flattened_vertices.push_back(vtx.z);
-            }
-        }
-        vVertexIndice.push_back(vertex_indices);
 
-        FBX::Node::WritePropertyNode(
-            "Vertices", flattened_vertices, outstream, binary, indent
-        );
+        std::vector<double> normal_data;
+        std::vector<double> color_data;
+
+        std::vector<int32_t> polygon_data;
+
+        std::vector<std::vector<double>> uv_data;
+        std::vector<std::vector<int32_t>> uv_indices;
+        std::map<aiVector3D, int32_t> index_by_uv;
+
+        std::vector<int32_t> offsets = { 0 };
+
+        indent = 2;
+
+        for (uint32_t n_mi = 0; n_mi < node->mNumMeshes; n_mi++) {
+          const auto mi = node->mMeshes[n_mi];
+          const aiMesh *m = mScene->mMeshes[mi];
+
+          size_t v_offset = vertex_indices.size();
+          size_t uniq_v_before = flattened_vertices.size() / 3;
+
+          // map of vertex value to its index in the data vector
+          std::map<aiVector3D,size_t> index_by_vertex_value;
+          if(bJoinIdenticalVertices){
+              int32_t index = 0;
+              for (size_t vi = 0; vi < m->mNumVertices; ++vi) {
+                  aiVector3D vtx = m->mVertices[vi];
+                  auto elem = index_by_vertex_value.find(vtx);
+                  if (elem == index_by_vertex_value.end()) {
+                      vertex_indices.push_back(index);
+                      index_by_vertex_value[vtx] = index;
+                      flattened_vertices.insert(flattened_vertices.end(), { vtx.x, vtx.y, vtx.z });
+                      ++index;
+                  } else {
+                      vertex_indices.push_back(int32_t(elem->second));
+                  }
+              }
+          } else { // do not join vertex, respect the export flag
+              vertex_indices.resize(v_offset + m->mNumVertices);
+              std::iota(vertex_indices.begin() + v_offset, vertex_indices.end(), (int)v_offset);
+              for(unsigned int v = 0; v < m->mNumVertices; ++ v) {
+                  aiVector3D vtx = m->mVertices[v];
+                  flattened_vertices.insert(flattened_vertices.end(), {vtx.x, vtx.y, vtx.z});
+              }
+          }
+          vVertexIndice[mi].insert(
+            // TODO test whether this can be end or not
+            vVertexIndice[mi].begin(),
+            vertex_indices.begin(),
+            vertex_indices.end()
+          );
+
+          // here could be edges but they're insane.
+          // it's optional anyway, so let's ignore it.
 
         // output polygon data as a flattened array of vertex indices.
         // the last vertex index of each polygon is negated and - 1
-        std::vector<int32_t> polygon_data;
-        for (size_t fi = 0; fi < m->mNumFaces; ++fi) {
+          for (size_t fi = 0; fi < m->mNumFaces; fi++) {
             const aiFace &f = m->mFaces[fi];
-            for (size_t pvi = 0; pvi < f.mNumIndices - 1; ++pvi) {
-                polygon_data.push_back(vertex_indices[f.mIndices[pvi]]);
+            size_t pvi = 0;
+            for (; pvi < f.mNumIndices - 1; pvi++) {
+              polygon_data.push_back(
+                static_cast<int32_t>(uniq_v_before + vertex_indices[v_offset + f.mIndices[pvi]])
+              );
             }
             polygon_data.push_back(
-                -1 - vertex_indices[f.mIndices[f.mNumIndices-1]]
+              static_cast<int32_t>(-1 - (uniq_v_before + vertex_indices[v_offset+f.mIndices[pvi]]))
             );
-        }
-        FBX::Node::WritePropertyNode(
-            "PolygonVertexIndex", polygon_data, outstream, binary, indent
-        );
-
-        // here could be edges but they're insane.
-        // it's optional anyway, so let's ignore it.
+          }
 
-        FBX::Node::WritePropertyNode(
-            "GeometryVersion", int32_t(124), outstream, binary, indent
-        );
+          uniq_v_before_mi.push_back(static_cast<uint32_t>(uniq_v_before));
 
-        // normals, if any
-        if (m->HasNormals()) {
-            FBX::Node normals("LayerElementNormal", int32_t(0));
-            normals.Begin(outstream, binary, indent);
-            normals.DumpProperties(outstream, binary, indent);
-            normals.EndProperties(outstream, binary, indent);
-            normals.BeginChildren(outstream, binary, indent);
-            indent = 3;
-            FBX::Node::WritePropertyNode(
-                "Version", int32_t(101), outstream, binary, indent
-            );
-            FBX::Node::WritePropertyNode(
-                "Name", "", outstream, binary, indent
-            );
-            FBX::Node::WritePropertyNode(
-                "MappingInformationType", "ByPolygonVertex",
-                outstream, binary, indent
-            );
-            // TODO: vertex-normals or indexed normals when appropriate
-            FBX::Node::WritePropertyNode(
-                "ReferenceInformationType", "Direct",
-                outstream, binary, indent
-            );
-            std::vector<double> normal_data;
+          if (m->HasNormals()) {
             normal_data.reserve(3 * polygon_data.size());
-            for (size_t fi = 0; fi < m->mNumFaces; ++fi) {
-                const aiFace &f = m->mFaces[fi];
-                for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) {
-                    const aiVector3D &curN = m->mNormals[f.mIndices[pvi]];
-                    normal_data.push_back(curN.x);
-                    normal_data.push_back(curN.y);
-                    normal_data.push_back(curN.z);
-                }
+            for (size_t fi = 0; fi < m->mNumFaces; fi++) {
+              const aiFace & f = m->mFaces[fi];
+              for (size_t pvi = 0; pvi < f.mNumIndices; pvi++) {
+                const aiVector3D &curN = m->mNormals[f.mIndices[pvi]];
+                normal_data.insert(normal_data.end(), { curN.x, curN.y, curN.z });
+              }
             }
-            FBX::Node::WritePropertyNode(
-                "Normals", normal_data, outstream, binary, indent
-            );
-            // note: version 102 has a NormalsW also... not sure what it is,
-            // so we can stick with version 101 for now.
-            indent = 2;
-            normals.End(outstream, binary, indent, true);
-        }
+          }
 
-        // colors, if any
-        // TODO only one color channel currently
-        const int32_t colorChannelIndex = 0;
-        if (m->HasVertexColors(colorChannelIndex)) {
-            FBX::Node vertexcolors("LayerElementColor", int32_t(colorChannelIndex));
-            vertexcolors.Begin(outstream, binary, indent);
-            vertexcolors.DumpProperties(outstream, binary, indent);
-            vertexcolors.EndProperties(outstream, binary, indent);
-            vertexcolors.BeginChildren(outstream, binary, indent);
-            indent = 3;
-            FBX::Node::WritePropertyNode(
-                "Version", int32_t(101), outstream, binary, indent
-            );
-            char layerName[8];
-            snprintf(layerName, sizeof(layerName), "COLOR_%d", colorChannelIndex);
-            FBX::Node::WritePropertyNode(
-                "Name", (const char*)layerName, outstream, binary, indent
-            );
-            FBX::Node::WritePropertyNode(
-                "MappingInformationType", "ByPolygonVertex",
-                outstream, binary, indent
-            );
-            FBX::Node::WritePropertyNode(
-                "ReferenceInformationType", "Direct",
-                outstream, binary, indent
-            );
-            std::vector<double> color_data;
+          const int32_t colorChannelIndex = 0;
+          if (m->HasVertexColors(colorChannelIndex)) {
             color_data.reserve(4 * polygon_data.size());
-            for (size_t fi = 0; fi < m->mNumFaces; ++fi) {
-                const aiFace &f = m->mFaces[fi];
-                for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) {
-                    const aiColor4D &c = m->mColors[colorChannelIndex][f.mIndices[pvi]];
-                    color_data.push_back(c.r);
-                    color_data.push_back(c.g);
-                    color_data.push_back(c.b);
-                    color_data.push_back(c.a);
-                }
+            for (size_t fi = 0; fi < m->mNumFaces; fi++) {
+              const aiFace &f = m->mFaces[fi];
+              for (size_t pvi = 0; pvi < f.mNumIndices; pvi++) {
+                const aiColor4D &c = m->mColors[colorChannelIndex][f.mIndices[pvi]];
+                color_data.insert(color_data.end(), { c.r, c.g, c.b, c.a });
+              }
             }
-            FBX::Node::WritePropertyNode(
-                "Colors", color_data, outstream, binary, indent
-            );
-            indent = 2;
-            vertexcolors.End(outstream, binary, indent, true);
-        }
+          }
 
-        // uvs, if any
-        for (size_t uvi = 0; uvi < m->GetNumUVChannels(); ++uvi) {
+          const auto num_uv = static_cast<size_t>(m->GetNumUVChannels());
+          uv_indices.resize(std::max(num_uv, uv_indices.size()));
+          uv_data.resize(std::max(num_uv, uv_data.size()));
+
+          // uvs, if any
+          for (size_t uvi = 0; uvi < m->GetNumUVChannels(); uvi++) {
             if (m->mNumUVComponents[uvi] > 2) {
                 // FBX only supports 2-channel UV maps...
                 // or at least i'm not sure how to indicate a different number
@@ -1278,71 +1215,111 @@ void FBXExporter::WriteObjects ()
                 err << " but may be incorrectly interpreted on load.";
                 ASSIMP_LOG_WARN(err.str());
             }
-            FBX::Node uv("LayerElementUV", int32_t(uvi));
-            uv.Begin(outstream, binary, indent);
-            uv.DumpProperties(outstream, binary, indent);
-            uv.EndProperties(outstream, binary, indent);
-            uv.BeginChildren(outstream, binary, indent);
-            indent = 3;
-            FBX::Node::WritePropertyNode(
-                "Version", int32_t(101), outstream, binary, indent
-            );
-            // it doesn't seem like assimp keeps the uv map name,
-            // so just leave it blank.
-            FBX::Node::WritePropertyNode(
-                "Name", "", outstream, binary, indent
-            );
-            FBX::Node::WritePropertyNode(
-                "MappingInformationType", "ByPolygonVertex",
-                outstream, binary, indent
-            );
-            FBX::Node::WritePropertyNode(
-                "ReferenceInformationType", "IndexToDirect",
-                outstream, binary, indent
-            );
 
-            std::vector<double> uv_data;
-            std::vector<int32_t> uv_indices;
-            std::map<aiVector3D,int32_t> index_by_uv;
             int32_t index = 0;
-            for (size_t fi = 0; fi < m->mNumFaces; ++fi) {
-                const aiFace &f = m->mFaces[fi];
-                for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) {
-                    const aiVector3D &curUv =
-                        m->mTextureCoords[uvi][f.mIndices[pvi]];
-                    auto elem = index_by_uv.find(curUv);
-                    if (elem == index_by_uv.end()) {
-                        index_by_uv[curUv] = index;
-                        uv_indices.push_back(index);
-                        for (unsigned int x = 0; x < m->mNumUVComponents[uvi]; ++x) {
-                            uv_data.push_back(curUv[x]);
-                        }
-                        ++index;
-                    } else {
-                        uv_indices.push_back(elem->second);
-                    }
+            for (size_t fi = 0; fi < m->mNumFaces; fi++) {
+              const aiFace &f = m->mFaces[fi];
+              for (size_t pvi = 0; pvi < f.mNumIndices; pvi++) {
+                const aiVector3D &curUv = m->mTextureCoords[uvi][f.mIndices[pvi]];
+                auto elem = index_by_uv.find(curUv);
+                if (elem == index_by_uv.end()) {
+                  index_by_uv[curUv] = index;
+                  uv_indices[uvi].push_back(index);
+                  for (uint32_t x = 0; x < m->mNumUVComponents[uvi]; ++x) {
+                    uv_data[uvi].push_back(curUv[x]);
+                  }
+                  ++index;
+                } else {
+                  uv_indices[uvi].push_back(elem->second);
                 }
+              }
             }
-            FBX::Node::WritePropertyNode(
-                "UV", uv_data, outstream, binary, indent
-            );
-            FBX::Node::WritePropertyNode(
-                "UVIndex", uv_indices, outstream, binary, indent
-            );
-            indent = 2;
-            uv.End(outstream, binary, indent, true);
+          }
+
+          offsets.push_back((int32_t)polygon_data.size());
+        }
+
+
+        FBX::Node::WritePropertyNode("Vertices", flattened_vertices, outstream, binary, indent);
+        FBX::Node::WritePropertyNode("PolygonVertexIndex", polygon_data, outstream, binary, indent);
+        FBX::Node::WritePropertyNode("GeometryVersion", int32_t(124), outstream, binary, indent);
+
+        FBX::Node normals("LayerElementNormal", int32_t(0));
+        normals.Begin(outstream, binary, indent);
+        normals.DumpProperties(outstream, binary, indent);
+        normals.EndProperties(outstream, binary, indent);
+        normals.BeginChildren(outstream, binary, indent);
+        indent = 3;
+        FBX::Node::WritePropertyNode("Version", int32_t(101),outstream,binary,indent);
+        FBX::Node::WritePropertyNode("Name", "",outstream,binary,indent);
+        FBX::Node::WritePropertyNode("MappingInformationType", "ByPolygonVertex",outstream,binary,indent);
+        FBX::Node::WritePropertyNode("ReferenceInformationType", "Direct",outstream,binary,indent);
+        FBX::Node::WritePropertyNode("Normals", normal_data,outstream,binary,indent);
+        // note: version 102 has a NormalsW also... not sure what it is,
+        // so stick with version 101 for now.
+        indent = 2;
+        normals.End(outstream,binary,indent,true);
+
+        const auto colorChannelIndex = 0;
+        FBX::Node vertexcolors("LayerElementColor", int32_t(colorChannelIndex));
+        vertexcolors.Begin(outstream, binary, indent);
+        vertexcolors.DumpProperties(outstream, binary, indent);
+        vertexcolors.EndProperties(outstream, binary, indent);
+        vertexcolors.BeginChildren(outstream, binary, indent);
+        indent = 3;
+        FBX::Node::WritePropertyNode("Version", int32_t(101), outstream, binary, indent);
+        char layerName[8];
+        snprintf(layerName, sizeof(layerName), "COLOR_%d", colorChannelIndex);
+        FBX::Node::WritePropertyNode("Name", (const char *)layerName, outstream, binary, indent);
+        FBX::Node::WritePropertyNode("MappingInformationType", "ByPolygonVertex", outstream, binary, indent);
+        FBX::Node::WritePropertyNode("ReferenceInformationType", "Direct", outstream, binary, indent);
+        FBX::Node::WritePropertyNode("Colors", color_data, outstream, binary, indent);
+        indent = 2;
+        vertexcolors.End(outstream, binary, indent, true);
+
+        for (uint32_t uvi = 0; uvi < uv_data.size(); uvi++) {
+          FBX::Node uv("LayerElementUV", int32_t(uvi));
+          uv.Begin(outstream, binary, indent);
+          uv.DumpProperties(outstream, binary, indent);
+          uv.EndProperties(outstream, binary, indent);
+          uv.BeginChildren(outstream, binary, indent);
+          indent = 3;
+          FBX::Node::WritePropertyNode("Version", int32_t(101), outstream, binary, indent);
+          FBX::Node::WritePropertyNode("Name", "", outstream, binary, indent);
+          FBX::Node::WritePropertyNode("MappingInformationType", "ByPolgonVertex", outstream, binary, indent);
+          FBX::Node::WritePropertyNode("ReferenceInformationType", "IndexToDirect", outstream, binary, indent);
+          FBX::Node::WritePropertyNode("UV", uv_data[uvi], outstream, binary, indent);
+          FBX::Node::WritePropertyNode("UVIndex", uv_indices[uvi], outstream, binary, indent);
+          indent = 2;
+          uv.End(outstream, binary, indent, true);
         }
 
-        // i'm not really sure why this material section exists,
-        // as the material is linked via "Connections".
-        // it seems to always have the same "0" value.
+
+        // When merging multiple meshes, we instead use by polygon so the correct material is
+        // assigned to each face. Previously, this LayerElementMaterial always had 0 since it
+        // assumed there was 1 material for each node for all meshes.
         FBX::Node mat("LayerElementMaterial", int32_t(0));
         mat.AddChild("Version", int32_t(101));
         mat.AddChild("Name", "");
-        mat.AddChild("MappingInformationType", "AllSame");
-        mat.AddChild("ReferenceInformationType", "IndexToDirect");
-        std::vector<int32_t> mat_indices = {0};
-        mat.AddChild("Materials", mat_indices);
+        if (node->mNumMeshes == 1) {
+          mat.AddChild("MappingInformationType", "AllSame");
+          mat.AddChild("ReferenceInformationType", "IndexToDirect");
+          std::vector<int32_t> mat_indices = {0};
+          mat.AddChild("Materials", mat_indices);
+        } else {
+          mat.AddChild("MappingInformationType", "ByPolygon");
+          mat.AddChild("ReferenceInformationType", "IndexToDirect");
+          std::vector<int32_t> mat_indices(polygon_data.size());
+          uint32_t curr_offset = 0;
+          for (uint32_t mi = 0; mi < node->mNumMeshes; mi++) {
+            uint32_t num_faces = mScene->mMeshes[node->mMeshes[mi]]->mNumFaces;
+            for (uint32_t fi = 0; fi < num_faces; fi++) {
+              mat_indices[curr_offset + fi] = mi;
+            }
+            curr_offset += num_faces;
+          }
+          mat.AddChild("Materials", mat_indices);
+        }
         mat.Dump(outstream, binary, indent);
 
         // finally we have the layer specifications,
@@ -1354,11 +1331,12 @@ void FBXExporter::WriteObjects ()
         le.AddChild("Type", "LayerElementNormal");
         le.AddChild("TypedIndex", int32_t(0));
         layer.AddChild(le);
-        // TODO only 1 color channel currently
+
         le = FBX::Node("LayerElement");
         le.AddChild("Type", "LayerElementColor");
         le.AddChild("TypedIndex", int32_t(0));
         layer.AddChild(le);
+
         le = FBX::Node("LayerElement");
         le.AddChild("Type", "LayerElementMaterial");
         le.AddChild("TypedIndex", int32_t(0));
@@ -1369,8 +1347,7 @@ void FBXExporter::WriteObjects ()
         layer.AddChild(le);
         layer.Dump(outstream, binary, indent);
 
-        for(unsigned int lr = 1; lr < m->GetNumUVChannels(); ++ lr)
-        {
+        for(unsigned int lr = 1; lr < uv_data.size(); ++ lr) {
             FBX::Node layerExtra("Layer", int32_t(lr));
             layerExtra.AddChild("Version", int32_t(100));
             FBX::Node leExtra("LayerElement");
@@ -1382,7 +1359,14 @@ void FBXExporter::WriteObjects ()
         // finish the node record
         indent = 1;
         n.End(outstream, binary, indent, true);
-    }
+
+        for (uint32_t ni = 0; ni < node->mNumChildren; ni++) {
+          visit_node_geo(node->mChildren[ni]);
+        }
+        return;
+    };
+
+    visit_node_geo(mScene->mRootNode);
 
 
     // aiMaterial
@@ -1747,7 +1731,8 @@ void FBXExporter::WriteObjects ()
       dnode.AddChild("Version", int32_t(101));
       dnode.Dump(outstream, binary, indent);
       // connect it
-      connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]);
+      const auto node = get_node_for_mesh((unsigned int)mi, mScene->mRootNode);
+      connections.emplace_back("C", "OO", deformer_uid, mesh_uids[node]);
       std::vector<int32_t> vertex_indices = vVertexIndice[mi];
 
       for (unsigned int am = 0; am < m->mNumAnimMeshes; ++am) {
@@ -1757,7 +1742,7 @@ void FBXExporter::WriteObjects ()
         // start the node record
         FBX::Node bsnode("Geometry");
         int64_t blendshape_uid = generate_uid();
-        mesh_uids.push_back(blendshape_uid);
+        blendshape_uids.push_back(blendshape_uid);
         bsnode.AddProperty(blendshape_uid);
         bsnode.AddProperty(blendshape_name + FBX::SEPARATOR + "Geometry");
         bsnode.AddProperty("Shape");
@@ -1769,23 +1754,25 @@ void FBXExporter::WriteObjects ()
         indent++;
         if (pAnimMesh->HasPositions()) {
           std::vector<int32_t>shape_indices;
-          std::vector<double>pPositionDiff;
-          std::vector<double>pNormalDiff;
+          std::vector<float>pPositionDiff;
+          std::vector<float>pNormalDiff;
 
           for (unsigned int vt = 0; vt < vertex_indices.size(); ++vt) {
               aiVector3D pDiff = (pAnimMesh->mVertices[vertex_indices[vt]] - m->mVertices[vertex_indices[vt]]);
-              if(pDiff.Length()>1e-8){
-                shape_indices.push_back(vertex_indices[vt]);
-                pPositionDiff.push_back(pDiff[0]);
-                pPositionDiff.push_back(pDiff[1]);
-                pPositionDiff.push_back(pDiff[2]);
-
-                if (pAnimMesh->HasNormals()) {
-                    aiVector3D nDiff = (pAnimMesh->mNormals[vertex_indices[vt]] - m->mNormals[vertex_indices[vt]]);
-                    pNormalDiff.push_back(nDiff[0]);
-                    pNormalDiff.push_back(nDiff[1]);
-                    pNormalDiff.push_back(nDiff[2]);
-                }
+              shape_indices.push_back(vertex_indices[vt]);
+              pPositionDiff.push_back(pDiff[0]);
+              pPositionDiff.push_back(pDiff[1]);
+              pPositionDiff.push_back(pDiff[2]);
+
+              if (pAnimMesh->HasNormals()) {
+                  aiVector3D nDiff = (pAnimMesh->mNormals[vertex_indices[vt]] - m->mNormals[vertex_indices[vt]]);
+                  pNormalDiff.push_back(nDiff[0]);
+                  pNormalDiff.push_back(nDiff[1]);
+                  pNormalDiff.push_back(nDiff[2]);
+              } else {
+                  pNormalDiff.push_back(0.0);
+                  pNormalDiff.push_back(0.0);
+                  pNormalDiff.push_back(0.0);
               }
           }
 
@@ -1942,22 +1929,15 @@ void FBXExporter::WriteObjects ()
                 // otherwise check if this is the root of the skeleton
                 bool end = false;
                 // is the mesh part of this node?
-                for (size_t i = 0; i < parent->mNumMeshes; ++i) {
-                    if (parent->mMeshes[i] == mi) {
-                        end = true;
-                        break;
-                    }
+                for (size_t i = 0; i < parent->mNumMeshes && !end; ++i) {
+                    end |= parent->mMeshes[i] == mi;
                 }
                 // is the mesh in one of the children of this node?
-                for (size_t j = 0; j < parent->mNumChildren; ++j) {
+                for (size_t j = 0; j < parent->mNumChildren && !end; ++j) {
                     aiNode* child = parent->mChildren[j];
-                    for (size_t i = 0; i < child->mNumMeshes; ++i) {
-                        if (child->mMeshes[i] == mi) {
-                            end = true;
-                            break;
-                        }
+                    for (size_t i = 0; i < child->mNumMeshes && !end; ++i) {
+                        end |= child->mMeshes[i] == mi;
                     }
-                    if (end) { break; }
                 }
 
                 // if it was the skeleton root we can finish here
@@ -1971,8 +1951,7 @@ void FBXExporter::WriteObjects ()
     for (size_t i = 0; i < mScene->mNumMeshes; ++i) {
         auto &s = skeleton_by_mesh[i];
         for (const aiNode* n : s) {
-            auto elem = node_uids.find(n);
-            if (elem == node_uids.end()) {
+            if (node_uids.find(n) == node_uids.end()) {
                 node_uids[n] = generate_uid();
             }
         }
@@ -1988,6 +1967,8 @@ void FBXExporter::WriteObjects ()
         if (!m->HasBones()) {
             continue;
         }
+
+        const aiNode *mesh_node = get_node_for_mesh((uint32_t)mi, mScene->mRootNode);
         // make a deformer for this mesh
         int64_t deformer_uid = generate_uid();
         FBX::Node dnode("Deformer");
@@ -1999,10 +1980,7 @@ void FBXExporter::WriteObjects ()
         dnode.Dump(outstream, binary, indent);
 
         // connect it
-        connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]);
-
-        //computed before
-        std::vector<int32_t>& vertex_indices = vVertexIndice[mi];
+        connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mesh_node]);
 
         // TODO, FIXME: this won't work if anything is not in the bind pose.
         // for now if such a situation is detected, we throw an exception.
@@ -2016,7 +1994,6 @@ void FBXExporter::WriteObjects ()
         // as it can be instanced to many nodes.
         // All we can do is assume no instancing,
         // and take the first node we find that contains the mesh.
-        aiNode* mesh_node = get_node_for_mesh((unsigned int)mi, mScene->mRootNode);
         aiMatrix4x4 mesh_xform = get_world_transform(mesh_node, mScene);
 
         // now make a subdeformer for each bone in the skeleton
@@ -2045,14 +2022,15 @@ void FBXExporter::WriteObjects ()
             sdnode.AddChild("Version", int32_t(100));
             sdnode.AddChild("UserData", "", "");
 
-            std::set<int32_t> setWeightedVertex;
             // add indices and weights, if any
             if (b) {
+                std::set<int32_t> setWeightedVertex;
                 std::vector<int32_t> subdef_indices;
                 std::vector<double> subdef_weights;
                 int32_t last_index = -1;
                 for (size_t wi = 0; wi < b->mNumWeights; ++wi) {
-                    int32_t vi = vertex_indices[b->mWeights[wi].mVertexId];
+                    int32_t vi = vVertexIndice[mi][b->mWeights[wi].mVertexId] \
+                      + uniq_v_before_mi[mi];
                     bool bIsWeightedAlready = (setWeightedVertex.find(vi) != setWeightedVertex.end());
                     if (vi == last_index || bIsWeightedAlready) {
                         // only for vertices we exported to fbx
@@ -2486,6 +2464,57 @@ const std::map<std::string,std::pair<std::string,char>> transform_types = {
     {"GeometricScalingInverse", {"GeometricScalingInverse", 'i'}}
 };
 
+//add metadata to fbx property
+void add_meta(FBX::Node& fbx_node, const aiNode* node){
+    if(node->mMetaData == nullptr) return;
+    aiMetadata* meta = node->mMetaData;
+    for (unsigned int i = 0; i < meta->mNumProperties; ++i) {
+        aiString key = meta->mKeys[i];
+        aiMetadataEntry* entry = &meta->mValues[i];
+        switch (entry->mType) {
+        case AI_BOOL:{
+            bool val = *static_cast<bool *>(entry->mData);
+            fbx_node.AddP70bool(key.C_Str(), val);
+            break;
+        }
+        case AI_INT32:{
+            int32_t val = *static_cast<int32_t *>(entry->mData);
+            fbx_node.AddP70int(key.C_Str(), val);
+            break;
+        }
+        case AI_UINT64:{
+            //use string to add uint64
+            uint64_t val = *static_cast<uint64_t *>(entry->mData);
+            fbx_node.AddP70string(key.C_Str(), std::to_string(val).c_str());
+            break;
+        }
+        case AI_FLOAT:{
+            float val = *static_cast<float *>(entry->mData);
+            fbx_node.AddP70double(key.C_Str(), val);
+            break;
+        }
+        case AI_DOUBLE:{
+            double val = *static_cast<double *>(entry->mData);
+            fbx_node.AddP70double(key.C_Str(), val);
+            break;
+        }
+        case AI_AISTRING:{
+            aiString val = *static_cast<aiString *>(entry->mData);
+            fbx_node.AddP70string(key.C_Str(), val.C_Str());
+            break;
+        }
+        case AI_AIMETADATA: {
+            //ignore
+            break;
+        }
+        default:
+            break;
+        }
+        
+    }
+    
+}
+
 // write a single model node to the stream
 void FBXExporter::WriteModelNode(
     StreamWriterLE& outstream,
@@ -2553,6 +2582,7 @@ void FBXExporter::WriteModelNode(
             }
         }
     }
+    add_meta(p, node);
     m.AddChild(p);
 
     // not sure what these are for,
@@ -2647,9 +2677,8 @@ void FBXExporter::WriteModelNodes(
         // handled later
     } else if (node->mNumMeshes == 1) {
         // connect to child mesh, which should have been written previously
-        connections.emplace_back(
-            "C", "OO", mesh_uids[node->mMeshes[0]], node_uid
-        );
+        // TODO double check this line
+        connections.emplace_back("C", "OO", mesh_uids[node], node_uid);
         // also connect to the material for the child mesh
         connections.emplace_back(
             "C", "OO",
@@ -2674,6 +2703,16 @@ void FBXExporter::WriteModelNodes(
         na.Dump(outstream, binary, 1);
         // and connect them
         connections.emplace_back("C", "OO", node_attribute_uid, node_uid);
+    } else if (node->mNumMeshes >= 1) {
+      connections.emplace_back("C", "OO", mesh_uids[node], node_uid);
+      for (size_t i = 0; i < node->mNumMeshes; i++) {
+        connections.emplace_back(
+          "C", "OO",
+          material_uids[mScene->mMeshes[node->mMeshes[i]]->mMaterialIndex],
+          node_uid
+        );
+      }
+      WriteModelNode(outstream, binary, node, node_uid, "Mesh", transform_chain);
     } else {
         const auto& lightIt = lights_uids.find(node->mName.C_Str());
         if(lightIt != lights_uids.end()) {
@@ -2690,34 +2729,20 @@ void FBXExporter::WriteModelNodes(
         }
     }
 
-    // if more than one child mesh, make nodes for each mesh
-    if (node->mNumMeshes > 1 || node == mScene->mRootNode) {
-        for (size_t i = 0; i < node->mNumMeshes; ++i) {
-            // make a new model node
-            int64_t new_node_uid = generate_uid();
-            // connect to parent node
-            connections.emplace_back("C", "OO", new_node_uid, node_uid);
-            // connect to child mesh, which should have been written previously
-            connections.emplace_back(
-                "C", "OO", mesh_uids[node->mMeshes[i]], new_node_uid
-            );
-            // also connect to the material for the child mesh
-            connections.emplace_back(
-                "C", "OO",
-                material_uids[
-                    mScene->mMeshes[node->mMeshes[i]]->mMaterialIndex
-                ],
-                new_node_uid
-            );
-
-            aiNode new_node;
-            // take name from mesh name, if it exists
-            new_node.mName = mScene->mMeshes[node->mMeshes[i]]->mName;
-            // write model node
-            WriteModelNode(
-                outstream, binary, &new_node, new_node_uid, "Mesh", std::vector<std::pair<std::string,aiVector3D>>()
-            );
-        }
+    if (node == mScene->mRootNode && node->mNumMeshes > 0) {
+      int64_t new_node_uid = generate_uid();
+      connections.emplace_back("C", "OO", new_node_uid, node_uid);
+      connections.emplace_back("C", "OO", mesh_uids[node], new_node_uid);
+      for (size_t i = 0; i < node->mNumMeshes; ++i) {
+        connections.emplace_back(
+          "C", "OO",
+          material_uids[mScene->mMeshes[node->mMeshes[i]]->mMaterialIndex],
+          new_node_uid
+        );
+      }
+      aiNode new_node;
+      new_node.mName = mScene->mMeshes[0]->mName;
+      WriteModelNode(outstream, binary, &new_node, new_node_uid, "Mesh", {});
     }
 
     // now recurse into children

+ 2 - 2
code/AssetLib/FBX/FBXExporter.h

@@ -51,7 +51,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "FBXCommon.h" // FBX::TransformInheritance
 
 #include <assimp/types.h>
-//#include <assimp/material.h>
 #include <assimp/StreamWriter.h> // StreamWriterLE
 #include <assimp/Exceptional.h> // DeadlyExportError
 
@@ -91,7 +90,8 @@ namespace Assimp {
 
         std::vector<FBX::Node> connections; // connection storage
 
-        std::vector<int64_t> mesh_uids;
+        std::map<const aiNode*, int64_t> mesh_uids;
+        std::vector<int64_t> blendshape_uids;
         std::vector<int64_t> material_uids;
         std::map<const aiNode*,int64_t> node_uids;
         std::map<std::string,int64_t> lights_uids;

+ 3 - 0
code/AssetLib/FBX/FBXImportSettings.h

@@ -156,6 +156,9 @@ struct ImportSettings {
     /** Set to true to perform a conversion from cm to meter after the import
     */
     bool convertToMeters;
+
+    // Set to true to ignore the axis configuration in the file
+    bool ignoreUpDirection = false;
 };
 
 } // namespace FBX

+ 1 - 0
code/AssetLib/FBX/FBXImporter.cpp

@@ -117,6 +117,7 @@ void FBXImporter::SetupProperties(const Importer *pImp) {
     mSettings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false);
     mSettings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true);
     mSettings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false);
+    mSettings.ignoreUpDirection = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_IGNORE_UP_DIRECTION, false);
     mSettings.useSkeleton = pImp->GetPropertyBool(AI_CONFIG_FBX_USE_SKELETON_BONE_CONTAINER, false);
 }
 

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

@@ -134,10 +134,6 @@ Material::Material(uint64_t id, const Element& element, const Document& doc, con
     }
 }
 
-
-// ------------------------------------------------------------------------------------------------
-Material::~Material() = default;
-
 // ------------------------------------------------------------------------------------------------
 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

@@ -685,11 +685,14 @@ ShapeGeometry::ShapeGeometry(uint64_t id, const Element& element, const std::str
         DOMError("failed to read Geometry object (class: Shape), no data scope found");
     }
     const Element& Indexes = GetRequiredElement(*sc, "Indexes", &element);
-    const Element& Normals = GetRequiredElement(*sc, "Normals", &element);
     const Element& Vertices = GetRequiredElement(*sc, "Vertices", &element);
     ParseVectorDataArray(m_indices, Indexes);
     ParseVectorDataArray(m_vertices, Vertices);
-    ParseVectorDataArray(m_normals, Normals);
+
+    if ((*sc)["Normals"]) {
+        const Element& Normals = GetRequiredElement(*sc, "Normals", &element);
+        ParseVectorDataArray(m_normals, Normals);
+    }
 }
 
 // ------------------------------------------------------------------------------------------------

+ 1 - 2
code/AssetLib/FBX/FBXParser.h

@@ -84,8 +84,7 @@ using ElementCollection = std::pair<ElementMap::const_iterator,ElementMap::const
  *  As can be seen in this sample, elements can contain nested #Scope
  *  as their trailing member.  
 **/
-class Element
-{
+class Element {
 public:
     Element(const Token& key_token, Parser& parser);
     ~Element();

+ 4 - 16
code/AssetLib/FBX/FBXProperties.cpp

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -57,15 +56,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp {
 namespace FBX {
 
-    using namespace Util;
+using namespace Util;
 
 // ------------------------------------------------------------------------------------------------
-    Property::Property() = default;
-
-    // ------------------------------------------------------------------------------------------------
-    Property::~Property() = default;
 
-    namespace {
+namespace {
 
     void checkTokenCount(const TokenList &tok, unsigned int expectedCount) {
         ai_assert(expectedCount >= 2);
@@ -146,9 +141,9 @@ Property* ReadTypedProperty(const Element& element)
 
 // ------------------------------------------------------------------------------------------------
 // peek into an element and check if it contains a FBX property, if so return its name.
-std::string PeekPropertyName(const Element& element)
-{
+std::string PeekPropertyName(const Element& element) {
     ai_assert(element.KeyToken().StringContents() == "P");
+
     const TokenList& tok = element.Tokens();
     if(tok.size() < 4) {
         return std::string();
@@ -160,13 +155,6 @@ std::string PeekPropertyName(const Element& element)
 } //! anon
 
 
-// ------------------------------------------------------------------------------------------------
-PropertyTable::PropertyTable()
-: templateProps()
-, element()
-{
-}
-
 // ------------------------------------------------------------------------------------------------
 PropertyTable::PropertyTable(const Element &element, std::shared_ptr<const PropertyTable> templateProps) :
         templateProps(std::move(templateProps)), element(&element) {

+ 20 - 25
code/AssetLib/FBX/FBXProperties.h

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -56,34 +55,33 @@ namespace FBX {
 // Forward declarations
 class Element;
 
-/** Represents a dynamic property. Type info added by deriving classes,
- *  see #TypedProperty.
- Example:
- @verbatim
-   P: "ShininessExponent", "double", "Number", "",0.5
- @endvebatim
-*/
+/**
+ * Represents a dynamic property. Type info added by deriving classes,
+ * see #TypedProperty.
+ * Example:
+ *
+ * @verbatim
+ *  P: "ShininessExponent", "double", "Number", "",0.5
+ * @endvebatim
+ */
 class Property {
-protected:
-    Property();
 
 public:
-    virtual ~Property();
+    virtual ~Property() = default;
 
-public:
     template <typename T>
     const T* As() const {
         return dynamic_cast<const T*>(this);
     }
+
+protected:
+    Property() = default;
 };
 
 template<typename T>
 class TypedProperty : public Property {
 public:
-    explicit TypedProperty(const T& value)
-    : value(value) {
-        // empty
-    }
+    explicit TypedProperty(const T& value) : value(value) {}
 
     const T& Value() const {
         return value;
@@ -93,10 +91,9 @@ private:
     T value;
 };
 
-
-typedef std::fbx_unordered_map<std::string,std::shared_ptr<Property> > DirectPropertyMap;
-typedef std::fbx_unordered_map<std::string,const Property*>            PropertyMap;
-typedef std::fbx_unordered_map<std::string,const Element*>             LazyPropertyMap;
+using DirectPropertyMap = std::fbx_unordered_map<std::string,std::shared_ptr<Property> >;
+using PropertyMap = std::fbx_unordered_map<std::string,const Property*>;
+using LazyPropertyMap = std::fbx_unordered_map<std::string,const Element*>;
 
 /**
  *  Represents a property table as can be found in the newer FBX files (Properties60, Properties70)
@@ -104,7 +101,7 @@ typedef std::fbx_unordered_map<std::string,const Element*>             LazyPrope
 class PropertyTable {
 public:
     // in-memory property table with no source element
-    PropertyTable();
+    PropertyTable() : element() {}
     PropertyTable(const Element& element, std::shared_ptr<const PropertyTable> templateProps);
     ~PropertyTable();
 
@@ -130,8 +127,7 @@ private:
 
 // ------------------------------------------------------------------------------------------------
 template <typename T>
-inline
-T PropertyGet(const PropertyTable& in, const std::string& name, const T& defaultValue) {
+inline T PropertyGet(const PropertyTable& in, const std::string& name, const T& defaultValue) {
     const Property* const prop = in.Get(name);
     if( nullptr == prop) {
         return defaultValue;
@@ -148,8 +144,7 @@ T PropertyGet(const PropertyTable& in, const std::string& name, const T& default
 
 // ------------------------------------------------------------------------------------------------
 template <typename T>
-inline
-T PropertyGet(const PropertyTable& in, const std::string& name, bool& result, bool useTemplate=false ) {
+inline T PropertyGet(const PropertyTable& in, const std::string& name, bool& result, bool useTemplate=false ) {
     const Property* prop = in.Get(name);
     if( nullptr == prop) {
         if ( ! useTemplate ) {

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

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2024, assimp team
 
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -53,10 +52,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp {
 namespace FBX {
 
-
 namespace Util {
 
-
 /** helper for std::for_each to delete all heap-allocated items in a container */
 template<typename T>
 struct delete_fun
@@ -88,7 +85,6 @@ const char* TokenTypeString(TokenType t);
  *  @return A string of the following format: " (offset 0x{offset}) "*/
 std::string GetOffsetText(size_t offset);
 
-
 /** Format log/error messages using a given line location in the source file.
  *
  *  @param line Line index, 1-based
@@ -96,7 +92,6 @@ std::string GetOffsetText(size_t offset);
  *  @return A string of the following format: " (line {line}, col {column}) "*/
 std::string GetLineAndColumnText(unsigned int line, unsigned int column);
 
-
 /** Format log/error messages using a given cursor token.
  *
  *  @param tok Token where parsing/processing stopped

+ 7 - 1
code/AssetLib/HMP/HMPLoader.cpp

@@ -163,8 +163,14 @@ void HMPImporter::ValidateHeader_HMP457() {
                                 "120 bytes, this file is smaller)");
     }
 
+    if (!std::isfinite(pcHeader->ftrisize_x) || !std::isfinite(pcHeader->ftrisize_y))
+        throw DeadlyImportError("Size of triangles in either x or y direction is not finite");
+
     if (!pcHeader->ftrisize_x || !pcHeader->ftrisize_y)
-        throw DeadlyImportError("Size of triangles in either  x or y direction is zero");
+        throw DeadlyImportError("Size of triangles in either x or y direction is zero");
+
+    if (!std::isfinite(pcHeader->fnumverts_x))
+        throw DeadlyImportError("Number of triangles in x direction is not finite");
 
     if (pcHeader->fnumverts_x < 1.0f || (pcHeader->numverts / pcHeader->fnumverts_x) < 1.0f)
         throw DeadlyImportError("Number of triangles in either x or y direction is zero");

+ 2 - 0
code/AssetLib/LWO/LWOLoader.cpp

@@ -527,6 +527,7 @@ void LWOImporter::ComputeNormals(aiMesh *mesh, const std::vector<unsigned int> &
                         continue;
                     vNormals += v;
                 }
+                mesh->mNormals[idx] = vNormals.Normalize();
             }
         }
     }
@@ -547,6 +548,7 @@ 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;

+ 28 - 2
code/AssetLib/LWS/LWSLoader.cpp

@@ -78,7 +78,15 @@ static constexpr aiImporterDesc desc = {
 
 // ------------------------------------------------------------------------------------------------
 // Recursive parsing of LWS files
-void LWS::Element::Parse(const char *&buffer, const char *end) {
+namespace {
+    constexpr int MAX_DEPTH = 1000; // Define the maximum depth allowed
+}
+
+void LWS::Element::Parse(const char *&buffer, const char *end, int depth) {
+    if (depth > MAX_DEPTH) {
+        throw std::runtime_error("Maximum recursion depth exceeded in LWS::Element::Parse");
+    }
+
     for (; SkipSpacesAndLineEnd(&buffer, end); SkipLine(&buffer, end)) {
 
         // begin of a new element with children
@@ -121,7 +129,7 @@ void LWS::Element::Parse(const char *&buffer, const char *end) {
 
         // parse more elements recursively
         if (sub) {
-            children.back().Parse(buffer, end);
+            children.back().Parse(buffer, end, depth + 1);
         }
     }
 }
@@ -577,6 +585,15 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
             // and add the file to the import list
             SkipSpaces(&c, end);
             std::string path = FindLWOFile(c);
+
+            if (path.empty()) {
+                throw DeadlyImportError("LWS: Invalid LoadObjectLayer: empty path.");
+            }
+
+            if (path == pFile) {
+                throw DeadlyImportError("LWS: Invalid LoadObjectLayer: self reference.");
+            }
+
             d.path = path;
             d.id = batch.AddLoadRequest(path, 0, &props);
 
@@ -594,6 +611,15 @@ void LWSImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
                 d.number = cur_object++;
             }
             std::string path = FindLWOFile(c);
+
+            if (path.empty()) {
+                throw DeadlyImportError("LWS: Invalid LoadObject: empty path.");
+            }
+
+            if (path == pFile) {
+                throw DeadlyImportError("LWS: Invalid LoadObject: self reference.");
+            }
+
             d.id = batch.AddLoadRequest(path, 0, nullptr);
 
             d.path = path;

+ 1 - 1
code/AssetLib/LWS/LWSLoader.h

@@ -76,7 +76,7 @@ public:
     std::list<Element> children;
 
     //! Recursive parsing function
-    void Parse(const char *&buffer, const char *end);
+    void Parse(const char *&buffer, const char *end, int depth = 0);
 };
 
 #define AI_LWS_MASK (0xffffffff >> 4u)

+ 11 - 0
code/AssetLib/MD3/MD3Loader.cpp

@@ -724,6 +724,7 @@ void MD3Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
     std::vector<unsigned char> mBuffer2(fileSize);
     file->Read(&mBuffer2[0], 1, fileSize);
     mBuffer = &mBuffer2[0];
+    const unsigned char* bufferEnd = mBuffer + fileSize;
 
     pcHeader = (BE_NCONST MD3::Header *)mBuffer;
 
@@ -749,9 +750,15 @@ void MD3Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
 
     // Navigate to the list of surfaces
     BE_NCONST MD3::Surface *pcSurfaces = (BE_NCONST MD3::Surface *)(mBuffer + pcHeader->OFS_SURFACES);
+    if ((const unsigned char*)pcSurfaces + sizeof(MD3::Surface) * pcHeader->NUM_SURFACES > bufferEnd) {
+        throw DeadlyImportError("MD3 surface headers are outside the file");
+    }
 
     // Navigate to the list of tags
     BE_NCONST MD3::Tag *pcTags = (BE_NCONST MD3::Tag *)(mBuffer + pcHeader->OFS_TAGS);
+    if ((const unsigned char*)pcTags + sizeof(MD3::Tag) * pcHeader->NUM_TAGS > bufferEnd) {
+        throw DeadlyImportError("MD3 tags are outside the file");
+    }
 
     // Allocate output storage
     pScene->mNumMeshes = pcHeader->NUM_SURFACES;
@@ -1026,6 +1033,10 @@ void MD3Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
 
         for (unsigned int i = 0; i < pcHeader->NUM_TAGS; ++i, ++pcTags) {
             aiNode *nd = pScene->mRootNode->mChildren[i] = new aiNode();
+            if ((const unsigned char*)pcTags + sizeof(MD3::Tag) > bufferEnd) {
+                throw DeadlyImportError("MD3 tag is outside the file");
+            }
+
             nd->mName.Set((const char *)pcTags->NAME);
             nd->mParent = pScene->mRootNode;
 

+ 9 - 5
code/AssetLib/MD5/MD5Parser.cpp

@@ -115,14 +115,18 @@ void MD5Parser::ParseHeader() {
         ReportError("MD5 version tag is unknown (10 is expected)");
     }
     SkipLine();
-    if (buffer == bufferEnd) {
-        return;
-    }
 
     // print the command line options to the console
-    // FIX: can break the log length limit, so we need to be careful
     char *sz = buffer;
-    while (!IsLineEnd(*buffer++));
+    while (buffer < bufferEnd) {
+        if (IsLineEnd(*buffer++)) {
+            break;
+        }
+    }
+
+    if (buffer == bufferEnd) {
+        return;
+    }
     
     ASSIMP_LOG_INFO(std::string(sz, std::min((uintptr_t)MAX_LOG_MESSAGE_LENGTH, (uintptr_t)(buffer - sz))));
     SkipSpacesAndLineEnd();

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

@@ -325,13 +325,13 @@ void MDLImporter::SizeCheck(const void *szPos, const char *szFile, unsigned int
 // Validate a quake file header
 void MDLImporter::ValidateHeader_Quake1(const MDL::Header *pcHeader) {
     // some values may not be nullptr
-    if (!pcHeader->num_frames)
+    if (pcHeader->num_frames <= 0)
         throw DeadlyImportError("[Quake 1 MDL] There are no frames in the file");
 
-    if (!pcHeader->num_verts)
+    if (pcHeader->num_verts <= 0)
         throw DeadlyImportError("[Quake 1 MDL] There are no vertices in the file");
 
-    if (!pcHeader->num_tris)
+    if (pcHeader->num_tris <= 0)
         throw DeadlyImportError("[Quake 1 MDL] There are no triangles in the file");
 
     // check whether the maxima are exceeded ...however, this applies for Quake 1 MDLs only

+ 2 - 2
code/AssetLib/NFF/NFFLoader.cpp

@@ -546,9 +546,9 @@ void NFFImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSys
                         // We need to add a new mesh to the list. We assign
                         // an unique name to it to make sure the scene will
                         // pass the validation step for the moment.
-                        // TODO: fix naming of objects in the scenegraph later
+                        // TODO: fix naming of objects in the scene-graph later
                         if (objectName.length()) {
-                            ::strcpy(mesh->name, objectName.c_str());
+                            ::strncpy(mesh->name, objectName.c_str(), objectName.size());
                             ASSIMP_itoa10(&mesh->name[objectName.length()], 30, subMeshIdx++);
                         }
 

+ 0 - 1
code/AssetLib/NFF/NFFLoader.h

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

+ 5 - 2
code/AssetLib/Obj/ObjExporter.cpp

@@ -2,7 +2,7 @@
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 
-Copyright (c) 2006-2020, assimp team
+Copyright (c) 2006-2024, assimp team
 
 
 All rights reserved.
@@ -174,9 +174,12 @@ void ObjExporter::WriteHeader(std::ostringstream& out) {
 
 // ------------------------------------------------------------------------------------------------
 std::string ObjExporter::GetMaterialName(unsigned int index) {
+    static const std::string EmptyStr;
+    if ( nullptr == pScene->mMaterials ) {
+        return EmptyStr;
+    }
     const aiMaterial* const mat = pScene->mMaterials[index];
     if ( nullptr == mat ) {
-        static const std::string EmptyStr;
         return EmptyStr;
     }
 

+ 2 - 2
code/AssetLib/Obj/ObjExporter.h

@@ -2,7 +2,7 @@
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 
-Copyright (c) 2006-2020, assimp team
+Copyright (c) 2006-2024, assimp team
 
 
 All rights reserved.
@@ -62,7 +62,7 @@ namespace Assimp {
 // ------------------------------------------------------------------------------------------------
 /** Helper class to export a given scene to an OBJ file. */
 // ------------------------------------------------------------------------------------------------
-class ObjExporter {
+class ObjExporter final {
 public:
     /// Constructor for a specific scene to export
     ObjExporter(const char* filename, const aiScene* pScene, bool noMtl=false, const ExportProperties* props = nullptr);

+ 24 - 19
code/AssetLib/Obj/ObjFileImporter.cpp

@@ -67,7 +67,7 @@ static constexpr aiImporterDesc desc = {
     "obj"
 };
 
-static const unsigned int ObjMinSize = 16;
+static constexpr unsigned int ObjMinSize = 16u;
 
 namespace Assimp {
 
@@ -163,7 +163,7 @@ void ObjFileImporter::InternReadFile(const std::string &file, aiScene *pScene, I
 // ------------------------------------------------------------------------------------------------
 //  Create the data from parsed obj-file
 void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene *pScene) {
-    if (nullptr == pModel) {
+    if (pModel == nullptr) {
         return;
     }
 
@@ -178,7 +178,6 @@ void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene
     }
 
     if (!pModel->mObjects.empty()) {
-
         unsigned int meshCount = 0;
         unsigned int childCount = 0;
 
@@ -258,8 +257,7 @@ void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene
 aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile::Object *pObject,
         aiNode *pParent, aiScene *pScene,
         std::vector<std::unique_ptr<aiMesh>> &MeshArray) {
-    ai_assert(nullptr != pModel);
-    if (nullptr == pObject) {
+    if (nullptr == pObject || pModel == nullptr) {
         return nullptr;
     }
 
@@ -311,16 +309,13 @@ aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile
 // ------------------------------------------------------------------------------------------------
 //  Create topology data
 std::unique_ptr<aiMesh> ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, unsigned int meshIndex) {
-    // Checking preconditions
-    ai_assert(nullptr != pModel);
-
-    if (nullptr == pData) {
+    if (nullptr == pData || pModel == nullptr) {    
         return nullptr;
     }
 
     // Create faces
     ObjFile::Mesh *pObjMesh = pModel->mMeshes[meshIndex];
-    if (!pObjMesh) {
+    if (pObjMesh == nullptr) {
         return nullptr;
     }
 
@@ -335,7 +330,10 @@ std::unique_ptr<aiMesh> ObjFileImporter::createTopology(const ObjFile::Model *pM
 
     for (size_t index = 0; index < pObjMesh->m_Faces.size(); index++) {
         const ObjFile::Face *inp = pObjMesh->m_Faces[index];
-
+        if (inp == nullptr) {
+            continue;
+        }
+        
         if (inp->mPrimitiveType == aiPrimitiveType_LINE) {
             pMesh->mNumFaces += static_cast<unsigned int>(inp->m_vertices.size() - 1);
             pMesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
@@ -352,14 +350,14 @@ std::unique_ptr<aiMesh> ObjFileImporter::createTopology(const ObjFile::Model *pM
         }
     }
 
-    unsigned int uiIdxCount(0u);
+    unsigned int uiIdxCount = 0u;
     if (pMesh->mNumFaces > 0) {
         pMesh->mFaces = new aiFace[pMesh->mNumFaces];
         if (pObjMesh->m_uiMaterialIndex != ObjFile::Mesh::NoMaterial) {
             pMesh->mMaterialIndex = pObjMesh->m_uiMaterialIndex;
         }
 
-        unsigned int outIndex(0);
+        unsigned int outIndex = 0u;
 
         // Copy all data from all stored meshes
         for (auto &face : pObjMesh->m_Faces) {
@@ -403,11 +401,14 @@ void ObjFileImporter::createVertexArray(const ObjFile::Model *pModel,
         aiMesh *pMesh,
         unsigned int numIndices) {
     // Checking preconditions
-    ai_assert(nullptr != pCurrentObject);
+    if (pCurrentObject == nullptr || pModel == nullptr || pMesh == nullptr) {
+        return;
+    }     
 
     // Break, if no faces are stored in object
-    if (pCurrentObject->m_Meshes.empty())
+    if (pCurrentObject->m_Meshes.empty()) {
         return;
+    }
 
     // Get current mesh
     ObjFile::Mesh *pObjMesh = pModel->mMeshes[uiMeshIndex];
@@ -586,11 +587,12 @@ void ObjFileImporter::createMaterials(const ObjFile::Model *pModel, aiScene *pSc
         it = pModel->mMaterialMap.find(pModel->mMaterialLib[matIndex]);
 
         // No material found, use the default material
-        if (pModel->mMaterialMap.end() == it)
+        if (pModel->mMaterialMap.end() == it) {
             continue;
+        }
 
         aiMaterial *mat = new aiMaterial;
-        ObjFile::Material *pCurrentMaterial = (*it).second;
+        ObjFile::Material *pCurrentMaterial = it->second;
         mat->AddProperty(&pCurrentMaterial->MaterialName, AI_MATKEY_NAME);
 
         // convert illumination model
@@ -777,8 +779,11 @@ void ObjFileImporter::createMaterials(const ObjFile::Model *pModel, aiScene *pSc
 //  Appends this node to the parent node
 void ObjFileImporter::appendChildToParentNode(aiNode *pParent, aiNode *pChild) {
     // Checking preconditions
-    ai_assert(nullptr != pParent);
-    ai_assert(nullptr != pChild);
+    if (pParent == nullptr || pChild == nullptr) {
+        ai_assert(nullptr != pParent);
+        ai_assert(nullptr != pChild);
+        return;
+    }
 
     // Assign parent to child
     pChild->mParent = pParent;

+ 1 - 1
code/AssetLib/Obj/ObjFileImporter.h

@@ -61,7 +61,7 @@ struct Model;
 /// \class  ObjFileImporter
 /// \brief  Imports a waveform obj file
 // ------------------------------------------------------------------------------------------------
-class ObjFileImporter : public BaseImporter {
+class ObjFileImporter final : public BaseImporter {
 public:
     /// \brief  Default constructor
     ObjFileImporter();

+ 19 - 7
code/AssetLib/Obj/ObjFileMtlImporter.cpp

@@ -3,7 +3,7 @@
 Open Asset Import Library (assimp)
 ---------------------------------------------------------------------------
 
-Copyright (c) 2006-2020, assimp team
+Copyright (c) 2006-2024, assimp team
 
 All rights reserved.
 
@@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "ObjFileMtlImporter.h"
 #include "ObjFileData.h"
 #include "ObjTools.h"
+#include <assimp/DefaultIOSystem.h>
 #include <assimp/ParsingUtils.h>
 #include <assimp/fast_atof.h>
 #include <assimp/material.h>
@@ -89,8 +90,9 @@ static constexpr char TypeOption[] = "-type";
 // -------------------------------------------------------------------
 //  Constructor
 ObjFileMtlImporter::ObjFileMtlImporter(std::vector<char> &buffer,
-        const std::string &,
+        const std::string &strAbsPath,
         ObjFile::Model *pModel) :
+        m_strAbsPath(strAbsPath),
         m_DataIt(buffer.begin()),
         m_DataItEnd(buffer.end()),
         m_pModel(pModel),
@@ -103,13 +105,23 @@ ObjFileMtlImporter::ObjFileMtlImporter(std::vector<char> &buffer,
         m_pModel->mDefaultMaterial = new ObjFile::Material;
         m_pModel->mDefaultMaterial->MaterialName.Set("default");
     }
+    
+    // Try with OS folder separator first
+    char folderSeparator = DefaultIOSystem().getOsSeparator();
+    std::size_t found = m_strAbsPath.find_last_of(folderSeparator);
+    if (found == std::string::npos) {
+        // Not found, try alternative folder separator
+        folderSeparator = (folderSeparator == '/' ? '\\' : '/');
+        found = m_strAbsPath.find_last_of(folderSeparator);
+    }
+    if (found != std::string::npos) {
+        m_strAbsPath = m_strAbsPath.substr(0, found + 1);
+    } else {
+        m_strAbsPath = "";
+    }
     load();
 }
 
-// -------------------------------------------------------------------
-//  Destructor
-ObjFileMtlImporter::~ObjFileMtlImporter() = default;
-
 // -------------------------------------------------------------------
 //  Loads the material description
 void ObjFileMtlImporter::load() {
@@ -446,7 +458,7 @@ void ObjFileMtlImporter::getTexture() {
     std::string texture;
     m_DataIt = getName<DataArrayIt>(m_DataIt, m_DataItEnd, texture);
     if (nullptr != out) {
-        out->Set(texture);
+        out->Set(m_strAbsPath + texture);
     }
 }
 

+ 2 - 5
code/AssetLib/Obj/ObjFileMtlImporter.h

@@ -2,7 +2,7 @@
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 
-Copyright (c) 2006-2020, assimp team
+Copyright (c) 2006-2024, assimp team
 
 All rights reserved.
 
@@ -71,13 +71,12 @@ public:
             ObjFile::Model *pModel);
 
     //! \brief  The class destructor
-    ~ObjFileMtlImporter();
+    ~ObjFileMtlImporter() = default;
 
     ObjFileMtlImporter(const ObjFileMtlImporter &rOther) = delete;
     ObjFileMtlImporter &operator=(const ObjFileMtlImporter &rOther) = delete;
 
 private:
-    /// Copy constructor, empty.
     /// Load the whole material description
     void load();
     /// Get color data.
@@ -109,8 +108,6 @@ private:
     std::vector<char> m_buffer;
 };
 
-// ------------------------------------------------------------------------------------------------
-
 } // Namespace Assimp
 
 #endif // OBJFILEMTLIMPORTER_H_INC

+ 6 - 6
code/AssetLib/Obj/ObjFileParser.cpp

@@ -111,8 +111,8 @@ void ObjFileParser::parseFile(IOStreamBuffer<char> &streamBuffer) {
     //const unsigned int updateProgressEveryBytes = 100 * 1024;
     const unsigned int bytesToProcess = static_cast<unsigned int>(streamBuffer.size());
     const unsigned int progressTotal = bytesToProcess;
-    unsigned int processed = 0;
-    size_t lastFilePos(0);
+    unsigned int processed = 0u;
+    size_t lastFilePos = 0u;
 
     bool insideCstype = false;
     std::vector<char> buffer;
@@ -300,7 +300,7 @@ size_t ObjFileParser::getNumComponentsInDataDefinition() {
         } else if (IsLineEnd(*tmp)) {
             end_of_definition = true;
         }
-        if (!SkipSpaces(&tmp, mEnd)) {
+        if (!SkipSpaces(&tmp, mEnd) || *tmp == '#') {
             break;
         }
         const bool isNum(IsNumeric(*tmp) || isNanOrInf(tmp));
@@ -308,11 +308,11 @@ size_t ObjFileParser::getNumComponentsInDataDefinition() {
         if (isNum) {
             ++numComponents;
         }
-        if (!SkipSpaces(&tmp, mEnd)) {
+        if (!SkipSpaces(&tmp, mEnd) || *tmp == '#') {
             break;
         }
     }
-    
+
     return numComponents;
 }
 
@@ -451,7 +451,7 @@ void ObjFileParser::getFace(aiPrimitiveType type) {
     while (m_DataIt < m_DataItEnd) {
         int iStep = 1;
 
-        if (IsLineEnd(*m_DataIt)) {
+        if (IsLineEnd(*m_DataIt) || *m_DataIt == '#') {
             break;
         }
 

+ 7 - 4
code/AssetLib/Obj/ObjFileParser.h

@@ -55,18 +55,21 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 namespace Assimp {
 
+// Forward declarations
 class ObjFileImporter;
 class IOSystem;
 class ProgressHandler;
 
+// ------------------------------------------------------------------------------------------------
 /// \class  ObjFileParser
 /// \brief  Parser for a obj waveform file
+// ------------------------------------------------------------------------------------------------
 class ASSIMP_API ObjFileParser {
 public:
-    static const size_t Buffersize = 4096;
-    typedef std::vector<char> DataArray;
-    typedef std::vector<char>::iterator DataArrayIt;
-    typedef std::vector<char>::const_iterator ConstDataArrayIt;
+    static constexpr size_t Buffersize = 4096;
+    using DataArray = std::vector<char>;
+    using DataArrayIt = std::vector<char>::iterator;
+    using ConstDataArrayIt = std::vector<char>::const_iterator;
 
     /// @brief  The default constructor.
     ObjFileParser();

+ 66 - 77
code/AssetLib/Ply/PlyLoader.cpp

@@ -81,6 +81,27 @@ namespace {
 
         return props[idx];
     }
+
+    // ------------------------------------------------------------------------------------------------
+    static bool isBigEndian(const char *szMe) {
+        ai_assert(nullptr != szMe);
+
+        // binary_little_endian
+        // binary_big_endian
+        bool isBigEndian{ false };
+#if (defined AI_BUILD_BIG_ENDIAN)
+        if ('l' == *szMe || 'L' == *szMe) {
+            isBigEndian = true;
+        }
+#else
+        if ('b' == *szMe || 'B' == *szMe) {
+            isBigEndian = true;
+        }
+#endif // ! AI_BUILD_BIG_ENDIAN
+
+        return isBigEndian;
+    }
+
 } // namespace
 
 // ------------------------------------------------------------------------------------------------
@@ -92,6 +113,11 @@ PLYImporter::PLYImporter() :
     // empty
 }
 
+// ------------------------------------------------------------------------------------------------
+PLYImporter::~PLYImporter() {
+    delete mGeneratedMesh;
+}
+
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 bool PLYImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
@@ -104,26 +130,6 @@ const aiImporterDesc *PLYImporter::GetInfo() const {
     return &desc;
 }
 
-// ------------------------------------------------------------------------------------------------
-static bool isBigEndian(const char *szMe) {
-    ai_assert(nullptr != szMe);
-
-    // binary_little_endian
-    // binary_big_endian
-    bool isBigEndian(false);
-#if (defined AI_BUILD_BIG_ENDIAN)
-    if ('l' == *szMe || 'L' == *szMe) {
-        isBigEndian = true;
-    }
-#else
-    if ('b' == *szMe || 'B' == *szMe) {
-        isBigEndian = true;
-    }
-#endif // ! AI_BUILD_BIG_ENDIAN
-
-    return isBigEndian;
-}
-
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure.
 void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
@@ -134,7 +140,7 @@ void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
     }
 
     // Get the file-size
-    const size_t fileSize(fileStream->FileSize());
+    const size_t fileSize = fileStream->FileSize();
     if (0 == fileSize) {
         throw DeadlyImportError("File ", pFile, " is empty.");
     }
@@ -180,7 +186,7 @@ void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
             }
         } else if (!::strncmp(szMe, "binary_", 7)) {
             szMe += 7;
-            const bool bIsBE(isBigEndian(szMe));
+            const bool bIsBE = isBigEndian(szMe);
 
             // skip the line, parse the rest of the header and build the DOM
             if (!PLY::DOM::ParseInstanceBinary(streamedBuffer, &sPlyDom, this, bIsBE)) {
@@ -241,7 +247,9 @@ void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
     // fill the mesh list
     pScene->mNumMeshes = 1;
     pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
-    pScene->mMeshes[0] = mGeneratedMesh;
+    pScene->mMeshes[0] = mGeneratedMesh;    
+
+    // Move the mesh ownership into the scene instance
     mGeneratedMesh = nullptr;
 
     // generate a simple node structure
@@ -254,20 +262,22 @@ void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
     }
 }
 
+static constexpr ai_uint NotSet = 0xFFFFFFFF;
+
 void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementInstance *instElement, unsigned int pos) {
     ai_assert(nullptr != pcElement);
     ai_assert(nullptr != instElement);
 
-    ai_uint aiPositions[3] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
+    ai_uint aiPositions[3] = { NotSet, NotSet, NotSet };
     PLY::EDataType aiTypes[3] = { EDT_Char, EDT_Char, EDT_Char };
 
-    ai_uint aiNormal[3] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
+    ai_uint aiNormal[3] = { NotSet, NotSet, NotSet };
     PLY::EDataType aiNormalTypes[3] = { EDT_Char, EDT_Char, EDT_Char };
 
-    unsigned int aiColors[4] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
+    unsigned int aiColors[4] = { NotSet, NotSet, NotSet, NotSet };
     PLY::EDataType aiColorsTypes[4] = { EDT_Char, EDT_Char, EDT_Char, EDT_Char };
 
-    unsigned int aiTexcoord[2] = { 0xFFFFFFFF, 0xFFFFFFFF };
+    unsigned int aiTexcoord[2] = { NotSet, NotSet };
     PLY::EDataType aiTexcoordTypes[2] = { EDT_Char, EDT_Char };
 
     // now check whether which normal components are available
@@ -337,17 +347,17 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
     if (0 != cnt) {
         // Position
         aiVector3D vOut;
-        if (0xFFFFFFFF != aiPositions[0]) {
+        if (NotSet != aiPositions[0]) {
             vOut.x = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiPositions[0]).avList.front(), aiTypes[0]);
         }
 
-        if (0xFFFFFFFF != aiPositions[1]) {
+        if (NotSet != aiPositions[1]) {
             vOut.y = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiPositions[1]).avList.front(), aiTypes[1]);
         }
 
-        if (0xFFFFFFFF != aiPositions[2]) {
+        if (NotSet != aiPositions[2]) {
             vOut.z = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiPositions[2]).avList.front(), aiTypes[2]);
         }
@@ -355,19 +365,19 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
         // Normals
         aiVector3D nOut;
         bool haveNormal = false;
-        if (0xFFFFFFFF != aiNormal[0]) {
+        if (NotSet != aiNormal[0]) {
             nOut.x = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiNormal[0]).avList.front(), aiNormalTypes[0]);
             haveNormal = true;
         }
 
-        if (0xFFFFFFFF != aiNormal[1]) {
+        if (NotSet != aiNormal[1]) {
             nOut.y = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiNormal[1]).avList.front(), aiNormalTypes[1]);
             haveNormal = true;
         }
 
-        if (0xFFFFFFFF != aiNormal[2]) {
+        if (NotSet != aiNormal[2]) {
             nOut.z = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiNormal[2]).avList.front(), aiNormalTypes[2]);
             haveNormal = true;
@@ -376,7 +386,7 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
         // Colors
         aiColor4D cOut;
         bool haveColor = false;
-        if (0xFFFFFFFF != aiColors[0]) {
+        if (NotSet != aiColors[0]) {
             cOut.r = NormalizeColorValue(GetProperty(instElement->alProperties,
                                                  aiColors[0])
                                                  .avList.front(),
@@ -384,7 +394,7 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
             haveColor = true;
         }
 
-        if (0xFFFFFFFF != aiColors[1]) {
+        if (NotSet != aiColors[1]) {
             cOut.g = NormalizeColorValue(GetProperty(instElement->alProperties,
                                                  aiColors[1])
                                                  .avList.front(),
@@ -392,7 +402,7 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
             haveColor = true;
         }
 
-        if (0xFFFFFFFF != aiColors[2]) {
+        if (NotSet != aiColors[2]) {
             cOut.b = NormalizeColorValue(GetProperty(instElement->alProperties,
                                                  aiColors[2])
                                                  .avList.front(),
@@ -401,7 +411,7 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
         }
 
         // assume 1.0 for the alpha channel if it is not set
-        if (0xFFFFFFFF == aiColors[3]) {
+        if (NotSet == aiColors[3]) {
             cOut.a = 1.0;
         } else {
             cOut.a = NormalizeColorValue(GetProperty(instElement->alProperties,
@@ -416,13 +426,13 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
         aiVector3D tOut;
         tOut.z = 0;
         bool haveTextureCoords = false;
-        if (0xFFFFFFFF != aiTexcoord[0]) {
+        if (NotSet != aiTexcoord[0]) {
             tOut.x = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiTexcoord[0]).avList.front(), aiTexcoordTypes[0]);
             haveTextureCoords = true;
         }
 
-        if (0xFFFFFFFF != aiTexcoord[1]) {
+        if (NotSet != aiTexcoord[1]) {
             tOut.y = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiTexcoord[1]).avList.front(), aiTexcoordTypes[1]);
             haveTextureCoords = true;
@@ -438,6 +448,9 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
             mGeneratedMesh->mNumVertices = pcElement->NumOccur;
             mGeneratedMesh->mVertices = new aiVector3D[mGeneratedMesh->mNumVertices];
         }
+        if (pos >= mGeneratedMesh->mNumVertices) {
+            throw DeadlyImportError("Invalid .ply file: Too many vertices");
+        }
 
         mGeneratedMesh->mVertices[pos] = vOut;
 
@@ -504,16 +517,12 @@ void PLYImporter::LoadFace(const PLY::Element *pcElement, const PLY::ElementInst
     bool bOne = false;
 
     // index of the vertex index list
-    unsigned int iProperty = 0xFFFFFFFF;
+    unsigned int iProperty = NotSet;
     PLY::EDataType eType = EDT_Char;
     bool bIsTriStrip = false;
 
-    // index of the material index property
-    // unsigned int iMaterialIndex = 0xFFFFFFFF;
-    // PLY::EDataType eType2 = EDT_Char;
-
     // texture coordinates
-    unsigned int iTextureCoord = 0xFFFFFFFF;
+    unsigned int iTextureCoord = NotSet;
     PLY::EDataType eType3 = EDT_Char;
 
     // face = unique number of vertex indices
@@ -572,7 +581,7 @@ void PLYImporter::LoadFace(const PLY::Element *pcElement, const PLY::ElementInst
 
         if (!bIsTriStrip) {
             // parse the list of vertex indices
-            if (0xFFFFFFFF != iProperty) {
+            if (NotSet != iProperty) {
                 const unsigned int iNum = (unsigned int)GetProperty(instElement->alProperties, iProperty).avList.size();
                 mGeneratedMesh->mFaces[pos].mNumIndices = iNum;
                 mGeneratedMesh->mFaces[pos].mIndices = new unsigned int[iNum];
@@ -585,15 +594,7 @@ void PLYImporter::LoadFace(const PLY::Element *pcElement, const PLY::ElementInst
                 }
             }
 
-            // parse the material index
-            // cannot be handled without processing the whole file first
-            /*if (0xFFFFFFFF != iMaterialIndex)
-        {
-            mGeneratedMesh->mFaces[pos]. = PLY::PropertyInstance::ConvertTo<unsigned int>(
-            GetProperty(instElement->alProperties, iMaterialIndex).avList.front(), eType2);
-        }*/
-
-            if (0xFFFFFFFF != iTextureCoord) {
+            if (NotSet != iTextureCoord) {
                 const unsigned int iNum = (unsigned int)GetProperty(instElement->alProperties, iTextureCoord).avList.size();
 
                 // should be 6 coords
@@ -679,41 +680,29 @@ void PLYImporter::GetMaterialColor(const std::vector<PLY::PropertyInstance> &avL
         aiColor4D *clrOut) {
     ai_assert(nullptr != clrOut);
 
-    if (0xFFFFFFFF == aiPositions[0])
+    if (NotSet == aiPositions[0]) {
         clrOut->r = 0.0f;
-    else {
-        clrOut->r = NormalizeColorValue(GetProperty(avList,
-                                                aiPositions[0])
-                                                .avList.front(),
-                aiTypes[0]);
+    } else {
+        clrOut->r = NormalizeColorValue(GetProperty(avList, aiPositions[0]).avList.front(), aiTypes[0]);
     }
 
-    if (0xFFFFFFFF == aiPositions[1])
+    if (NotSet == aiPositions[1]) {
         clrOut->g = 0.0f;
-    else {
-        clrOut->g = NormalizeColorValue(GetProperty(avList,
-                                                aiPositions[1])
-                                                .avList.front(),
-                aiTypes[1]);
+    } else {
+        clrOut->g = NormalizeColorValue(GetProperty(avList, aiPositions[1]).avList.front(), aiTypes[1]);
     }
 
-    if (0xFFFFFFFF == aiPositions[2])
+    if (NotSet == aiPositions[2])
         clrOut->b = 0.0f;
     else {
-        clrOut->b = NormalizeColorValue(GetProperty(avList,
-                                                aiPositions[2])
-                                                .avList.front(),
-                aiTypes[2]);
+        clrOut->b = NormalizeColorValue(GetProperty(avList, aiPositions[2]).avList.front(), aiTypes[2]);
     }
 
     // assume 1.0 for the alpha channel ifit is not set
-    if (0xFFFFFFFF == aiPositions[3])
+    if (NotSet == aiPositions[3])
         clrOut->a = 1.0f;
     else {
-        clrOut->a = NormalizeColorValue(GetProperty(avList,
-                                                aiPositions[3])
-                                                .avList.front(),
-                aiTypes[3]);
+        clrOut->a = NormalizeColorValue(GetProperty(avList, aiPositions[3]).avList.front(), aiTypes[3]);
     }
 }
 

+ 3 - 7
code/AssetLib/Ply/PlyLoader.h

@@ -62,10 +62,10 @@ using namespace PLY;
 // ---------------------------------------------------------------------------
 /** Importer class to load the stanford PLY file format
 */
-class PLYImporter : public BaseImporter {
+class PLYImporter final : public BaseImporter {
 public:
     PLYImporter();
-    ~PLYImporter() override = default;
+    ~PLYImporter() override;
 
     // -------------------------------------------------------------------
     /** Returns whether the class can handle the format of the given file.
@@ -120,13 +120,9 @@ protected:
             PLY::PropertyInstance::ValueUnion val,
             PLY::EDataType eType);
 
-    /** Buffer to hold the loaded file */
+private:
     unsigned char *mBuffer;
-
-    /** Document object model representation extracted from the file */
     PLY::DOM *pcDOM;
-
-    /** Mesh generated by loader */
     aiMesh *mGeneratedMesh;
 };
 

+ 29 - 0
code/AssetLib/Ply/PlyParser.cpp

@@ -48,10 +48,29 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/ByteSwapper.h>
 #include <assimp/fast_atof.h>
 #include <assimp/DefaultLogger.hpp>
+#include <unordered_set>
 #include <utility>
 
 namespace Assimp {
 
+std::string to_string(EElementSemantic e) {
+
+    switch (e) {
+    case EEST_Vertex:
+        return std::string{ "vertex" };
+    case EEST_TriStrip:
+        return std::string{ "tristrips" };
+    case EEST_Edge:
+        return std::string{ "edge" };
+    case EEST_Material:
+        return std::string{ "material" };
+    case EEST_TextureFile:
+        return std::string{ "TextureFile" };
+    default:
+        return std::string{ "invalid" };
+    }
+}
+
 // ------------------------------------------------------------------------------------------------
 PLY::EDataType PLY::Property::ParseDataType(std::vector<char> &buffer) {
     ai_assert(!buffer.empty());
@@ -281,6 +300,8 @@ bool PLY::Element::ParseElement(IOStreamBuffer<char> &streamBuffer, std::vector<
         // if the exact semantic can't be determined, just store
         // the original string identifier
         pOut->szName = std::string(&buffer[0], &buffer[0] + strlen(&buffer[0]));
+        auto pos = pOut->szName.find_last_of(' ');
+        pOut->szName.erase(pos, pOut->szName.size());
     }
 
     if (!PLY::DOM::SkipSpaces(buffer))
@@ -413,6 +434,7 @@ bool PLY::DOM::SkipComments(std::vector<char> buffer) {
 bool PLY::DOM::ParseHeader(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, bool isBinary) {
     ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseHeader() begin");
 
+    std::unordered_set<std::string> definedAlElements;
     // parse all elements
     while (!buffer.empty()) {
         // skip all comments
@@ -421,6 +443,13 @@ bool PLY::DOM::ParseHeader(IOStreamBuffer<char> &streamBuffer, std::vector<char>
         PLY::Element out;
         if (PLY::Element::ParseElement(streamBuffer, buffer, &out)) {
             // add the element to the list of elements
+
+            const auto propertyName = (out.szName.empty()) ? to_string(out.eSemantic) : out.szName;
+            auto alreadyDefined = definedAlElements.find(propertyName);
+            if (alreadyDefined != definedAlElements.end()) {
+                throw DeadlyImportError("Property '" + propertyName + "' in header already defined ");
+            }
+            definedAlElements.insert(propertyName);
             alElements.push_back(out);
         } else if (TokenMatch(buffer, "end_header", 10)) {
             // we have reached the end of the header

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

@@ -400,8 +400,12 @@ void SMDImporter::AddBoneChildren(aiNode* pcNode, uint32_t iParent) {
         }
     }
 
+    // nothing to do
+    if (pcNode->mNumChildren == 0)
+        return;
+
     // now allocate the output array
-    pcNode->mChildren = new aiNode*[pcNode->mNumChildren];
+    pcNode->mChildren = new aiNode *[pcNode->mNumChildren];
 
     // and fill all subnodes
     unsigned int qq( 0 );
@@ -451,11 +455,10 @@ void SMDImporter::CreateOutputNodes() {
         delete pcOldRoot;
 
         pScene->mRootNode->mParent = nullptr;
-    }
-    else
-    {
-        ::strcpy(pScene->mRootNode->mName.data, "<SMD_root>");
+    } else {
+        static constexpr char rootName[11] = "<SMD_root>";
         pScene->mRootNode->mName.length = 10;
+        ::strncpy(pScene->mRootNode->mName.data, rootName, pScene->mRootNode->mName.length);
     }
 }
 

+ 7 - 15
code/AssetLib/STEPParser/STEPFileReader.cpp

@@ -499,21 +499,13 @@ STEP::LazyObject::LazyObject(DB& db, uint64_t id,uint64_t /*line*/, const char*
     int64_t skip_depth( 0 );
     while ( *a ) {
         handleSkippedDepthFromToken(a, skip_depth);
-        /*if (*a == '(') {
-            ++skip_depth;
-        } else if (*a == ')') {
-            --skip_depth;
-        }*/
-
-		if (skip_depth >= 1 && *a=='#') {
-			if (*(a + 1) != '#') {
-				/*const char *tmp;
-				const int64_t num = static_cast<int64_t>(strtoul10_64(a + 1, &tmp));
-				db.MarkRef(num, id);*/
-                db.MarkRef(getIdFromToken(a), id);
-			} else {
-				++a;
-			}
+
+	if (skip_depth >= 1 && *a=='#') {
+		if (*(a + 1) != '#') {
+			db.MarkRef(getIdFromToken(a), id);
+		} else {
+			++a;
+		}
         }
         ++a;
     }

+ 17 - 16
code/AssetLib/STL/STLExporter.cpp

@@ -39,9 +39,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 ----------------------------------------------------------------------
 */
-
-
-
 #if !defined(ASSIMP_BUILD_NO_EXPORT) && !defined(ASSIMP_BUILD_NO_STL_EXPORTER)
 
 #include "STLExporter.h"
@@ -55,7 +52,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 using namespace Assimp;
 
-namespace Assimp    {
+namespace Assimp {
 
 // ------------------------------------------------------------------------------------------------
 // Worker function for exporting a scene to Stereolithograpy. Prototyped and registered in Exporter.cpp
@@ -78,6 +75,7 @@ void ExportSceneSTL(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene
 
     outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1);
 }
+
 void ExportSceneSTLBinary(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties )
 {
     bool exportPointClouds = pProperties->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS);
@@ -100,13 +98,11 @@ void ExportSceneSTLBinary(const char* pFile,IOSystem* pIOSystem, const aiScene*
 
 } // end of namespace Assimp
 
-static const char *SolidToken = "solid";
-static const char *EndSolidToken = "endsolid";
+static constexpr char SolidToken[]    = "solid";
+static constexpr char EndSolidToken[] = "endsolid";
 
 // ------------------------------------------------------------------------------------------------
-STLExporter::STLExporter(const char* _filename, const aiScene* pScene, bool exportPointClouds, bool binary)
-: filename(_filename)
-, endl("\n")
+STLExporter::STLExporter(const char* _filename, const aiScene* pScene, bool exportPointClouds, bool binary) : filename(_filename) , endl("\n")
 {
     // make sure that all formatting happens using the standard, C locale and not the user's current locale
     const std::locale& l = std::locale("C");
@@ -173,24 +169,26 @@ void STLExporter::WritePointCloud(const std::string &name, const aiScene* pScene
 }
 
 // ------------------------------------------------------------------------------------------------
-void STLExporter::WriteMesh(const aiMesh* m)
-{
+void STLExporter::WriteMesh(const aiMesh* m) {
     for (unsigned int i = 0; i < m->mNumFaces; ++i) {
         const aiFace& f = m->mFaces[i];
+        if (f.mNumIndices < 3) {
+            continue;
+        }
 
         // we need per-face normals. We specified aiProcess_GenNormals as pre-requisite for this exporter,
         // but nonetheless we have to expect per-vertex normals.
         aiVector3D nor;
         if (m->mNormals) {
-            for(unsigned int a = 0; a < f.mNumIndices; ++a) {
+            for (unsigned int a = 0; a < f.mNumIndices; ++a) {
                 nor += m->mNormals[f.mIndices[a]];
             }
             nor.NormalizeSafe();
         }
         mOutput << " facet normal " << nor.x << " " << nor.y << " " << nor.z << endl;
         mOutput << "  outer loop" << endl;
-        for(unsigned int a = 0; a < f.mNumIndices; ++a) {
-            const aiVector3D& v  = m->mVertices[f.mIndices[a]];
+        for (unsigned int a = 0; a < f.mNumIndices; ++a) {
+            const aiVector3D &v = m->mVertices[f.mIndices[a]];
             mOutput << "  vertex " << v.x << " " << v.y << " " << v.z << endl;
         }
 
@@ -199,10 +197,13 @@ void STLExporter::WriteMesh(const aiMesh* m)
     }
 }
 
-void STLExporter::WriteMeshBinary(const aiMesh* m)
-{
+void STLExporter::WriteMeshBinary(const aiMesh* m) {
     for (unsigned int i = 0; i < m->mNumFaces; ++i) {
         const aiFace& f = m->mFaces[i];
+        if (f.mNumIndices < 3) {
+            continue;
+        }
+        
         // we need per-face normals. We specified aiProcess_GenNormals as pre-requisite for this exporter,
         // but nonetheless we have to expect per-vertex normals.
         aiVector3D nor;

+ 280 - 58
code/AssetLib/USD/USDLoaderImplTinyusdz.cpp

@@ -58,6 +58,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/importerdesc.h>
 #include <assimp/IOStreamBuffer.h>
 #include <assimp/IOSystem.hpp>
+#include "assimp/MemoryIOWrapper.h"
 #include <assimp/StringUtils.h>
 #include <assimp/StreamReader.h>
 
@@ -72,7 +73,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "../../../contrib/tinyusdz/assimp_tinyusdz_logging.inc"
 
 namespace {
-    static constexpr char Tag[] = "tinyusdz loader";
+    static constexpr char TAG[] = "tinyusdz loader";
 }
 
 namespace Assimp {
@@ -81,7 +82,7 @@ using namespace std;
 void USDImporterImplTinyusdz::InternReadFile(
         const std::string &pFile,
         aiScene *pScene,
-        IOSystem *) {
+        IOSystem *pIOHandler) {
     // Grab filename for logging purposes
     size_t pos = pFile.find_last_of('/');
     string basePath = pFile.substr(0, pos);
@@ -91,29 +92,48 @@ void USDImporterImplTinyusdz::InternReadFile(
     ss << "InternReadFile(): model" << nameWExt;
     TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
 
+    bool is_load_from_mem{ pFile.substr(0, AI_MEMORYIO_MAGIC_FILENAME_LENGTH) == AI_MEMORYIO_MAGIC_FILENAME };
+    std::vector<uint8_t> in_mem_data;
+    if (is_load_from_mem) {
+        auto stream_closer = [pIOHandler](IOStream *pStream) {
+            pIOHandler->Close(pStream);
+        };
+        std::unique_ptr<IOStream, decltype(stream_closer)> file_stream(pIOHandler->Open(pFile, "rb"), stream_closer);
+        if (!file_stream) {
+            throw DeadlyImportError("Failed to open file ", pFile, ".");
+        }
+        size_t file_size{ file_stream->FileSize() };
+        in_mem_data.resize(file_size);
+        file_stream->Read(in_mem_data.data(), 1, file_size);
+    }
+
     bool ret{ false };
     tinyusdz::USDLoadOptions options;
     tinyusdz::Stage stage;
     std::string warn, err;
     bool is_usdz{ false };
     if (isUsdc(pFile)) {
-        ret = LoadUSDCFromFile(pFile, &stage, &warn, &err, options);
+        ret = is_load_from_mem ? LoadUSDCFromMemory(in_mem_data.data(), in_mem_data.size(), pFile, &stage, &warn, &err, options) :
+                                 LoadUSDCFromFile(pFile, &stage, &warn, &err, options);
         ss.str("");
         ss << "InternReadFile(): LoadUSDCFromFile() result: " << ret;
         TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
     } else if (isUsda(pFile)) {
-        ret = LoadUSDAFromFile(pFile, &stage, &warn, &err, options);
+        ret = is_load_from_mem ? LoadUSDAFromMemory(in_mem_data.data(), in_mem_data.size(), pFile, &stage, &warn, &err, options) :
+                                 LoadUSDAFromFile(pFile, &stage, &warn, &err, options);
         ss.str("");
         ss << "InternReadFile(): LoadUSDAFromFile() result: " << ret;
         TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
     } else if (isUsdz(pFile)) {
-        ret = LoadUSDZFromFile(pFile, &stage, &warn, &err, options);
+        ret = is_load_from_mem ? LoadUSDZFromMemory(in_mem_data.data(), in_mem_data.size(), pFile, &stage, &warn, &err, options) :
+                                 LoadUSDZFromFile(pFile, &stage, &warn, &err, options);
         is_usdz = true;
         ss.str("");
         ss << "InternReadFile(): LoadUSDZFromFile() result: " << ret;
         TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
     } else if (isUsd(pFile)) {
-        ret = LoadUSDFromFile(pFile, &stage, &warn, &err, options);
+        ret = is_load_from_mem ? LoadUSDFromMemory(in_mem_data.data(), in_mem_data.size(), pFile, &stage, &warn, &err, options) :
+                                 LoadUSDFromFile(pFile, &stage, &warn, &err, options);
         ss.str("");
         ss << "InternReadFile(): LoadUSDFromFile() result: " << ret;
         TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
@@ -149,7 +169,9 @@ void USDImporterImplTinyusdz::InternReadFile(
     // NOTE: Pointer address of usdz_asset must be valid until the call of RenderSceneConverter::ConvertToRenderScene.
     tinyusdz::USDZAsset usdz_asset;
     if (is_usdz) {
-        if (!tinyusdz::ReadUSDZAssetInfoFromFile(pFile, &usdz_asset, &warn, &err)) {
+        bool is_read_USDZ_asset = is_load_from_mem ? tinyusdz::ReadUSDZAssetInfoFromMemory(in_mem_data.data(), in_mem_data.size(), false, &usdz_asset, &warn, &err) :
+                                                     tinyusdz::ReadUSDZAssetInfoFromFile(pFile, &usdz_asset, &warn, &err);
+        if (!is_read_USDZ_asset) {
             if (!warn.empty()) {
                 ss.str("");
                 ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: WARNING reported: " << warn;
@@ -190,18 +212,141 @@ void USDImporterImplTinyusdz::InternReadFile(
         return;
     }
 
-//    sanityCheckNodesRecursive(pScene->mRootNode);
+    // sanityCheckNodesRecursive(pScene->mRootNode);
+    animations(render_scene, pScene);
     meshes(render_scene, pScene, nameWExt);
     materials(render_scene, pScene, nameWExt);
     textures(render_scene, pScene, nameWExt);
     textureImages(render_scene, pScene, nameWExt);
     buffers(render_scene, pScene, nameWExt);
-
-    std::map<size_t, tinyusdz::tydra::Node> meshNodes;
-    setupNodes(render_scene, pScene, meshNodes, nameWExt);
+    pScene->mRootNode = nodesRecursive(nullptr, render_scene.nodes[0], render_scene.skeletons);
 
     setupBlendShapes(render_scene, pScene, nameWExt);
 }
+void USDImporterImplTinyusdz::animations(
+    const tinyusdz::tydra::RenderScene& render_scene,
+    aiScene* pScene) {
+    if (render_scene.animations.empty()) {
+        return;
+    }
+
+    pScene->mNumAnimations = unsigned(render_scene.animations.size());
+    pScene->mAnimations = new aiAnimation *[pScene->mNumAnimations];
+
+    for (unsigned animationIndex = 0; animationIndex < pScene->mNumAnimations; ++animationIndex) {
+
+        const auto &animation = render_scene.animations[animationIndex];
+
+        auto newAiAnimation = new aiAnimation();
+        pScene->mAnimations[animationIndex] = newAiAnimation;
+
+        newAiAnimation->mName = animation.abs_path;
+
+        if (animation.channels_map.empty()) {
+            newAiAnimation->mNumChannels = 0;
+            continue;
+        }
+
+        // each channel affects a node (joint)
+        newAiAnimation->mTicksPerSecond = render_scene.meta.framesPerSecond;
+        newAiAnimation->mNumChannels = unsigned(animation.channels_map.size());
+
+        newAiAnimation->mChannels = new aiNodeAnim *[newAiAnimation->mNumChannels];
+        int channelIndex = 0;
+        for (const auto &[jointName, animationChannelMap] : animation.channels_map) {
+            auto newAiNodeAnim = new aiNodeAnim();
+            newAiAnimation->mChannels[channelIndex] = newAiNodeAnim;
+            newAiNodeAnim->mNodeName = jointName;
+            newAiAnimation->mDuration = 0;
+
+            std::vector<aiVectorKey> positionKeys;
+            std::vector<aiQuatKey> rotationKeys;
+            std::vector<aiVectorKey> scalingKeys;
+
+            for (const auto &[channelType, animChannel] : animationChannelMap) {
+                switch (channelType) {
+                case tinyusdz::tydra::AnimationChannel::ChannelType::Rotation:
+                    if (animChannel.rotations.static_value.has_value()) {
+                        rotationKeys.emplace_back(0, tinyUsdzQuatToAiQuat(animChannel.rotations.static_value.value()));
+                    }
+                    for (const auto &rotationAnimSampler : animChannel.rotations.samples) {
+                        if (rotationAnimSampler.t > newAiAnimation->mDuration) {
+                            newAiAnimation->mDuration = rotationAnimSampler.t;
+                        }
+
+                        rotationKeys.emplace_back(rotationAnimSampler.t, tinyUsdzQuatToAiQuat(rotationAnimSampler.value));
+                    }
+                    break;
+                case tinyusdz::tydra::AnimationChannel::ChannelType::Scale:
+                    if (animChannel.scales.static_value.has_value()) {
+                        scalingKeys.emplace_back(0, tinyUsdzScaleOrPosToAssimp(animChannel.scales.static_value.value()));
+                    }
+                    for (const auto &scaleAnimSampler : animChannel.scales.samples) {
+                        if (scaleAnimSampler.t > newAiAnimation->mDuration) {
+                            newAiAnimation->mDuration = scaleAnimSampler.t;
+                        }
+                        scalingKeys.emplace_back(scaleAnimSampler.t, tinyUsdzScaleOrPosToAssimp(scaleAnimSampler.value));
+                    }
+                    break;
+                case tinyusdz::tydra::AnimationChannel::ChannelType::Transform:
+                    if (animChannel.transforms.static_value.has_value()) {
+                        aiVector3D position;
+                        aiVector3D scale;
+                        aiQuaternion rotation;
+                        tinyUsdzMat4ToAiMat4(animChannel.transforms.static_value.value().m).Decompose(scale, rotation, position);
+
+                        positionKeys.emplace_back(0, position);
+                        scalingKeys.emplace_back(0, scale);
+                        rotationKeys.emplace_back(0, rotation);
+                    }
+                    for (const auto &transformAnimSampler : animChannel.transforms.samples) {
+                        if (transformAnimSampler.t > newAiAnimation->mDuration) {
+                            newAiAnimation->mDuration = transformAnimSampler.t;
+                        }
+
+                        aiVector3D position;
+                        aiVector3D scale;
+                        aiQuaternion rotation;
+                        tinyUsdzMat4ToAiMat4(transformAnimSampler.value.m).Decompose(scale, rotation, position);
+
+                        positionKeys.emplace_back(transformAnimSampler.t, position);
+                        scalingKeys.emplace_back(transformAnimSampler.t, scale);
+                        rotationKeys.emplace_back(transformAnimSampler.t, rotation);
+                    }
+                    break;
+                case tinyusdz::tydra::AnimationChannel::ChannelType::Translation:
+                    if (animChannel.translations.static_value.has_value()) {
+                        positionKeys.emplace_back(0, tinyUsdzScaleOrPosToAssimp(animChannel.translations.static_value.value()));
+                    }
+                    for (const auto &translationAnimSampler : animChannel.translations.samples) {
+                        if (translationAnimSampler.t > newAiAnimation->mDuration) {
+                            newAiAnimation->mDuration = translationAnimSampler.t;
+                        }
+
+                        positionKeys.emplace_back(translationAnimSampler.t, tinyUsdzScaleOrPosToAssimp(translationAnimSampler.value));
+                    }
+                    break;
+                default:
+                    TINYUSDZLOGW(TAG, "Unsupported animation channel type (%s). Please update the USD importer to support this animation channel.", tinyusdzAnimChannelTypeFor(channelType).c_str());
+                }
+            }
+
+            newAiNodeAnim->mNumPositionKeys = unsigned(positionKeys.size());
+            newAiNodeAnim->mPositionKeys = new aiVectorKey[newAiNodeAnim->mNumPositionKeys];
+            std::move(positionKeys.begin(), positionKeys.end(), newAiNodeAnim->mPositionKeys);
+
+            newAiNodeAnim->mNumRotationKeys = unsigned(rotationKeys.size());
+            newAiNodeAnim->mRotationKeys = new aiQuatKey[newAiNodeAnim->mNumRotationKeys];
+            std::move(rotationKeys.begin(), rotationKeys.end(), newAiNodeAnim->mRotationKeys);
+
+            newAiNodeAnim->mNumScalingKeys = unsigned(scalingKeys.size());
+            newAiNodeAnim->mScalingKeys = new aiVectorKey[newAiNodeAnim->mNumScalingKeys];
+            std::move(scalingKeys.begin(), scalingKeys.end(), newAiNodeAnim->mScalingKeys);
+
+            ++channelIndex;
+        }
+    }
+}
 
 void USDImporterImplTinyusdz::meshes(
         const tinyusdz::tydra::RenderScene &render_scene,
@@ -247,8 +392,66 @@ void USDImporterImplTinyusdz::verticesForMesh(
         size_t meshIdx,
         const std::string &nameWExt) {
     UNUSED(nameWExt);
-    pScene->mMeshes[meshIdx]->mNumVertices = static_cast<unsigned int>(render_scene.meshes[meshIdx].points.size());
+    const auto numVertices = static_cast<unsigned int>(render_scene.meshes[meshIdx].points.size());
+    pScene->mMeshes[meshIdx]->mNumVertices = numVertices;
     pScene->mMeshes[meshIdx]->mVertices = new aiVector3D[pScene->mMeshes[meshIdx]->mNumVertices];
+
+    // Check if this is a skinned mesh
+    if (int skeleton_id = render_scene.meshes[meshIdx].skel_id; skeleton_id > -1) {
+        // Recursively iterate to collect all the joints in the hierarchy into a flattened array
+        std::vector<const tinyusdz::tydra::SkelNode *> skeletonNodes;
+        skeletonNodes.push_back(&render_scene.skeletons[skeleton_id].root_node);
+        for (int i = 0; i < skeletonNodes.size(); ++i) {
+            for (const auto &child : skeletonNodes[i]->children) {
+                skeletonNodes.push_back(&child);
+            }
+        }
+
+        // Convert USD skeleton joints to Assimp bones
+        const unsigned int numBones = unsigned(skeletonNodes.size());
+        pScene->mMeshes[meshIdx]->mNumBones = numBones;
+        pScene->mMeshes[meshIdx]->mBones = new aiBone *[numBones];
+
+        for (unsigned int i = 0; i < numBones; ++i) {
+            const tinyusdz::tydra::SkelNode *skeletonNode = skeletonNodes[i];
+            const int boneIndex = skeletonNode->joint_id;
+
+            // Sorted so that Assimp bone ids align with USD joint id
+            auto outputBone = new aiBone();
+            outputBone->mName = aiString(skeletonNode->joint_name);
+            outputBone->mOffsetMatrix = tinyUsdzMat4ToAiMat4(skeletonNode->bind_transform.m).Inverse();
+            pScene->mMeshes[meshIdx]->mBones[boneIndex] = outputBone;
+        }
+
+        // Vertex weights
+        std::vector<std::vector<aiVertexWeight>> aiBonesVertexWeights;
+        aiBonesVertexWeights.resize(numBones);
+
+        const std::vector<int> &jointIndices = render_scene.meshes[meshIdx].joint_and_weights.jointIndices;
+        const std::vector<float> &jointWeightIndices = render_scene.meshes[meshIdx].joint_and_weights.jointWeights;
+        const int numWeightsPerVertex = render_scene.meshes[meshIdx].joint_and_weights.elementSize;
+
+        for (unsigned int vertexIndex = 0; vertexIndex < numVertices; ++vertexIndex) {
+            for (int weightIndex = 0; weightIndex < numWeightsPerVertex; ++weightIndex) {
+                const unsigned int index = vertexIndex * numWeightsPerVertex + weightIndex;
+                const float jointWeight = jointWeightIndices[index];
+
+                if (jointWeight > 0) {
+                    const int jointIndex = jointIndices[index];
+                    aiBonesVertexWeights[jointIndex].emplace_back(vertexIndex, jointWeight);
+                }
+            }
+        }
+
+        for (unsigned boneIndex = 0; boneIndex < numBones; ++boneIndex) {
+            const auto numWeightsForBone = unsigned(aiBonesVertexWeights[boneIndex].size());
+            pScene->mMeshes[meshIdx]->mBones[boneIndex]->mWeights = new aiVertexWeight[numWeightsForBone];
+            pScene->mMeshes[meshIdx]->mBones[boneIndex]->mNumWeights = numWeightsForBone;
+
+            std::swap_ranges(aiBonesVertexWeights[boneIndex].begin(), aiBonesVertexWeights[boneIndex].end(), pScene->mMeshes[meshIdx]->mBones[boneIndex]->mWeights);
+        }
+    }  // Skinned mesh end
+
     for (size_t j = 0; j < pScene->mMeshes[meshIdx]->mNumVertices; ++j) {
         pScene->mMeshes[meshIdx]->mVertices[j].x = render_scene.meshes[meshIdx].points[j][0];
         pScene->mMeshes[meshIdx]->mVertices[j].y = render_scene.meshes[meshIdx].points[j][1];
@@ -506,7 +709,7 @@ static aiTexture *ownedEmbeddedTextureFor(
     string embTexName{image.asset_identifier.substr(pos + 1)};
     tex->mFilename.Set(image.asset_identifier.c_str());
     tex->mHeight = image.height;
-//    const size_t imageBytesCount{render_scene.buffers[image.buffer_id].data.size() / image.channels};
+
     tex->mWidth = image.width;
     if (tex->mHeight == 0) {
         pos = embTexName.find_last_of('.');
@@ -595,54 +798,25 @@ void USDImporterImplTinyusdz::buffers(
     }
 }
 
-void USDImporterImplTinyusdz::setupNodes(
-        const tinyusdz::tydra::RenderScene &render_scene,
-        aiScene *pScene,
-        std::map<size_t, tinyusdz::tydra::Node> &meshNodes,
-        const std::string &nameWExt) {
-    stringstream ss;
-
-    pScene->mRootNode = nodes(render_scene, meshNodes, nameWExt);
-    pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
-    pScene->mRootNode->mMeshes = new unsigned int[pScene->mRootNode->mNumMeshes];
-    ss.str("");
-    ss << "setupNodes(): pScene->mNumMeshes: " << pScene->mNumMeshes;
-    if (pScene->mRootNode != nullptr) {
-        ss << ", mRootNode->mNumMeshes: " << pScene->mRootNode->mNumMeshes;
-    }
-    TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
-
-    for (unsigned int meshIdx = 0; meshIdx < pScene->mNumMeshes; meshIdx++) {
-        pScene->mRootNode->mMeshes[meshIdx] = meshIdx;
-    }
-
-}
-
-aiNode *USDImporterImplTinyusdz::nodes(
-        const tinyusdz::tydra::RenderScene &render_scene,
-        std::map<size_t, tinyusdz::tydra::Node> &meshNodes,
-        const std::string &nameWExt) {
-    const size_t numNodes{render_scene.nodes.size()};
-    (void) numNodes; // Ignore unused variable when -Werror enabled
-    stringstream ss;
-    ss.str("");
-    ss << "nodes(): model" << nameWExt << ", numNodes: " << numNodes;
-    TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
-    return nodesRecursive(nullptr, render_scene.nodes[0], meshNodes);
-}
-
 using Assimp::tinyusdzNodeTypeFor;
 using Assimp::tinyUsdzMat4ToAiMat4;
 using tinyusdz::tydra::NodeType;
 aiNode *USDImporterImplTinyusdz::nodesRecursive(
         aiNode *pNodeParent,
         const tinyusdz::tydra::Node &node,
-        std::map<size_t, tinyusdz::tydra::Node> &meshNodes) {
+        const std::vector<tinyusdz::tydra::SkelHierarchy> &skeletons) {
     stringstream ss;
     aiNode *cNode = new aiNode();
     cNode->mParent = pNodeParent;
     cNode->mName.Set(node.prim_name);
     cNode->mTransformation = tinyUsdzMat4ToAiMat4(node.local_matrix.m);
+
+    if (node.nodeType == NodeType::Mesh) {
+        cNode->mNumMeshes = 1;
+        cNode->mMeshes = new unsigned int[cNode->mNumMeshes];
+        cNode->mMeshes[0] = node.id;
+    }
+
     ss.str("");
     ss << "nodesRecursive(): node " << cNode->mName.C_Str() <<
             " type: |" << tinyusdzNodeTypeFor(node.nodeType) <<
@@ -651,21 +825,69 @@ aiNode *USDImporterImplTinyusdz::nodesRecursive(
         ss << " (parent " << cNode->mParent->mName.C_Str() << ")";
     }
     ss << " has " << node.children.size() << " children";
-    if (node.id > -1) {
+    if (node.nodeType == NodeType::Mesh) {
         ss << "\n    node mesh id: " << node.id << " (node type: " << tinyusdzNodeTypeFor(node.nodeType) << ")";
-        meshNodes[node.id] = node;
     }
     TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
-    if (!node.children.empty()) {
-        cNode->mNumChildren = static_cast<unsigned int>(node.children.size());
-        cNode->mChildren = new aiNode *[cNode->mNumChildren];
+
+    unsigned int numChildren = unsigned(node.children.size());
+
+    // Find any tinyusdz skeletons which might begin at this node
+    // Add the skeleton bones as child nodes
+    const tinyusdz::tydra::SkelNode *skelNode = nullptr;
+    for (const auto &skeleton : skeletons) {
+        if (skeleton.abs_path == node.abs_path) {
+            // Add this skeleton's bones as child nodes
+            ++numChildren;
+            skelNode = &skeleton.root_node;
+            break;
+        }
     }
 
-    size_t i{0};
-    for (const auto &childNode: node.children) {
-        cNode->mChildren[i] = nodesRecursive(cNode, childNode, meshNodes);
+    cNode->mNumChildren = numChildren;
+
+    // Done. No more children.
+    if (numChildren == 0) {
+        return cNode;
+    }
+
+    cNode->mChildren = new aiNode *[cNode->mNumChildren];
+
+    size_t i{ 0 };
+    for (const auto &childNode : node.children) {
+        cNode->mChildren[i] = nodesRecursive(cNode, childNode, skeletons);
         ++i;
     }
+
+    if (skelNode != nullptr) {
+        // Convert USD skeleton into an Assimp node and make it the last child
+        cNode->mChildren[cNode->mNumChildren-1] = skeletonNodesRecursive(cNode, *skelNode);
+    }
+
+    return cNode;
+}
+
+aiNode *USDImporterImplTinyusdz::skeletonNodesRecursive(
+        aiNode* pNodeParent,
+        const tinyusdz::tydra::SkelNode& joint) {
+    auto *cNode = new aiNode(joint.joint_path);
+    cNode->mParent = pNodeParent;
+    cNode->mNumMeshes = 0; // not a mesh node
+    cNode->mTransformation = tinyUsdzMat4ToAiMat4(joint.rest_transform.m);
+
+    // Done. No more children.
+    if (joint.children.empty()) {
+        return cNode;
+    }
+
+    cNode->mNumChildren = static_cast<unsigned int>(joint.children.size());
+    cNode->mChildren = new aiNode *[cNode->mNumChildren];
+
+    for (unsigned i = 0; i < cNode->mNumChildren; ++i) {
+        const tinyusdz::tydra::SkelNode &childJoint = joint.children[i];
+        cNode->mChildren[i] = skeletonNodesRecursive(cNode, childJoint);
+    }
+
     return cNode;
 }
 

+ 9 - 13
code/AssetLib/USD/USDLoaderImplTinyusdz.h

@@ -65,6 +65,10 @@ public:
             aiScene *pScene,
             IOSystem *pIOHandler);
 
+    void animations(
+            const tinyusdz::tydra::RenderScene &render_scene,
+            aiScene *pScene);
+
     void meshes(
             const tinyusdz::tydra::RenderScene &render_scene,
             aiScene *pScene,
@@ -120,22 +124,14 @@ public:
             aiScene *pScene,
             const std::string &nameWExt);
 
-    void setupNodes(
-            const tinyusdz::tydra::RenderScene &render_scene,
-            aiScene *pScene,
-            std::map<size_t, tinyusdz::tydra::Node> &meshNodes,
-            const std::string &nameWExt
-            );
-
-    aiNode *nodes(
-            const tinyusdz::tydra::RenderScene &render_scene,
-            std::map<size_t, tinyusdz::tydra::Node> &meshNodes,
-            const std::string &nameWExt);
-
     aiNode *nodesRecursive(
             aiNode *pNodeParent,
             const tinyusdz::tydra::Node &node,
-            std::map<size_t, tinyusdz::tydra::Node> &meshNodes);
+            const std::vector<tinyusdz::tydra::SkelHierarchy> &skeletons);
+
+    aiNode *skeletonNodesRecursive(
+            aiNode *pNodeParent,
+            const tinyusdz::tydra::SkelNode &joint);
 
     void sanityCheckNodesRecursive(
             aiNode *pNode);

+ 0 - 37
code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp

@@ -100,43 +100,6 @@ std::string Assimp::tinyusdzNodeTypeFor(NodeType type) {
     }
 }
 
-aiMatrix4x4 Assimp::tinyUsdzMat4ToAiMat4(const double matIn[4][4]) {
-    aiMatrix4x4 matOut;
-    matOut.a1 = matIn[0][0];
-    matOut.a2 = matIn[0][1];
-    matOut.a3 = matIn[0][2];
-    matOut.a4 = matIn[0][3];
-    matOut.b1 = matIn[1][0];
-    matOut.b2 = matIn[1][1];
-    matOut.b3 = matIn[1][2];
-    matOut.b4 = matIn[1][3];
-    matOut.c1 = matIn[2][0];
-    matOut.c2 = matIn[2][1];
-    matOut.c3 = matIn[2][2];
-    matOut.c4 = matIn[2][3];
-    matOut.d1 = matIn[3][0];
-    matOut.d2 = matIn[3][1];
-    matOut.d3 = matIn[3][2];
-    matOut.d4 = matIn[3][3];
-//    matOut.a1 = matIn[0][0];
-//    matOut.a2 = matIn[1][0];
-//    matOut.a3 = matIn[2][0];
-//    matOut.a4 = matIn[3][0];
-//    matOut.b1 = matIn[0][1];
-//    matOut.b2 = matIn[1][1];
-//    matOut.b3 = matIn[2][1];
-//    matOut.b4 = matIn[3][1];
-//    matOut.c1 = matIn[0][2];
-//    matOut.c2 = matIn[1][2];
-//    matOut.c3 = matIn[2][2];
-//    matOut.c4 = matIn[3][2];
-//    matOut.d1 = matIn[0][3];
-//    matOut.d2 = matIn[1][3];
-//    matOut.d3 = matIn[2][3];
-//    matOut.d4 = matIn[3][3];
-    return matOut;
-}
-
 aiVector3D Assimp::tinyUsdzScaleOrPosToAssimp(const std::array<float, 3> &scaleOrPosIn) {
     return aiVector3D(scaleOrPosIn[0], scaleOrPosIn[1], scaleOrPosIn[2]);
 }

+ 23 - 1
code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h

@@ -48,14 +48,36 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/types.h>
 #include "tinyusdz.hh"
 #include "tydra/render-data.hh"
+#include <type_traits>
 
 namespace Assimp {
 
 std::string tinyusdzAnimChannelTypeFor(
         tinyusdz::tydra::AnimationChannel::ChannelType animChannel);
 std::string tinyusdzNodeTypeFor(tinyusdz::tydra::NodeType type);
-aiMatrix4x4 tinyUsdzMat4ToAiMat4(const double matIn[4][4]);
 
+template <typename T>
+aiMatrix4x4 tinyUsdzMat4ToAiMat4(const T matIn[4][4]) {
+    static_assert(std::is_floating_point_v<T>, "Only floating-point types are allowed.");
+    aiMatrix4x4 matOut;
+    matOut.a1 = ai_real(matIn[0][0]);
+    matOut.a2 = ai_real(matIn[1][0]);
+    matOut.a3 = ai_real(matIn[2][0]);
+    matOut.a4 = ai_real(matIn[3][0]);
+    matOut.b1 = ai_real(matIn[0][1]);
+    matOut.b2 = ai_real(matIn[1][1]);
+    matOut.b3 = ai_real(matIn[2][1]);
+    matOut.b4 = ai_real(matIn[3][1]);
+    matOut.c1 = ai_real(matIn[0][2]);
+    matOut.c2 = ai_real(matIn[1][2]);
+    matOut.c3 = ai_real(matIn[2][2]);
+    matOut.c4 = ai_real(matIn[3][2]);
+    matOut.d1 = ai_real(matIn[0][3]);
+    matOut.d2 = ai_real(matIn[1][3]);
+    matOut.d3 = ai_real(matIn[2][3]);
+    matOut.d4 = ai_real(matIn[3][3]);
+    return matOut;
+}
 aiVector3D tinyUsdzScaleOrPosToAssimp(const std::array<float, 3> &scaleOrPosIn);
 
 /**

+ 21 - 0
code/AssetLib/VRML/README.md

@@ -0,0 +1,21 @@
+# WRL/X3DV to X3D file format converter
+
+## VRML and X3D 3D model formats background
+"VRML" 3D model files use either `VRML97` (`.wrl`) or "Classic VRML" (`.x3dv`)
+file formats.
+
+The X3D model specification was introduced after these formats, as a superset of both WRL and X3DV.
+While X3D can understand the _content_ of WRL/X3DV files, it can't directly parse them because
+X3D uses `.xml` files, rather than `VRML97` or "Classic VRML" format.
+
+But, if a converter is available to migrate just the file format (preserving the content), so that
+the `.wrl`/`.x3dv` files can be converted to an X3D-compatible `.xml` file, then the X3D importer
+will be able to load the resulting model file.
+
+## How this code is used
+The sole purpose of `Parser`/`Scanner` (adopted from the `meshlab` project) is to take a 
+`VRML97` (`.wrl`) or "Classic VRML" (`.x3dv`) file as input, and convert to an X3D `.xml` file.
+That's it.
+
+By passing the converted in-memory `.xml` file content to the `X3DImporter`, the `.wrl` or `x3dv`
+model can be loaded via assimp.

+ 103 - 0
code/AssetLib/VRML/VrmlConverter.cpp

@@ -0,0 +1,103 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2024, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+/// \file   VrmlImporter.cpp
+/// \brief  Convert VRML-formatted (.wrl, .x3dv) files to X3D .xml format
+/// \date   2024
+/// \author tellypresence
+
+#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER
+
+#include <memory> // std::unique_ptr
+#include "VrmlConverter.hpp"
+
+namespace Assimp {
+
+bool isFileWrlVrml97Ext(const std::string &pFile) {
+    size_t pos = pFile.find_last_of('.');
+    if (pos == std::string::npos) {
+        return false;
+    }
+    std::string ext = pFile.substr(pos + 1);
+    if (ext.size() != 3) {
+        return false;
+    }
+    return (ext[0] == 'w' || ext[0] == 'W') && (ext[1] == 'r' || ext[1] == 'R') && (ext[2] == 'l' || ext[2] == 'L');
+}
+
+bool isFileX3dvClassicVrmlExt(const std::string &pFile) {
+    size_t pos = pFile.find_last_of('.');
+    if (pos == std::string::npos) {
+        return false;
+    }
+    std::string ext = pFile.substr(pos + 1);
+    if (ext.size() != 4) {
+        return false;
+    }
+    return (ext[0] == 'x' || ext[0] == 'X') && (ext[1] == '3') && (ext[2] == 'd' || ext[2] == 'D') && (ext[3] == 'v' || ext[3] == 'V');
+}
+
+#if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+static VrmlTranslator::Scanner createScanner(const std::string &pFile) {
+    std::unique_ptr<wchar_t[]> wide_stringPtr{ new wchar_t[ pFile.length() + 1 ] };
+    std::copy(pFile.begin(), pFile.end(), wide_stringPtr.get());
+    wide_stringPtr[ pFile.length() ] = 0;
+
+    return VrmlTranslator::Scanner(wide_stringPtr.get());
+} // wide_stringPtr auto-deleted when leaving scope
+#endif // #if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+
+std::stringstream ConvertVrmlFileToX3dXmlFile(const std::string &pFile) {
+    std::stringstream ss;
+    if (isFileWrlVrml97Ext(pFile) || isFileX3dvClassicVrmlExt(pFile)) {
+#if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+        VrmlTranslator::Scanner scanner = createScanner(pFile);
+        VrmlTranslator::Parser parser(&scanner);
+        parser.Parse();
+        ss.str("");
+        parser.doc_.save(ss);
+#endif // #if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+    }
+    return ss;
+}
+
+} // namespace Assimp
+
+#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER

+ 57 - 0
code/AssetLib/VRML/VrmlConverter.hpp

@@ -0,0 +1,57 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2024, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+#pragma once
+
+#include <sstream>
+#include <string>
+
+#if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+#include "contrib/meshlab/autoclone/meshlab_repo-src/src/meshlabplugins/io_x3d/vrml/Parser.h"
+#endif // #if !defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+
+namespace Assimp {
+
+bool isFileWrlVrml97Ext(const std::string &pFile);
+bool isFileX3dvClassicVrmlExt(const std::string &pFile);
+
+std::stringstream ConvertVrmlFileToX3dXmlFile(const std::string &pFile);
+} // namespace Assimp

+ 37 - 8
code/AssetLib/X3D/X3DImporter.cpp

@@ -45,6 +45,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #ifndef ASSIMP_BUILD_NO_X3D_IMPORTER
 
+#include "AssetLib/VRML/VrmlConverter.hpp"
 #include "X3DImporter.hpp"
 #include "X3DImporter_Macro.hpp"
 
@@ -54,11 +55,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <iterator>
 #include <memory>
 
+#if defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+#define X3D_FORMATS_DESCR_STR "Extensible 3D(X3D, X3DB) Importer"
+#define X3D_FORMATS_EXTENSIONS_STR "x3d x3db"
+#else
+#define X3D_FORMATS_DESCR_STR "VRML(WRL, X3DV) and Extensible 3D(X3D, X3DB) Importer"
+#define X3D_FORMATS_EXTENSIONS_STR "wrl x3d x3db x3dv"
+#endif // #if defined(ASSIMP_BUILD_NO_VRML_IMPORTER)
+
 namespace Assimp {
 
 /// Constant which holds the importer description
 const aiImporterDesc X3DImporter::Description = {
-    "Extensible 3D(X3D) Importer",
+    X3D_FORMATS_DESCR_STR,
     "smalcom",
     "",
     "See documentation in source code. Chapter: Limitations.",
@@ -67,7 +76,7 @@ const aiImporterDesc X3DImporter::Description = {
     0,
     0,
     0,
-    "x3d x3db"
+    X3D_FORMATS_EXTENSIONS_STR
 };
 
 bool X3DImporter::isNodeEmpty(XmlNode &node) {
@@ -215,7 +224,19 @@ void X3DImporter::ParseFile(const std::string &file, IOSystem *pIOHandler) {
     if (!theParser.parse(fileStream.get())) {
         return;
     }
+    ParseFile(theParser);
+}
+
+void X3DImporter::ParseFile(std::istream &myIstream) {
+    XmlParser theParser;
+    if (!theParser.parse(myIstream)) {
+        LogInfo("ParseFile(): ERROR: failed to convert VRML istream to xml");
+        return;
+    }
+    ParseFile(theParser);
+}
 
+void X3DImporter::ParseFile(XmlParser &theParser) {
     XmlNode *node = theParser.findNode("X3D");
     if (nullptr == node) {
         return;
@@ -246,9 +267,13 @@ void X3DImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
     mpIOHandler = pIOHandler;
 
     Clear();
-    std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
-    if (!stream) {
-        throw DeadlyImportError("Could not open file for reading");
+    std::stringstream ss = ConvertVrmlFileToX3dXmlFile(pFile);
+    const bool isReadFromMem{ ss.str().length() > 0 };
+    if (!isReadFromMem) {
+        std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
+        if (!stream) {
+            throw DeadlyImportError("Could not open file for reading");
+        }
     }
     std::string::size_type slashPos = pFile.find_last_of("\\/");
 
@@ -257,9 +282,13 @@ void X3DImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
     pScene->mRootNode->mParent = nullptr;
     pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED;
 
-    pIOHandler->PushDirectory(slashPos == std::string::npos ? std::string() : pFile.substr(0, slashPos + 1));
-    ParseFile(pFile, pIOHandler);
-    pIOHandler->PopDirectory();
+    if (isReadFromMem) {
+        ParseFile(ss);
+    } else {
+        pIOHandler->PushDirectory(slashPos == std::string::npos ? std::string() : pFile.substr(0, slashPos + 1));
+        ParseFile(pFile, pIOHandler);
+        pIOHandler->PopDirectory();
+    }
 
     //search for root node element
 

+ 3 - 1
code/AssetLib/X3D/X3DImporter.hpp

@@ -275,7 +275,9 @@ public:
     /// Also exception can be thrown if trouble will found.
     /// \param [in] pFile - name of file to be parsed.
     /// \param [in] pIOHandler - pointer to IO helper object.
-    void ParseFile(const std::string &pFile, IOSystem *pIOHandler);
+    void ParseFile(const std::string &file, IOSystem *pIOHandler);
+    void ParseFile(std::istream &myIstream);
+    void ParseFile(XmlParser &theParser);
     bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool pCheckSig) const;
     void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler);
     const aiImporterDesc *GetInfo() const;

+ 16 - 16
code/AssetLib/glTF/glTFImporter.cpp

@@ -44,7 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "AssetLib/glTF/glTFImporter.h"
 #include "AssetLib/glTF/glTFAsset.h"
 #if !defined(ASSIMP_BUILD_NO_EXPORT)
-#include "AssetLib/glTF/glTFAssetWriter.h"
+#   include "AssetLib/glTF/glTFAssetWriter.h"
 #endif
 #include "PostProcessing/MakeVerboseFormat.h"
 
@@ -67,7 +67,11 @@ static constexpr aiImporterDesc desc = {
     "",
     "",
     "",
-    aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental,
+    aiImporterFlags_SupportTextFlavour | 
+        aiImporterFlags_SupportBinaryFlavour | 
+        aiImporterFlags_SupportCompressedFlavour | 
+        aiImporterFlags_LimitedSupport | 
+        aiImporterFlags_Experimental,
     0,
     0,
     0,
@@ -129,11 +133,8 @@ void glTFImporter::ImportMaterials(glTF::Asset &r) {
         aiMaterial *aimat = mScene->mMaterials[i] = new aiMaterial();
 
         Material &mat = r.materials[i];
-
-        /*if (!mat.name.empty())*/ {
-            aiString str(mat.id /*mat.name*/);
-            aimat->AddProperty(&str, AI_MATKEY_NAME);
-        }
+        aiString str(mat.id);
+        aimat->AddProperty(&str, AI_MATKEY_NAME);
 
         SetMaterialColorProperty(embeddedTexIdxs, r, mat.ambient, aimat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT);
         SetMaterialColorProperty(embeddedTexIdxs, r, mat.diffuse, aimat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE);
@@ -616,10 +617,6 @@ void glTFImporter::ImportNodes(glTF::Asset &r) {
         }
         mScene->mRootNode = root;
     }
-
-    //if (!mScene->mRootNode) {
-    //  mScene->mRootNode = new aiNode("EMPTY");
-    //}
 }
 
 void glTFImporter::ImportEmbeddedTextures(glTF::Asset &r) {
@@ -631,8 +628,9 @@ void glTFImporter::ImportEmbeddedTextures(glTF::Asset &r) {
             numEmbeddedTexs += 1;
     }
 
-    if (numEmbeddedTexs == 0)
+    if (numEmbeddedTexs == 0) {
         return;
+    }
 
     mScene->mTextures = new aiTexture *[numEmbeddedTexs];
 
@@ -657,12 +655,14 @@ void glTFImporter::ImportEmbeddedTextures(glTF::Asset &r) {
         if (!img.mimeType.empty()) {
             const char *ext = strchr(img.mimeType.c_str(), '/') + 1;
             if (ext) {
-                if (strcmp(ext, "jpeg") == 0) ext = "jpg";
+                if (strncmp(ext, "jpeg", 4) == 0) {
+                    ext = "jpg";
+                }
 
+                tex->achFormatHint[3] = '\0';
                 size_t len = strlen(ext);
-                if (len <= 3) {
-                    strcpy(tex->achFormatHint, ext);
-                }
+                if (len > 3) len = 3;
+                memcpy(tex->achFormatHint, ext, len);
             }
         }
     }

+ 16 - 0
code/AssetLib/glTF2/glTF2Asset.h

@@ -53,6 +53,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *   KHR_materials_volume full
  *   KHR_materials_ior full
  *   KHR_materials_emissive_strength full
+ *   KHR_materials_anisotropy full
  */
 #ifndef GLTF2ASSET_H_INC
 #define GLTF2ASSET_H_INC
@@ -821,6 +822,15 @@ struct MaterialEmissiveStrength {
     void SetDefaults();
 };
 
+struct MaterialAnisotropy {
+    float anisotropyStrength = 0.f;
+    float anisotropyRotation = 0.f;
+    TextureInfo anisotropyTexture;
+
+    MaterialAnisotropy() { SetDefaults(); }
+    void SetDefaults();
+};
+
 //! The material appearance of a primitive.
 struct Material : public Object {
     //PBR metallic roughness properties
@@ -859,6 +869,9 @@ struct Material : public Object {
     //extension: KHR_materials_emissive_strength
     Nullable<MaterialEmissiveStrength> materialEmissiveStrength;
 
+    //extension: KHR_materials_anisotropy
+    Nullable<MaterialAnisotropy> materialAnisotropy;
+
     //extension: KHR_materials_unlit
     bool unlit;
 
@@ -1133,6 +1146,7 @@ public:
         bool KHR_materials_volume;
         bool KHR_materials_ior;
         bool KHR_materials_emissive_strength;
+        bool KHR_materials_anisotropy;
         bool KHR_draco_mesh_compression;
         bool FB_ngon_encoding;
         bool KHR_texture_basisu;
@@ -1149,6 +1163,7 @@ public:
                 KHR_materials_volume(false),
                 KHR_materials_ior(false),
                 KHR_materials_emissive_strength(false),
+                KHR_materials_anisotropy(false),
                 KHR_draco_mesh_compression(false),
                 FB_ngon_encoding(false),
                 KHR_texture_basisu(false) {
@@ -1252,6 +1267,7 @@ private:
     size_t mBodyOffset;
     size_t mBodyLength;
     IdMap mUsedIds;
+    std::map<std::string, int, std::less<>> mUsedNamesMap;
     Ref<Buffer> mBodyBuffer;
 };
 

+ 36 - 9
code/AssetLib/glTF2/glTF2Asset.inl

@@ -294,6 +294,8 @@ inline void SetDecodedIndexBuffer_Draco(const draco::Mesh &dracoMesh, Mesh::Prim
     // Usually uint32_t but shouldn't assume
     if (sizeof(dracoMesh.face(draco::FaceIndex(0))[0]) == componentBytes) {
         memcpy(decodedIndexBuffer->GetPointer(), &dracoMesh.face(draco::FaceIndex(0))[0], decodedIndexBuffer->byteLength);
+        // Assign this alternate data buffer to the accessor
+        prim.indices->decodedBuffer.swap(decodedIndexBuffer);
         return;
     }
 
@@ -1401,6 +1403,18 @@ inline void Material::Read(Value &material, Asset &r) {
             }
         }
 
+        if (r.extensionsUsed.KHR_materials_anisotropy) {
+            if (Value *curMaterialAnisotropy = FindObject(*extensions, "KHR_materials_anisotropy")) {
+                MaterialAnisotropy anisotropy;
+
+                ReadMember(*curMaterialAnisotropy, "anisotropyStrength", anisotropy.anisotropyStrength);
+                ReadMember(*curMaterialAnisotropy, "anisotropyRotation", anisotropy.anisotropyRotation);
+                ReadTextureProperty(r, *curMaterialAnisotropy, "anisotropyTexture", anisotropy.anisotropyTexture);
+
+                this->materialAnisotropy = Nullable<MaterialAnisotropy>(anisotropy);
+            }
+        }
+
         unlit = nullptr != FindObject(*extensions, "KHR_materials_unlit");
     }
 }
@@ -1454,6 +1468,12 @@ inline void MaterialEmissiveStrength::SetDefaults() {
     emissiveStrength = 0.f;
 }
 
+inline void MaterialAnisotropy::SetDefaults() {
+    //KHR_materials_anisotropy properties
+    anisotropyStrength = 0.f;
+    anisotropyRotation = 0.f;
+}
+
 inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) {
     Value *curName = FindMember(pJSON_Object, "name");
     if (nullptr != curName && curName->IsString()) {
@@ -2043,6 +2063,12 @@ inline void Asset::Load(const std::string &pFile, bool isBinary)
         mDicts[i]->AttachToDocument(doc);
     }
 
+    // Read the "extensions" property, then add it to each scene's metadata.
+    CustomExtension customExtensions;
+    if (Value *extensionsObject = FindObject(doc, "extensions")) {
+        customExtensions = glTF2::ReadExtensions("extensions", *extensionsObject);
+    }
+
     // Read the "scene" property, which specifies which scene to load
     // and recursively load everything referenced by it
     unsigned int sceneIndex = 0;
@@ -2054,6 +2080,8 @@ inline void Asset::Load(const std::string &pFile, bool isBinary)
     if (Value *scenesArray = FindArray(doc, "scenes")) {
         if (sceneIndex < scenesArray->Size()) {
             this->scene = scenes.Retrieve(sceneIndex);
+
+            this->scene->customExtensions = customExtensions;
         }
     }
 
@@ -2143,6 +2171,7 @@ inline void Asset::ReadExtensionsUsed(Document &doc) {
     CHECK_EXT(KHR_materials_volume);
     CHECK_EXT(KHR_materials_ior);
     CHECK_EXT(KHR_materials_emissive_strength);
+    CHECK_EXT(KHR_materials_anisotropy);
     CHECK_EXT(KHR_draco_mesh_compression);
     CHECK_EXT(KHR_texture_basisu);
 
@@ -2166,8 +2195,10 @@ inline std::string Asset::FindUniqueID(const std::string &str, const char *suffi
     std::string id = str;
 
     if (!id.empty()) {
-        if (mUsedIds.find(id) == mUsedIds.end())
+        if (mUsedIds.find(id) == mUsedIds.end()){
+            mUsedNamesMap[id] = 0;
             return id;
+        }
 
         id += "_";
     }
@@ -2176,17 +2207,13 @@ inline std::string Asset::FindUniqueID(const std::string &str, const char *suffi
 
     Asset::IdMap::iterator it = mUsedIds.find(id);
     if (it == mUsedIds.end()) {
+        mUsedNamesMap[id] = 0;
         return id;
     }
 
-    std::vector<char> buffer;
-    buffer.resize(id.size() + 16);
-    int offset = ai_snprintf(buffer.data(), buffer.size(), "%s_", id.c_str());
-    for (int i = 0; it != mUsedIds.end(); ++i) {
-        ai_snprintf(buffer.data() + offset, buffer.size() - offset, "%d", i);
-        id = buffer.data();
-        it = mUsedIds.find(id);
-    }
+    auto key = id;
+    id += "_" + std::to_string(mUsedNamesMap[key]);
+    mUsedNamesMap[key] = mUsedNamesMap[key] + 1;
 
     return id;
 }

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

@@ -53,6 +53,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *   KHR_materials_volume: full
  *   KHR_materials_ior: full
  *   KHR_materials_emissive_strength: full
+ *   KHR_materials_anisotropy: full
  */
 #ifndef GLTF2ASSETWRITER_H_INC
 #define GLTF2ASSETWRITER_H_INC

+ 26 - 1
code/AssetLib/glTF2/glTF2AssetWriter.inl

@@ -546,6 +546,26 @@ namespace glTF2 {
             }
         }
 
+        if (m.materialAnisotropy.isPresent) {
+            Value materialAnisotropy(rapidjson::Type::kObjectType);
+
+            MaterialAnisotropy &anisotropy = m.materialAnisotropy.value;
+
+            if (anisotropy.anisotropyStrength != 0.f) {
+                WriteFloat(materialAnisotropy, anisotropy.anisotropyStrength, "anisotropyStrength", w.mAl);
+            }
+
+            if (anisotropy.anisotropyRotation != 0.f) {
+                WriteFloat(materialAnisotropy, anisotropy.anisotropyRotation, "anisotropyRotation", w.mAl);
+            }
+
+            WriteTex(materialAnisotropy, anisotropy.anisotropyTexture, "anisotropyTexture", w.mAl);
+
+            if (!materialAnisotropy.ObjectEmpty()) {
+                exts.AddMember("KHR_materials_anisotropy", materialAnisotropy, w.mAl);
+            }
+        }
+
         if (!exts.ObjectEmpty()) {
             obj.AddMember("extensions", exts, w.mAl);
         }
@@ -608,6 +628,7 @@ namespace glTF2 {
                 {
                     WriteAttrs(w, attrs, p.attributes.position, "POSITION");
                     WriteAttrs(w, attrs, p.attributes.normal, "NORMAL");
+                    WriteAttrs(w, attrs, p.attributes.tangent, "TANGENT");
                     WriteAttrs(w, attrs, p.attributes.texcoord, "TEXCOORD", true);
                     WriteAttrs(w, attrs, p.attributes.color, "COLOR", true);
                     WriteAttrs(w, attrs, p.attributes.joint, "JOINTS", true);
@@ -940,7 +961,7 @@ namespace glTF2 {
             if (outfile->Write(bodyBuffer->GetPointer(), 1, bodyBuffer->byteLength) != bodyBuffer->byteLength) {
                 throw DeadlyExportError("Failed to write body data!");
             }
-            if (curPaddingLength && outfile->Write(&padding, 1, paddingLength) != paddingLength) {
+            if (curPaddingLength && outfile->Write(&padding, 1, curPaddingLength) != curPaddingLength) {
                 throw DeadlyExportError("Failed to write body data padding!");
             }
         }
@@ -1017,6 +1038,10 @@ namespace glTF2 {
                 exts.PushBack(StringRef("KHR_materials_emissive_strength"), mAl);
             }
 
+            if (this->mAsset.extensionsUsed.KHR_materials_anisotropy) {
+                exts.PushBack(StringRef("KHR_materials_anisotropy"), mAl);
+            }
+
             if (this->mAsset.extensionsUsed.FB_ngon_encoding) {
                 exts.PushBack(StringRef("FB_ngon_encoding"), mAl);
             }

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