Ver código fonte

Merge branch 'master' into bugfix/fix_gltf2_animation

Kim Kulling 7 meses atrás
pai
commit
aa329fb24d
100 arquivos alterados com 2110 adições e 1395 exclusões
  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 ]
     branches: [ master ]
 
 
 permissions:
 permissions:
-  contents: read # to fetch code (actions/checkout)
+  contents: write # to fetch code (actions/checkout),and release
 
 
 jobs:
 jobs:
-  job:
+  build:
     name: ${{ matrix.name }}-build-and-test
     name: ${{ matrix.name }}-build-and-test
     runs-on: ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     strategy:
     strategy:
@@ -37,6 +37,9 @@ jobs:
             cc: gcc
             cc: gcc
 
 
     steps:
     steps:
+    - name: ccache
+      uses: hendrikmuhs/[email protected]
+      
     - uses: actions/checkout@v4
     - uses: actions/checkout@v4
       with:
       with:
           submodules: true
           submodules: true
@@ -70,12 +73,12 @@ jobs:
     - name: Set Windows specific CMake arguments
     - name: Set Windows specific CMake arguments
       if: contains(matrix.name, 'windows')
       if: contains(matrix.name, 'windows')
       id: windows_extra_cmake_args
       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
     - name: Set Hunter specific CMake arguments
       if: contains(matrix.name, 'hunter')
       if: contains(matrix.name, 'hunter')
       id: hunter_extra_cmake_args
       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
     - name: configure and build
       uses: lukka/run-cmake@v3
       uses: lukka/run-cmake@v3
@@ -92,7 +95,7 @@ jobs:
     - name: Exclude certain tests in Hunter specific builds
     - name: Exclude certain tests in Hunter specific builds
       if: contains(matrix.name, 'hunter')
       if: contains(matrix.name, 'hunter')
       id: hunter_extra_test_args
       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
     - name: test
       run: cd build/bin && ./unit ${{ steps.hunter_extra_test_args.outputs.args }}
       run: cd build/bin && ./unit ${{ steps.hunter_extra_test_args.outputs.args }}
@@ -101,5 +104,89 @@ jobs:
     - uses: actions/upload-artifact@v4
     - uses: actions/upload-artifact@v4
       if: matrix.name == 'windows-msvc'
       if: matrix.name == 'windows-msvc'
       with:
       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/'
           buildDirectory: '${{ github.workspace }}/build/'
 
 
       - name: Compile .ISS to .EXE Installer
       - name: Compile .ISS to .EXE Installer
-        uses: Minionguyjpro/[email protected].2
+        uses: Minionguyjpro/[email protected].5
         with:
         with:
           path: packaging/windows-innosetup/script_x64.iss
           path: packaging/windows-innosetup/script_x64.iss
           options: /O+
           options: /O+

+ 2 - 0
.gitignore

@@ -122,5 +122,7 @@ tools/assimp_qt_viewer/ui_mainwindow.h
 generated/*
 generated/*
 
 
 # 3rd party cloned repos/tarballs etc
 # 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
 # tinyusdz repo, automatically cloned via CMake
 contrib/tinyusdz/autoclone
 contrib/tinyusdz/autoclone

+ 41 - 43
Build.md

@@ -1,36 +1,10 @@
 # Build / Install Instructions
 # 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
 ## 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
 ### Get the source
 Make sure you have a working git-installation. Open a command prompt and clone the Asset-Importer-Lib via:
 Make sure you have a working git-installation. Open a command prompt and clone the Asset-Importer-Lib via:
@@ -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
 git clone https://github.com/assimp/assimp.git
 ```
 ```
 ### Build from source:
 ### 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
 ```bash
 cd assimp
 cd assimp
-cmake CMakeLists.txt 
+cmake CMakeLists.txt -DASSIMP_BUILD_ASSIMP_TOOLS=ON
 cmake --build .
 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.
 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
 ### Build instructions for Windows with Visual-Studio
-
 First, you have to install Visual-Studio on your windows-system. You can get the Community-Version for free here: https://visualstudio.microsoft.com/de/downloads/
 First, you have to install Visual-Studio on your windows-system. You can get the Community-Version for free here: https://visualstudio.microsoft.com/de/downloads/
 To generate the build environment for your IDE open a command prompt, navigate to your repo and type:
 To generate the build environment for your IDE open a command prompt, navigate to your repo and type:
 ```bash
 ```bash
@@ -57,17 +38,6 @@ This will generate the project files for the visual studio. All dependencies use
 ### Build instructions for Windows with UWP
 ### Build instructions for Windows with UWP
 See <https://stackoverflow.com/questions/40803170/cmake-uwp-using-cmake-to-build-universal-windows-app>
 See <https://stackoverflow.com/questions/40803170/cmake-uwp-using-cmake-to-build-universal-windows-app>
 
 
-### Build instructions for Linux / Unix
-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
 ### Build instructions for MinGW
  Older versions of MinGW's compiler (e.g. 5.1.0) do not support the -mbig_obj flag 
  Older versions of MinGW's compiler (e.g. 5.1.0) do not support the -mbig_obj flag 
 required to compile some of assimp's files, especially for debug builds.
 required to compile some of assimp's files, especially for debug builds.
@@ -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.
 - **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_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).
 - **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 )
 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
 # Experimental USD importer: disabled, need to opt-in
 # Note: assimp github PR automatic checks will fail the PR due to compiler warnings 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
 # 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_IMPORTER "Enable USD file import" off)
 option(ASSIMP_BUILD_USD_VERBOSE_LOGS "Enable verbose USD import debug logging" 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
 # Toggles the use of the hunter package manager
 option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" OFF)
 option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" OFF)
 
 
 IF(ASSIMP_HUNTER_ENABLED)
 IF(ASSIMP_HUNTER_ENABLED)
   include("cmake-modules/HunterGate.cmake")
   include("cmake-modules/HunterGate.cmake")
   HunterGate(
   HunterGate(
-    URL "https://github.com/cpp-pm/hunter/archive/v0.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)
   add_definitions(-DASSIMP_USE_HUNTER)
 ENDIF()
 ENDIF()
 
 
-PROJECT(Assimp VERSION 5.4.1)
+PROJECT(Assimp VERSION 5.4.3)
 
 
 # All supported options ###############################################
 # All supported options ###############################################
 
 
@@ -154,7 +195,7 @@ IF (WIN32)
 
 
   IF(MSVC)
   IF(MSVC)
     OPTION( ASSIMP_INSTALL_PDB
     OPTION( ASSIMP_INSTALL_PDB
-      "Install MSVC debug files."
+      "Create MSVC debug symbol files and add to Install target."
       ON )
       ON )
     IF(NOT (MSVC_VERSION LESS 1900))
     IF(NOT (MSVC_VERSION LESS 1900))
       # Multibyte character set has been deprecated since at least MSVC2015 (possibly earlier)
       # 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)
     SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
   ENDIF()
   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")
     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>")
     ADD_COMPILE_OPTIONS("$<$<COMPILE_LANGUAGE:CXX>:-Wno-dangling-reference>")
   ENDIF()
   ENDIF()
@@ -299,8 +340,15 @@ ELSEIF(MSVC)
   # supress warning for double to float conversion if Double precision is activated
   # supress warning for double to float conversion if Double precision is activated
   ADD_COMPILE_OPTIONS(/wd4244)
   ADD_COMPILE_OPTIONS(/wd4244)
   SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG /Zi /Od")
   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" )
 ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang" )
   IF(NOT ASSIMP_HUNTER_ENABLED)
   IF(NOT ASSIMP_HUNTER_ENABLED)
     SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
     SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
@@ -643,7 +691,7 @@ ELSE()
   IF ( ASSIMP_BUILD_DRACO )
   IF ( ASSIMP_BUILD_DRACO )
     # Primarily for glTF v2
     # Primarily for glTF v2
     # Enable Draco glTF feature set
     # 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
     # Disable unnecessary or omitted components
     set(DRACO_JS_GLUE OFF CACHE BOOL "" FORCE)
     set(DRACO_JS_GLUE OFF CACHE BOOL "" FORCE)
     set(DRACO_WASM OFF CACHE BOOL "" FORCE)
     set(DRACO_WASM OFF CACHE BOOL "" FORCE)
@@ -857,24 +905,24 @@ if(WIN32)
   IF(MSVC12 OR MSVC14 OR MSVC15 )
   IF(MSVC12 OR MSVC14 OR MSVC15 )
     ADD_CUSTOM_TARGET(UpdateAssimpLibsDebugSymbolsAndDLLs COMMENT "Copying Assimp Libraries ..." VERBATIM)
     ADD_CUSTOM_TARGET(UpdateAssimpLibsDebugSymbolsAndDLLs COMMENT "Copying Assimp Libraries ..." VERBATIM)
     IF(CMAKE_GENERATOR MATCHES "^Visual Studio")
     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()
     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()
   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
 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' \
     cmake -G 'Ninja' \
     -DCMAKE_BUILD_TYPE=Release \
     -DCMAKE_BUILD_TYPE=Release \
     -DASSIMP_BUILD_ASSIMP_TOOLS=ON \
     -DASSIMP_BUILD_ASSIMP_TOOLS=ON \
     .. && \
     .. && \
     ninja -j4 && ninja install
     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 (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)
 ![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)
 [![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")
 [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Average time to resolve an issue")
 [![Percentage of issues still open](http://isitmaintained.com/badge/open/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Percentage of issues still open")
 [![Percentage of issues still open](http://isitmaintained.com/badge/open/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Percentage of issues still open")
+[![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>
 <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.
 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 ###
 ### Documentation ###
 Read [our latest documentation](https://assimp-docs.readthedocs.io/en/latest/).
 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)
 - Find us on [https://discord.gg/s9KJfaem](https://discord.gg/kKazXMXDy2)
 - Ask [the Assimp community on Reddit](https://www.reddit.com/r/Assimp/).
 - 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). 
 - 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 ####
 #### Supported file formats ####
 See [the complete list of supported formats](https://github.com/assimp/assimp/blob/master/doc/Fileformats.md).
 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 ###
 ### Ports ###
 * [Android](port/AndroidJNI/README.md)
 * [Android](port/AndroidJNI/README.md)
 * [Python](port/PyAssimp/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)
 * [Pascal](port/AssimpPascal/Readme.md)
 * [Javascript (Alpha)](https://github.com/makc/assimp2json)
 * [Javascript (Alpha)](https://github.com/makc/assimp2json)
 * [Javascript/Node.js Interface](https://github.com/kovacsv/assimpjs)
 * [Javascript/Node.js Interface](https://github.com/kovacsv/assimpjs)
 * [Unity 3d Plugin](https://ricardoreis.net/trilib-2/)
 * [Unity 3d Plugin](https://ricardoreis.net/trilib-2/)
 * [Unreal Engine Plugin](https://github.com/irajsb/UE4_Assimp/)
 * [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.
 * [HAXE-Port](https://github.com/longde123/assimp-haxe) The Assimp-HAXE-port.
 * [Rust](https://github.com/jkvargas/russimp)
 * [Rust](https://github.com/jkvargas/russimp)
 
 
 ### Other tools ###
 ### 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).
 [Assimp-Viewer](https://github.com/assimp/assimp_view) is an experimental implementation for an Asset-Viewer based on ImGUI and Assimp (experimental).
 
 
 #### Repository structure ####
 #### Repository structure ####
@@ -59,7 +63,7 @@ Open Asset Import Library is implemented in C++. The directory structure looks l
 
 
 	/code		Source code
 	/code		Source code
 	/contrib	Third-party libraries
 	/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
 	/fuzz           Contains the test code for the Google Fuzzer project
 	/include	Public header C and C++ header files
 	/include	Public header C and C++ header files
 	/scripts 	Scripts are used to generate the loading code for some formats
 	/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
 	code/AssetLib/<FormatName>	Implementation for import and export of the format
 
 
 ### Contributing ###
 ### 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.
 a pull request with your changes against the main repository's `master` branch.
 
 
 ## Contributors
 ## Contributors
@@ -101,7 +105,7 @@ Become a financial contributor and help us sustain our community. [[Contribute](
 
 
 #### Organizations
 #### 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>
 <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 -
 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.
 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.
 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
     // 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()];
     pcOut->mChildren = new aiNode *[pcIn->mChildren.size()];
 
 
     // Recursively process all children
     // Recursively process all children
-    const unsigned int size = static_cast<unsigned int>(pcIn->mChildren.size());
+    
     for (unsigned int i = 0; i < size; ++i) {
     for (unsigned int i = 0; i < size; ++i) {
         pcOut->mChildren[i] = new aiNode();
         pcOut->mChildren[i] = new aiNode();
         pcOut->mChildren[i]->mParent = pcOut;
         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
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -52,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/StringComparison.h>
 #include <assimp/StringComparison.h>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/Exporter.hpp>
 #include <assimp/Exporter.hpp>
+#include <assimp/Exceptional.h>
 #include <assimp/IOSystem.hpp>
 #include <assimp/IOSystem.hpp>
 
 
 #include <memory>
 #include <memory>
@@ -102,7 +102,7 @@ private:
 // preserves the mesh's given name if it has one. |index| is the index
 // preserves the mesh's given name if it has one. |index| is the index
 // of the mesh in |aiScene::mMeshes|.
 // of the mesh in |aiScene::mMeshes|.
 std::string GetMeshName(const aiMesh &mesh, unsigned int index, const aiNode &node) {
 std::string GetMeshName(const aiMesh &mesh, unsigned int index, const aiNode &node) {
-    static const char underscore = '_';
+    static constexpr char underscore = '_';
     char postfix[10] = { 0 };
     char postfix[10] = { 0 };
     ASSIMP_itoa10(postfix, index);
     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) {
 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
     // 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.
  *  @brief  Helper class to export a given scene to a 3DS file.
  */
  */
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-class Discreet3DSExporter {
+class Discreet3DSExporter final {
 public:
 public:
     Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, const aiScene* pScene);
     Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, const aiScene* pScene);
-    ~Discreet3DSExporter();
+    ~Discreet3DSExporter() = default;
 
 
 private:
 private:
     void WriteMeshes();
     void WriteMeshes();
@@ -88,7 +88,6 @@ private:
 
 
     using MeshesByNodeMap = std::multimap<const aiNode*, unsigned int>;
     using MeshesByNodeMap = std::multimap<const aiNode*, unsigned int>;
     MeshesByNodeMap meshes;
     MeshesByNodeMap meshes;
-
 };
 };
 
 
 } // Namespace Assimp
 } // Namespace Assimp

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

@@ -5,7 +5,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2024, assimp team
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -64,7 +63,7 @@ using namespace D3DS;
 // ---------------------------------------------------------------------------------
 // ---------------------------------------------------------------------------------
 /** Importer class for 3D Studio r3 and r4 3DS files
 /** Importer class for 3D Studio r3 and r4 3DS files
  */
  */
-class Discreet3DSImporter : public BaseImporter {
+class Discreet3DSImporter final : public BaseImporter {
 public:
 public:
     Discreet3DSImporter();
     Discreet3DSImporter();
     ~Discreet3DSImporter() override = default;
     ~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) {
             if (color.r <= 1 && color.g <= 1 && color.b <= 1 && color.a <= 1) {
 
 
                 hexDiffuseColor = ai_rgba2hex(
                 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);
                         true);
 
 
             } else {
             } else {

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

@@ -107,7 +107,7 @@ void D3MFImporter::InternReadFile(const std::string &filename, aiScene *pScene,
 
 
     XmlParser xmlParser;
     XmlParser xmlParser;
     if (xmlParser.parse(opcPackage.RootStream())) {
     if (xmlParser.parse(opcPackage.RootStream())) {
-        XmlSerializer xmlSerializer(&xmlParser);
+        XmlSerializer xmlSerializer(xmlParser);
         xmlSerializer.ImportXml(pScene);
         xmlSerializer.ImportXml(pScene);
 
 
         const std::vector<aiTexture*> &tex =  opcPackage.GetEmbeddedTextures();
         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
 } // namespace
 
 
-XmlSerializer::XmlSerializer(XmlParser *xmlParser) :
+XmlSerializer::XmlSerializer(XmlParser &xmlParser) :
         mResourcesDictionnary(),
         mResourcesDictionnary(),
         mMeshCount(0),
         mMeshCount(0),
         mXmlParser(xmlParser) {
         mXmlParser(xmlParser) {
-    ai_assert(nullptr != xmlParser);
+    // empty
 }
 }
 
 
 XmlSerializer::~XmlSerializer() {
 XmlSerializer::~XmlSerializer() {
@@ -218,7 +218,7 @@ void XmlSerializer::ImportXml(aiScene *scene) {
     }
     }
 
 
     scene->mRootNode = new aiNode(XmlTag::RootTag);
     scene->mRootNode = new aiNode(XmlTag::RootTag);
-    XmlNode node = mXmlParser->getRootNode().child(XmlTag::model);
+    XmlNode node = mXmlParser.getRootNode().child(XmlTag::model);
     if (node.empty()) {
     if (node.empty()) {
         return;
         return;
     }
     }

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

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

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

@@ -144,10 +144,6 @@ AC3DImporter::AC3DImporter() :
     // nothing to be done here
     // nothing to be done here
 }
 }
 
 
-// ------------------------------------------------------------------------------------------------
-// Destructor, private as well
-AC3DImporter::~AC3DImporter() = default;
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 // Returns whether the class can handle the format of the given file.
 bool AC3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
 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
 // Parse an object section in an AC file
 bool AC3DImporter::LoadObjectSection(std::vector<Object> &objects) {
 bool AC3DImporter::LoadObjectSection(std::vector<Object> &objects) {
-    if (!TokenMatch(mBuffer.data, "OBJECT", 6))
+    if (!TokenMatch(mBuffer.data, "OBJECT", 6)) {
         return false;
         return false;
+    }
 
 
     SkipSpaces(&mBuffer.data, mBuffer.end);
     SkipSpaces(&mBuffer.data, mBuffer.end);
 
 
@@ -192,7 +189,6 @@ bool AC3DImporter::LoadObjectSection(std::vector<Object> &objects) {
         light->mAttenuationConstant = 1.f;
         light->mAttenuationConstant = 1.f;
 
 
         // Generate a default name for both the light source and the node
         // 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);
         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);
         obj.name = std::string(light->mName.data);
 
 
@@ -202,8 +198,10 @@ bool AC3DImporter::LoadObjectSection(std::vector<Object> &objects) {
         obj.type = Object::Group;
         obj.type = Object::Group;
     } else if (!ASSIMP_strincmp(mBuffer.data, "world", 5)) {
     } else if (!ASSIMP_strincmp(mBuffer.data, "world", 5)) {
         obj.type = Object::World;
         obj.type = Object::World;
-    } else
+    } else {
         obj.type = Object::Poly;
         obj.type = Object::Poly;
+    }
+
     while (GetNextLine()) {
     while (GetNextLine()) {
         if (TokenMatch(mBuffer.data, "kids", 4)) {
         if (TokenMatch(mBuffer.data, "kids", 4)) {
             SkipSpaces(&mBuffer.data, mBuffer.end);
             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");
     ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: \'kids\' line was expected");
+
     return false;
     return false;
 }
 }
 
 

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

@@ -63,7 +63,7 @@ namespace Assimp {
 class AC3DImporter : public BaseImporter {
 class AC3DImporter : public BaseImporter {
 public:
 public:
     AC3DImporter();
     AC3DImporter();
-    ~AC3DImporter() override;
+    ~AC3DImporter() override = default;
 
 
     // Represents an AC3D material
     // Represents an AC3D material
     struct Material {
     struct Material {
@@ -103,7 +103,7 @@ public:
 
 
         unsigned int mat, flags;
         unsigned int mat, flags;
 
 
-        typedef std::pair<unsigned int, aiVector2D> SurfaceEntry;
+        using SurfaceEntry = std::pair<unsigned int, aiVector2D>;
         std::vector<SurfaceEntry> entries;
         std::vector<SurfaceEntry> entries;
 
 
         // Type is low nibble of flags
         // 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
     // read attribute
     ne = new AMFMetadata(mNodeElement_Cur);
     ne = new AMFMetadata(mNodeElement_Cur);
-    ((AMFMetadata *)ne)->Type = type;
+    ((AMFMetadata *)ne)->MetaType = type;
     ((AMFMetadata *)ne)->Value = value;
     ((AMFMetadata *)ne)->Value = value;
     mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element
     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.
     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)->MaterialID = node.attribute("materialid").as_string();
 
 
-    ((AMFVolume *)ne)->Type = type;
+    ((AMFVolume *)ne)->VolumeType = type;
     // Check for child nodes
     // Check for child nodes
     bool col_read = false;
     bool col_read = false;
     if (!node.empty()) {
     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();
             const std::string &name = currentNode.name();
             if (name == "utex1") {
             if (name == "utex1") {
 				read_flag[0] = true;
 				read_flag[0] = true;
-                XmlParser::getValueAsReal(node, als.TextureCoordinate[0].x);
+                XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[0].x);
             } else if (name == "utex2") {
             } else if (name == "utex2") {
 				read_flag[1] = true;
 				read_flag[1] = true;
-                XmlParser::getValueAsReal(node, als.TextureCoordinate[1].x);
+                XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[1].x);
             } else if (name == "utex3") {
             } else if (name == "utex3") {
 				read_flag[2] = true;
 				read_flag[2] = true;
-                XmlParser::getValueAsReal(node, als.TextureCoordinate[2].x);
+                XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[2].x);
             } else if (name == "vtex1") {
             } else if (name == "vtex1") {
 				read_flag[3] = true;
 				read_flag[3] = true;
-                XmlParser::getValueAsReal(node, als.TextureCoordinate[0].y);
+                XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[0].y);
             } else if (name == "vtex2") {
             } else if (name == "vtex2") {
 				read_flag[4] = true;
 				read_flag[4] = true;
-                XmlParser::getValueAsReal(node, als.TextureCoordinate[1].y);
+                XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[1].y);
             } else if (name == "vtex3") {
             } else if (name == "vtex3") {
 				read_flag[5] = true;
 				read_flag[5] = true;
-                XmlParser::getValueAsReal(node, als.TextureCoordinate[2].y);
+                XmlParser::getValueAsReal(currentNode, als.TextureCoordinate[2].y);
 			}
 			}
 		}
 		}
         ParseHelper_Node_Exit();
         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.
 	AMFNodeElementBase *Parent; ///< Parent element. If nullptr then this node is root.
 	std::list<AMFNodeElementBase *> Child; ///< Child elements.
 	std::list<AMFNodeElementBase *> Child; ///< Child elements.
 
 
-public: /// Destructor, virtual..
+public: 
+	/// Destructor, virtual..
 	virtual ~AMFNodeElementBase() = default;
 	virtual ~AMFNodeElementBase() = default;
 
 
 	/// Disabled copy constructor and co.
 	/// Disabled copy constructor and co.
@@ -97,10 +98,10 @@ public: /// Destructor, virtual..
 
 
 protected:
 protected:
 	/// In constructor inheritor must set element type.
 	/// In constructor inheritor must set element type.
-	/// \param [in] pType - element type.
+	/// \param [in] type - element type.
 	/// \param [in] pParent - parent element.
 	/// \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
 		// empty
 	}
 	}
 }; // class IAMFImporter_NodeElement
 }; // class IAMFImporter_NodeElement
@@ -135,8 +136,8 @@ struct AMFInstance : public AMFNodeElementBase {
 /// Structure that define metadata node.
 /// Structure that define metadata node.
 struct AMFMetadata : public AMFNodeElementBase {
 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.
 	/// Constructor.
 	/// \param [in] pParent - pointer to parent node.
 	/// \param [in] pParent - pointer to parent node.
@@ -225,7 +226,7 @@ struct AMFVertices : public AMFNodeElementBase {
 /// Structure that define volume node.
 /// Structure that define volume node.
 struct AMFVolume : public AMFNodeElementBase {
 struct AMFVolume : public AMFNodeElementBase {
 	std::string MaterialID; ///< Which material to use.
 	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.
 	/// Constructor.
 	/// \param [in] pParent - pointer to parent node.
 	/// \param [in] pParent - pointer to parent node.

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

@@ -1,4 +1,4 @@
-/*
+/*
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
 Open Asset Import Library (assimp)
 Open Asset Import Library (assimp)
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
@@ -333,7 +333,7 @@ void AMFImporter::Postprocess_AddMetadata(const AMFMetaDataArray &metadataList,
     size_t meta_idx(0);
     size_t meta_idx(0);
 
 
     for (const AMFMetadata *metadata : metadataList) {
     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];
     char s[32];
-    in->Read(s, sizeof(char), 32);
+    const size_t read = in->Read(s, sizeof(char), 32);
 
 
     pIOHandler->Close(in);
     pIOHandler->Close(in);
+    
+    if (read < 19) {
+      return false;
+    }
 
 
     return strncmp(s, "ASSIMP.binary-dump.", 19) == 0;
     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 versionMajor = Read<unsigned int>(stream);
     unsigned int versionMinor = Read<unsigned int>(stream);
     unsigned int versionMinor = Read<unsigned int>(stream);
     if (versionMinor != ASSBIN_VERSION_MINOR || versionMajor != ASSBIN_VERSION_MAJOR) {
     if (versionMinor != ASSBIN_VERSION_MINOR || versionMajor != ASSBIN_VERSION_MAJOR) {
+        pIOHandler->Close(stream);
         throw DeadlyImportError("Invalid version, data format not compatible!");
         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;
     shortened = Read<uint16_t>(stream) > 0;
     compressed = 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!");
         throw DeadlyImportError("Shortened binaries are not supported!");
+    }
 
 
     stream->Seek(256, aiOrigin_CUR); // original filename
     stream->Seek(256, aiOrigin_CUR); // original filename
     stream->Seek(128, aiOrigin_CUR); // options
     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
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -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
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 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 majorVersion(aiGetVersionMajor());
     const unsigned int minorVersion(aiGetVersionMinor());
     const unsigned int minorVersion(aiGetVersionMinor());
     const unsigned int rev(aiGetVersionRevision());
     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);
     ioprintf(io, header.c_str(), majorVersion, minorVersion, rev, pFile, c.c_str(), curtime, scene->mFlags, 0u);
 
 
     // write the node graph
     // 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
 Copyright (c) 2006-2024, assimp team
 
 
-
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -85,11 +83,9 @@ BVHLoader::BVHLoader() :
         mLine(),
         mLine(),
         mAnimTickDuration(),
         mAnimTickDuration(),
         mAnimNumFrames(),
         mAnimNumFrames(),
-        noSkeletonMesh() {}
-
-// ------------------------------------------------------------------------------------------------
-// Destructor, private as well
-BVHLoader::~BVHLoader() = default;
+        noSkeletonMesh() {
+    // empty
+}
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 // Returns whether the class can handle the format of the given file.

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

@@ -80,32 +80,27 @@ class BVHLoader : public BaseImporter {
         std::vector<ChannelType> mChannels;
         std::vector<ChannelType> mChannels;
         std::vector<float> mChannelValues; // motion data values for that node. Of size NumChannels * NumFrames
         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:
 public:
     BVHLoader();
     BVHLoader();
-    ~BVHLoader();
+    ~BVHLoader() override = default;
 
 
-public:
     /** Returns whether the class can handle the format of the given file.
     /** Returns whether the class can handle the format of the given file.
      * See BaseImporter::CanRead() for details. */
      * See BaseImporter::CanRead() for details. */
-    bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool 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:
 protected:
     /** Imports the given file into the given scene structure.
     /** Imports the given file into the given scene structure.
      * See BaseImporter::InternReadFile() for details
      * See BaseImporter::InternReadFile() for details
      */
      */
-    void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler);
+    void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
 
 
-protected:
     /** Reads the file */
     /** Reads the file */
     void ReadStructure(aiScene *pScene);
     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
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,

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

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

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

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

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

@@ -1,11 +1,9 @@
-
 /*
 /*
 Open Asset Import Library (assimp)
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 
 
 Copyright (c) 2006-2024, assimp team
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -495,8 +493,9 @@ void BlenderImporter::BuildDefaultMaterial(Blender::ConversionData &conv_data) {
             if (index == static_cast<unsigned int>(-1)) {
             if (index == static_cast<unsigned int>(-1)) {
                 // Setup a default material.
                 // Setup a default material.
                 std::shared_ptr<Material> p(new 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.
                 // Note: MSVC11 does not zero-initialize Material here, although it should.
                 // Thus all relevant fields should be explicitly initialized. We cannot add
                 // 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)
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 
 
-Copyright (c) 2006-2021, assimp team
+Copyright (c) 2006-2024, assimp team
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -86,7 +86,7 @@ namespace Assimp {
     }
     }
 }
 }
 
 
-static const aiImporterDesc desc = {
+static constexpr aiImporterDesc desc = {
     "Cinema4D Importer",
     "Cinema4D Importer",
     "",
     "",
     "",
     "",
@@ -99,13 +99,6 @@ static const aiImporterDesc desc = {
     "c4d"
     "c4d"
 };
 };
 
 
-
-// ------------------------------------------------------------------------------------------------
-C4DImporter::C4DImporter() = default;
-
-// ------------------------------------------------------------------------------------------------
-C4DImporter::~C4DImporter() = default;
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 bool C4DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
 bool C4DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
     const std::string& extension = GetExtension(pFile);
     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);
     std::copy(materials.begin(), materials.end(), pScene->mMaterials);
 }
 }
 
 
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 bool C4DImporter::ReadShader(aiMaterial* out, BaseShader* shader) {
 bool C4DImporter::ReadShader(aiMaterial* out, BaseShader* shader) {
     // based on Cineware sample code (C4DImportExport.cpp)
     // 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> {
 class C4DImporter : public BaseImporter, public LogFunctions<C4DImporter> {
 public:
 public:
-    C4DImporter();
-    ~C4DImporter() override;
+    C4DImporter() = default;
+    ~C4DImporter() override = default;
     bool CanRead( const std::string& pFile, IOSystem*, bool checkSig) const override;
     bool CanRead( const std::string& pFile, IOSystem*, bool checkSig) const override;
 
 
 protected:
 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);
         const Mesh &ndmesh = (const Mesh &)(root);
         if (ndmesh.vertex_positions.size() && ndmesh.texture_coords.size()) {
         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) {
             for (const Entry &reflist : ndmesh.temp_map) {
                 { // create mesh
                 { // create mesh
                     size_t n = 0;
                     size_t n = 0;
@@ -372,9 +372,11 @@ aiNode *COBImporter::BuildNodes(const Node &root, const Scene &scin, aiScene *fi
     }
     }
 
 
     // add children recursively
     // 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;
     return nd;

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

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2024, assimp team
 Copyright (c) 2006-2024, assimp team
 
 
-
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -73,10 +71,9 @@ static constexpr aiImporterDesc desc = {
     "csm"
     "csm"
 };
 };
 
 
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 // Constructor to be privately used by Importer
-CSMImporter::CSMImporter() : noSkeletonMesh(){
+CSMImporter::CSMImporter() : noSkeletonMesh() {
     // empty
     // empty
 }
 }
 
 
@@ -102,8 +99,7 @@ void CSMImporter::SetupProperties(const Importer* pImp) {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure.
 // Imports the given file into the given scene structure.
 void CSMImporter::InternReadFile( const std::string& pFile,
 void CSMImporter::InternReadFile( const std::string& pFile,
-    aiScene* pScene, IOSystem* pIOHandler)
-{
+        aiScene* pScene, IOSystem* pIOHandler) {
     std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
     std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
 
 
     // Check whether we can read from the file
     // Check whether we can read from the file
@@ -127,11 +123,11 @@ void CSMImporter::InternReadFile( const std::string& pFile,
 
 
         if ('$'  == *buffer)    {
         if ('$'  == *buffer)    {
             ++buffer;
             ++buffer;
-            if (TokenMatchI(buffer,"firstframe",10))    {
+            if (TokenMatchI(buffer,"firstframe",10)) {
                 SkipSpaces(&buffer, end);
                 SkipSpaces(&buffer, end);
                 first = strtol10(buffer,&buffer);
                 first = strtol10(buffer,&buffer);
             }
             }
-            else if (TokenMatchI(buffer,"lastframe",9))     {
+            else if (TokenMatchI(buffer,"lastframe",9)) {
                 SkipSpaces(&buffer, end);
                 SkipSpaces(&buffer, end);
                 last = strtol10(buffer,&buffer);
                 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
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -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
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 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 {
 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
 // 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*/) {
 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();
     WriteFile();
 }
 }
 
 
-// ------------------------------------------------------------------------------------------------
-// Destructor
-ColladaExporter::~ColladaExporter() = default;
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Starts writing the contents
 // Starts writing the contents
 void ColladaExporter::WriteFile() {
 void ColladaExporter::WriteFile() {
@@ -331,60 +356,68 @@ void ColladaExporter::WriteHeader() {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Write the embedded textures
 // Write the embedded textures
 void ColladaExporter::WriteTextures() {
 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
 // Write the embedded textures
 void ColladaExporter::WriteCamerasLibrary() {
 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) {
 void ColladaExporter::WriteCamera(size_t pIndex) {
 
 
     const aiCamera *cam = mScene->mCameras[pIndex];
     const aiCamera *cam = mScene->mCameras[pIndex];
+    if (cam == nullptr) {
+        return;
+    }
+
     const std::string cameraId = GetObjectUniqueId(AiObjectType::Camera, pIndex);
     const std::string cameraId = GetObjectUniqueId(AiObjectType::Camera, pIndex);
     const std::string cameraName = GetObjectName(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
 // Write the embedded textures
 void ColladaExporter::WriteLightsLibrary() {
 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) {
 void ColladaExporter::WriteLight(size_t pIndex) {
 
 
     const aiLight *light = mScene->mLights[pIndex];
     const aiLight *light = mScene->mLights[pIndex];
+    if (light == nullptr) {
+        return;
+    }
     const std::string lightId = GetObjectUniqueId(AiObjectType::Light, pIndex);
     const std::string lightId = GetObjectUniqueId(AiObjectType::Light, pIndex);
     const std::string lightName = GetObjectName(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_AREA:
     case aiLightSource_UNDEFINED:
     case aiLightSource_UNDEFINED:
     case _aiLightSource_Force32Bit:
     case _aiLightSource_Force32Bit:
+    default:
         break;
         break;
     }
     }
     PopTag();
     PopTag();
@@ -521,10 +560,6 @@ void ColladaExporter::WriteSpotLight(const aiLight *const light) {
     mOutput << startstr << "<quadratic_attenuation>"
     mOutput << startstr << "<quadratic_attenuation>"
             << light->mAttenuationQuadratic
             << light->mAttenuationQuadratic
             << "</quadratic_attenuation>" << endstr;
             << "</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);
     const ai_real fallOffAngle = AI_RAD_TO_DEG(light->mAngleInnerCone);
     mOutput << startstr << "<falloff_angle sid=\"fall_off_angle\">"
     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
 // 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) {
 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 {
         } else {
-            poSurface.texture = texfile.C_Str();
+            throw DeadlyExportError("could not find embedded texture at index " + index_str);
         }
         }
-
-        poSurface.channel = uvChannel;
-        poSurface.exist = true;
     } else {
     } 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;
     return poSurface.exist;
 }
 }
 
 
@@ -606,79 +643,87 @@ static bool isalnum_C(char c) {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Writes an image entry for the given surface
 // Writes an image entry for the given surface
 void ColladaExporter::WriteImageEntry(const Surface &pSurface, const std::string &imageId) {
 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
 // Writes a color-or-texture entry into an effect definition
 void ColladaExporter::WriteTextureColorEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &imageId) {
 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
 // 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) {
 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 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
 // Writes a scalar property
 void ColladaExporter::WriteFloatEntry(const Property &pProperty, const std::string &pTypeName) {
 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) {
 void ColladaExporter::WriteController(size_t pIndex) {
     const aiMesh *mesh = mScene->mMeshes[pIndex];
     const aiMesh *mesh = mScene->mMeshes[pIndex];
     // Is there a skin controller?
     // 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;
         return;
+    }
 
 
     const std::string idstr = GetObjectUniqueId(AiObjectType::Mesh, pIndex);
     const std::string idstr = GetObjectUniqueId(AiObjectType::Mesh, pIndex);
     const std::string namestr = GetObjectName(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 << "\">";
     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 << GetBoneUniqueId(mesh->mBones[i]) << ' ';
+    }
 
 
     mOutput << "</Name_array>" << endstr;
     mOutput << "</Name_array>" << endstr;
 
 
@@ -888,9 +935,11 @@ void ColladaExporter::WriteController(size_t pIndex) {
 
 
     std::vector<ai_real> bind_poses;
     std::vector<ai_real> bind_poses;
     bind_poses.reserve(mesh->mNumBones * 16);
     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);
             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);
     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;
     std::vector<ai_real> skin_weights;
     skin_weights.reserve(mesh->mNumVertices * mesh->mNumBones);
     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);
             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());
     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>";
     mOutput << startstr << "<vcount>";
 
 
     std::vector<ai_uint> num_influences(mesh->mNumVertices, (ai_uint)0);
     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];
             ++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 << num_influences[i] << " ";
+    }
 
 
     mOutput << "</vcount>" << endstr;
     mOutput << "</vcount>" << endstr;
 
 
@@ -945,7 +999,7 @@ void ColladaExporter::WriteController(size_t pIndex) {
 
 
     ai_uint weight_index = 0;
     ai_uint weight_index = 0;
     std::vector<ai_int> joint_weight_indices(2 * joint_weight_indices_length, (ai_int)-1);
     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) {
         for (unsigned j = 0; j < mesh->mBones[i]->mNumWeights; ++j) {
             unsigned int vId = mesh->mBones[i]->mWeights[j].mVertexId;
             unsigned int vId = mesh->mBones[i]->mWeights[j].mVertexId;
             for (ai_uint k = 0; k < num_influences[vId]; ++k) {
             for (ai_uint k = 0; k < num_influences[vId]; ++k) {
@@ -957,9 +1011,11 @@ void ColladaExporter::WriteController(size_t pIndex) {
             }
             }
             ++weight_index;
             ++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] << " ";
         mOutput << joint_weight_indices[i] << " ";
+    }
 
 
     num_influences.clear();
     num_influences.clear();
     accum_influences.clear();
     accum_influences.clear();
@@ -983,8 +1039,9 @@ void ColladaExporter::WriteGeometryLibrary() {
     mOutput << startstr << "<library_geometries>" << endstr;
     mOutput << startstr << "<library_geometries>" << endstr;
     PushTag();
     PushTag();
 
 
-    for (size_t a = 0; a < mScene->mNumMeshes; ++a)
+    for (size_t a = 0; a < mScene->mNumMeshes; ++a) {
         WriteGeometry(a);
         WriteGeometry(a);
+    }
 
 
     PopTag();
     PopTag();
     mOutput << startstr << "</library_geometries>" << endstr;
     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 geometryId = GetObjectUniqueId(AiObjectType::Mesh, pIndex);
     const std::string geometryName = GetObjectName(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;
         return;
+    }
 
 
     // opening tag
     // opening tag
     mOutput << startstr << "<geometry id=\"" << geometryId << "\" name=\"" << geometryName << "\" >" << endstr;
     mOutput << startstr << "<geometry id=\"" << geometryId << "\" name=\"" << geometryName << "\" >" << endstr;
@@ -1010,8 +1068,9 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
     // Positions
     // Positions
     WriteFloatArray(geometryId + "-positions", FloatType_Vector, (ai_real *)mesh->mVertices, mesh->mNumVertices);
     WriteFloatArray(geometryId + "-positions", FloatType_Vector, (ai_real *)mesh->mVertices, mesh->mNumVertices);
     // Normals, if any
     // Normals, if any
-    if (mesh->HasNormals())
+    if (mesh->HasNormals()) {
         WriteFloatArray(geometryId + "-normals", FloatType_Vector, (ai_real *)mesh->mNormals, mesh->mNumVertices);
         WriteFloatArray(geometryId + "-normals", FloatType_Vector, (ai_real *)mesh->mNormals, mesh->mNumVertices);
+    }
 
 
     // texture coords
     // texture coords
     for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
     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 countLines = 0;
     int countPoly = 0;
     int countPoly = 0;
     for (size_t a = 0; a < mesh->mNumFaces; ++a) {
     for (size_t a = 0; a < mesh->mNumFaces; ++a) {
-        if (mesh->mFaces[a].mNumIndices == 2)
+        if (mesh->mFaces[a].mNumIndices == 2) {
             countLines++;
             countLines++;
-        else if (mesh->mFaces[a].mNumIndices >= 3)
+        } else if (mesh->mFaces[a].mNumIndices >= 3) {
             countPoly++;
             countPoly++;
+        }
     }
     }
 
 
     // lines
     // lines
@@ -1051,13 +1111,18 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
         mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr;
         mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr;
         PushTag();
         PushTag();
         mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
         mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
-        if (mesh->HasNormals())
+        if (mesh->HasNormals()) {
             mOutput << startstr << "<input semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
             mOutput << startstr << "<input semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
+        }
         for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
         for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
-            if (mesh->HasTextureCoords(static_cast<unsigned int>(a)))
-                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 << "\""
                         << "set=\"" << a << "\""
                         << " />" << endstr;
                         << " />" << endstr;
+            }
         }
         }
         for (size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) {
         for (size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) {
             if (mesh->HasVertexColors(static_cast<unsigned int>(a)))
             if (mesh->HasVertexColors(static_cast<unsigned int>(a)))
@@ -1070,8 +1135,9 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
         for (size_t a = 0; a < mesh->mNumFaces; ++a) {
         for (size_t a = 0; a < mesh->mNumFaces; ++a) {
             const aiFace &face = mesh->mFaces[a];
             const aiFace &face = mesh->mFaces[a];
             if (face.mNumIndices != 2) continue;
             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 << face.mIndices[b] << " ";
+            }
         }
         }
         mOutput << "</p>" << endstr;
         mOutput << "</p>" << endstr;
         PopTag();
         PopTag();
@@ -1085,8 +1151,9 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
         mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr;
         mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr;
         PushTag();
         PushTag();
         mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
         mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
-        if (mesh->HasNormals())
+        if (mesh->HasNormals()) {
             mOutput << startstr << "<input offset=\"0\" semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
             mOutput << startstr << "<input offset=\"0\" semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
+        }
         for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
         for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
             if (mesh->HasTextureCoords(static_cast<unsigned int>(a)))
             if (mesh->HasTextureCoords(static_cast<unsigned int>(a)))
                 mOutput << startstr << "<input offset=\"0\" semantic=\"TEXCOORD\" source=\"#" << geometryId << "-tex" << 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) {
         for (size_t a = 0; a < mesh->mNumFaces; ++a) {
             const aiFace &face = mesh->mFaces[a];
             const aiFace &face = mesh->mFaces[a];
             if (face.mNumIndices < 3) continue;
             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 << face.mIndices[b] << " ";
+            }
         }
         }
         mOutput << "</p>" << endstr;
         mOutput << "</p>" << endstr;
         PopTag();
         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) {
 void ColladaExporter::WriteFloatArray(const std::string &pIdString, FloatDataType pType, const ai_real *pData, size_t pElementCount) {
     size_t floatsPerElement = 0;
     size_t floatsPerElement = 0;
     switch (pType) {
     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:
     default:
         return;
         return;
     }
     }
@@ -1163,8 +1245,9 @@ void ColladaExporter::WriteFloatArray(const std::string &pIdString, FloatDataTyp
             mOutput << pData[a * 4 + 2] << " ";
             mOutput << pData[a * 4 + 2] << " ";
         }
         }
     } else {
     } else {
-        for (size_t a = 0; a < pElementCount * floatsPerElement; ++a)
+        for (size_t a = 0; a < pElementCount * floatsPerElement; ++a) {
             mOutput << pData[a] << " ";
             mOutput << pData[a] << " ";
+        }
     }
     }
     mOutput << "</float_array>" << endstr;
     mOutput << "</float_array>" << endstr;
     PopTag();
     PopTag();
@@ -1256,9 +1339,13 @@ void ColladaExporter::WriteSceneLibrary() {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void ColladaExporter::WriteAnimationLibrary(size_t pIndex) {
 void ColladaExporter::WriteAnimationLibrary(size_t pIndex) {
     const aiAnimation *anim = mScene->mAnimations[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;
         return;
+    }
 
 
     const std::string animationNameEscaped = GetObjectName(AiObjectType::Animation, pIndex);
     const std::string animationNameEscaped = GetObjectName(AiObjectType::Animation, pIndex);
     const std::string idstrEscaped = GetObjectUniqueId(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;
     std::string cur_node_idstr;
     for (size_t a = 0; a < anim->mNumChannels; ++a) {
     for (size_t a = 0; a < anim->mNumChannels; ++a) {
         const aiNodeAnim *nodeAnim = anim->mChannels[a];
         const aiNodeAnim *nodeAnim = anim->mChannels[a];
+        if (nodeAnim == nullptr) {
+            continue;
+        }
 
 
-        // sanity check
+        // sanity checks
         if (nodeAnim->mNumPositionKeys != nodeAnim->mNumScalingKeys || nodeAnim->mNumPositionKeys != nodeAnim->mNumRotationKeys) {
         if (nodeAnim->mNumPositionKeys != nodeAnim->mNumScalingKeys || nodeAnim->mNumPositionKeys != nodeAnim->mNumRotationKeys) {
             continue;
             continue;
         }
         }
@@ -1369,6 +1459,9 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex) {
 
 
     for (size_t a = 0; a < anim->mNumChannels; ++a) {
     for (size_t a = 0; a < anim->mNumChannels; ++a) {
         const aiNodeAnim *nodeAnim = anim->mChannels[a];
         const aiNodeAnim *nodeAnim = anim->mChannels[a];
+        if (nodeAnim == nullptr) {
+            continue;
+        }
 
 
         {
         {
             // samplers
             // samplers
@@ -1387,97 +1480,42 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex) {
 
 
     for (size_t a = 0; a < anim->mNumChannels; ++a) {
     for (size_t a = 0; a < anim->mNumChannels; ++a) {
         const aiNodeAnim *nodeAnim = anim->mChannels[a];
         const aiNodeAnim *nodeAnim = anim->mChannels[a];
+        if (nodeAnim == nullptr) {
+            continue;
+        }
 
 
         {
         {
             // channels
             // 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();
     PopTag();
     mOutput << startstr << "</animation>" << endstr;
     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
     // Assimp-specific: nodes with no name cannot be associated with bones
     const char *node_type;
     const char *node_type;
     bool is_joint, is_skeleton_root = false;
     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";
         node_type = "NODE";
         is_joint = false;
         is_joint = false;
     } else {
     } else {
         node_type = "JOINT";
         node_type = "JOINT";
         is_joint = true;
         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;
             is_skeleton_root = true;
         }
         }
     }
     }
@@ -1532,7 +1570,6 @@ void ColladaExporter::WriteNode(const aiNode *pNode) {
     }
     }
 
 
     // customized, sid should be 'matrix' to match with loader code.
     // customized, sid should be 'matrix' to match with loader code.
-    //mOutput << startstr << "<matrix sid=\"transform\">";
     mOutput << startstr << "<matrix sid=\"matrix\">";
     mOutput << startstr << "<matrix sid=\"matrix\">";
 
 
     mOutput << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << " ";
     mOutput << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << " ";
@@ -1556,7 +1593,6 @@ void ColladaExporter::WriteNode(const aiNode *pNode) {
                 break;
                 break;
             }
             }
         }
         }
-
     } else
     } else
         // instance every geometry
         // instance every geometry
         for (size_t a = 0; a < pNode->mNumMeshes; ++a) {
         for (size_t a = 0; a < pNode->mNumMeshes; ++a) {
@@ -1612,8 +1648,9 @@ void ColladaExporter::WriteNode(const aiNode *pNode) {
         }
         }
 
 
     // recurse into subnodes
     // 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]);
         WriteNode(pNode->mChildren[a]);
+    }
 
 
     PopTag();
     PopTag();
     mOutput << startstr << "</node>" << endstr;
     mOutput << startstr << "</node>" << endstr;
@@ -1628,8 +1665,9 @@ void ColladaExporter::CreateNodeIds(const aiNode *node) {
 std::string ColladaExporter::GetNodeUniqueId(const aiNode *node) {
 std::string ColladaExporter::GetNodeUniqueId(const aiNode *node) {
     // Use the pointer as the key. This is safe because the scene is immutable.
     // Use the pointer as the key. This is safe because the scene is immutable.
     auto idIt = mNodeIdMap.find(node);
     auto idIt = mNodeIdMap.find(node);
-    if (idIt != mNodeIdMap.cend())
+    if (idIt != mNodeIdMap.cend()) {
         return idIt->second;
         return idIt->second;
+    }
 
 
     // Prefer the requested Collada Id if extant
     // Prefer the requested Collada Id if extant
     std::string idStr;
     std::string idStr;
@@ -1640,36 +1678,42 @@ std::string ColladaExporter::GetNodeUniqueId(const aiNode *node) {
         idStr = node->mName.C_Str();
         idStr = node->mName.C_Str();
     }
     }
     // Make sure the requested id is valid
     // Make sure the requested id is valid
-    if (idStr.empty())
+    if (idStr.empty()) {
         idStr = "node";
         idStr = "node";
-    else
+    } else {
         idStr = XMLIDEncode(idStr);
         idStr = XMLIDEncode(idStr);
+    }
 
 
     // Ensure it's unique
     // Ensure it's unique
     idStr = MakeUniqueId(mUniqueIds, idStr, std::string());
     idStr = MakeUniqueId(mUniqueIds, idStr, std::string());
     mUniqueIds.insert(idStr);
     mUniqueIds.insert(idStr);
     mNodeIdMap.insert(std::make_pair(node, idStr));
     mNodeIdMap.insert(std::make_pair(node, idStr));
+    
     return idStr;
     return idStr;
 }
 }
 
 
 std::string ColladaExporter::GetNodeName(const aiNode *node) {
 std::string ColladaExporter::GetNodeName(const aiNode *node) {
-
+    if (node == nullptr) {
+        return std::string();
+    }
     return XMLEscape(node->mName.C_Str());
     return XMLEscape(node->mName.C_Str());
 }
 }
 
 
 std::string ColladaExporter::GetBoneUniqueId(const aiBone *bone) {
 std::string ColladaExporter::GetBoneUniqueId(const aiBone *bone) {
     // Find the Node that is this 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 std::string();
+    }
 
 
     return GetNodeUniqueId(boneNode);
     return GetNodeUniqueId(boneNode);
 }
 }
 
 
 std::string ColladaExporter::GetObjectUniqueId(AiObjectType type, size_t pIndex) {
 std::string ColladaExporter::GetObjectUniqueId(AiObjectType type, size_t pIndex) {
     auto idIt = GetObjectIdMap(type).find(pIndex);
     auto idIt = GetObjectIdMap(type).find(pIndex);
-    if (idIt != GetObjectIdMap(type).cend())
+    if (idIt != GetObjectIdMap(type).cend()) {
         return idIt->second;
         return idIt->second;
+    }
 
 
     // Not seen this object before, create and add
     // Not seen this object before, create and add
     NameIdPair result = AddObjectIndexToMaps(type, pIndex);
     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) {
 std::string ColladaExporter::GetObjectName(AiObjectType type, size_t pIndex) {
     auto objectName = GetObjectNameMap(type).find(pIndex);
     auto objectName = GetObjectNameMap(type).find(pIndex);
-    if (objectName != GetObjectNameMap(type).cend())
+    if (objectName != GetObjectNameMap(type).cend()) {
         return objectName->second;
         return objectName->second;
+    }
 
 
     // Not seen this object before, create and add
     // Not seen this object before, create and add
     NameIdPair result = AddObjectIndexToMaps(type, pIndex);
     NameIdPair result = AddObjectIndexToMaps(type, pIndex);
@@ -1699,9 +1744,15 @@ ColladaExporter::NameIdPair ColladaExporter::AddObjectIndexToMaps(AiObjectType t
 
 
     // Get the name and id postfix
     // Get the name and id postfix
     switch (type) {
     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:
     case AiObjectType::Light:
         name = mScene->mLights[index]->mName.C_Str();
         name = mScene->mLights[index]->mName.C_Str();
         idPostfix = "-light";
         idPostfix = "-light";
@@ -1710,7 +1761,8 @@ ColladaExporter::NameIdPair ColladaExporter::AddObjectIndexToMaps(AiObjectType t
         name = mScene->mCameras[index]->mName.C_Str();
         name = mScene->mCameras[index]->mName.C_Str();
         idPostfix = "-camera";
         idPostfix = "-camera";
         break;
         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()) {
     if (name.empty()) {
@@ -1728,8 +1780,9 @@ ColladaExporter::NameIdPair ColladaExporter::AddObjectIndexToMaps(AiObjectType t
         idStr = XMLIDEncode(name);
         idStr = XMLIDEncode(name);
     }
     }
 
 
-    if (!name.empty())
+    if (!name.empty()) {
         name = XMLEscape(name);
         name = XMLEscape(name);
+    }
 
 
     idStr = MakeUniqueId(mUniqueIds, idStr, idPostfix);
     idStr = MakeUniqueId(mUniqueIds, idStr, idPostfix);
 
 
@@ -1743,5 +1796,5 @@ ColladaExporter::NameIdPair ColladaExporter::AddObjectIndexToMaps(AiObjectType t
 
 
 } // end of namespace Assimp
 } // 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
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -72,7 +71,7 @@ public:
     ColladaExporter(const aiScene *pScene, IOSystem *pIOSystem, const std::string &path, const std::string &file);
     ColladaExporter(const aiScene *pScene, IOSystem *pIOSystem, const std::string &path, const std::string &file);
 
 
     /// Destructor
     /// Destructor
-    virtual ~ColladaExporter();
+    virtual ~ColladaExporter() = default;
 
 
 protected:
 protected:
     /// Starts writing the contents
     /// 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;
             return mKey.mWeight;
         }
         }
     }
     }
+    
     // no value at key found, try to interpolate if present at other keys. if not, return zero
     // no value at key found, try to interpolate if present at other keys. if not, return zero
     // TODO: interpolation
     // TODO: interpolation
     return 0.0f;
     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
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -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 {
 class ColladaLoader : public BaseImporter {
 public:
 public:
@@ -102,50 +104,51 @@ protected:
     /// See #BaseImporter::InternReadFile for the details
     /// See #BaseImporter::InternReadFile for the details
     void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
     void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
 
 
-    /** Recursively constructs a scene node for the given parser node and returns it. */
+    /// Recursively constructs a scene node for the given parser node and returns it.
     aiNode *BuildHierarchy(const ColladaParser &pParser, const Collada::Node *pNode);
     aiNode *BuildHierarchy(const ColladaParser &pParser, const Collada::Node *pNode);
 
 
-    /** Resolve node instances */
+    /// Resolve node instances
     void ResolveNodeInstances(const ColladaParser &pParser, const Collada::Node *pNode,
     void ResolveNodeInstances(const ColladaParser &pParser, const Collada::Node *pNode,
             std::vector<const Collada::Node *> &resolved);
             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,
     void BuildMeshesForNode(const ColladaParser &pParser, const Collada::Node *pNode,
             aiNode *pTarget);
             aiNode *pTarget);
 
 
+    /// Lookup for meshes by their name
     aiMesh *findMesh(const std::string &meshid);
     aiMesh *findMesh(const std::string &meshid);
 
 
-    /** Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh */
+    /// Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh
     aiMesh *CreateMesh(const ColladaParser &pParser, const Collada::Mesh *pSrcMesh, const Collada::SubMesh &pSubMesh,
     aiMesh *CreateMesh(const ColladaParser &pParser, const Collada::Mesh *pSrcMesh, const Collada::SubMesh &pSubMesh,
             const Collada::Controller *pSrcController, size_t pStartVertex, size_t pStartFace);
             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,
     void BuildCamerasForNode(const ColladaParser &pParser, const Collada::Node *pNode,
             aiNode *pTarget);
             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,
     void BuildLightsForNode(const ColladaParser &pParser, const Collada::Node *pNode,
             aiNode *pTarget);
             aiNode *pTarget);
 
 
-    /** Stores all meshes in the given scene */
+    /// Stores all meshes in the given scene
     void StoreSceneMeshes(aiScene *pScene);
     void StoreSceneMeshes(aiScene *pScene);
 
 
-    /** Stores all materials in the given scene */
+    /// Stores all materials in the given scene
     void StoreSceneMaterials(aiScene *pScene);
     void StoreSceneMaterials(aiScene *pScene);
 
 
-    /** Stores all lights in the given scene */
+    /// Stores all lights in the given scene
     void StoreSceneLights(aiScene *pScene);
     void StoreSceneLights(aiScene *pScene);
 
 
-    /** Stores all cameras in the given scene */
+    /// Stores all cameras in the given scene
     void StoreSceneCameras(aiScene *pScene);
     void StoreSceneCameras(aiScene *pScene);
 
 
-    /** Stores all textures in the given scene */
+    /// Stores all textures in the given scene
     void StoreSceneTextures(aiScene *pScene);
     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
     /** Stores all animations for the given source anim and its nested child animations
      * @param pScene target scene to store the anims
      * @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
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -62,10 +61,7 @@ namespace DXF {
 // do NOT skip empty lines. In DXF files, they count as valid data.
 // do NOT skip empty lines. In DXF files, they count as valid data.
 class LineReader {
 class LineReader {
 public:
 public:
-    LineReader(StreamReaderLE& reader)
-    : splitter(reader,false,true)
-    , groupcode( 0 )
-    , end() {
+    LineReader(StreamReaderLE& reader) : splitter(reader,false,true), groupcode( 0 ), end() {
         // empty
         // empty
     }
     }
 
 
@@ -165,8 +161,7 @@ private:
 
 
 // represents a POLYLINE or a LWPOLYLINE. or even a 3DFACE The data is converted as needed.
 // represents a POLYLINE or a LWPOLYLINE. or even a 3DFACE The data is converted as needed.
 struct PolyLine {
 struct PolyLine {
-    PolyLine()
-    : flags() {
+    PolyLine() : flags() {
         // empty
         // empty
     }
     }
 
 
@@ -182,10 +177,7 @@ struct PolyLine {
 
 
 // reference to a BLOCK. Specifies its own coordinate system.
 // reference to a BLOCK. Specifies its own coordinate system.
 struct InsertBlock {
 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
         // empty
     }
     }
 
 
@@ -198,8 +190,7 @@ struct InsertBlock {
 
 
 
 
 // keeps track of all geometry in a single BLOCK.
 // keeps track of all geometry in a single BLOCK.
-struct Block
-{
+struct Block {
     std::vector< std::shared_ptr<PolyLine> > lines;
     std::vector< std::shared_ptr<PolyLine> > lines;
     std::vector<InsertBlock> insertions;
     std::vector<InsertBlock> insertions;
 
 
@@ -207,14 +198,12 @@ struct Block
     aiVector3D base;
     aiVector3D base;
 };
 };
 
 
-
-struct FileData
-{
+struct FileData {
     // note: the LAST block always contains the stuff from ENTITIES.
     // note: the LAST block always contains the stuff from ENTITIES.
     std::vector<Block> blocks;
     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 {
 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;
     return curves;

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

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2024, assimp team
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -60,58 +59,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp {
 namespace Assimp {
 namespace FBX {
 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(sbegin);
     ai_assert(send);
     ai_assert(send);
 
 

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

@@ -51,7 +51,8 @@ namespace Assimp {
 namespace FBX {
 namespace FBX {
 
 
 static constexpr size_t NumNullRecords = 25;
 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', '\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?)
 }; // 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) {
     if (out->mNumMeshes == 0) {
         out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
         out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
     } else {
     } 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: get bone from stack
 /// todo: make map of aiBone* to aiNode*
 /// todo: make map of aiBone* to aiNode*
 /// then update convert clusters to the new format
 /// 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");
     const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(id, "Model");
 
 
     std::vector<PotentialNode> nodes;
     std::vector<PotentialNode> nodes;
@@ -276,7 +278,7 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node)
         if (nullptr != model) {
         if (nullptr != model) {
             nodes_chain.clear();
             nodes_chain.clear();
             post_nodes_chain.clear();
             post_nodes_chain.clear();
-            aiMatrix4x4 new_abs_transform = parent->mTransformation;
+            aiMatrix4x4 new_abs_transform = parent_transform;
             std::string node_name = FixNodeName(model->Name());
             std::string node_name = FixNodeName(model->Name());
             // even though there is only a single input node, the design of
             // even though there is only a single input node, the design of
             // assimp (or rather: the complicated transformation chain that
             // assimp (or rather: the complicated transformation chain that
@@ -310,6 +312,8 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node)
 
 
                 child->mParent = last_parent;
                 child->mParent = last_parent;
                 last_parent = child.mNode;
                 last_parent = child.mNode;
+
+                new_abs_transform *= child->mTransformation;
             }
             }
 
 
             // attach geometry
             // attach geometry
@@ -332,6 +336,8 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node)
 
 
                     postnode->mParent = last_parent;
                     postnode->mParent = last_parent;
                     last_parent = postnode.mNode;
                     last_parent = postnode.mNode;
+
+                    new_abs_transform *= postnode->mTransformation;
                 }
                 }
             } else {
             } else {
                 // free the nodes we allocated as we don't need them
                 // 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
             // 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) {
             if (doc.Settings().readLights) {
                 ConvertLights(*model, node_name);
                 ConvertLights(*model, node_name);
@@ -357,12 +363,12 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node)
     if (nodes.empty()) {
     if (nodes.empty()) {
         parent->mNumChildren = 0;
         parent->mNumChildren = 0;
         parent->mChildren = nullptr;
         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;
             out_light->mType = aiLightSource_UNDEFINED;
             break;
             break;
         default:
         default:
-            ai_assert(false);
+            FBXImporter::LogError("Not handled light type: ", light.LightType());
+            break;
     }
     }
 
 
     float decay = light.DecayStart();
     float decay = light.DecayStart();
@@ -457,7 +464,7 @@ void FBXConverter::ConvertLight(const Light &light, const std::string &orig_name
             out_light->mAttenuationQuadratic = 1.0f;
             out_light->mAttenuationQuadratic = 1.0f;
             break;
             break;
         default:
         default:
-            ai_assert(false);
+            FBXImporter::LogError("Not handled light decay type: ", light.DecayType());
             break;
             break;
     }
     }
 }
 }
@@ -595,7 +602,7 @@ const char *FBXConverter::NameTransformationCompProperty(TransformationComp comp
             return "GeometricRotationInverse";
             return "GeometricRotationInverse";
         case TransformationComp_GeometricTranslationInverse:
         case TransformationComp_GeometricTranslationInverse:
             return "GeometricTranslationInverse";
             return "GeometricTranslationInverse";
-        case TransformationComp_MAXIMUM: // this is to silence compiler warnings
+        case TransformationComp_MAXIMUM:
             break;
             break;
     }
     }
 
 
@@ -711,8 +718,7 @@ bool FBXConverter::NeedsComplexTransformationChain(const Model &model) {
     for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
     for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
         const TransformationComp comp = static_cast<TransformationComp>(i);
         const TransformationComp comp = static_cast<TransformationComp>(i);
 
 
-        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;
             continue;
         }
         }
 
 
@@ -1248,9 +1254,9 @@ unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, c
         for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) {
         for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) {
             const auto& shapeGeometries = blendShapeChannel->GetShapeGeometries();
             const auto& shapeGeometries = blendShapeChannel->GetShapeGeometries();
             for (const ShapeGeometry *shapeGeometry : shapeGeometries) {
             for (const ShapeGeometry *shapeGeometry : shapeGeometries) {
-                aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh);
-                const auto &curVertices = shapeGeometry->GetVertices();
                 const auto &curNormals = shapeGeometry->GetNormals();
                 const auto &curNormals = shapeGeometry->GetNormals();
+                aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh, true, !curNormals.empty());
+                const auto &curVertices = shapeGeometry->GetVertices();
                 const auto &curIndices = shapeGeometry->GetIndices();
                 const auto &curIndices = shapeGeometry->GetIndices();
                 //losing channel name if using shapeGeometry->Name()
                 //losing channel name if using shapeGeometry->Name()
                 // if blendShapeChannel Name is empty or doesn't have a ".", add geoMetryName;
                 // 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++) {
                 for (size_t j = 0; j < curIndices.size(); j++) {
                     const unsigned int curIndex = curIndices.at(j);
                     const unsigned int curIndex = curIndices.at(j);
                     aiVector3D vertex = curVertices.at(j);
                     aiVector3D vertex = curVertices.at(j);
-                    aiVector3D normal = curNormals.at(j);
+                    aiVector3D normal = curNormals.empty() ? aiVector3D() : curNormals.at(j);
                     unsigned int count = 0;
                     unsigned int count = 0;
                     const unsigned int *outIndices = mesh.ToOutputVertexIndex(curIndex, count);
                     const unsigned int *outIndices = mesh.ToOutputVertexIndex(curIndex, count);
                     for (unsigned int k = 0; k < count; k++) {
                     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()) {
         for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) {
             const auto& shapeGeometries = blendShapeChannel->GetShapeGeometries();
             const auto& shapeGeometries = blendShapeChannel->GetShapeGeometries();
             for (const ShapeGeometry *shapeGeometry : shapeGeometries) {
             for (const ShapeGeometry *shapeGeometry : shapeGeometries) {
-                aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh);
-                const auto& curVertices = shapeGeometry->GetVertices();
                 const auto& curNormals = shapeGeometry->GetNormals();
                 const auto& curNormals = shapeGeometry->GetNormals();
+                aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh, true, !curNormals.empty());
+                const auto& curVertices = shapeGeometry->GetVertices();
                 const auto& curIndices = shapeGeometry->GetIndices();
                 const auto& curIndices = shapeGeometry->GetIndices();
                 animMesh->mName.Set(FixAnimMeshName(shapeGeometry->Name()));
                 animMesh->mName.Set(FixAnimMeshName(shapeGeometry->Name()));
                 for (size_t j = 0; j < curIndices.size(); j++) {
                 for (size_t j = 0; j < curIndices.size(); j++) {
                     unsigned int curIndex = curIndices.at(j);
                     unsigned int curIndex = curIndices.at(j);
                     aiVector3D vertex = curVertices.at(j);
                     aiVector3D vertex = curVertices.at(j);
-                    aiVector3D normal = curNormals.at(j);
+                    aiVector3D normal = curNormals.empty() ? aiVector3D() : curNormals.at(j);
                     unsigned int count = 0;
                     unsigned int count = 0;
                     const unsigned int *outIndices = mesh.ToOutputVertexIndex(curIndex, count);
                     const unsigned int *outIndices = mesh.ToOutputVertexIndex(curIndex, count);
                     for (unsigned int k = 0; k < count; k++) {
                     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();
         //bone->mOffsetMatrix = cluster->Transform();
         // store local transform link for post processing
         // store local transform link for post processing
-        
+
         bone->mOffsetMatrix = cluster->TransformLink();
         bone->mOffsetMatrix = cluster->TransformLink();
         bone->mOffsetMatrix.Inverse();
         bone->mOffsetMatrix.Inverse();
 
 
         const aiMatrix4x4 matrix = (aiMatrix4x4)absolute_transform;
         const aiMatrix4x4 matrix = (aiMatrix4x4)absolute_transform;
 
 
         bone->mOffsetMatrix = bone->mOffsetMatrix * matrix; // * mesh_offset
         bone->mOffsetMatrix = bone->mOffsetMatrix * matrix; // * mesh_offset
-        
+
         //
         //
         // Now calculate the aiVertexWeights
         // 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|emissionColor", aiTextureType_EMISSION_COLOR, mesh);
     TrySetTextureProperties(out_mat, _textures, "Maya|metalness", aiTextureType_METALNESS, 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|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
     // Maya stingray
     TrySetTextureProperties(out_mat, _textures, "Maya|TEX_color_map", aiTextureType_BASE_COLOR, mesh);
     TrySetTextureProperties(out_mat, _textures, "Maya|TEX_color_map", aiTextureType_BASE_COLOR, mesh);
@@ -3406,7 +3416,7 @@ FBXConverter::KeyFrameListList FBXConverter::GetRotationKeyframeList(const std::
     KeyFrameListList inputs;
     KeyFrameListList inputs;
     inputs.reserve(nodes.size() * 3);
     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_start = start - 10000;
     const int64_t adj_stop = stop + 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() == curve->GetValues().size());
             ai_assert(curve->GetKeys().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<KeyTimeList> Keys(new KeyTimeList());
             std::shared_ptr<KeyValueList> Values(new KeyValueList());
             std::shared_ptr<KeyValueList> Values(new KeyValueList());
             const size_t count = curve->GetKeys().size();
             const size_t count = curve->GetKeys().size();
@@ -3452,8 +3462,7 @@ FBXConverter::KeyFrameListList FBXConverter::GetRotationKeyframeList(const std::
                         if (tnew >= adj_start && tnew <= adj_stop) {
                         if (tnew >= adj_start && tnew <= adj_stop) {
                             Keys->push_back(tnew);
                             Keys->push_back(tnew);
                             Values->push_back(vnew);
                             Values->push_back(vnew);
-                        }
-                        else {
+                        } else {
                             // Something broke
                             // Something broke
                             break;
                             break;
                         }
                         }

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

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2024, assimp team
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -70,7 +69,7 @@ struct morphKeyData {
     std::vector<unsigned int> values;
     std::vector<unsigned int> values;
     std::vector<float> weights;
     std::vector<float> weights;
 };
 };
-typedef std::map<int64_t, morphKeyData*> morphAnimData;
+using morphAnimData = std::map<int64_t, morphKeyData*> ;
 
 
 namespace Assimp {
 namespace Assimp {
 namespace FBX {
 namespace FBX {
@@ -134,7 +133,7 @@ private:
 
 
     // ------------------------------------------------------------------------------------------------
     // ------------------------------------------------------------------------------------------------
     // collect and assign child nodes
     // 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 );
     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);
     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)
 Cluster::Cluster(uint64_t id, const Element& element, const Document& doc, const std::string& name)
 : Deformer(id,element,doc,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)
 Skin::Skin(uint64_t id, const Element& element, const Document& doc, const std::string& name)
 : Deformer(id,element,doc,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)
 BlendShape::BlendShape(uint64_t id, const Element& element, const Document& doc, const std::string& name)
     : Deformer(id, element, doc, 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)
 BlendShapeChannel::BlendShapeChannel(uint64_t id, const Element& element, const Document& doc, const std::string& name)
     : Deformer(id, element, doc, 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 {
 const Object* Connection::SourceObject() const {
     LazyObject* const lazy = doc.GetObject(src);
     LazyObject* const lazy = doc.GetObject(src);
     ai_assert(lazy);
     ai_assert(lazy);
+    if (lazy == nullptr) {
+        return nullptr;
+    }
+
     return lazy->Get();
     return lazy->Get();
 }
 }
 
 
@@ -670,6 +674,10 @@ const Object* Connection::SourceObject() const {
 const Object* Connection::DestinationObject() const {
 const Object* Connection::DestinationObject() const {
     LazyObject* const lazy = doc.GetObject(dest);
     LazyObject* const lazy = doc.GetObject(dest);
     ai_assert(lazy);
     ai_assert(lazy);
+    if (lazy == nullptr) {
+        return nullptr;
+    }
+
     return lazy->Get();
     return lazy->Get();
 }
 }
 
 

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

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

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

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2024, assimp team
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -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
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 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) {
     if(element) {
         DOMWarning(message,element->KeyToken());
         DOMWarning(message,element->KeyToken());
         return;
         return;
@@ -92,41 +89,39 @@ void DOMWarning(const std::string& message, const Element* element /*= nullptr*/
     }
     }
 }
 }
 
 
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // fetch a property table and the corresponding property template
 // fetch a property table and the corresponding property template
 std::shared_ptr<const PropertyTable> GetPropertyTable(const Document& doc,
 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"];
     const Element* const Properties70 = sc["Properties70"];
     std::shared_ptr<const PropertyTable> templateProps = std::shared_ptr<const PropertyTable>(
     std::shared_ptr<const PropertyTable> templateProps = std::shared_ptr<const PropertyTable>(
             static_cast<const PropertyTable *>(nullptr));
             static_cast<const PropertyTable *>(nullptr));
 
 
-    if(templateName.length()) {
+    if (templateName.length()) {
         PropertyTemplateMap::const_iterator it = doc.Templates().find(templateName);
         PropertyTemplateMap::const_iterator it = doc.Templates().find(templateName);
         if(it != doc.Templates().end()) {
         if(it != doc.Templates().end()) {
             templateProps = (*it).second;
             templateProps = (*it).second;
         }
         }
     }
     }
 
 
-    if(!Properties70 || !Properties70->Compound()) {
+    if (!Properties70 || !Properties70->Compound()) {
         if(!no_warn) {
         if(!no_warn) {
             DOMWarning("property table (Properties70) not found",&element);
             DOMWarning("property table (Properties70) not found",&element);
         }
         }
         if(templateProps) {
         if(templateProps) {
             return templateProps;
             return templateProps;
-        }
-        else {
+        } else {
             return std::make_shared<const PropertyTable>();
             return std::make_shared<const PropertyTable>();
         }
         }
     }
     }
     return std::make_shared<const PropertyTable>(*Properties70,templateProps);
     return std::make_shared<const PropertyTable>(*Properties70,templateProps);
 }
 }
+
 } // !Util
 } // !Util
 } // !FBX
 } // !FBX
 } // !Assimp
 } // !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
 } //!FBX
 } //!Assimp
 } //!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
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 */
 */
 #ifndef ASSIMP_BUILD_NO_EXPORT
 #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
 #include <memory> // shared_ptr
 
 
 namespace Assimp {
 namespace Assimp {
+
 // AddP70<type> helpers... there's no usable pattern here,
 // AddP70<type> helpers... there's no usable pattern here,
 // so all are defined as separate functions.
 // so all are defined as separate functions.
 // Even "animatable" properties are often completely different
 // Even "animatable" properties are often completely different
 // from the standard (nonanimated) property definition,
 // from the standard (nonanimated) property definition,
 // so they are specified with an 'A' suffix.
 // 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");
     FBX::Node n("P");
     n.AddProperties(cur_name, "int", "Integer", "", value);
     n.AddProperties(cur_name, "int", "Integer", "", value);
     AddChild(n);
     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");
     FBX::Node n("P");
     n.AddProperties(cur_name, "bool", "", "", int32_t(value));
     n.AddProperties(cur_name, "bool", "", "", int32_t(value));
     AddChild(n);
     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);
     n.AddProperties(cur_name, "double", "Number", "", value);
     AddChild(n);
     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");
     FBX::Node n("P");
     n.AddProperties(cur_name, "Number", "", "A", value);
     n.AddProperties(cur_name, "Number", "", "A", value);
     AddChild(n);
     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
     if (!has_children) { return; } // nothing to do
     s << '\n';
     s << '\n';
     for (int i = 0; i < indent; ++i) { s << '\t'; }
     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
 // ascii property node from vector of doubles
 void FBX::Node::WritePropertyNodeAscii(
 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];
     char buffer[32];
     FBX::Node node(name);
     FBX::Node node(name);
     node.Begin(s, false, indent);
     node.Begin(s, false, indent);
@@ -556,6 +547,8 @@ void FBX::Node::WritePropertyNode(
         FBX::Node::WritePropertyNodeAscii(name, v, s, indent);
         FBX::Node::WritePropertyNodeAscii(name, v, s, indent);
     }
     }
 }
 }
-}
+
+} // namespace Assimp
+
 #endif // ASSIMP_BUILD_NO_FBX_EXPORTER
 #endif // ASSIMP_BUILD_NO_FBX_EXPORTER
 #endif // ASSIMP_BUILD_NO_EXPORT
 #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
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 */
 */
 
 
@@ -70,7 +69,6 @@ public:
     // some nodes always pretend they have children...
     // some nodes always pretend they have children...
     bool force_has_children = false;
     bool force_has_children = false;
 
 
-public: // constructors
     /// The default class constructor.
     /// The default class constructor.
     Node() = default;
     Node() = default;
 
 
@@ -89,7 +87,6 @@ public: // constructors
         AddProperties(std::forward<More>(more)...);
         AddProperties(std::forward<More>(more)...);
     }
     }
 
 
-public: // functions to add properties or children
     // add a single property to the node
     // add a single property to the node
     template <typename T>
     template <typename T>
     void AddProperty(T&& value) {
     void AddProperty(T&& value) {
@@ -118,8 +115,6 @@ public: // functions to add properties or children
         children.push_back(std::move(c));
         children.push_back(std::move(c));
     }
     }
 
 
-public: // support specifically for dealing with Properties70 nodes
-
     // it really is simpler to make these all separate functions.
     // it really is simpler to make these all separate functions.
     // the versions with 'A' suffixes are for animatable properties.
     // the versions with 'A' suffixes are for animatable properties.
     // those often follow a completely different format internally in FBX.
     // those often follow a completely different format internally in FBX.
@@ -150,8 +145,6 @@ public: // support specifically for dealing with Properties70 nodes
         AddChild(n);
         AddChild(n);
     }
     }
 
 
-public: // member functions for writing data to a file or stream
-
     // write the full node to the given file or stream
     // write the full node to the given file or stream
     void Dump(
     void Dump(
             const std::shared_ptr<Assimp::IOStream> &outfile,
             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
         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,
     // convenience function to create a node with a single property,
     // and write it to the stream.
     // and write it to the stream.
@@ -235,7 +203,26 @@ public: // static member functions
         bool binary, int indent
         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(
     static void WritePropertyNodeAscii(
         const std::string& name,
         const std::string& name,
         const std::vector<double>& v,
         const std::vector<double>& v,
@@ -259,9 +246,13 @@ private: // static helper functions
         Assimp::StreamWriterLE& s
         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
 #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
         "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;
     indent = 0;
 
 
     // finish node
     // 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");
     FBX::Node gs("GlobalSettings");
     gs.AddChild("Version", int32_t(1000));
     gs.AddChild("Version", int32_t(1000));
 
 
@@ -493,8 +483,7 @@ void FBXExporter::WriteGlobalSettings ()
     gs.Dump(outfile, binary, 0);
     gs.Dump(outfile, binary, 0);
 }
 }
 
 
-void FBXExporter::WriteDocuments ()
-{
+void FBXExporter::WriteDocuments() {
     if (!binary) {
     if (!binary) {
         WriteAsciiSectionHeader("Documents Description");
         WriteAsciiSectionHeader("Documents Description");
     }
     }
@@ -523,8 +512,7 @@ void FBXExporter::WriteDocuments ()
     docs.Dump(outfile, binary, 0);
     docs.Dump(outfile, binary, 0);
 }
 }
 
 
-void FBXExporter::WriteReferences ()
-{
+void FBXExporter::WriteReferences() {
     if (!binary) {
     if (!binary) {
         WriteAsciiSectionHeader("Document References");
         WriteAsciiSectionHeader("Document References");
     }
     }
@@ -540,7 +528,6 @@ void FBXExporter::WriteReferences ()
 // some internal helper functions used for writing the definitions
 // some internal helper functions used for writing the definitions
 // (before any actual data is written)
 // (before any actual data is written)
 // ---------------------------------------------------------------
 // ---------------------------------------------------------------
-
 size_t count_nodes(const aiNode* n, const aiNode* root) {
 size_t count_nodes(const aiNode* n, const aiNode* root) {
     size_t count;
     size_t count;
     if (n == root) {
     if (n == root) {
@@ -556,8 +543,7 @@ size_t count_nodes(const aiNode* n, const aiNode* root) {
     return count;
     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
     // just search for any material with a shininess exponent
     for (size_t i = 0; i < scene->mNumMaterials; ++i) {
     for (size_t i = 0; i < scene->mNumMaterials; ++i) {
         aiMaterial* mat = scene->mMaterials[i];
         aiMaterial* mat = scene->mMaterials[i];
@@ -570,16 +556,12 @@ bool has_phong_mat(const aiScene* scene)
     return false;
     return false;
 }
 }
 
 
-size_t count_images(const aiScene* scene) {
+static size_t count_images(const aiScene* scene) {
     std::unordered_set<std::string> images;
     std::unordered_set<std::string> images;
     aiString texpath;
     aiString texpath;
     for (size_t i = 0; i < scene->mNumMaterials; ++i) {
     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 aiTextureType textype = static_cast<aiTextureType>(tt);
             const size_t texcount = mat->GetTextureCount(textype);
             const size_t texcount = mat->GetTextureCount(textype);
             for (unsigned int j = 0; j < texcount; ++j) {
             for (unsigned int j = 0; j < texcount; ++j) {
@@ -588,10 +570,11 @@ size_t count_images(const aiScene* scene) {
             }
             }
         }
         }
     }
     }
+
     return images.size();
     return images.size();
 }
 }
 
 
-size_t count_textures(const aiScene* scene) {
+static size_t count_textures(const aiScene* scene) {
     size_t count = 0;
     size_t count = 0;
     for (size_t i = 0; i < scene->mNumMaterials; ++i) {
     for (size_t i = 0; i < scene->mNumMaterials; ++i) {
         aiMaterial* mat = scene->mMaterials[i];
         aiMaterial* mat = scene->mMaterials[i];
@@ -609,7 +592,7 @@ size_t count_textures(const aiScene* scene) {
     return count;
     return count;
 }
 }
 
 
-size_t count_deformers(const aiScene* scene) {
+static size_t count_deformers(const aiScene* scene) {
     size_t count = 0;
     size_t count = 0;
     for (size_t i = 0; i < scene->mNumMeshes; ++i) {
     for (size_t i = 0; i < scene->mNumMeshes; ++i) {
         const size_t n = scene->mMeshes[i]->mNumBones;
         const size_t n = scene->mMeshes[i]->mNumBones;
@@ -621,8 +604,7 @@ size_t count_deformers(const aiScene* scene) {
     return count;
     return count;
 }
 }
 
 
-void FBXExporter::WriteDefinitions ()
-{
+void FBXExporter::WriteDefinitions () {
     // basically this is just bookkeeping:
     // basically this is just bookkeeping:
     // determining how many of each type of object there are
     // determining how many of each type of object there are
     // and specifying the base properties to use when otherwise unspecified.
     // 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
 // some internal helper functions used for writing the objects section
 // (which holds the actual data)
 // (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) {
     for (size_t i = 0; i < node->mNumMeshes; ++i) {
         if (node->mMeshes[i] == meshIndex) {
         if (node->mMeshes[i] == meshIndex) {
             return node;
             return node;
@@ -1048,8 +1028,7 @@ aiNode* get_node_for_mesh(unsigned int meshIndex, aiNode* node)
     return nullptr;
     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;
     std::vector<const aiNode*> node_chain;
     while (node != scene->mRootNode && node != nullptr) {
     while (node != scene->mRootNode && node != nullptr) {
         node_chain.push_back(node);
         node_chain.push_back(node);
@@ -1073,8 +1052,7 @@ inline int64_t to_ktime(double time) {
     return (static_cast<int64_t>(time * FBX::SECOND));
     return (static_cast<int64_t>(time * FBX::SECOND));
 }
 }
 
 
-void FBXExporter::WriteObjects ()
-{
+void FBXExporter::WriteObjects () {
     if (!binary) {
     if (!binary) {
         WriteAsciiSectionHeader("Object properties");
         WriteAsciiSectionHeader("Object properties");
     }
     }
@@ -1087,21 +1065,28 @@ void FBXExporter::WriteObjects ()
     object_node.BeginChildren(outstream, binary, indent);
     object_node.BeginChildren(outstream, binary, indent);
 
 
     bool bJoinIdenticalVertices = mProperties->GetPropertyBool("bJoinIdenticalVertices", true);
     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);
     const auto bTransparencyFactorReferencedToOpacity = mProperties->GetPropertyBool(AI_CONFIG_EXPORT_FBX_TRANSPARENCY_FACTOR_REFER_TO_OPACITY, false);
 
 
     // geometry (aiMesh)
     // geometry (aiMesh)
     mesh_uids.clear();
     mesh_uids.clear();
     indent = 1;
     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
         // start the node record
         FBX::Node n("Geometry");
         FBX::Node n("Geometry");
         int64_t uid = generate_uid();
         int64_t uid = generate_uid();
-        mesh_uids.push_back(uid);
+        mesh_uids[node] = uid;
         n.AddProperty(uid);
         n.AddProperty(uid);
         n.AddProperty(FBX::SEPARATOR + "Geometry");
         n.AddProperty(FBX::SEPARATOR + "Geometry");
         n.AddProperty("Mesh");
         n.AddProperty("Mesh");
@@ -1109,160 +1094,112 @@ void FBXExporter::WriteObjects ()
         n.DumpProperties(outstream, binary, indent);
         n.DumpProperties(outstream, binary, indent);
         n.EndProperties(outstream, binary, indent);
         n.EndProperties(outstream, binary, indent);
         n.BeginChildren(outstream, binary, indent);
         n.BeginChildren(outstream, binary, indent);
-        indent = 2;
 
 
         // output vertex data - each vertex should be unique (probably)
         // output vertex data - each vertex should be unique (probably)
         std::vector<double> flattened_vertices;
         std::vector<double> flattened_vertices;
         // index of original vertex in vertex data vector
         // index of original vertex in vertex data vector
         std::vector<int32_t> vertex_indices;
         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.
         // output polygon data as a flattened array of vertex indices.
         // the last vertex index of each polygon is negated and - 1
         // 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];
             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(
             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());
             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());
             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) {
             if (m->mNumUVComponents[uvi] > 2) {
                 // FBX only supports 2-channel UV maps...
                 // FBX only supports 2-channel UV maps...
                 // or at least i'm not sure how to indicate a different number
                 // 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.";
                 err << " but may be incorrectly interpreted on load.";
                 ASSIMP_LOG_WARN(err.str());
                 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;
             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));
         FBX::Node mat("LayerElementMaterial", int32_t(0));
         mat.AddChild("Version", int32_t(101));
         mat.AddChild("Version", int32_t(101));
         mat.AddChild("Name", "");
         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);
         mat.Dump(outstream, binary, indent);
 
 
         // finally we have the layer specifications,
         // finally we have the layer specifications,
@@ -1354,11 +1331,12 @@ void FBXExporter::WriteObjects ()
         le.AddChild("Type", "LayerElementNormal");
         le.AddChild("Type", "LayerElementNormal");
         le.AddChild("TypedIndex", int32_t(0));
         le.AddChild("TypedIndex", int32_t(0));
         layer.AddChild(le);
         layer.AddChild(le);
-        // TODO only 1 color channel currently
+
         le = FBX::Node("LayerElement");
         le = FBX::Node("LayerElement");
         le.AddChild("Type", "LayerElementColor");
         le.AddChild("Type", "LayerElementColor");
         le.AddChild("TypedIndex", int32_t(0));
         le.AddChild("TypedIndex", int32_t(0));
         layer.AddChild(le);
         layer.AddChild(le);
+
         le = FBX::Node("LayerElement");
         le = FBX::Node("LayerElement");
         le.AddChild("Type", "LayerElementMaterial");
         le.AddChild("Type", "LayerElementMaterial");
         le.AddChild("TypedIndex", int32_t(0));
         le.AddChild("TypedIndex", int32_t(0));
@@ -1369,8 +1347,7 @@ void FBXExporter::WriteObjects ()
         layer.AddChild(le);
         layer.AddChild(le);
         layer.Dump(outstream, binary, indent);
         layer.Dump(outstream, binary, indent);
 
 
-        for(unsigned int lr = 1; lr < m->GetNumUVChannels(); ++ lr)
-        {
+        for(unsigned int lr = 1; lr < uv_data.size(); ++ lr) {
             FBX::Node layerExtra("Layer", int32_t(lr));
             FBX::Node layerExtra("Layer", int32_t(lr));
             layerExtra.AddChild("Version", int32_t(100));
             layerExtra.AddChild("Version", int32_t(100));
             FBX::Node leExtra("LayerElement");
             FBX::Node leExtra("LayerElement");
@@ -1382,7 +1359,14 @@ void FBXExporter::WriteObjects ()
         // finish the node record
         // finish the node record
         indent = 1;
         indent = 1;
         n.End(outstream, binary, indent, true);
         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
     // aiMaterial
@@ -1747,7 +1731,8 @@ void FBXExporter::WriteObjects ()
       dnode.AddChild("Version", int32_t(101));
       dnode.AddChild("Version", int32_t(101));
       dnode.Dump(outstream, binary, indent);
       dnode.Dump(outstream, binary, indent);
       // connect it
       // 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];
       std::vector<int32_t> vertex_indices = vVertexIndice[mi];
 
 
       for (unsigned int am = 0; am < m->mNumAnimMeshes; ++am) {
       for (unsigned int am = 0; am < m->mNumAnimMeshes; ++am) {
@@ -1757,7 +1742,7 @@ void FBXExporter::WriteObjects ()
         // start the node record
         // start the node record
         FBX::Node bsnode("Geometry");
         FBX::Node bsnode("Geometry");
         int64_t blendshape_uid = generate_uid();
         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_uid);
         bsnode.AddProperty(blendshape_name + FBX::SEPARATOR + "Geometry");
         bsnode.AddProperty(blendshape_name + FBX::SEPARATOR + "Geometry");
         bsnode.AddProperty("Shape");
         bsnode.AddProperty("Shape");
@@ -1769,23 +1754,25 @@ void FBXExporter::WriteObjects ()
         indent++;
         indent++;
         if (pAnimMesh->HasPositions()) {
         if (pAnimMesh->HasPositions()) {
           std::vector<int32_t>shape_indices;
           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) {
           for (unsigned int vt = 0; vt < vertex_indices.size(); ++vt) {
               aiVector3D pDiff = (pAnimMesh->mVertices[vertex_indices[vt]] - m->mVertices[vertex_indices[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
                 // otherwise check if this is the root of the skeleton
                 bool end = false;
                 bool end = false;
                 // is the mesh part of this node?
                 // is the mesh part of this node?
-                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?
                 // 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];
                     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
                 // 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) {
     for (size_t i = 0; i < mScene->mNumMeshes; ++i) {
         auto &s = skeleton_by_mesh[i];
         auto &s = skeleton_by_mesh[i];
         for (const aiNode* n : s) {
         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();
                 node_uids[n] = generate_uid();
             }
             }
         }
         }
@@ -1988,6 +1967,8 @@ void FBXExporter::WriteObjects ()
         if (!m->HasBones()) {
         if (!m->HasBones()) {
             continue;
             continue;
         }
         }
+
+        const aiNode *mesh_node = get_node_for_mesh((uint32_t)mi, mScene->mRootNode);
         // make a deformer for this mesh
         // make a deformer for this mesh
         int64_t deformer_uid = generate_uid();
         int64_t deformer_uid = generate_uid();
         FBX::Node dnode("Deformer");
         FBX::Node dnode("Deformer");
@@ -1999,10 +1980,7 @@ void FBXExporter::WriteObjects ()
         dnode.Dump(outstream, binary, indent);
         dnode.Dump(outstream, binary, indent);
 
 
         // connect it
         // 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.
         // TODO, FIXME: this won't work if anything is not in the bind pose.
         // for now if such a situation is detected, we throw an exception.
         // for now if such a situation is detected, we throw an exception.
@@ -2016,7 +1994,6 @@ void FBXExporter::WriteObjects ()
         // as it can be instanced to many nodes.
         // as it can be instanced to many nodes.
         // All we can do is assume no instancing,
         // All we can do is assume no instancing,
         // and take the first node we find that contains the mesh.
         // 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);
         aiMatrix4x4 mesh_xform = get_world_transform(mesh_node, mScene);
 
 
         // now make a subdeformer for each bone in the skeleton
         // 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("Version", int32_t(100));
             sdnode.AddChild("UserData", "", "");
             sdnode.AddChild("UserData", "", "");
 
 
-            std::set<int32_t> setWeightedVertex;
             // add indices and weights, if any
             // add indices and weights, if any
             if (b) {
             if (b) {
+                std::set<int32_t> setWeightedVertex;
                 std::vector<int32_t> subdef_indices;
                 std::vector<int32_t> subdef_indices;
                 std::vector<double> subdef_weights;
                 std::vector<double> subdef_weights;
                 int32_t last_index = -1;
                 int32_t last_index = -1;
                 for (size_t wi = 0; wi < b->mNumWeights; ++wi) {
                 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());
                     bool bIsWeightedAlready = (setWeightedVertex.find(vi) != setWeightedVertex.end());
                     if (vi == last_index || bIsWeightedAlready) {
                     if (vi == last_index || bIsWeightedAlready) {
                         // only for vertices we exported to fbx
                         // 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'}}
     {"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
 // write a single model node to the stream
 void FBXExporter::WriteModelNode(
 void FBXExporter::WriteModelNode(
     StreamWriterLE& outstream,
     StreamWriterLE& outstream,
@@ -2553,6 +2582,7 @@ void FBXExporter::WriteModelNode(
             }
             }
         }
         }
     }
     }
+    add_meta(p, node);
     m.AddChild(p);
     m.AddChild(p);
 
 
     // not sure what these are for,
     // not sure what these are for,
@@ -2647,9 +2677,8 @@ void FBXExporter::WriteModelNodes(
         // handled later
         // handled later
     } else if (node->mNumMeshes == 1) {
     } else if (node->mNumMeshes == 1) {
         // connect to child mesh, which should have been written previously
         // 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
         // also connect to the material for the child mesh
         connections.emplace_back(
         connections.emplace_back(
             "C", "OO",
             "C", "OO",
@@ -2674,6 +2703,16 @@ void FBXExporter::WriteModelNodes(
         na.Dump(outstream, binary, 1);
         na.Dump(outstream, binary, 1);
         // and connect them
         // and connect them
         connections.emplace_back("C", "OO", node_attribute_uid, node_uid);
         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 {
     } else {
         const auto& lightIt = lights_uids.find(node->mName.C_Str());
         const auto& lightIt = lights_uids.find(node->mName.C_Str());
         if(lightIt != lights_uids.end()) {
         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
     // 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 "FBXCommon.h" // FBX::TransformInheritance
 
 
 #include <assimp/types.h>
 #include <assimp/types.h>
-//#include <assimp/material.h>
 #include <assimp/StreamWriter.h> // StreamWriterLE
 #include <assimp/StreamWriter.h> // StreamWriterLE
 #include <assimp/Exceptional.h> // DeadlyExportError
 #include <assimp/Exceptional.h> // DeadlyExportError
 
 
@@ -91,7 +90,8 @@ namespace Assimp {
 
 
         std::vector<FBX::Node> connections; // connection storage
         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::vector<int64_t> material_uids;
         std::map<const aiNode*,int64_t> node_uids;
         std::map<const aiNode*,int64_t> node_uids;
         std::map<std::string,int64_t> lights_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
     /** Set to true to perform a conversion from cm to meter after the import
     */
     */
     bool convertToMeters;
     bool convertToMeters;
+
+    // Set to true to ignore the axis configuration in the file
+    bool ignoreUpDirection = false;
 };
 };
 
 
 } // namespace FBX
 } // 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.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false);
     mSettings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true);
     mSettings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true);
     mSettings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false);
     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);
     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) :
 Texture::Texture(uint64_t id, const Element& element, const Document& doc, const std::string& name) :
         Object(id,element,name),
         Object(id,element,name),

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

@@ -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");
         DOMError("failed to read Geometry object (class: Shape), no data scope found");
     }
     }
     const Element& Indexes = GetRequiredElement(*sc, "Indexes", &element);
     const Element& Indexes = GetRequiredElement(*sc, "Indexes", &element);
-    const Element& Normals = GetRequiredElement(*sc, "Normals", &element);
     const Element& Vertices = GetRequiredElement(*sc, "Vertices", &element);
     const Element& Vertices = GetRequiredElement(*sc, "Vertices", &element);
     ParseVectorDataArray(m_indices, Indexes);
     ParseVectorDataArray(m_indices, Indexes);
     ParseVectorDataArray(m_vertices, Vertices);
     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 can be seen in this sample, elements can contain nested #Scope
  *  as their trailing member.  
  *  as their trailing member.  
 **/
 **/
-class Element
-{
+class Element {
 public:
 public:
     Element(const Token& key_token, Parser& parser);
     Element(const Token& key_token, Parser& parser);
     ~Element();
     ~Element();

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

@@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
 
 
 Copyright (c) 2006-2024, assimp team
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -57,15 +56,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp {
 namespace Assimp {
 namespace FBX {
 namespace FBX {
 
 
-    using namespace Util;
+using namespace Util;
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-    Property::Property() = default;
-
-    // ------------------------------------------------------------------------------------------------
-    Property::~Property() = default;
 
 
-    namespace {
+namespace {
 
 
     void checkTokenCount(const TokenList &tok, unsigned int expectedCount) {
     void checkTokenCount(const TokenList &tok, unsigned int expectedCount) {
         ai_assert(expectedCount >= 2);
         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.
 // 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");
     ai_assert(element.KeyToken().StringContents() == "P");
+
     const TokenList& tok = element.Tokens();
     const TokenList& tok = element.Tokens();
     if(tok.size() < 4) {
     if(tok.size() < 4) {
         return std::string();
         return std::string();
@@ -160,13 +155,6 @@ std::string PeekPropertyName(const Element& element)
 } //! anon
 } //! anon
 
 
 
 
-// ------------------------------------------------------------------------------------------------
-PropertyTable::PropertyTable()
-: templateProps()
-, element()
-{
-}
-
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 PropertyTable::PropertyTable(const Element &element, std::shared_ptr<const PropertyTable> templateProps) :
 PropertyTable::PropertyTable(const Element &element, std::shared_ptr<const PropertyTable> templateProps) :
         templateProps(std::move(templateProps)), element(&element) {
         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
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,
@@ -56,34 +55,33 @@ namespace FBX {
 // Forward declarations
 // Forward declarations
 class Element;
 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 {
 class Property {
-protected:
-    Property();
 
 
 public:
 public:
-    virtual ~Property();
+    virtual ~Property() = default;
 
 
-public:
     template <typename T>
     template <typename T>
     const T* As() const {
     const T* As() const {
         return dynamic_cast<const T*>(this);
         return dynamic_cast<const T*>(this);
     }
     }
+
+protected:
+    Property() = default;
 };
 };
 
 
 template<typename T>
 template<typename T>
 class TypedProperty : public Property {
 class TypedProperty : public Property {
 public:
 public:
-    explicit TypedProperty(const T& value)
-    : value(value) {
-        // empty
-    }
+    explicit TypedProperty(const T& value) : value(value) {}
 
 
     const T& Value() const {
     const T& Value() const {
         return value;
         return value;
@@ -93,10 +91,9 @@ private:
     T value;
     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)
  *  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 {
 class PropertyTable {
 public:
 public:
     // in-memory property table with no source element
     // in-memory property table with no source element
-    PropertyTable();
+    PropertyTable() : element() {}
     PropertyTable(const Element& element, std::shared_ptr<const PropertyTable> templateProps);
     PropertyTable(const Element& element, std::shared_ptr<const PropertyTable> templateProps);
     ~PropertyTable();
     ~PropertyTable();
 
 
@@ -130,8 +127,7 @@ private:
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 template <typename T>
 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);
     const Property* const prop = in.Get(name);
     if( nullptr == prop) {
     if( nullptr == prop) {
         return defaultValue;
         return defaultValue;
@@ -148,8 +144,7 @@ T PropertyGet(const PropertyTable& in, const std::string& name, const T& default
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 template <typename T>
 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);
     const Property* prop = in.Get(name);
     if( nullptr == prop) {
     if( nullptr == prop) {
         if ( ! useTemplate ) {
         if ( ! useTemplate ) {

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

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

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

@@ -78,7 +78,15 @@ static constexpr aiImporterDesc desc = {
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Recursive parsing of LWS files
 // 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)) {
     for (; SkipSpacesAndLineEnd(&buffer, end); SkipLine(&buffer, end)) {
 
 
         // begin of a new element with children
         // 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
         // parse more elements recursively
         if (sub) {
         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
             // and add the file to the import list
             SkipSpaces(&c, end);
             SkipSpaces(&c, end);
             std::string path = FindLWOFile(c);
             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.path = path;
             d.id = batch.AddLoadRequest(path, 0, &props);
             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++;
                 d.number = cur_object++;
             }
             }
             std::string path = FindLWOFile(c);
             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.id = batch.AddLoadRequest(path, 0, nullptr);
 
 
             d.path = path;
             d.path = path;

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

@@ -76,7 +76,7 @@ public:
     std::list<Element> children;
     std::list<Element> children;
 
 
     //! Recursive parsing function
     //! 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)
 #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);
     std::vector<unsigned char> mBuffer2(fileSize);
     file->Read(&mBuffer2[0], 1, fileSize);
     file->Read(&mBuffer2[0], 1, fileSize);
     mBuffer = &mBuffer2[0];
     mBuffer = &mBuffer2[0];
+    const unsigned char* bufferEnd = mBuffer + fileSize;
 
 
     pcHeader = (BE_NCONST MD3::Header *)mBuffer;
     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
     // Navigate to the list of surfaces
     BE_NCONST MD3::Surface *pcSurfaces = (BE_NCONST MD3::Surface *)(mBuffer + pcHeader->OFS_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
     // Navigate to the list of tags
     BE_NCONST MD3::Tag *pcTags = (BE_NCONST MD3::Tag *)(mBuffer + pcHeader->OFS_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
     // Allocate output storage
     pScene->mNumMeshes = pcHeader->NUM_SURFACES;
     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) {
         for (unsigned int i = 0; i < pcHeader->NUM_TAGS; ++i, ++pcTags) {
             aiNode *nd = pScene->mRootNode->mChildren[i] = new aiNode();
             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->mName.Set((const char *)pcTags->NAME);
             nd->mParent = pScene->mRootNode;
             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)");
         ReportError("MD5 version tag is unknown (10 is expected)");
     }
     }
     SkipLine();
     SkipLine();
-    if (buffer == bufferEnd) {
-        return;
-    }
 
 
     // print the command line options to the console
     // print the command line options to the console
-    // FIX: can break the log length limit, so we need to be careful
     char *sz = buffer;
     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))));
     ASSIMP_LOG_INFO(std::string(sz, std::min((uintptr_t)MAX_LOG_MESSAGE_LENGTH, (uintptr_t)(buffer - sz))));
     SkipSpacesAndLineEnd();
     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
 // Validate a quake file header
 void MDLImporter::ValidateHeader_Quake1(const MDL::Header *pcHeader) {
 void MDLImporter::ValidateHeader_Quake1(const MDL::Header *pcHeader) {
     // some values may not be nullptr
     // 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");
         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");
         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");
         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
     // 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
                         // We need to add a new mesh to the list. We assign
                         // an unique name to it to make sure the scene will
                         // an unique name to it to make sure the scene will
                         // pass the validation step for the moment.
                         // 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()) {
                         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++);
                             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
 Copyright (c) 2006-2024, assimp team
 
 
-
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use of this software in source and binary forms,
 Redistribution and use of this software in source and binary forms,

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

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

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

@@ -2,7 +2,7 @@
 Open Asset Import Library (assimp)
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 
 
-Copyright (c) 2006-2020, assimp team
+Copyright (c) 2006-2024, assimp team
 
 
 
 
 All rights reserved.
 All rights reserved.
@@ -62,7 +62,7 @@ namespace Assimp {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 /** Helper class to export a given scene to an OBJ file. */
 /** Helper class to export a given scene to an OBJ file. */
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
-class ObjExporter {
+class ObjExporter final {
 public:
 public:
     /// Constructor for a specific scene to export
     /// Constructor for a specific scene to export
     ObjExporter(const char* filename, const aiScene* pScene, bool noMtl=false, const ExportProperties* props = nullptr);
     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"
     "obj"
 };
 };
 
 
-static const unsigned int ObjMinSize = 16;
+static constexpr unsigned int ObjMinSize = 16u;
 
 
 namespace Assimp {
 namespace Assimp {
 
 
@@ -163,7 +163,7 @@ void ObjFileImporter::InternReadFile(const std::string &file, aiScene *pScene, I
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 //  Create the data from parsed obj-file
 //  Create the data from parsed obj-file
 void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene *pScene) {
 void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene *pScene) {
-    if (nullptr == pModel) {
+    if (pModel == nullptr) {
         return;
         return;
     }
     }
 
 
@@ -178,7 +178,6 @@ void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene
     }
     }
 
 
     if (!pModel->mObjects.empty()) {
     if (!pModel->mObjects.empty()) {
-
         unsigned int meshCount = 0;
         unsigned int meshCount = 0;
         unsigned int childCount = 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 *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile::Object *pObject,
         aiNode *pParent, aiScene *pScene,
         aiNode *pParent, aiScene *pScene,
         std::vector<std::unique_ptr<aiMesh>> &MeshArray) {
         std::vector<std::unique_ptr<aiMesh>> &MeshArray) {
-    ai_assert(nullptr != pModel);
-    if (nullptr == pObject) {
+    if (nullptr == pObject || pModel == nullptr) {
         return nullptr;
         return nullptr;
     }
     }
 
 
@@ -311,16 +309,13 @@ aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 //  Create topology data
 //  Create topology data
 std::unique_ptr<aiMesh> ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, unsigned int meshIndex) {
 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;
         return nullptr;
     }
     }
 
 
     // Create faces
     // Create faces
     ObjFile::Mesh *pObjMesh = pModel->mMeshes[meshIndex];
     ObjFile::Mesh *pObjMesh = pModel->mMeshes[meshIndex];
-    if (!pObjMesh) {
+    if (pObjMesh == nullptr) {
         return 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++) {
     for (size_t index = 0; index < pObjMesh->m_Faces.size(); index++) {
         const ObjFile::Face *inp = pObjMesh->m_Faces[index];
         const ObjFile::Face *inp = pObjMesh->m_Faces[index];
-
+        if (inp == nullptr) {
+            continue;
+        }
+        
         if (inp->mPrimitiveType == aiPrimitiveType_LINE) {
         if (inp->mPrimitiveType == aiPrimitiveType_LINE) {
             pMesh->mNumFaces += static_cast<unsigned int>(inp->m_vertices.size() - 1);
             pMesh->mNumFaces += static_cast<unsigned int>(inp->m_vertices.size() - 1);
             pMesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
             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) {
     if (pMesh->mNumFaces > 0) {
         pMesh->mFaces = new aiFace[pMesh->mNumFaces];
         pMesh->mFaces = new aiFace[pMesh->mNumFaces];
         if (pObjMesh->m_uiMaterialIndex != ObjFile::Mesh::NoMaterial) {
         if (pObjMesh->m_uiMaterialIndex != ObjFile::Mesh::NoMaterial) {
             pMesh->mMaterialIndex = pObjMesh->m_uiMaterialIndex;
             pMesh->mMaterialIndex = pObjMesh->m_uiMaterialIndex;
         }
         }
 
 
-        unsigned int outIndex(0);
+        unsigned int outIndex = 0u;
 
 
         // Copy all data from all stored meshes
         // Copy all data from all stored meshes
         for (auto &face : pObjMesh->m_Faces) {
         for (auto &face : pObjMesh->m_Faces) {
@@ -403,11 +401,14 @@ void ObjFileImporter::createVertexArray(const ObjFile::Model *pModel,
         aiMesh *pMesh,
         aiMesh *pMesh,
         unsigned int numIndices) {
         unsigned int numIndices) {
     // Checking preconditions
     // Checking preconditions
-    ai_assert(nullptr != pCurrentObject);
+    if (pCurrentObject == nullptr || pModel == nullptr || pMesh == nullptr) {
+        return;
+    }     
 
 
     // Break, if no faces are stored in object
     // Break, if no faces are stored in object
-    if (pCurrentObject->m_Meshes.empty())
+    if (pCurrentObject->m_Meshes.empty()) {
         return;
         return;
+    }
 
 
     // Get current mesh
     // Get current mesh
     ObjFile::Mesh *pObjMesh = pModel->mMeshes[uiMeshIndex];
     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]);
         it = pModel->mMaterialMap.find(pModel->mMaterialLib[matIndex]);
 
 
         // No material found, use the default material
         // No material found, use the default material
-        if (pModel->mMaterialMap.end() == it)
+        if (pModel->mMaterialMap.end() == it) {
             continue;
             continue;
+        }
 
 
         aiMaterial *mat = new aiMaterial;
         aiMaterial *mat = new aiMaterial;
-        ObjFile::Material *pCurrentMaterial = (*it).second;
+        ObjFile::Material *pCurrentMaterial = it->second;
         mat->AddProperty(&pCurrentMaterial->MaterialName, AI_MATKEY_NAME);
         mat->AddProperty(&pCurrentMaterial->MaterialName, AI_MATKEY_NAME);
 
 
         // convert illumination model
         // convert illumination model
@@ -777,8 +779,11 @@ void ObjFileImporter::createMaterials(const ObjFile::Model *pModel, aiScene *pSc
 //  Appends this node to the parent node
 //  Appends this node to the parent node
 void ObjFileImporter::appendChildToParentNode(aiNode *pParent, aiNode *pChild) {
 void ObjFileImporter::appendChildToParentNode(aiNode *pParent, aiNode *pChild) {
     // Checking preconditions
     // 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
     // Assign parent to child
     pChild->mParent = pParent;
     pChild->mParent = pParent;

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

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

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

@@ -3,7 +3,7 @@
 Open Asset Import Library (assimp)
 Open Asset Import Library (assimp)
 ---------------------------------------------------------------------------
 ---------------------------------------------------------------------------
 
 
-Copyright (c) 2006-2020, assimp team
+Copyright (c) 2006-2024, assimp team
 
 
 All rights reserved.
 All rights reserved.
 
 
@@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "ObjFileMtlImporter.h"
 #include "ObjFileMtlImporter.h"
 #include "ObjFileData.h"
 #include "ObjFileData.h"
 #include "ObjTools.h"
 #include "ObjTools.h"
+#include <assimp/DefaultIOSystem.h>
 #include <assimp/ParsingUtils.h>
 #include <assimp/ParsingUtils.h>
 #include <assimp/fast_atof.h>
 #include <assimp/fast_atof.h>
 #include <assimp/material.h>
 #include <assimp/material.h>
@@ -89,8 +90,9 @@ static constexpr char TypeOption[] = "-type";
 // -------------------------------------------------------------------
 // -------------------------------------------------------------------
 //  Constructor
 //  Constructor
 ObjFileMtlImporter::ObjFileMtlImporter(std::vector<char> &buffer,
 ObjFileMtlImporter::ObjFileMtlImporter(std::vector<char> &buffer,
-        const std::string &,
+        const std::string &strAbsPath,
         ObjFile::Model *pModel) :
         ObjFile::Model *pModel) :
+        m_strAbsPath(strAbsPath),
         m_DataIt(buffer.begin()),
         m_DataIt(buffer.begin()),
         m_DataItEnd(buffer.end()),
         m_DataItEnd(buffer.end()),
         m_pModel(pModel),
         m_pModel(pModel),
@@ -103,13 +105,23 @@ ObjFileMtlImporter::ObjFileMtlImporter(std::vector<char> &buffer,
         m_pModel->mDefaultMaterial = new ObjFile::Material;
         m_pModel->mDefaultMaterial = new ObjFile::Material;
         m_pModel->mDefaultMaterial->MaterialName.Set("default");
         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();
     load();
 }
 }
 
 
-// -------------------------------------------------------------------
-//  Destructor
-ObjFileMtlImporter::~ObjFileMtlImporter() = default;
-
 // -------------------------------------------------------------------
 // -------------------------------------------------------------------
 //  Loads the material description
 //  Loads the material description
 void ObjFileMtlImporter::load() {
 void ObjFileMtlImporter::load() {
@@ -446,7 +458,7 @@ void ObjFileMtlImporter::getTexture() {
     std::string texture;
     std::string texture;
     m_DataIt = getName<DataArrayIt>(m_DataIt, m_DataItEnd, texture);
     m_DataIt = getName<DataArrayIt>(m_DataIt, m_DataItEnd, texture);
     if (nullptr != out) {
     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)
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 
 
-Copyright (c) 2006-2020, assimp team
+Copyright (c) 2006-2024, assimp team
 
 
 All rights reserved.
 All rights reserved.
 
 
@@ -71,13 +71,12 @@ public:
             ObjFile::Model *pModel);
             ObjFile::Model *pModel);
 
 
     //! \brief  The class destructor
     //! \brief  The class destructor
-    ~ObjFileMtlImporter();
+    ~ObjFileMtlImporter() = default;
 
 
     ObjFileMtlImporter(const ObjFileMtlImporter &rOther) = delete;
     ObjFileMtlImporter(const ObjFileMtlImporter &rOther) = delete;
     ObjFileMtlImporter &operator=(const ObjFileMtlImporter &rOther) = delete;
     ObjFileMtlImporter &operator=(const ObjFileMtlImporter &rOther) = delete;
 
 
 private:
 private:
-    /// Copy constructor, empty.
     /// Load the whole material description
     /// Load the whole material description
     void load();
     void load();
     /// Get color data.
     /// Get color data.
@@ -109,8 +108,6 @@ private:
     std::vector<char> m_buffer;
     std::vector<char> m_buffer;
 };
 };
 
 
-// ------------------------------------------------------------------------------------------------
-
 } // Namespace Assimp
 } // Namespace Assimp
 
 
 #endif // OBJFILEMTLIMPORTER_H_INC
 #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 updateProgressEveryBytes = 100 * 1024;
     const unsigned int bytesToProcess = static_cast<unsigned int>(streamBuffer.size());
     const unsigned int bytesToProcess = static_cast<unsigned int>(streamBuffer.size());
     const unsigned int progressTotal = bytesToProcess;
     const unsigned int progressTotal = bytesToProcess;
-    unsigned int processed = 0;
-    size_t lastFilePos(0);
+    unsigned int processed = 0u;
+    size_t lastFilePos = 0u;
 
 
     bool insideCstype = false;
     bool insideCstype = false;
     std::vector<char> buffer;
     std::vector<char> buffer;
@@ -300,7 +300,7 @@ size_t ObjFileParser::getNumComponentsInDataDefinition() {
         } else if (IsLineEnd(*tmp)) {
         } else if (IsLineEnd(*tmp)) {
             end_of_definition = true;
             end_of_definition = true;
         }
         }
-        if (!SkipSpaces(&tmp, mEnd)) {
+        if (!SkipSpaces(&tmp, mEnd) || *tmp == '#') {
             break;
             break;
         }
         }
         const bool isNum(IsNumeric(*tmp) || isNanOrInf(tmp));
         const bool isNum(IsNumeric(*tmp) || isNanOrInf(tmp));
@@ -308,11 +308,11 @@ size_t ObjFileParser::getNumComponentsInDataDefinition() {
         if (isNum) {
         if (isNum) {
             ++numComponents;
             ++numComponents;
         }
         }
-        if (!SkipSpaces(&tmp, mEnd)) {
+        if (!SkipSpaces(&tmp, mEnd) || *tmp == '#') {
             break;
             break;
         }
         }
     }
     }
-    
+
     return numComponents;
     return numComponents;
 }
 }
 
 
@@ -451,7 +451,7 @@ void ObjFileParser::getFace(aiPrimitiveType type) {
     while (m_DataIt < m_DataItEnd) {
     while (m_DataIt < m_DataItEnd) {
         int iStep = 1;
         int iStep = 1;
 
 
-        if (IsLineEnd(*m_DataIt)) {
+        if (IsLineEnd(*m_DataIt) || *m_DataIt == '#') {
             break;
             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 {
 namespace Assimp {
 
 
+// Forward declarations
 class ObjFileImporter;
 class ObjFileImporter;
 class IOSystem;
 class IOSystem;
 class ProgressHandler;
 class ProgressHandler;
 
 
+// ------------------------------------------------------------------------------------------------
 /// \class  ObjFileParser
 /// \class  ObjFileParser
 /// \brief  Parser for a obj waveform file
 /// \brief  Parser for a obj waveform file
+// ------------------------------------------------------------------------------------------------
 class ASSIMP_API ObjFileParser {
 class ASSIMP_API ObjFileParser {
 public:
 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.
     /// @brief  The default constructor.
     ObjFileParser();
     ObjFileParser();

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

@@ -81,6 +81,27 @@ namespace {
 
 
         return props[idx];
         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
 } // namespace
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
@@ -92,6 +113,11 @@ PLYImporter::PLYImporter() :
     // empty
     // empty
 }
 }
 
 
+// ------------------------------------------------------------------------------------------------
+PLYImporter::~PLYImporter() {
+    delete mGeneratedMesh;
+}
+
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
 // Returns whether the class can handle the format of the given file.
 bool PLYImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
 bool PLYImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
@@ -104,26 +130,6 @@ const aiImporterDesc *PLYImporter::GetInfo() const {
     return &desc;
     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.
 // Imports the given file into the given scene structure.
 void PLYImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
 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
     // Get the file-size
-    const size_t fileSize(fileStream->FileSize());
+    const size_t fileSize = fileStream->FileSize();
     if (0 == fileSize) {
     if (0 == fileSize) {
         throw DeadlyImportError("File ", pFile, " is empty.");
         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)) {
         } else if (!::strncmp(szMe, "binary_", 7)) {
             szMe += 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
             // skip the line, parse the rest of the header and build the DOM
             if (!PLY::DOM::ParseInstanceBinary(streamedBuffer, &sPlyDom, this, bIsBE)) {
             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
     // fill the mesh list
     pScene->mNumMeshes = 1;
     pScene->mNumMeshes = 1;
     pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
     pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
-    pScene->mMeshes[0] = mGeneratedMesh;
+    pScene->mMeshes[0] = mGeneratedMesh;    
+
+    // Move the mesh ownership into the scene instance
     mGeneratedMesh = nullptr;
     mGeneratedMesh = nullptr;
 
 
     // generate a simple node structure
     // 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) {
 void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementInstance *instElement, unsigned int pos) {
     ai_assert(nullptr != pcElement);
     ai_assert(nullptr != pcElement);
     ai_assert(nullptr != instElement);
     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 };
     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 };
     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 };
     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 };
     PLY::EDataType aiTexcoordTypes[2] = { EDT_Char, EDT_Char };
 
 
     // now check whether which normal components are available
     // 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) {
     if (0 != cnt) {
         // Position
         // Position
         aiVector3D vOut;
         aiVector3D vOut;
-        if (0xFFFFFFFF != aiPositions[0]) {
+        if (NotSet != aiPositions[0]) {
             vOut.x = PLY::PropertyInstance::ConvertTo<ai_real>(
             vOut.x = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiPositions[0]).avList.front(), aiTypes[0]);
                     GetProperty(instElement->alProperties, aiPositions[0]).avList.front(), aiTypes[0]);
         }
         }
 
 
-        if (0xFFFFFFFF != aiPositions[1]) {
+        if (NotSet != aiPositions[1]) {
             vOut.y = PLY::PropertyInstance::ConvertTo<ai_real>(
             vOut.y = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiPositions[1]).avList.front(), aiTypes[1]);
                     GetProperty(instElement->alProperties, aiPositions[1]).avList.front(), aiTypes[1]);
         }
         }
 
 
-        if (0xFFFFFFFF != aiPositions[2]) {
+        if (NotSet != aiPositions[2]) {
             vOut.z = PLY::PropertyInstance::ConvertTo<ai_real>(
             vOut.z = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiPositions[2]).avList.front(), aiTypes[2]);
                     GetProperty(instElement->alProperties, aiPositions[2]).avList.front(), aiTypes[2]);
         }
         }
@@ -355,19 +365,19 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
         // Normals
         // Normals
         aiVector3D nOut;
         aiVector3D nOut;
         bool haveNormal = false;
         bool haveNormal = false;
-        if (0xFFFFFFFF != aiNormal[0]) {
+        if (NotSet != aiNormal[0]) {
             nOut.x = PLY::PropertyInstance::ConvertTo<ai_real>(
             nOut.x = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiNormal[0]).avList.front(), aiNormalTypes[0]);
                     GetProperty(instElement->alProperties, aiNormal[0]).avList.front(), aiNormalTypes[0]);
             haveNormal = true;
             haveNormal = true;
         }
         }
 
 
-        if (0xFFFFFFFF != aiNormal[1]) {
+        if (NotSet != aiNormal[1]) {
             nOut.y = PLY::PropertyInstance::ConvertTo<ai_real>(
             nOut.y = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiNormal[1]).avList.front(), aiNormalTypes[1]);
                     GetProperty(instElement->alProperties, aiNormal[1]).avList.front(), aiNormalTypes[1]);
             haveNormal = true;
             haveNormal = true;
         }
         }
 
 
-        if (0xFFFFFFFF != aiNormal[2]) {
+        if (NotSet != aiNormal[2]) {
             nOut.z = PLY::PropertyInstance::ConvertTo<ai_real>(
             nOut.z = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiNormal[2]).avList.front(), aiNormalTypes[2]);
                     GetProperty(instElement->alProperties, aiNormal[2]).avList.front(), aiNormalTypes[2]);
             haveNormal = true;
             haveNormal = true;
@@ -376,7 +386,7 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
         // Colors
         // Colors
         aiColor4D cOut;
         aiColor4D cOut;
         bool haveColor = false;
         bool haveColor = false;
-        if (0xFFFFFFFF != aiColors[0]) {
+        if (NotSet != aiColors[0]) {
             cOut.r = NormalizeColorValue(GetProperty(instElement->alProperties,
             cOut.r = NormalizeColorValue(GetProperty(instElement->alProperties,
                                                  aiColors[0])
                                                  aiColors[0])
                                                  .avList.front(),
                                                  .avList.front(),
@@ -384,7 +394,7 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
             haveColor = true;
             haveColor = true;
         }
         }
 
 
-        if (0xFFFFFFFF != aiColors[1]) {
+        if (NotSet != aiColors[1]) {
             cOut.g = NormalizeColorValue(GetProperty(instElement->alProperties,
             cOut.g = NormalizeColorValue(GetProperty(instElement->alProperties,
                                                  aiColors[1])
                                                  aiColors[1])
                                                  .avList.front(),
                                                  .avList.front(),
@@ -392,7 +402,7 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
             haveColor = true;
             haveColor = true;
         }
         }
 
 
-        if (0xFFFFFFFF != aiColors[2]) {
+        if (NotSet != aiColors[2]) {
             cOut.b = NormalizeColorValue(GetProperty(instElement->alProperties,
             cOut.b = NormalizeColorValue(GetProperty(instElement->alProperties,
                                                  aiColors[2])
                                                  aiColors[2])
                                                  .avList.front(),
                                                  .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
         // assume 1.0 for the alpha channel if it is not set
-        if (0xFFFFFFFF == aiColors[3]) {
+        if (NotSet == aiColors[3]) {
             cOut.a = 1.0;
             cOut.a = 1.0;
         } else {
         } else {
             cOut.a = NormalizeColorValue(GetProperty(instElement->alProperties,
             cOut.a = NormalizeColorValue(GetProperty(instElement->alProperties,
@@ -416,13 +426,13 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
         aiVector3D tOut;
         aiVector3D tOut;
         tOut.z = 0;
         tOut.z = 0;
         bool haveTextureCoords = false;
         bool haveTextureCoords = false;
-        if (0xFFFFFFFF != aiTexcoord[0]) {
+        if (NotSet != aiTexcoord[0]) {
             tOut.x = PLY::PropertyInstance::ConvertTo<ai_real>(
             tOut.x = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiTexcoord[0]).avList.front(), aiTexcoordTypes[0]);
                     GetProperty(instElement->alProperties, aiTexcoord[0]).avList.front(), aiTexcoordTypes[0]);
             haveTextureCoords = true;
             haveTextureCoords = true;
         }
         }
 
 
-        if (0xFFFFFFFF != aiTexcoord[1]) {
+        if (NotSet != aiTexcoord[1]) {
             tOut.y = PLY::PropertyInstance::ConvertTo<ai_real>(
             tOut.y = PLY::PropertyInstance::ConvertTo<ai_real>(
                     GetProperty(instElement->alProperties, aiTexcoord[1]).avList.front(), aiTexcoordTypes[1]);
                     GetProperty(instElement->alProperties, aiTexcoord[1]).avList.front(), aiTexcoordTypes[1]);
             haveTextureCoords = true;
             haveTextureCoords = true;
@@ -438,6 +448,9 @@ void PLYImporter::LoadVertex(const PLY::Element *pcElement, const PLY::ElementIn
             mGeneratedMesh->mNumVertices = pcElement->NumOccur;
             mGeneratedMesh->mNumVertices = pcElement->NumOccur;
             mGeneratedMesh->mVertices = new aiVector3D[mGeneratedMesh->mNumVertices];
             mGeneratedMesh->mVertices = new aiVector3D[mGeneratedMesh->mNumVertices];
         }
         }
+        if (pos >= mGeneratedMesh->mNumVertices) {
+            throw DeadlyImportError("Invalid .ply file: Too many vertices");
+        }
 
 
         mGeneratedMesh->mVertices[pos] = vOut;
         mGeneratedMesh->mVertices[pos] = vOut;
 
 
@@ -504,16 +517,12 @@ void PLYImporter::LoadFace(const PLY::Element *pcElement, const PLY::ElementInst
     bool bOne = false;
     bool bOne = false;
 
 
     // index of the vertex index list
     // index of the vertex index list
-    unsigned int iProperty = 0xFFFFFFFF;
+    unsigned int iProperty = NotSet;
     PLY::EDataType eType = EDT_Char;
     PLY::EDataType eType = EDT_Char;
     bool bIsTriStrip = false;
     bool bIsTriStrip = false;
 
 
-    // index of the material index property
-    // unsigned int iMaterialIndex = 0xFFFFFFFF;
-    // PLY::EDataType eType2 = EDT_Char;
-
     // texture coordinates
     // texture coordinates
-    unsigned int iTextureCoord = 0xFFFFFFFF;
+    unsigned int iTextureCoord = NotSet;
     PLY::EDataType eType3 = EDT_Char;
     PLY::EDataType eType3 = EDT_Char;
 
 
     // face = unique number of vertex indices
     // face = unique number of vertex indices
@@ -572,7 +581,7 @@ void PLYImporter::LoadFace(const PLY::Element *pcElement, const PLY::ElementInst
 
 
         if (!bIsTriStrip) {
         if (!bIsTriStrip) {
             // parse the list of vertex indices
             // parse the list of vertex indices
-            if (0xFFFFFFFF != iProperty) {
+            if (NotSet != iProperty) {
                 const unsigned int iNum = (unsigned int)GetProperty(instElement->alProperties, iProperty).avList.size();
                 const unsigned int iNum = (unsigned int)GetProperty(instElement->alProperties, iProperty).avList.size();
                 mGeneratedMesh->mFaces[pos].mNumIndices = iNum;
                 mGeneratedMesh->mFaces[pos].mNumIndices = iNum;
                 mGeneratedMesh->mFaces[pos].mIndices = new unsigned int[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();
                 const unsigned int iNum = (unsigned int)GetProperty(instElement->alProperties, iTextureCoord).avList.size();
 
 
                 // should be 6 coords
                 // should be 6 coords
@@ -679,41 +680,29 @@ void PLYImporter::GetMaterialColor(const std::vector<PLY::PropertyInstance> &avL
         aiColor4D *clrOut) {
         aiColor4D *clrOut) {
     ai_assert(nullptr != clrOut);
     ai_assert(nullptr != clrOut);
 
 
-    if (0xFFFFFFFF == aiPositions[0])
+    if (NotSet == aiPositions[0]) {
         clrOut->r = 0.0f;
         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;
         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;
         clrOut->b = 0.0f;
     else {
     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
     // assume 1.0 for the alpha channel ifit is not set
-    if (0xFFFFFFFF == aiPositions[3])
+    if (NotSet == aiPositions[3])
         clrOut->a = 1.0f;
         clrOut->a = 1.0f;
     else {
     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
 /** Importer class to load the stanford PLY file format
 */
 */
-class PLYImporter : public BaseImporter {
+class PLYImporter final : public BaseImporter {
 public:
 public:
     PLYImporter();
     PLYImporter();
-    ~PLYImporter() override = default;
+    ~PLYImporter() override;
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Returns whether the class can handle the format of the given file.
     /** Returns whether the class can handle the format of the given file.
@@ -120,13 +120,9 @@ protected:
             PLY::PropertyInstance::ValueUnion val,
             PLY::PropertyInstance::ValueUnion val,
             PLY::EDataType eType);
             PLY::EDataType eType);
 
 
-    /** Buffer to hold the loaded file */
+private:
     unsigned char *mBuffer;
     unsigned char *mBuffer;
-
-    /** Document object model representation extracted from the file */
     PLY::DOM *pcDOM;
     PLY::DOM *pcDOM;
-
-    /** Mesh generated by loader */
     aiMesh *mGeneratedMesh;
     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/ByteSwapper.h>
 #include <assimp/fast_atof.h>
 #include <assimp/fast_atof.h>
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/DefaultLogger.hpp>
+#include <unordered_set>
 #include <utility>
 #include <utility>
 
 
 namespace Assimp {
 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) {
 PLY::EDataType PLY::Property::ParseDataType(std::vector<char> &buffer) {
     ai_assert(!buffer.empty());
     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
         // if the exact semantic can't be determined, just store
         // the original string identifier
         // the original string identifier
         pOut->szName = std::string(&buffer[0], &buffer[0] + strlen(&buffer[0]));
         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))
     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) {
 bool PLY::DOM::ParseHeader(IOStreamBuffer<char> &streamBuffer, std::vector<char> &buffer, bool isBinary) {
     ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseHeader() begin");
     ASSIMP_LOG_VERBOSE_DEBUG("PLY::DOM::ParseHeader() begin");
 
 
+    std::unordered_set<std::string> definedAlElements;
     // parse all elements
     // parse all elements
     while (!buffer.empty()) {
     while (!buffer.empty()) {
         // skip all comments
         // skip all comments
@@ -421,6 +443,13 @@ bool PLY::DOM::ParseHeader(IOStreamBuffer<char> &streamBuffer, std::vector<char>
         PLY::Element out;
         PLY::Element out;
         if (PLY::Element::ParseElement(streamBuffer, buffer, &out)) {
         if (PLY::Element::ParseElement(streamBuffer, buffer, &out)) {
             // add the element to the list of elements
             // 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);
             alElements.push_back(out);
         } else if (TokenMatch(buffer, "end_header", 10)) {
         } else if (TokenMatch(buffer, "end_header", 10)) {
             // we have reached the end of the header
             // 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
     // now allocate the output array
-    pcNode->mChildren = new aiNode*[pcNode->mNumChildren];
+    pcNode->mChildren = new aiNode *[pcNode->mNumChildren];
 
 
     // and fill all subnodes
     // and fill all subnodes
     unsigned int qq( 0 );
     unsigned int qq( 0 );
@@ -451,11 +455,10 @@ void SMDImporter::CreateOutputNodes() {
         delete pcOldRoot;
         delete pcOldRoot;
 
 
         pScene->mRootNode->mParent = nullptr;
         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;
         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 );
     int64_t skip_depth( 0 );
     while ( *a ) {
     while ( *a ) {
         handleSkippedDepthFromToken(a, skip_depth);
         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;
         ++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)
 #if !defined(ASSIMP_BUILD_NO_EXPORT) && !defined(ASSIMP_BUILD_NO_STL_EXPORTER)
 
 
 #include "STLExporter.h"
 #include "STLExporter.h"
@@ -55,7 +52,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
 using namespace Assimp;
 using namespace Assimp;
 
 
-namespace Assimp    {
+namespace Assimp {
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Worker function for exporting a scene to Stereolithograpy. Prototyped and registered in Exporter.cpp
 // 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);
     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 )
 void ExportSceneSTLBinary(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties )
 {
 {
     bool exportPointClouds = pProperties->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS);
     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
 } // 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
     // 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");
     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) {
     for (unsigned int i = 0; i < m->mNumFaces; ++i) {
         const aiFace& f = m->mFaces[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,
         // we need per-face normals. We specified aiProcess_GenNormals as pre-requisite for this exporter,
         // but nonetheless we have to expect per-vertex normals.
         // but nonetheless we have to expect per-vertex normals.
         aiVector3D nor;
         aiVector3D nor;
         if (m->mNormals) {
         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 += m->mNormals[f.mIndices[a]];
             }
             }
             nor.NormalizeSafe();
             nor.NormalizeSafe();
         }
         }
         mOutput << " facet normal " << nor.x << " " << nor.y << " " << nor.z << endl;
         mOutput << " facet normal " << nor.x << " " << nor.y << " " << nor.z << endl;
         mOutput << "  outer loop" << 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;
             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) {
     for (unsigned int i = 0; i < m->mNumFaces; ++i) {
         const aiFace& f = m->mFaces[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,
         // we need per-face normals. We specified aiProcess_GenNormals as pre-requisite for this exporter,
         // but nonetheless we have to expect per-vertex normals.
         // but nonetheless we have to expect per-vertex normals.
         aiVector3D nor;
         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/importerdesc.h>
 #include <assimp/IOStreamBuffer.h>
 #include <assimp/IOStreamBuffer.h>
 #include <assimp/IOSystem.hpp>
 #include <assimp/IOSystem.hpp>
+#include "assimp/MemoryIOWrapper.h"
 #include <assimp/StringUtils.h>
 #include <assimp/StringUtils.h>
 #include <assimp/StreamReader.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"
 #include "../../../contrib/tinyusdz/assimp_tinyusdz_logging.inc"
 
 
 namespace {
 namespace {
-    static constexpr char Tag[] = "tinyusdz loader";
+    static constexpr char TAG[] = "tinyusdz loader";
 }
 }
 
 
 namespace Assimp {
 namespace Assimp {
@@ -81,7 +82,7 @@ using namespace std;
 void USDImporterImplTinyusdz::InternReadFile(
 void USDImporterImplTinyusdz::InternReadFile(
         const std::string &pFile,
         const std::string &pFile,
         aiScene *pScene,
         aiScene *pScene,
-        IOSystem *) {
+        IOSystem *pIOHandler) {
     // Grab filename for logging purposes
     // Grab filename for logging purposes
     size_t pos = pFile.find_last_of('/');
     size_t pos = pFile.find_last_of('/');
     string basePath = pFile.substr(0, pos);
     string basePath = pFile.substr(0, pos);
@@ -91,29 +92,48 @@ void USDImporterImplTinyusdz::InternReadFile(
     ss << "InternReadFile(): model" << nameWExt;
     ss << "InternReadFile(): model" << nameWExt;
     TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
     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 };
     bool ret{ false };
     tinyusdz::USDLoadOptions options;
     tinyusdz::USDLoadOptions options;
     tinyusdz::Stage stage;
     tinyusdz::Stage stage;
     std::string warn, err;
     std::string warn, err;
     bool is_usdz{ false };
     bool is_usdz{ false };
     if (isUsdc(pFile)) {
     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.str("");
         ss << "InternReadFile(): LoadUSDCFromFile() result: " << ret;
         ss << "InternReadFile(): LoadUSDCFromFile() result: " << ret;
         TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
         TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
     } else if (isUsda(pFile)) {
     } 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.str("");
         ss << "InternReadFile(): LoadUSDAFromFile() result: " << ret;
         ss << "InternReadFile(): LoadUSDAFromFile() result: " << ret;
         TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
         TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
     } else if (isUsdz(pFile)) {
     } 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;
         is_usdz = true;
         ss.str("");
         ss.str("");
         ss << "InternReadFile(): LoadUSDZFromFile() result: " << ret;
         ss << "InternReadFile(): LoadUSDZFromFile() result: " << ret;
         TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
         TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
     } else if (isUsd(pFile)) {
     } 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.str("");
         ss << "InternReadFile(): LoadUSDFromFile() result: " << ret;
         ss << "InternReadFile(): LoadUSDFromFile() result: " << ret;
         TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
         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.
     // NOTE: Pointer address of usdz_asset must be valid until the call of RenderSceneConverter::ConvertToRenderScene.
     tinyusdz::USDZAsset usdz_asset;
     tinyusdz::USDZAsset usdz_asset;
     if (is_usdz) {
     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()) {
             if (!warn.empty()) {
                 ss.str("");
                 ss.str("");
                 ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: WARNING reported: " << warn;
                 ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: WARNING reported: " << warn;
@@ -190,18 +212,141 @@ void USDImporterImplTinyusdz::InternReadFile(
         return;
         return;
     }
     }
 
 
-//    sanityCheckNodesRecursive(pScene->mRootNode);
+    // sanityCheckNodesRecursive(pScene->mRootNode);
+    animations(render_scene, pScene);
     meshes(render_scene, pScene, nameWExt);
     meshes(render_scene, pScene, nameWExt);
     materials(render_scene, pScene, nameWExt);
     materials(render_scene, pScene, nameWExt);
     textures(render_scene, pScene, nameWExt);
     textures(render_scene, pScene, nameWExt);
     textureImages(render_scene, pScene, nameWExt);
     textureImages(render_scene, pScene, nameWExt);
     buffers(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);
     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(
 void USDImporterImplTinyusdz::meshes(
         const tinyusdz::tydra::RenderScene &render_scene,
         const tinyusdz::tydra::RenderScene &render_scene,
@@ -247,8 +392,66 @@ void USDImporterImplTinyusdz::verticesForMesh(
         size_t meshIdx,
         size_t meshIdx,
         const std::string &nameWExt) {
         const std::string &nameWExt) {
     UNUSED(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];
     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) {
     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].x = render_scene.meshes[meshIdx].points[j][0];
         pScene->mMeshes[meshIdx]->mVertices[j].y = render_scene.meshes[meshIdx].points[j][1];
         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)};
     string embTexName{image.asset_identifier.substr(pos + 1)};
     tex->mFilename.Set(image.asset_identifier.c_str());
     tex->mFilename.Set(image.asset_identifier.c_str());
     tex->mHeight = image.height;
     tex->mHeight = image.height;
-//    const size_t imageBytesCount{render_scene.buffers[image.buffer_id].data.size() / image.channels};
+
     tex->mWidth = image.width;
     tex->mWidth = image.width;
     if (tex->mHeight == 0) {
     if (tex->mHeight == 0) {
         pos = embTexName.find_last_of('.');
         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::tinyusdzNodeTypeFor;
 using Assimp::tinyUsdzMat4ToAiMat4;
 using Assimp::tinyUsdzMat4ToAiMat4;
 using tinyusdz::tydra::NodeType;
 using tinyusdz::tydra::NodeType;
 aiNode *USDImporterImplTinyusdz::nodesRecursive(
 aiNode *USDImporterImplTinyusdz::nodesRecursive(
         aiNode *pNodeParent,
         aiNode *pNodeParent,
         const tinyusdz::tydra::Node &node,
         const tinyusdz::tydra::Node &node,
-        std::map<size_t, tinyusdz::tydra::Node> &meshNodes) {
+        const std::vector<tinyusdz::tydra::SkelHierarchy> &skeletons) {
     stringstream ss;
     stringstream ss;
     aiNode *cNode = new aiNode();
     aiNode *cNode = new aiNode();
     cNode->mParent = pNodeParent;
     cNode->mParent = pNodeParent;
     cNode->mName.Set(node.prim_name);
     cNode->mName.Set(node.prim_name);
     cNode->mTransformation = tinyUsdzMat4ToAiMat4(node.local_matrix.m);
     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.str("");
     ss << "nodesRecursive(): node " << cNode->mName.C_Str() <<
     ss << "nodesRecursive(): node " << cNode->mName.C_Str() <<
             " type: |" << tinyusdzNodeTypeFor(node.nodeType) <<
             " type: |" << tinyusdzNodeTypeFor(node.nodeType) <<
@@ -651,21 +825,69 @@ aiNode *USDImporterImplTinyusdz::nodesRecursive(
         ss << " (parent " << cNode->mParent->mName.C_Str() << ")";
         ss << " (parent " << cNode->mParent->mName.C_Str() << ")";
     }
     }
     ss << " has " << node.children.size() << " children";
     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) << ")";
         ss << "\n    node mesh id: " << node.id << " (node type: " << tinyusdzNodeTypeFor(node.nodeType) << ")";
-        meshNodes[node.id] = node;
     }
     }
     TINYUSDZLOGD(TAG, "%s", ss.str().c_str());
     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;
         ++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;
     return cNode;
 }
 }
 
 

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

@@ -65,6 +65,10 @@ public:
             aiScene *pScene,
             aiScene *pScene,
             IOSystem *pIOHandler);
             IOSystem *pIOHandler);
 
 
+    void animations(
+            const tinyusdz::tydra::RenderScene &render_scene,
+            aiScene *pScene);
+
     void meshes(
     void meshes(
             const tinyusdz::tydra::RenderScene &render_scene,
             const tinyusdz::tydra::RenderScene &render_scene,
             aiScene *pScene,
             aiScene *pScene,
@@ -120,22 +124,14 @@ public:
             aiScene *pScene,
             aiScene *pScene,
             const std::string &nameWExt);
             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 *nodesRecursive(
             aiNode *pNodeParent,
             aiNode *pNodeParent,
             const tinyusdz::tydra::Node &node,
             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(
     void sanityCheckNodesRecursive(
             aiNode *pNode);
             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) {
 aiVector3D Assimp::tinyUsdzScaleOrPosToAssimp(const std::array<float, 3> &scaleOrPosIn) {
     return aiVector3D(scaleOrPosIn[0], scaleOrPosIn[1], scaleOrPosIn[2]);
     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 <assimp/types.h>
 #include "tinyusdz.hh"
 #include "tinyusdz.hh"
 #include "tydra/render-data.hh"
 #include "tydra/render-data.hh"
+#include <type_traits>
 
 
 namespace Assimp {
 namespace Assimp {
 
 
 std::string tinyusdzAnimChannelTypeFor(
 std::string tinyusdzAnimChannelTypeFor(
         tinyusdz::tydra::AnimationChannel::ChannelType animChannel);
         tinyusdz::tydra::AnimationChannel::ChannelType animChannel);
 std::string tinyusdzNodeTypeFor(tinyusdz::tydra::NodeType type);
 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);
 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
 #ifndef ASSIMP_BUILD_NO_X3D_IMPORTER
 
 
+#include "AssetLib/VRML/VrmlConverter.hpp"
 #include "X3DImporter.hpp"
 #include "X3DImporter.hpp"
 #include "X3DImporter_Macro.hpp"
 #include "X3DImporter_Macro.hpp"
 
 
@@ -54,11 +55,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <iterator>
 #include <iterator>
 #include <memory>
 #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 {
 namespace Assimp {
 
 
 /// Constant which holds the importer description
 /// Constant which holds the importer description
 const aiImporterDesc X3DImporter::Description = {
 const aiImporterDesc X3DImporter::Description = {
-    "Extensible 3D(X3D) Importer",
+    X3D_FORMATS_DESCR_STR,
     "smalcom",
     "smalcom",
     "",
     "",
     "See documentation in source code. Chapter: Limitations.",
     "See documentation in source code. Chapter: Limitations.",
@@ -67,7 +76,7 @@ const aiImporterDesc X3DImporter::Description = {
     0,
     0,
     0,
     0,
     0,
     0,
-    "x3d x3db"
+    X3D_FORMATS_EXTENSIONS_STR
 };
 };
 
 
 bool X3DImporter::isNodeEmpty(XmlNode &node) {
 bool X3DImporter::isNodeEmpty(XmlNode &node) {
@@ -215,7 +224,19 @@ void X3DImporter::ParseFile(const std::string &file, IOSystem *pIOHandler) {
     if (!theParser.parse(fileStream.get())) {
     if (!theParser.parse(fileStream.get())) {
         return;
         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");
     XmlNode *node = theParser.findNode("X3D");
     if (nullptr == node) {
     if (nullptr == node) {
         return;
         return;
@@ -246,9 +267,13 @@ void X3DImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
     mpIOHandler = pIOHandler;
     mpIOHandler = pIOHandler;
 
 
     Clear();
     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("\\/");
     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->mRootNode->mParent = nullptr;
     pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED;
     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
     //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.
     /// Also exception can be thrown if trouble will found.
     /// \param [in] pFile - name of file to be parsed.
     /// \param [in] pFile - name of file to be parsed.
     /// \param [in] pIOHandler - pointer to IO helper object.
     /// \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;
     bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool pCheckSig) const;
     void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler);
     void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler);
     const aiImporterDesc *GetInfo() const;
     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/glTFImporter.h"
 #include "AssetLib/glTF/glTFAsset.h"
 #include "AssetLib/glTF/glTFAsset.h"
 #if !defined(ASSIMP_BUILD_NO_EXPORT)
 #if !defined(ASSIMP_BUILD_NO_EXPORT)
-#include "AssetLib/glTF/glTFAssetWriter.h"
+#   include "AssetLib/glTF/glTFAssetWriter.h"
 #endif
 #endif
 #include "PostProcessing/MakeVerboseFormat.h"
 #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,
     0,
     0,
     0,
@@ -129,11 +133,8 @@ void glTFImporter::ImportMaterials(glTF::Asset &r) {
         aiMaterial *aimat = mScene->mMaterials[i] = new aiMaterial();
         aiMaterial *aimat = mScene->mMaterials[i] = new aiMaterial();
 
 
         Material &mat = r.materials[i];
         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.ambient, aimat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT);
         SetMaterialColorProperty(embeddedTexIdxs, r, mat.diffuse, aimat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE);
         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;
         mScene->mRootNode = root;
     }
     }
-
-    //if (!mScene->mRootNode) {
-    //  mScene->mRootNode = new aiNode("EMPTY");
-    //}
 }
 }
 
 
 void glTFImporter::ImportEmbeddedTextures(glTF::Asset &r) {
 void glTFImporter::ImportEmbeddedTextures(glTF::Asset &r) {
@@ -631,8 +628,9 @@ void glTFImporter::ImportEmbeddedTextures(glTF::Asset &r) {
             numEmbeddedTexs += 1;
             numEmbeddedTexs += 1;
     }
     }
 
 
-    if (numEmbeddedTexs == 0)
+    if (numEmbeddedTexs == 0) {
         return;
         return;
+    }
 
 
     mScene->mTextures = new aiTexture *[numEmbeddedTexs];
     mScene->mTextures = new aiTexture *[numEmbeddedTexs];
 
 
@@ -657,12 +655,14 @@ void glTFImporter::ImportEmbeddedTextures(glTF::Asset &r) {
         if (!img.mimeType.empty()) {
         if (!img.mimeType.empty()) {
             const char *ext = strchr(img.mimeType.c_str(), '/') + 1;
             const char *ext = strchr(img.mimeType.c_str(), '/') + 1;
             if (ext) {
             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);
                 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_volume full
  *   KHR_materials_ior full
  *   KHR_materials_ior full
  *   KHR_materials_emissive_strength full
  *   KHR_materials_emissive_strength full
+ *   KHR_materials_anisotropy full
  */
  */
 #ifndef GLTF2ASSET_H_INC
 #ifndef GLTF2ASSET_H_INC
 #define GLTF2ASSET_H_INC
 #define GLTF2ASSET_H_INC
@@ -821,6 +822,15 @@ struct MaterialEmissiveStrength {
     void SetDefaults();
     void SetDefaults();
 };
 };
 
 
+struct MaterialAnisotropy {
+    float anisotropyStrength = 0.f;
+    float anisotropyRotation = 0.f;
+    TextureInfo anisotropyTexture;
+
+    MaterialAnisotropy() { SetDefaults(); }
+    void SetDefaults();
+};
+
 //! The material appearance of a primitive.
 //! The material appearance of a primitive.
 struct Material : public Object {
 struct Material : public Object {
     //PBR metallic roughness properties
     //PBR metallic roughness properties
@@ -859,6 +869,9 @@ struct Material : public Object {
     //extension: KHR_materials_emissive_strength
     //extension: KHR_materials_emissive_strength
     Nullable<MaterialEmissiveStrength> materialEmissiveStrength;
     Nullable<MaterialEmissiveStrength> materialEmissiveStrength;
 
 
+    //extension: KHR_materials_anisotropy
+    Nullable<MaterialAnisotropy> materialAnisotropy;
+
     //extension: KHR_materials_unlit
     //extension: KHR_materials_unlit
     bool unlit;
     bool unlit;
 
 
@@ -1133,6 +1146,7 @@ public:
         bool KHR_materials_volume;
         bool KHR_materials_volume;
         bool KHR_materials_ior;
         bool KHR_materials_ior;
         bool KHR_materials_emissive_strength;
         bool KHR_materials_emissive_strength;
+        bool KHR_materials_anisotropy;
         bool KHR_draco_mesh_compression;
         bool KHR_draco_mesh_compression;
         bool FB_ngon_encoding;
         bool FB_ngon_encoding;
         bool KHR_texture_basisu;
         bool KHR_texture_basisu;
@@ -1149,6 +1163,7 @@ public:
                 KHR_materials_volume(false),
                 KHR_materials_volume(false),
                 KHR_materials_ior(false),
                 KHR_materials_ior(false),
                 KHR_materials_emissive_strength(false),
                 KHR_materials_emissive_strength(false),
+                KHR_materials_anisotropy(false),
                 KHR_draco_mesh_compression(false),
                 KHR_draco_mesh_compression(false),
                 FB_ngon_encoding(false),
                 FB_ngon_encoding(false),
                 KHR_texture_basisu(false) {
                 KHR_texture_basisu(false) {
@@ -1252,6 +1267,7 @@ private:
     size_t mBodyOffset;
     size_t mBodyOffset;
     size_t mBodyLength;
     size_t mBodyLength;
     IdMap mUsedIds;
     IdMap mUsedIds;
+    std::map<std::string, int, std::less<>> mUsedNamesMap;
     Ref<Buffer> mBodyBuffer;
     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
     // Usually uint32_t but shouldn't assume
     if (sizeof(dracoMesh.face(draco::FaceIndex(0))[0]) == componentBytes) {
     if (sizeof(dracoMesh.face(draco::FaceIndex(0))[0]) == componentBytes) {
         memcpy(decodedIndexBuffer->GetPointer(), &dracoMesh.face(draco::FaceIndex(0))[0], decodedIndexBuffer->byteLength);
         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;
         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");
         unlit = nullptr != FindObject(*extensions, "KHR_materials_unlit");
     }
     }
 }
 }
@@ -1454,6 +1468,12 @@ inline void MaterialEmissiveStrength::SetDefaults() {
     emissiveStrength = 0.f;
     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) {
 inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) {
     Value *curName = FindMember(pJSON_Object, "name");
     Value *curName = FindMember(pJSON_Object, "name");
     if (nullptr != curName && curName->IsString()) {
     if (nullptr != curName && curName->IsString()) {
@@ -2043,6 +2063,12 @@ inline void Asset::Load(const std::string &pFile, bool isBinary)
         mDicts[i]->AttachToDocument(doc);
         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
     // Read the "scene" property, which specifies which scene to load
     // and recursively load everything referenced by it
     // and recursively load everything referenced by it
     unsigned int sceneIndex = 0;
     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 (Value *scenesArray = FindArray(doc, "scenes")) {
         if (sceneIndex < scenesArray->Size()) {
         if (sceneIndex < scenesArray->Size()) {
             this->scene = scenes.Retrieve(sceneIndex);
             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_volume);
     CHECK_EXT(KHR_materials_ior);
     CHECK_EXT(KHR_materials_ior);
     CHECK_EXT(KHR_materials_emissive_strength);
     CHECK_EXT(KHR_materials_emissive_strength);
+    CHECK_EXT(KHR_materials_anisotropy);
     CHECK_EXT(KHR_draco_mesh_compression);
     CHECK_EXT(KHR_draco_mesh_compression);
     CHECK_EXT(KHR_texture_basisu);
     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;
     std::string id = str;
 
 
     if (!id.empty()) {
     if (!id.empty()) {
-        if (mUsedIds.find(id) == mUsedIds.end())
+        if (mUsedIds.find(id) == mUsedIds.end()){
+            mUsedNamesMap[id] = 0;
             return id;
             return id;
+        }
 
 
         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);
     Asset::IdMap::iterator it = mUsedIds.find(id);
     if (it == mUsedIds.end()) {
     if (it == mUsedIds.end()) {
+        mUsedNamesMap[id] = 0;
         return id;
         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;
     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_volume: full
  *   KHR_materials_ior: full
  *   KHR_materials_ior: full
  *   KHR_materials_emissive_strength: full
  *   KHR_materials_emissive_strength: full
+ *   KHR_materials_anisotropy: full
  */
  */
 #ifndef GLTF2ASSETWRITER_H_INC
 #ifndef GLTF2ASSETWRITER_H_INC
 #define 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()) {
         if (!exts.ObjectEmpty()) {
             obj.AddMember("extensions", exts, w.mAl);
             obj.AddMember("extensions", exts, w.mAl);
         }
         }
@@ -608,6 +628,7 @@ namespace glTF2 {
                 {
                 {
                     WriteAttrs(w, attrs, p.attributes.position, "POSITION");
                     WriteAttrs(w, attrs, p.attributes.position, "POSITION");
                     WriteAttrs(w, attrs, p.attributes.normal, "NORMAL");
                     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.texcoord, "TEXCOORD", true);
                     WriteAttrs(w, attrs, p.attributes.color, "COLOR", true);
                     WriteAttrs(w, attrs, p.attributes.color, "COLOR", true);
                     WriteAttrs(w, attrs, p.attributes.joint, "JOINTS", 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) {
             if (outfile->Write(bodyBuffer->GetPointer(), 1, bodyBuffer->byteLength) != bodyBuffer->byteLength) {
                 throw DeadlyExportError("Failed to write body data!");
                 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!");
                 throw DeadlyExportError("Failed to write body data padding!");
             }
             }
         }
         }
@@ -1017,6 +1038,10 @@ namespace glTF2 {
                 exts.PushBack(StringRef("KHR_materials_emissive_strength"), mAl);
                 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) {
             if (this->mAsset.extensionsUsed.FB_ngon_encoding) {
                 exts.PushBack(StringRef("FB_ngon_encoding"), mAl);
                 exts.PushBack(StringRef("FB_ngon_encoding"), mAl);
             }
             }

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff