Pārlūkot izejas kodu

Missing bits, oops.

Did not mean to commit/push the current state of master. But rather than
mess up source control history with a force push, I'll just try to hurry
to a stable point.
Par Winzell 7 gadi atpakaļ
vecāks
revīzija
f988cb7aa7
70 mainītis faili ar 2195 papildinājumiem un 1013 dzēšanām
  1. 76 48
      CMakeLists.txt
  2. BIN
      models/Pinecone.glb
  3. BIN
      models/SphereWithTangents.glb
  4. BIN
      models/apple.glb
  5. BIN
      models/roughness.glb
  6. BIN
      models/shininess_example.glb
  7. 4 4
      src/FBX2glTF.cpp
  8. 6 5
      src/FBX2glTF.h
  9. 11 641
      src/fbx/Fbx2Raw.cpp
  10. 2 5
      src/fbx/Fbx2Raw.hpp
  11. 58 0
      src/fbx/FbxBlendShapesAccess.cpp
  12. 103 0
      src/fbx/FbxBlendShapesAccess.hpp
  13. 83 0
      src/fbx/FbxLayerElementAccess.hpp
  14. 22 0
      src/fbx/FbxMaterialInfo.hpp
  15. 74 0
      src/fbx/FbxMaterialsAccess.cpp
  16. 32 0
      src/fbx/FbxMaterialsAccess.hpp
  17. 68 0
      src/fbx/FbxRoughMetMaterialInfo.cpp
  18. 42 0
      src/fbx/FbxRoughMetMaterialInfo.hpp
  19. 92 0
      src/fbx/FbxSkinningAccess.cpp
  20. 92 0
      src/fbx/FbxSkinningAccess.hpp
  21. 124 0
      src/fbx/FbxTraditionalMaterialInfo.cpp
  22. 44 0
      src/fbx/FbxTraditionalMaterialInfo.hpp
  23. 125 0
      src/fbx/materials/3dsMaxPhysicalMaterial.cpp
  24. 78 0
      src/fbx/materials/FbxMaterials.cpp
  25. 60 0
      src/fbx/materials/FbxMaterials.hpp
  26. 81 0
      src/fbx/materials/RoughnessMetallicMaterials.hpp
  27. 71 0
      src/fbx/materials/StingrayPBSMaterial.cpp
  28. 123 0
      src/fbx/materials/TraditionalMaterials.cpp
  29. 43 0
      src/fbx/materials/TraditionalMaterials.hpp
  30. 10 0
      src/gltf/GltfModel.cpp
  31. 189 0
      src/gltf/GltfModel.hpp
  32. 26 28
      src/gltf/Raw2Gltf.cpp
  33. 2 5
      src/gltf/Raw2Gltf.hpp
  34. 206 0
      src/gltf/TextureBuilder.cpp
  35. 68 0
      src/gltf/TextureBuilder.hpp
  36. 2 2
      src/gltf/properties/AccessorData.cpp
  37. 2 5
      src/gltf/properties/AccessorData.hpp
  38. 3 3
      src/gltf/properties/AnimationData.cpp
  39. 2 5
      src/gltf/properties/AnimationData.hpp
  40. 1 1
      src/gltf/properties/BufferData.cpp
  41. 2 6
      src/gltf/properties/BufferData.hpp
  42. 2 2
      src/gltf/properties/BufferViewData.cpp
  43. 2 5
      src/gltf/properties/BufferViewData.hpp
  44. 1 1
      src/gltf/properties/CameraData.cpp
  45. 2 5
      src/gltf/properties/CameraData.hpp
  46. 2 2
      src/gltf/properties/ImageData.cpp
  47. 2 5
      src/gltf/properties/ImageData.hpp
  48. 2 2
      src/gltf/properties/MaterialData.cpp
  49. 2 5
      src/gltf/properties/MaterialData.hpp
  50. 2 2
      src/gltf/properties/MeshData.cpp
  51. 3 6
      src/gltf/properties/MeshData.hpp
  52. 2 2
      src/gltf/properties/NodeData.cpp
  53. 2 5
      src/gltf/properties/NodeData.hpp
  54. 6 6
      src/gltf/properties/PrimitiveData.cpp
  55. 2 5
      src/gltf/properties/PrimitiveData.hpp
  56. 3 6
      src/gltf/properties/SamplerData.hpp
  57. 2 2
      src/gltf/properties/SceneData.cpp
  58. 1 6
      src/gltf/properties/SceneData.hpp
  59. 3 3
      src/gltf/properties/SkinData.cpp
  60. 1 6
      src/gltf/properties/SkinData.hpp
  61. 3 3
      src/gltf/properties/TextureData.cpp
  62. 1 6
      src/gltf/properties/TextureData.hpp
  63. 12 14
      src/mathfu.hpp
  64. 8 9
      src/raw/RawModel.cpp
  65. 5 6
      src/raw/RawModel.hpp
  66. 6 4
      src/utils/File_Utils.hpp
  67. 0 46
      src/utils/Image_Utils.h
  68. 37 0
      src/utils/Image_Utils.hpp
  69. 0 91
      src/utils/String_Utils.h
  70. 54 0
      src/utils/String_Utils.hpp

+ 76 - 48
CMakeLists.txt

@@ -32,11 +32,12 @@ endif()
 # DRACO
 ExternalProject_Add(Draco
   GIT_REPOSITORY https://github.com/google/draco
-  GIT_TAG 1.3.1
+  GIT_TAG 1.3.4
   PREFIX draco
   INSTALL_DIR
   CMAKE_ARGS
-	-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
+        -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
+        -DBUILD_FOR_GLTF=1
 )
 set(DRACO_INCLUDE_DIR "${CMAKE_BINARY_DIR}/draco/include")
 if (WIN32)
@@ -134,54 +135,70 @@ if (APPLE)
     set(FRAMEWORKS ${CF_FRAMEWORK})
 endif()
 
-set(SOURCE_FILES
+set(LIB_SOURCE_FILES
         src/FBX2glTF.h
-        src/Fbx2Raw.cpp
-        src/Fbx2Raw.h
-        src/Raw2Gltf.cpp
-        src/Raw2Gltf.h
-        src/RawModel.cpp
-        src/RawModel.h
-        src/glTF/AccessorData.cpp
-        src/glTF/AccessorData.h
-        src/glTF/AnimationData.cpp
-        src/glTF/AnimationData.h
-        src/glTF/BufferData.cpp
-        src/glTF/BufferData.h
-        src/glTF/BufferViewData.cpp
-        src/glTF/BufferViewData.h
-        src/glTF/CameraData.cpp
-        src/glTF/CameraData.h
-        src/glTF/ImageData.cpp
-        src/glTF/ImageData.h
-        src/glTF/MaterialData.cpp
-        src/glTF/MaterialData.h
-        src/glTF/MeshData.cpp
-        src/glTF/MeshData.h
-        src/glTF/NodeData.cpp
-        src/glTF/NodeData.h
-        src/glTF/PrimitiveData.cpp
-        src/glTF/PrimitiveData.h
-        src/glTF/SamplerData.h
-        src/glTF/SceneData.cpp
-        src/glTF/SceneData.h
-        src/glTF/SkinData.cpp
-        src/glTF/SkinData.h
-        src/glTF/TextureData.cpp
-        src/glTF/TextureData.h
-        src/main.cpp
-        src/mathfu.h
+        src/fbx/Fbx2Raw.cpp
+        src/fbx/Fbx2Raw.hpp
+        src/fbx/FbxLayerElementAccess.hpp
+        src/fbx/FbxBlendShapesAccess.hpp
+        src/fbx/FbxMaterialInfo.hpp
+        src/gltf/Raw2Gltf.cpp
+        src/gltf/Raw2Gltf.hpp
+        src/raw/RawModel.cpp
+        src/raw/RawModel.hpp
+        src/gltf/properties/AccessorData.cpp
+        src/gltf/properties/AccessorData.hpp
+        src/gltf/properties/AnimationData.cpp
+        src/gltf/properties/AnimationData.hpp
+        src/gltf/properties/BufferData.cpp
+        src/gltf/properties/BufferData.hpp
+        src/gltf/properties/BufferViewData.cpp
+        src/gltf/properties/BufferViewData.hpp
+        src/gltf/properties/CameraData.cpp
+        src/gltf/properties/CameraData.hpp
+        src/gltf/properties/ImageData.cpp
+        src/gltf/properties/ImageData.hpp
+        src/gltf/properties/MaterialData.cpp
+        src/gltf/properties/MaterialData.hpp
+        src/gltf/properties/MeshData.cpp
+        src/gltf/properties/MeshData.hpp
+        src/gltf/properties/NodeData.cpp
+        src/gltf/properties/NodeData.hpp
+        src/gltf/properties/PrimitiveData.cpp
+        src/gltf/properties/PrimitiveData.hpp
+        src/gltf/properties/SamplerData.hpp
+        src/gltf/properties/SceneData.cpp
+        src/gltf/properties/SceneData.hpp
+        src/gltf/properties/SkinData.cpp
+        src/gltf/properties/SkinData.hpp
+        src/gltf/properties/TextureData.cpp
+        src/gltf/properties/TextureData.hpp
+        src/mathfu.hpp
         src/utils/File_Utils.cpp
-        src/utils/File_Utils.h
+        src/utils/File_Utils.hpp
         src/utils/Image_Utils.cpp
-        src/utils/Image_Utils.h
+        src/utils/Image_Utils.hpp
         src/utils/String_Utils.cpp
-        src/utils/String_Utils.h
+        src/utils/String_Utils.hpp
+        src/fbx/FbxBlendShapesAccess.cpp
+        src/fbx/FbxBlendShapesAccess.hpp
+        src/fbx/FbxLayerElementAccess.hpp
+        src/fbx/FbxMaterialsAccess.cpp
+        src/fbx/FbxMaterialsAccess.hpp
+        src/fbx/FbxRoughMetMaterialInfo.cpp
+        src/fbx/FbxRoughMetMaterialInfo.hpp
+        src/fbx/FbxSkinningAccess.cpp
+        src/fbx/FbxSkinningAccess.hpp
+        src/fbx/FbxTraditionalMaterialInfo.cpp
+        src/fbx/FbxTraditionalMaterialInfo.hpp
 )
 
-add_executable(FBX2glTF ${SOURCE_FILES})
+add_library(libFBX2glTF STATIC ${LIB_SOURCE_FILES})
+set_target_properties(libFBX2glTF PROPERTIES OUTPUT_NAME "FBX2glTF")
+add_executable(appFBX2glTF src/FBX2glTF.cpp)
+set_target_properties(appFBX2glTF PROPERTIES OUTPUT_NAME "FBX2glTF")
 
-add_dependencies(FBX2glTF
+add_dependencies(libFBX2glTF
   Draco
   MathFu
   FiFoMap
@@ -194,13 +211,17 @@ add_dependencies(FBX2glTF
 
 if (NOT MSVC)
   # Disable annoying & spammy warning from FBX SDK header file
-  target_compile_options(FBX2glTF PRIVATE
+  target_compile_options(libFBX2glTF PRIVATE
+    "-Wno-null-dereference"
+    "-Wunused"
+    )
+  target_compile_options(appFBX2glTF PRIVATE
     "-Wno-null-dereference"
     "-Wunused"
     )
 endif()
 
-target_link_libraries(FBX2glTF
+target_link_libraries(libFBX2glTF
   ${FRAMEWORKS}
   ${DRACO_LIB}
   ${FMT_LIB}
@@ -210,17 +231,24 @@ target_link_libraries(FBX2glTF
   ${CMAKE_THREAD_LIBS_INIT}
 )
 
-target_include_directories(FBX2glTF PUBLIC
+target_include_directories(libFBX2glTF PUBLIC
   ${CMAKE_CURRENT_SOURCE_DIR}/src
   ${FBXSDK_INCLUDE_DIR}
   ${DRACO_INCLUDE_DIR}
   ${MATHFU_INCLUDE_DIRS}
   ${FIFO_MAP_INCLUDE_DIR}
   ${JSON_INCLUDE_DIR}
-  ${CXXOPTS_INCLUDE_DIR}
   ${STB_INCLUDE_DIR}
   ${CPPCODEC_INCLUDE_DIR}
   ${FMT_INCLUDE_DIR}
 )
 
-install (TARGETS FBX2glTF DESTINATION bin)
+target_include_directories(appFBX2glTF PUBLIC
+  ${CXXOPTS_INCLUDE_DIR}
+)
+target_link_libraries(appFBX2glTF libFBX2glTF)
+
+install (TARGETS libFBX2glTF appFBX2glTF
+        RUNTIME DESTINATION bin
+        ARCHIVE DESTINATION lib
+)

BIN
models/Pinecone.glb


BIN
models/SphereWithTangents.glb


BIN
models/apple.glb


BIN
models/roughness.glb


BIN
models/shininess_example.glb


+ 4 - 4
src/main.cpp → src/FBX2glTF.cpp

@@ -23,10 +23,10 @@
 #include <cxxopts.hpp>
 
 #include "FBX2glTF.h"
-#include "utils/String_Utils.h"
-#include "utils/File_Utils.h"
-#include "Fbx2Raw.h"
-#include "Raw2Gltf.h"
+#include "utils/String_Utils.hpp"
+#include "utils/File_Utils.hpp"
+#include "fbx/Fbx2Raw.hpp"
+#include "gltf/Raw2Gltf.hpp"
 
 bool verboseOutput = false;
 

+ 6 - 5
src/FBX2glTF.h

@@ -7,8 +7,9 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef __FBX2GLTF_H__
-#define __FBX2GLTF_H__
+#pragma once
+
+#include <string>
 
 #if defined ( _WIN32 )
 // Tell Windows not to define min() and max() macros
@@ -16,7 +17,7 @@
 #include <Windows.h>
 #endif
 
-const std::string FBX2GLTF_VERSION = "0.9.5";
+#define FBX2GLTF_VERSION std::string("0.9.5")
 
 #include <fmt/printf.h>
 #include <fbxsdk.h>
@@ -26,6 +27,6 @@ const std::string FBX2GLTF_VERSION = "0.9.5";
 #undef isnan
 #endif
 
-#include "mathfu.h"
+#include "mathfu.hpp"
 
-#endif // !__FBX2GLTF_H__
+extern bool verboseOutput;

+ 11 - 641
src/Fbx2Raw.cpp → src/fbx/Fbx2Raw.cpp

@@ -20,648 +20,18 @@
 #include <cmath>
 
 #include "FBX2glTF.h"
-#include "utils/File_Utils.h"
-#include "utils/String_Utils.h"
-#include "RawModel.h"
-#include "Fbx2Raw.h"
 
-extern bool verboseOutput;
+#include "utils/File_Utils.hpp"
+#include "utils/String_Utils.hpp"
+#include "raw/RawModel.hpp"
+#include "Fbx2Raw.hpp"
 
-float scaleFactor;
-
-template<typename _type_>
-class FbxLayerElementAccess
-{
-public:
-
-    FbxLayerElementAccess(const FbxLayerElementTemplate<_type_> *layer, int count) :
-        mappingMode(FbxGeometryElement::eNone),
-        elements(nullptr),
-        indices(nullptr)
-    {
-        if (count <= 0 || layer == nullptr) {
-            return;
-        }
-        const FbxGeometryElement::EMappingMode newMappingMode = layer->GetMappingMode();
-        if (newMappingMode == FbxGeometryElement::eByControlPoint ||
-            newMappingMode == FbxGeometryElement::eByPolygonVertex ||
-            newMappingMode == FbxGeometryElement::eByPolygon) {
-            mappingMode = newMappingMode;
-            elements    = &layer->GetDirectArray();
-            indices     = (
-                layer->GetReferenceMode() == FbxGeometryElement::eIndexToDirect ||
-                layer->GetReferenceMode() == FbxGeometryElement::eIndex) ? &layer->GetIndexArray() : nullptr;
-        }
-    }
-
-    bool LayerPresent() const
-    {
-        return (mappingMode != FbxGeometryElement::eNone);
-    }
-
-    _type_ GetElement(const int polygonIndex, const int polygonVertexIndex, const int controlPointIndex, const _type_ defaultValue) const
-    {
-        if (mappingMode != FbxGeometryElement::eNone) {
-            int index = (mappingMode == FbxGeometryElement::eByControlPoint) ? controlPointIndex :
-                        ((mappingMode == FbxGeometryElement::eByPolygonVertex) ? polygonVertexIndex : polygonIndex);
-            index = (indices != nullptr) ? (*indices)[index] : index;
-            _type_ element = elements->GetAt(index);
-            return element;
-        }
-        return defaultValue;
-    }
-
-    _type_ GetElement(
-        const int polygonIndex, const int polygonVertexIndex, const int controlPointIndex, const _type_ defaultValue,
-        const FbxMatrix &transform, const bool normalize) const
-    {
-        if (mappingMode != FbxGeometryElement::eNone) {
-            _type_ element = transform.MultNormalize(GetElement(polygonIndex, polygonVertexIndex, controlPointIndex, defaultValue));
-            if (normalize) {
-                element.Normalize();
-            }
-            return element;
-        }
-        return defaultValue;
-    }
-
-private:
-    FbxGeometryElement::EMappingMode           mappingMode;
-    const FbxLayerElementArrayTemplate<_type_> *elements;
-    const FbxLayerElementArrayTemplate<int>    *indices;
-};
-
-struct FbxMaterialInfo {
-    FbxMaterialInfo(const FbxString &name, const FbxString &shadingModel)
-        : name(name),
-          shadingModel(shadingModel)
-    {}
-    const FbxString name;
-    const FbxString shadingModel;
-};
-
-struct FbxRoughMetMaterialInfo : FbxMaterialInfo {
-    static constexpr const char *FBX_SHADER_METROUGH = "MetallicRoughness";
-
-    FbxRoughMetMaterialInfo(const FbxString &name, const FbxString &shadingModel)
-        : FbxMaterialInfo(name, shadingModel)
-    {}
-    const FbxFileTexture *texColor {};
-    FbxVector4           colBase {};
-    const FbxFileTexture *texNormal {};
-    const FbxFileTexture *texMetallic {};
-    FbxDouble            metallic {};
-    const FbxFileTexture *texRoughness {};
-    FbxDouble            roughness {};
-    const FbxFileTexture *texEmissive {};
-    FbxVector4           colEmissive {};
-    FbxDouble            emissiveIntensity {};
-    const FbxFileTexture *texAmbientOcclusion {};
-
-    static std::unique_ptr<FbxRoughMetMaterialInfo> From(
-        FbxSurfaceMaterial *fbxMaterial,
-        const std::map<const FbxTexture *, FbxString> &textureLocations)
-    {
-        std::unique_ptr<FbxRoughMetMaterialInfo> res(new FbxRoughMetMaterialInfo(fbxMaterial->GetName(), FBX_SHADER_METROUGH));
-
-        const FbxProperty mayaProp = fbxMaterial->FindProperty("Maya");
-        if (mayaProp.GetPropertyDataType() != FbxCompoundDT) {
-            return nullptr;
-        }
-        if (!fbxMaterial->ShadingModel.Get().IsEmpty()) {
-            fmt::printf("Warning: Material %s has surprising shading model: %s\n",
-                fbxMaterial->GetName(), fbxMaterial->ShadingModel.Get());
-        }
-
-        auto getTex = [&](std::string propName) {
-            const FbxFileTexture *ptr = nullptr;
-
-            const FbxProperty useProp = mayaProp.FindHierarchical(("use_" + propName + "_map").c_str());
-            if (useProp.IsValid() && useProp.Get<bool>()) {
-                const FbxProperty texProp = mayaProp.FindHierarchical(("TEX_" + propName + "_map").c_str());
-                if (texProp.IsValid()) {
-                    ptr = texProp.GetSrcObject<FbxFileTexture>();
-                    if (ptr != nullptr && textureLocations.find(ptr) == textureLocations.end()) {
-                        ptr = nullptr;
-                    }
-                }
-            } else if (verboseOutput && useProp.IsValid()) {
-                fmt::printf("Note: Property '%s' of material '%s' exists, but is flagged as 'do not use'.\n",
-                    propName, fbxMaterial->GetName());
-            }
-            return ptr;
-        };
-
-        auto getVec = [&](std::string propName) -> FbxDouble3 {
-            const FbxProperty vecProp = mayaProp.FindHierarchical(propName.c_str());
-            return vecProp.IsValid() ? vecProp.Get<FbxDouble3>() : FbxDouble3(1, 1, 1);
-        };
-
-        auto getVal = [&](std::string propName) -> FbxDouble {
-            const FbxProperty vecProp = mayaProp.FindHierarchical(propName .c_str());
-            return vecProp.IsValid() ? vecProp.Get<FbxDouble>() : 0;
-        };
-
-        res->texNormal = getTex("normal");
-        res->texColor = getTex("color");
-        res->colBase = getVec("base_color");
-        res->texAmbientOcclusion = getTex("ao");
-        res->texEmissive = getTex("emissive");
-        res->colEmissive = getVec("emissive");
-        res->emissiveIntensity = getVal("emissive_intensity");
-        res->texMetallic = getTex("metallic");
-        res->metallic = getVal("metallic");
-        res->texRoughness = getTex("roughness");
-        res->roughness = getVal("roughness");
-
-        return res;
-    }
-};
-
-struct FbxTraditionalMaterialInfo : FbxMaterialInfo {
-    static constexpr const char *FBX_SHADER_LAMBERT = "Lambert";
-    static constexpr const char *FBX_SHADER_BLINN   = "Blinn";
-    static constexpr const char *FBX_SHADER_PHONG   = "Phong";
-
-    FbxTraditionalMaterialInfo(const FbxString &name, const FbxString &shadingModel)
-        : FbxMaterialInfo(name, shadingModel)
-    {}
-
-    FbxFileTexture *texAmbient {};
-    FbxVector4     colAmbient {};
-    FbxFileTexture *texSpecular {};
-    FbxVector4     colSpecular {};
-    FbxFileTexture *texDiffuse {};
-    FbxVector4     colDiffuse {};
-    FbxFileTexture *texEmissive {};
-    FbxVector4     colEmissive {};
-    FbxFileTexture *texNormal {};
-    FbxFileTexture *texShininess {};
-    FbxDouble      shininess {};
-
-    static std::unique_ptr<FbxTraditionalMaterialInfo> From(
-        FbxSurfaceMaterial *fbxMaterial,
-        const std::map<const FbxTexture *, FbxString> &textureLocations)
-    {
-        auto getSurfaceScalar = [&](const char *propName) -> std::tuple<FbxDouble, FbxFileTexture *> {
-            const FbxProperty prop = fbxMaterial->FindProperty(propName);
-
-            FbxDouble val(0);
-            FbxFileTexture *tex = prop.GetSrcObject<FbxFileTexture>();
-            if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
-                tex = nullptr;
-            }
-            if (tex == nullptr && prop.IsValid()) {
-                val = prop.Get<FbxDouble>();
-            }
-            return std::make_tuple(val, tex);
-        };
-
-        auto getSurfaceVector = [&](const char *propName) -> std::tuple<FbxDouble3, FbxFileTexture *> {
-            const FbxProperty prop = fbxMaterial->FindProperty(propName);
-
-            FbxDouble3 val(1, 1, 1);
-            FbxFileTexture *tex = prop.GetSrcObject<FbxFileTexture>();
-            if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
-                tex = nullptr;
-            }
-            if (tex == nullptr && prop.IsValid()) {
-                val = prop.Get<FbxDouble3>();
-            }
-            return std::make_tuple(val, tex);
-        };
-
-        auto getSurfaceValues = [&](const char *colName, const char *facName) -> std::tuple<FbxVector4, FbxFileTexture *, FbxFileTexture *> {
-            const FbxProperty colProp = fbxMaterial->FindProperty(colName);
-            const FbxProperty facProp = fbxMaterial->FindProperty(facName);
-
-            FbxDouble3 colorVal(1, 1, 1);
-            FbxDouble  factorVal(1);
-
-            FbxFileTexture *colTex = colProp.GetSrcObject<FbxFileTexture>();
-            if (colTex != nullptr && textureLocations.find(colTex) == textureLocations.end()) {
-                colTex = nullptr;
-            }
-            if (colTex == nullptr && colProp.IsValid()) {
-                colorVal = colProp.Get<FbxDouble3>();
-            }
-            FbxFileTexture *facTex = facProp.GetSrcObject<FbxFileTexture>();
-            if (facTex != nullptr && textureLocations.find(facTex) == textureLocations.end()) {
-                facTex = nullptr;
-            }
-            if (facTex == nullptr && facProp.IsValid()) {
-                factorVal = facProp.Get<FbxDouble>();
-            }
-
-            auto val = FbxVector4(
-                colorVal[0] * factorVal,
-                colorVal[1] * factorVal,
-                colorVal[2] * factorVal,
-                factorVal);
-            return std::make_tuple(val, colTex, facTex);
-        };
-
-        std::string name = fbxMaterial->GetName();
-        std::unique_ptr<FbxTraditionalMaterialInfo> res(new FbxTraditionalMaterialInfo(name.c_str(), fbxMaterial->ShadingModel.Get()));
-
-        // four properties are on the same structure and follow the same rules
-        auto handleBasicProperty = [&](const char *colName, const char *facName) -> std::tuple<FbxVector4, FbxFileTexture *>{
-            FbxFileTexture *colTex, *facTex;
-            FbxVector4     vec;
-
-            std::tie(vec, colTex, facTex) = getSurfaceValues(colName, facName);
-            if (colTex) {
-                if (facTex) {
-                    fmt::printf("Warning: Mat [%s]: Can't handle both %s and %s textures; discarding %s.\n", name, colName, facName, facName);
-                }
-                return std::make_tuple(vec, colTex);
-            }
-            return std::make_tuple(vec, facTex);
-        };
-
-        std::tie(res->colAmbient, res->texAmbient) =
-            handleBasicProperty(FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor);
-        std::tie(res->colSpecular, res->texSpecular) =
-            handleBasicProperty(FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor);
-        std::tie(res->colDiffuse, res->texDiffuse) =
-            handleBasicProperty(FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor);
-        std::tie(res->colEmissive, res->texEmissive) =
-            handleBasicProperty(FbxSurfaceMaterial::sEmissive, FbxSurfaceMaterial::sEmissiveFactor);
-
-        // the normal map can only ever be a map, ignore everything else
-        std::tie(std::ignore, res->texNormal) = getSurfaceVector(FbxSurfaceMaterial::sNormalMap);
-
-        // shininess can be a map or a factor; afaict the map is always 'ShininessExponent' and the
-        // value is always found in 'Shininess' but only sometimes in 'ShininessExponent'.
-        std::tie(std::ignore, res->texShininess) = getSurfaceScalar("ShininessExponent");
-        std::tie(res->shininess, std::ignore) = getSurfaceScalar("Shininess");
-
-        // for transparency we just want a constant vector value;
-        FbxVector4 transparency;
-        // extract any existing textures only so we can warn that we're throwing them away
-        FbxFileTexture *colTex, *facTex;
-        std::tie(transparency, colTex, facTex) =
-            getSurfaceValues(FbxSurfaceMaterial::sTransparentColor, FbxSurfaceMaterial::sTransparencyFactor);
-        if (colTex) {
-            fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparentColor);
-        }
-        if (facTex) {
-            fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparencyFactor);
-        }
-        // FBX color is RGB, so we calculate the A channel as the average of the FBX transparency color vector
-        res->colDiffuse[3] = 1.0 - (transparency[0] + transparency[1] + transparency[2])/3.0;
-
-        return res;
-    }
-};
-
-
-std::unique_ptr<FbxMaterialInfo>
-GetMaterialInfo(FbxSurfaceMaterial *material, const std::map<const FbxTexture *, FbxString> &textureLocations)
-{
-    std::unique_ptr<FbxMaterialInfo> res;
-    res = FbxRoughMetMaterialInfo::From(material, textureLocations);
-    if (!res) {
-        res = FbxTraditionalMaterialInfo::From(material, textureLocations);
-    }
-    return res;
-}
-
-class FbxMaterialsAccess
-{
-public:
-
-    FbxMaterialsAccess(const FbxMesh *pMesh, const std::map<const FbxTexture *, FbxString> &textureLocations) :
-        mappingMode(FbxGeometryElement::eNone),
-        mesh(nullptr),
-        indices(nullptr)
-    {
-        if (pMesh->GetElementMaterialCount() <= 0) {
-            return;
-        }
-
-        const FbxGeometryElement::EMappingMode materialMappingMode = pMesh->GetElementMaterial()->GetMappingMode();
-        if (materialMappingMode != FbxGeometryElement::eByPolygon && materialMappingMode != FbxGeometryElement::eAllSame) {
-            return;
-        }
-
-        const FbxGeometryElement::EReferenceMode materialReferenceMode = pMesh->GetElementMaterial()->GetReferenceMode();
-        if (materialReferenceMode != FbxGeometryElement::eIndexToDirect) {
-            return;
-        }
-
-        mappingMode = materialMappingMode;
-        mesh        = pMesh;
-        indices     = &pMesh->GetElementMaterial()->GetIndexArray();
-
-        for (int ii = 0; ii < indices->GetCount(); ii++) {
-            int materialNum = indices->GetAt(ii);
-            if (materialNum < 0) {
-                continue;
-            }
-            if (materialNum >= summaries.size()) {
-                summaries.resize(materialNum + 1);
-            }
-            auto summary = summaries[materialNum];
-            if (summary == nullptr) {
-                summary = summaries[materialNum] = GetMaterialInfo(
-                    mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(materialNum),
-                    textureLocations);
-            }
-        }
-    }
-
-    const std::shared_ptr<FbxMaterialInfo> GetMaterial(const int polygonIndex) const
-    {
-        if (mappingMode != FbxGeometryElement::eNone) {
-            const int materialNum = indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0);
-            if (materialNum < 0) {
-                return nullptr;
-            }
-            return summaries.at((unsigned long) materialNum);
-        }
-        return nullptr;
-    }
-
-private:
-    FbxGeometryElement::EMappingMode              mappingMode;
-    std::vector<std::shared_ptr<FbxMaterialInfo>> summaries {};
-    const FbxMesh                                 *mesh;
-    const FbxLayerElementArrayTemplate<int>       *indices;
-};
-
-class FbxSkinningAccess
-{
-public:
-
-    static const int MAX_WEIGHTS = 4;
-
-    FbxSkinningAccess(const FbxMesh *pMesh, FbxScene *pScene, FbxNode *pNode)
-        : rootIndex(-1)
-    {
-        for (int deformerIndex = 0; deformerIndex < pMesh->GetDeformerCount(); deformerIndex++) {
-            FbxSkin *skin = reinterpret_cast< FbxSkin * >( pMesh->GetDeformer(deformerIndex, FbxDeformer::eSkin));
-            if (skin != nullptr) {
-                const int clusterCount = skin->GetClusterCount();
-                if (clusterCount == 0) {
-                    continue;
-                }
-                int controlPointCount = pMesh->GetControlPointsCount();
-                vertexJointIndices.resize(controlPointCount, Vec4i(0, 0, 0, 0));
-                vertexJointWeights.resize(controlPointCount, Vec4f(0.0f, 0.0f, 0.0f, 0.0f));
+#include "FbxBlendShapesAccess.hpp"
+#include "FbxLayerElementAccess.hpp"
+#include "FbxMaterialsAccess.hpp"
+#include "FbxSkinningAccess.hpp"
 
-                for (int clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++) {
-                    FbxCluster   *cluster        = skin->GetCluster(clusterIndex);
-                    const int    indexCount      = cluster->GetControlPointIndicesCount();
-                    const int    *clusterIndices = cluster->GetControlPointIndices();
-                    const double *clusterWeights = cluster->GetControlPointWeights();
-
-                    assert(cluster->GetLinkMode() == FbxCluster::eNormalize);
-
-                    // Transform link matrix.
-                    FbxAMatrix transformLinkMatrix;
-                    cluster->GetTransformLinkMatrix(transformLinkMatrix);
-
-                    // The transformation of the mesh at binding time
-                    FbxAMatrix transformMatrix;
-                    cluster->GetTransformMatrix(transformMatrix);
-
-                    // Inverse bind matrix.
-                    FbxAMatrix globalBindposeInverseMatrix = transformLinkMatrix.Inverse() * transformMatrix;
-                    inverseBindMatrices.emplace_back(globalBindposeInverseMatrix);
-
-                    jointNodes.push_back(cluster->GetLink());
-                    jointIds.push_back(cluster->GetLink()->GetUniqueID());
-
-                    const FbxAMatrix globalNodeTransform = cluster->GetLink()->EvaluateGlobalTransform();
-                    jointSkinningTransforms.push_back(FbxMatrix(globalNodeTransform * globalBindposeInverseMatrix));
-                    jointInverseGlobalTransforms.push_back(FbxMatrix(globalNodeTransform.Inverse()));
-
-                    for (int i = 0; i < indexCount; i++) {
-                        if (clusterIndices[i] < 0 || clusterIndices[i] >= controlPointCount) {
-                            continue;
-                        }
-                        if (clusterWeights[i] <= vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1]) {
-                            continue;
-                        }
-                        vertexJointIndices[clusterIndices[i]][MAX_WEIGHTS - 1] = clusterIndex;
-                        vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1] = (float) clusterWeights[i];
-                        for (int j = MAX_WEIGHTS - 1; j > 0; j--) {
-                            if (vertexJointWeights[clusterIndices[i]][j - 1] >= vertexJointWeights[clusterIndices[i]][j]) {
-                                break;
-                            }
-                            std::swap(vertexJointIndices[clusterIndices[i]][j - 1], vertexJointIndices[clusterIndices[i]][j]);
-                            std::swap(vertexJointWeights[clusterIndices[i]][j - 1], vertexJointWeights[clusterIndices[i]][j]);
-                        }
-                    }
-
-                }
-                for (int i = 0; i < controlPointCount; i++) {
-                    vertexJointWeights[i] = vertexJointWeights[i].Normalized();
-                }
-            }
-        }
-
-        rootIndex = -1;
-        for (size_t i = 0; i < jointNodes.size() && rootIndex == -1; i++) {
-            rootIndex = (int) i;
-            FbxNode *parent = jointNodes[i]->GetParent();
-            if (parent == nullptr) {
-                break;
-            }
-            for (size_t j = 0; j < jointNodes.size(); j++) {
-                if (jointNodes[j] == parent) {
-                    rootIndex = -1;
-                    break;
-                }
-            }
-        }
-    }
-
-    bool IsSkinned() const
-    {
-        return (vertexJointWeights.size() > 0);
-    }
-
-    int GetNodeCount() const
-    {
-        return (int) jointNodes.size();
-    }
-
-    FbxNode *GetJointNode(const int jointIndex) const
-    {
-        return jointNodes[jointIndex];
-    }
-
-    const long GetJointId(const int jointIndex) const
-    {
-        return jointIds[jointIndex];
-    }
-
-    const FbxMatrix &GetJointSkinningTransform(const int jointIndex) const
-    {
-        return jointSkinningTransforms[jointIndex];
-    }
-
-    const FbxMatrix &GetJointInverseGlobalTransforms(const int jointIndex) const
-    {
-        return jointInverseGlobalTransforms[jointIndex];
-    }
-
-    const long GetRootNode() const
-    {
-        assert(rootIndex != -1);
-        return jointIds[rootIndex];
-    }
-
-    const FbxAMatrix &GetInverseBindMatrix(const int jointIndex) const
-    {
-        return inverseBindMatrices[jointIndex];
-    }
-
-    const Vec4i GetVertexIndices(const int controlPointIndex) const
-    {
-        return (!vertexJointIndices.empty()) ?
-               vertexJointIndices[controlPointIndex] : Vec4i(0, 0, 0, 0);
-    }
-
-    const Vec4f GetVertexWeights(const int controlPointIndex) const
-    {
-        return (!vertexJointWeights.empty()) ?
-               vertexJointWeights[controlPointIndex] : Vec4f(0, 0, 0, 0);
-    }
-
-private:
-    int                      rootIndex;
-    std::vector<long>        jointIds;
-    std::vector<FbxNode *>   jointNodes;
-    std::vector<FbxMatrix>   jointSkinningTransforms;
-    std::vector<FbxMatrix>   jointInverseGlobalTransforms;
-    std::vector<FbxAMatrix>  inverseBindMatrices;
-    std::vector<Vec4i>       vertexJointIndices;
-    std::vector<Vec4f>       vertexJointWeights;
-};
-
-/**
- * At the FBX level, each Mesh can have a set of FbxBlendShape deformers; organisational units that contain no data
- * of their own. The actual deformation is determined by one or more FbxBlendShapeChannels, whose influences are all
- * additively applied to the mesh. In a simpler world, each such channel would extend each base vertex with alternate
- * position, and optionally normal and tangent.
- *
- * It's not quite so simple, though. We also have progressive morphing, where one logical morph actually consists of
- * several concrete ones, each applied in sequence. For us, this means each channel contains a sequence of FbxShapes
- * (aka target shape); these are the actual data-holding entities that provide the alternate vertex attributes. As such
- * a channel is given more weight, it moves from one target shape to another.
- *
- * The total number of alternate sets of attributes, then, is the total number of target shapes across all the channels
- * of all the blend shapes of the mesh.
- *
- * Each animation in the scene stack can yield one or zero FbxAnimCurves per channel (not target shape). We evaluate
- * these curves to get the weight of the channel: this weight is further introspected on to figure out which target
- * shapes we're currently interpolation between.
- */
-class FbxBlendShapesAccess
-{
-public:
-    /**
-     * A target shape is on a 1:1 basis with the eventual glTF morph target, and is the object which contains the
-     * actual morphed vertex data.
-     */
-    struct TargetShape
-    {
-        explicit TargetShape(const FbxShape *shape, FbxDouble fullWeight) :
-            shape(shape),
-            fullWeight(fullWeight),
-            count(shape->GetControlPointsCount()),
-            positions(shape->GetControlPoints()),
-            normals(FbxLayerElementAccess<FbxVector4>(shape->GetElementNormal(), shape->GetElementNormalCount())),
-            tangents(FbxLayerElementAccess<FbxVector4>(shape->GetElementTangent(), shape->GetElementTangentCount()))
-        {}
-
-        const FbxShape                          *shape;
-        const FbxDouble                         fullWeight;
-        const unsigned int                      count;
-        const FbxVector4                        *positions;
-        const FbxLayerElementAccess<FbxVector4> normals;
-        const FbxLayerElementAccess<FbxVector4> tangents;
-    };
-
-    /**
-     * A channel collects a sequence (often of length 1) of target shapes.
-     */
-    struct BlendChannel
-    {
-        BlendChannel(
-            FbxMesh *mesh,
-            const unsigned int blendShapeIx,
-            const unsigned int channelIx,
-            const FbxDouble deformPercent,
-            const std::vector<TargetShape> &targetShapes
-        ) : mesh(mesh),
-            blendShapeIx(blendShapeIx),
-            channelIx(channelIx),
-            deformPercent(deformPercent),
-            targetShapes(targetShapes)
-        {}
-
-        FbxAnimCurve *ExtractAnimation(unsigned int animIx) const {
-            FbxAnimStack *stack = mesh->GetScene()->GetSrcObject<FbxAnimStack>(animIx);
-            FbxAnimLayer *layer = stack->GetMember<FbxAnimLayer>(0);
-            return mesh->GetShapeChannel(blendShapeIx, channelIx, layer, true);
-        }
-
-        FbxMesh *const mesh;
-
-        const unsigned int blendShapeIx;
-        const unsigned int channelIx;
-        const std::vector<TargetShape> targetShapes;
-
-        const FbxDouble deformPercent;
-    };
-
-    explicit FbxBlendShapesAccess(FbxMesh *mesh) :
-        channels(extractChannels(mesh))
-    { }
-
-    size_t GetChannelCount() const { return channels.size(); }
-    const BlendChannel &GetBlendChannel(size_t channelIx) const {
-        return channels.at(channelIx);
-    }
-
-    size_t GetTargetShapeCount(size_t channelIx) const { return channels[channelIx].targetShapes.size(); }
-    const TargetShape &GetTargetShape(size_t channelIx, size_t targetShapeIx) const {
-        return channels.at(channelIx).targetShapes[targetShapeIx];
-    }
-
-    FbxAnimCurve * GetAnimation(size_t channelIx, size_t animIx) const {
-        return channels.at(channelIx).ExtractAnimation(animIx);
-    }
-
-private:
-    std::vector<BlendChannel> extractChannels(FbxMesh *mesh) const {
-        std::vector<BlendChannel> channels;
-        for (int shapeIx = 0; shapeIx < mesh->GetDeformerCount(FbxDeformer::eBlendShape); shapeIx++) {
-            auto *fbxBlendShape = static_cast<FbxBlendShape *>(mesh->GetDeformer(shapeIx, FbxDeformer::eBlendShape));
-
-            for (int channelIx = 0; channelIx < fbxBlendShape->GetBlendShapeChannelCount(); ++channelIx) {
-                FbxBlendShapeChannel *fbxChannel = fbxBlendShape->GetBlendShapeChannel(channelIx);
-
-                if (fbxChannel->GetTargetShapeCount() > 0) {
-                    std::vector<TargetShape> targetShapes;
-                    const double *fullWeights = fbxChannel->GetTargetShapeFullWeights();
-                    for (int targetIx = 0; targetIx < fbxChannel->GetTargetShapeCount(); targetIx ++) {
-                        FbxShape *fbxShape = fbxChannel->GetTargetShape(targetIx);
-                        targetShapes.push_back(TargetShape(fbxShape, fullWeights[targetIx]));
-                    }
-                    channels.push_back(BlendChannel(mesh, shapeIx, channelIx, fbxChannel->DeformPercent * 0.01, targetShapes));
-                }
-            }
-        }
-        return channels;
-    }
-
-    const std::vector<BlendChannel> channels;
-};
+float scaleFactor;
 
 static bool TriangleTexturePolarity(const Vec2f &uv0, const Vec2f &uv1, const Vec2f &uv2)
 {
@@ -934,7 +304,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
             }
 
             if (skinning.IsSkinned()) {
-                const int jointIndices[FbxSkinningAccess::MAX_WEIGHTS] = {
+                const int jointIndices[FbxSkinningAccess::MAX_WEIGHTS]   = {
                     vertex.jointIndices[0],
                     vertex.jointIndices[1],
                     vertex.jointIndices[2],
@@ -946,7 +316,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std:
                     vertex.jointWeights[2],
                     vertex.jointWeights[3]
                 };
-                const FbxMatrix skinningMatrix =
+                const FbxMatrix skinningMatrix                           =
                     skinning.GetJointSkinningTransform(jointIndices[0]) * jointWeights[0] +
                     skinning.GetJointSkinningTransform(jointIndices[1]) * jointWeights[1] +
                     skinning.GetJointSkinningTransform(jointIndices[2]) * jointWeights[2] +

+ 2 - 5
src/Fbx2Raw.h → src/fbx/Fbx2Raw.hpp

@@ -7,11 +7,8 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef __FBX2RAW_H__
-#define __FBX2RAW_H__
+#pragma once
 
-#include "RawModel.h"
+#include "raw/RawModel.hpp"
 
 bool LoadFBXFile(RawModel &raw, const char *fbxFileName, const char *textureExtensions);
-
-#endif // !__FBX2RAW_H__

+ 58 - 0
src/fbx/FbxBlendShapesAccess.cpp

@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include "FbxBlendShapesAccess.hpp"
+
+FbxBlendShapesAccess::TargetShape::TargetShape(const FbxShape *shape, FbxDouble fullWeight) :
+    shape(shape),
+    fullWeight(fullWeight),
+    count(shape->GetControlPointsCount()),
+    positions(shape->GetControlPoints()),
+    normals(FbxLayerElementAccess<FbxVector4>(shape->GetElementNormal(), shape->GetElementNormalCount())),
+    tangents(FbxLayerElementAccess<FbxVector4>(shape->GetElementTangent(), shape->GetElementTangentCount()))
+{}
+
+FbxAnimCurve *FbxBlendShapesAccess::BlendChannel::ExtractAnimation(unsigned int animIx) const
+{
+    FbxAnimStack *stack = mesh->GetScene()->GetSrcObject<FbxAnimStack>(animIx);
+    FbxAnimLayer *layer = stack->GetMember<FbxAnimLayer>(0);
+    return mesh->GetShapeChannel(blendShapeIx, channelIx, layer, true);
+}
+
+FbxBlendShapesAccess::BlendChannel::BlendChannel(
+    FbxMesh *mesh, const unsigned int blendShapeIx, const unsigned int channelIx, const FbxDouble deformPercent,
+    const std::vector<FbxBlendShapesAccess::TargetShape> &targetShapes) : mesh(mesh),
+                                                                          blendShapeIx(blendShapeIx),
+                                                                          channelIx(channelIx),
+                                                                          deformPercent(deformPercent),
+                                                                          targetShapes(targetShapes)
+{}
+
+std::vector<FbxBlendShapesAccess::BlendChannel> FbxBlendShapesAccess::extractChannels(FbxMesh *mesh) const
+{
+    std::vector<BlendChannel> channels;
+    for (int                       shapeIx = 0; shapeIx < mesh->GetDeformerCount(FbxDeformer::eBlendShape); shapeIx++) {
+        auto *fbxBlendShape = dynamic_cast<FbxBlendShape *>(mesh->GetDeformer(shapeIx, FbxDeformer::eBlendShape));
+
+        for (int channelIx = 0; channelIx < fbxBlendShape->GetBlendShapeChannelCount(); ++channelIx) {
+            FbxBlendShapeChannel *fbxChannel = fbxBlendShape->GetBlendShapeChannel(channelIx);
+
+            if (fbxChannel->GetTargetShapeCount() > 0) {
+                std::vector<TargetShape> targetShapes;
+                const double *fullWeights = fbxChannel->GetTargetShapeFullWeights();
+                for (int targetIx = 0; targetIx < fbxChannel->GetTargetShapeCount(); targetIx ++) {
+                    FbxShape *fbxShape = fbxChannel->GetTargetShape(targetIx);
+                    targetShapes.emplace_back(fbxShape, fullWeights[targetIx]);
+                }
+                channels.emplace_back(mesh, shapeIx, channelIx, fbxChannel->DeformPercent * 0.01, targetShapes);
+            }
+        }
+    }
+    return channels;
+}

+ 103 - 0
src/fbx/FbxBlendShapesAccess.hpp

@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <fstream>
+#include <string>
+#include <vector>
+#include <algorithm>
+
+#include "FBX2glTF.h"
+#include "FbxLayerElementAccess.hpp"
+
+/**
+ * At the FBX level, each Mesh can have a set of FbxBlendShape deformers; organisational units that contain no data
+ * of their own. The actual deformation is determined by one or more FbxBlendShapeChannels, whose influences are all
+ * additively applied to the mesh. In a simpler world, each such channel would extend each base vertex with alternate
+ * position, and optionally normal and tangent.
+ *
+ * It's not quite so simple, though. We also have progressive morphing, where one logical morph actually consists of
+ * several concrete ones, each applied in sequence. For us, this means each channel contains a sequence of FbxShapes
+ * (aka target shape); these are the actual data-holding entities that provide the alternate vertex attributes. As such
+ * a channel is given more weight, it moves from one target shape to another.
+ *
+ * The total number of alternate sets of attributes, then, is the total number of target shapes across all the channels
+ * of all the blend shapes of the mesh.
+ *
+ * Each animation in the scene stack can yield one or zero FbxAnimCurves per channel (not target shape). We evaluate
+ * these curves to get the weight of the channel: this weight is further introspected on to figure out which target
+ * shapes we're currently interpolation between.
+ */
+class FbxBlendShapesAccess
+{
+public:
+    /**
+     * A target shape is on a 1:1 basis with the eventual glTF morph target, and is the object which contains the
+     * actual morphed vertex data.
+     */
+    struct TargetShape
+    {
+        explicit TargetShape(const FbxShape *shape, FbxDouble fullWeight);
+
+        const FbxShape                          *shape;
+        const FbxDouble                         fullWeight;
+        const unsigned int                      count;
+        const FbxVector4                        *positions;
+        const FbxLayerElementAccess<FbxVector4> normals;
+        const FbxLayerElementAccess<FbxVector4> tangents;
+    };
+
+    /**
+     * A channel collects a sequence (often of length 1) of target shapes.
+     */
+    struct BlendChannel
+    {
+        BlendChannel(
+            FbxMesh *mesh,
+            const unsigned int blendShapeIx,
+            const unsigned int channelIx,
+            const FbxDouble deformPercent,
+            const std::vector<TargetShape> &targetShapes
+        );
+
+        FbxAnimCurve *ExtractAnimation(unsigned int animIx) const;
+
+        FbxMesh *const mesh;
+
+        const unsigned int                  blendShapeIx;
+        const unsigned int                  channelIx;
+        const std::vector<TargetShape> targetShapes;
+
+        const FbxDouble deformPercent;
+    };
+
+    explicit FbxBlendShapesAccess(FbxMesh *mesh) :
+        channels(extractChannels(mesh))
+    { }
+
+    size_t GetChannelCount() const { return channels.size(); }
+    const BlendChannel &GetBlendChannel(size_t channelIx) const {
+        return channels.at(channelIx);
+    }
+
+    size_t GetTargetShapeCount(size_t channelIx) const { return channels[channelIx].targetShapes.size(); }
+    const TargetShape &GetTargetShape(size_t channelIx, size_t targetShapeIx) const {
+        return channels.at(channelIx).targetShapes[targetShapeIx];
+    }
+
+    FbxAnimCurve * GetAnimation(size_t channelIx, size_t animIx) const {
+        return channels.at(channelIx).ExtractAnimation(animIx);
+    }
+
+private:
+    std::vector<BlendChannel> extractChannels(FbxMesh *mesh) const;
+
+    const std::vector<BlendChannel> channels;
+};

+ 83 - 0
src/fbx/FbxLayerElementAccess.hpp

@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+#pragma once
+#include "FBX2glTF.h"
+
+template<typename _type_>
+class FbxLayerElementAccess
+{
+public:
+
+    FbxLayerElementAccess(const FbxLayerElementTemplate<_type_> *layer, int count);
+
+    bool LayerPresent() const
+    {
+        return (mappingMode != FbxLayerElement::eNone);
+    }
+
+    _type_ GetElement(const int polygonIndex, const int polygonVertexIndex, const int controlPointIndex, const _type_ defaultValue) const;
+    _type_ GetElement(
+        const int polygonIndex, const int polygonVertexIndex, const int controlPointIndex, const _type_ defaultValue,
+        const FbxMatrix &transform, const bool normalize) const;
+
+private:
+    FbxLayerElement::EMappingMode              mappingMode;
+    const FbxLayerElementArrayTemplate<_type_> *elements;
+    const FbxLayerElementArrayTemplate<int>    *indices;
+};
+
+template<typename _type_>
+FbxLayerElementAccess<_type_>::FbxLayerElementAccess(const FbxLayerElementTemplate<_type_> *layer, int count) :
+    mappingMode(FbxLayerElement::eNone),
+    elements(nullptr),
+    indices(nullptr)
+{
+    if (count <= 0 || layer == nullptr) {
+        return;
+    }
+    const FbxLayerElement::EMappingMode newMappingMode = layer->GetMappingMode();
+    if (newMappingMode == FbxLayerElement::eByControlPoint ||
+        newMappingMode == FbxLayerElement::eByPolygonVertex ||
+        newMappingMode == FbxLayerElement::eByPolygon) {
+        mappingMode = newMappingMode;
+        elements    = &layer->GetDirectArray();
+        indices     = (
+            layer->GetReferenceMode() == FbxLayerElement::eIndexToDirect ||
+            layer->GetReferenceMode() == FbxLayerElement::eIndex) ? &layer->GetIndexArray() : nullptr;
+    }
+}
+
+template<typename _type_>
+_type_ FbxLayerElementAccess<_type_>::GetElement(
+    const int polygonIndex, const int polygonVertexIndex, const int controlPointIndex, const _type_ defaultValue) const
+{
+    if (mappingMode != FbxLayerElement::eNone) {
+        int index = (mappingMode == FbxLayerElement::eByControlPoint) ? controlPointIndex :
+            ((mappingMode == FbxLayerElement::eByPolygonVertex) ? polygonVertexIndex : polygonIndex);
+        index = (indices != nullptr) ? (*indices)[index] : index;
+        _type_ element = elements->GetAt(index);
+        return element;
+    }
+    return defaultValue;
+}
+
+template<typename _type_>
+_type_ FbxLayerElementAccess<_type_>::GetElement(
+    const int polygonIndex, const int polygonVertexIndex, const int controlPointIndex, const _type_ defaultValue,
+    const FbxMatrix &transform, const bool normalize) const
+{
+    if (mappingMode != FbxLayerElement::eNone) {
+        _type_ element = transform.MultNormalize(GetElement(polygonIndex, polygonVertexIndex, controlPointIndex, defaultValue));
+        if (normalize) {
+            element.Normalize();
+        }
+        return element;
+    }
+    return defaultValue;
+}

+ 22 - 0
src/fbx/FbxMaterialInfo.hpp

@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include "FBX2glTF.h"
+
+class FbxMaterialInfo {
+public:
+    FbxMaterialInfo(const FbxString &name, const FbxString &shadingModel)
+        : name(name),
+          shadingModel(shadingModel) {}
+
+    const FbxString name;
+    const FbxString shadingModel;
+};

+ 74 - 0
src/fbx/FbxMaterialsAccess.cpp

@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include "FbxMaterialsAccess.hpp"
+
+FbxMaterialsAccess::FbxMaterialsAccess(const FbxMesh *pMesh, const std::map<const FbxTexture *, FbxString> &textureLocations) :
+    mappingMode(FbxGeometryElement::eNone),
+    mesh(nullptr),
+    indices(nullptr)
+{
+    if (pMesh->GetElementMaterialCount() <= 0) {
+        return;
+    }
+
+    const FbxGeometryElement::EMappingMode materialMappingMode = pMesh->GetElementMaterial()->GetMappingMode();
+    if (materialMappingMode != FbxGeometryElement::eByPolygon && materialMappingMode != FbxGeometryElement::eAllSame) {
+        return;
+    }
+
+    const FbxGeometryElement::EReferenceMode materialReferenceMode = pMesh->GetElementMaterial()->GetReferenceMode();
+    if (materialReferenceMode != FbxGeometryElement::eIndexToDirect) {
+        return;
+    }
+
+    mappingMode = materialMappingMode;
+    mesh        = pMesh;
+    indices     = &pMesh->GetElementMaterial()->GetIndexArray();
+
+    for (int ii = 0; ii < indices->GetCount(); ii++) {
+        int materialNum = indices->GetAt(ii);
+        if (materialNum < 0) {
+            continue;
+        }
+        if (materialNum >= summaries.size()) {
+            summaries.resize(materialNum + 1);
+        }
+        auto summary = summaries[materialNum];
+        if (summary == nullptr) {
+            summary = summaries[materialNum] = GetMaterialInfo(
+                mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(materialNum),
+                textureLocations);
+        }
+    }
+}
+
+const std::shared_ptr<FbxMaterialInfo> FbxMaterialsAccess::GetMaterial(const int polygonIndex) const
+{
+    if (mappingMode != FbxGeometryElement::eNone) {
+        const int materialNum = indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0);
+        if (materialNum < 0) {
+            return nullptr;
+        }
+        return summaries.at((unsigned long) materialNum);
+    }
+    return nullptr;
+}
+
+std::unique_ptr<FbxMaterialInfo>
+FbxMaterialsAccess::GetMaterialInfo(FbxSurfaceMaterial *material, const std::map<const FbxTexture *, FbxString> &textureLocations)
+{
+    std::unique_ptr<FbxMaterialInfo> res;
+    res = FbxRoughMetMaterialInfo::From(material, textureLocations);
+    if (!res) {
+        res = FbxTraditionalMaterialInfo::From(material, textureLocations);
+    }
+    return res;
+}
+

+ 32 - 0
src/fbx/FbxMaterialsAccess.hpp

@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include "FbxMaterialInfo.hpp"
+#include "FbxTraditionalMaterialInfo.hpp"
+#include "FbxRoughMetMaterialInfo.hpp"
+
+class FbxMaterialsAccess
+{
+public:
+
+    FbxMaterialsAccess(const FbxMesh *pMesh, const std::map<const FbxTexture *, FbxString> &textureLocations);
+
+    const std::shared_ptr<FbxMaterialInfo> GetMaterial(const int polygonIndex) const;
+
+    std::unique_ptr<FbxMaterialInfo>
+    GetMaterialInfo(FbxSurfaceMaterial *material, const std::map<const FbxTexture *, FbxString> &textureLocations);
+
+private:
+    FbxGeometryElement::EMappingMode              mappingMode;
+    std::vector<std::shared_ptr<FbxMaterialInfo>> summaries {};
+    const FbxMesh                                 *mesh;
+    const FbxLayerElementArrayTemplate<int>       *indices;
+};

+ 68 - 0
src/fbx/FbxRoughMetMaterialInfo.cpp

@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+ #include "FbxRoughMetMaterialInfo.hpp"
+
+std::unique_ptr<FbxRoughMetMaterialInfo>
+FbxRoughMetMaterialInfo::From(FbxSurfaceMaterial *fbxMaterial, const std::map<const FbxTexture *, FbxString> &textureLocations)
+{
+    std::unique_ptr<FbxRoughMetMaterialInfo> res(new FbxRoughMetMaterialInfo(fbxMaterial->GetName(), FBX_SHADER_METROUGH));
+
+    const FbxProperty mayaProp = fbxMaterial->FindProperty("Maya");
+    if (mayaProp.GetPropertyDataType() != FbxCompoundDT) {
+        return nullptr;
+    }
+    if (!fbxMaterial->ShadingModel.Get().IsEmpty()) {
+        ::fmt::printf("Warning: Material %s has surprising shading model: %s\n",
+            fbxMaterial->GetName(), fbxMaterial->ShadingModel.Get());
+    }
+
+    auto getTex = [&](std::string propName) {
+        const FbxFileTexture *ptr = nullptr;
+
+        const FbxProperty useProp = mayaProp.FindHierarchical(("use_" + propName + "_map").c_str());
+        if (useProp.IsValid() && useProp.Get<bool>()) {
+            const FbxProperty texProp = mayaProp.FindHierarchical(("TEX_" + propName + "_map").c_str());
+            if (texProp.IsValid()) {
+                ptr = texProp.GetSrcObject<FbxFileTexture>();
+                if (ptr != nullptr && textureLocations.find(ptr) == textureLocations.end()) {
+                    ptr = nullptr;
+                }
+            }
+        } else if (verboseOutput && useProp.IsValid()) {
+            fmt::printf("Note: Property '%s' of material '%s' exists, but is flagged as 'do not use'.\n",
+                propName, fbxMaterial->GetName());
+        }
+        return ptr;
+    };
+
+    auto getVec = [&](std::string propName) -> FbxDouble3 {
+        const FbxProperty vecProp = mayaProp.FindHierarchical(propName.c_str());
+        return vecProp.IsValid() ? vecProp.Get<FbxDouble3>() : FbxDouble3(1, 1, 1);
+    };
+
+    auto getVal = [&](std::string propName) -> FbxDouble {
+        const FbxProperty vecProp = mayaProp.FindHierarchical(propName .c_str());
+        return vecProp.IsValid() ? vecProp.Get<FbxDouble>() : 0;
+    };
+
+    res->texNormal = getTex("normal");
+    res->texColor = getTex("color");
+    res->colBase = getVec("base_color");
+    res->texAmbientOcclusion = getTex("ao");
+    res->texEmissive = getTex("emissive");
+    res->colEmissive = getVec("emissive");
+    res->emissiveIntensity = getVal("emissive_intensity");
+    res->texMetallic = getTex("metallic");
+    res->metallic = getVal("metallic");
+    res->texRoughness = getTex("roughness");
+    res->roughness = getVal("roughness");
+
+    return res;
+}

+ 42 - 0
src/fbx/FbxRoughMetMaterialInfo.hpp

@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <fstream>
+#include <string>
+#include <set>
+#include <map>
+#include <unordered_map>
+#include <vector>
+#include <algorithm>
+
+#include "FbxMaterialInfo.hpp"
+
+struct FbxRoughMetMaterialInfo : FbxMaterialInfo {
+    static constexpr const char *FBX_SHADER_METROUGH = "MetallicRoughness";
+
+    static std::unique_ptr<FbxRoughMetMaterialInfo> From(
+        FbxSurfaceMaterial *fbxMaterial,
+        const std::map<const FbxTexture *, FbxString> &textureLocations);
+
+    FbxRoughMetMaterialInfo(const FbxString &name, const FbxString &shadingModel)
+        : FbxMaterialInfo(name, shadingModel)
+    {}
+
+    const FbxFileTexture *texColor {};
+    FbxVector4           colBase {};
+    const FbxFileTexture *texNormal {};
+    const FbxFileTexture *texMetallic {};
+    FbxDouble            metallic {};
+    const FbxFileTexture *texRoughness {};
+    FbxDouble            roughness {};
+    const FbxFileTexture *texEmissive {};
+    FbxVector4           colEmissive {};
+    FbxDouble            emissiveIntensity {};
+    const FbxFileTexture *texAmbientOcclusion {};
+};

+ 92 - 0
src/fbx/FbxSkinningAccess.cpp

@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include "FbxSkinningAccess.hpp"
+
+FbxSkinningAccess::FbxSkinningAccess(const FbxMesh *pMesh, FbxScene *pScene, FbxNode *pNode)
+    : rootIndex(-1)
+{
+    for (int deformerIndex = 0; deformerIndex < pMesh->GetDeformerCount(); deformerIndex++) {
+        FbxSkin *skin = reinterpret_cast< FbxSkin * >( pMesh->GetDeformer(deformerIndex, FbxDeformer::eSkin));
+        if (skin != nullptr) {
+            const int clusterCount = skin->GetClusterCount();
+            if (clusterCount == 0) {
+                continue;
+            }
+            int controlPointCount = pMesh->GetControlPointsCount();
+            vertexJointIndices.resize(controlPointCount, Vec4i(0, 0, 0, 0));
+            vertexJointWeights.resize(controlPointCount, Vec4f(0.0f, 0.0f, 0.0f, 0.0f));
+
+            for (int clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++) {
+                FbxCluster   *cluster        = skin->GetCluster(clusterIndex);
+                const int    indexCount      = cluster->GetControlPointIndicesCount();
+                const int    *clusterIndices = cluster->GetControlPointIndices();
+                const double *clusterWeights = cluster->GetControlPointWeights();
+
+                assert(cluster->GetLinkMode() == FbxCluster::eNormalize);
+
+                // Transform link matrix.
+                FbxAMatrix transformLinkMatrix;
+                cluster->GetTransformLinkMatrix(transformLinkMatrix);
+
+                // The transformation of the mesh at binding time
+                FbxAMatrix transformMatrix;
+                cluster->GetTransformMatrix(transformMatrix);
+
+                // Inverse bind matrix.
+                FbxAMatrix globalBindposeInverseMatrix = transformLinkMatrix.Inverse() * transformMatrix;
+                inverseBindMatrices.emplace_back(globalBindposeInverseMatrix);
+
+                jointNodes.push_back(cluster->GetLink());
+                jointIds.push_back(cluster->GetLink()->GetUniqueID());
+
+                const FbxAMatrix globalNodeTransform = cluster->GetLink()->EvaluateGlobalTransform();
+                jointSkinningTransforms.push_back(FbxMatrix(globalNodeTransform * globalBindposeInverseMatrix));
+                jointInverseGlobalTransforms.push_back(FbxMatrix(globalNodeTransform.Inverse()));
+
+                for (int i = 0; i < indexCount; i++) {
+                    if (clusterIndices[i] < 0 || clusterIndices[i] >= controlPointCount) {
+                        continue;
+                    }
+                    if (clusterWeights[i] <= vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1]) {
+                        continue;
+                    }
+                    vertexJointIndices[clusterIndices[i]][MAX_WEIGHTS - 1] = clusterIndex;
+                    vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1] = (float) clusterWeights[i];
+                    for (int j = MAX_WEIGHTS - 1; j > 0; j--) {
+                        if (vertexJointWeights[clusterIndices[i]][j - 1] >= vertexJointWeights[clusterIndices[i]][j]) {
+                            break;
+                        }
+                        std::swap(vertexJointIndices[clusterIndices[i]][j - 1], vertexJointIndices[clusterIndices[i]][j]);
+                        std::swap(vertexJointWeights[clusterIndices[i]][j - 1], vertexJointWeights[clusterIndices[i]][j]);
+                    }
+                }
+
+            }
+            for (int i = 0; i < controlPointCount; i++) {
+                vertexJointWeights[i] = vertexJointWeights[i].Normalized();
+            }
+        }
+    }
+
+    rootIndex = -1;
+    for (size_t i = 0; i < jointNodes.size() && rootIndex == -1; i++) {
+        rootIndex = (int) i;
+        FbxNode *parent = jointNodes[i]->GetParent();
+        if (parent == nullptr) {
+            break;
+        }
+        for (size_t j = 0; j < jointNodes.size(); j++) {
+            if (jointNodes[j] == parent) {
+                rootIndex = -1;
+                break;
+            }
+        }
+    }
+}

+ 92 - 0
src/fbx/FbxSkinningAccess.hpp

@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <fstream>
+#include <string>
+#include <set>
+#include <map>
+#include <unordered_map>
+#include <vector>
+#include <algorithm>
+
+#include "FBX2glTF.h"
+
+class FbxSkinningAccess
+{
+public:
+
+    static const int MAX_WEIGHTS = 4;
+
+    FbxSkinningAccess(const FbxMesh *pMesh, FbxScene *pScene, FbxNode *pNode);
+
+    bool IsSkinned() const
+    {
+        return (vertexJointWeights.size() > 0);
+    }
+
+    int GetNodeCount() const
+    {
+        return (int) jointNodes.size();
+    }
+
+    FbxNode *GetJointNode(const int jointIndex) const
+    {
+        return jointNodes[jointIndex];
+    }
+
+    const long GetJointId(const int jointIndex) const
+    {
+        return jointIds[jointIndex];
+    }
+
+    const FbxMatrix &GetJointSkinningTransform(const int jointIndex) const
+    {
+        return jointSkinningTransforms[jointIndex];
+    }
+
+    const FbxMatrix &GetJointInverseGlobalTransforms(const int jointIndex) const
+    {
+        return jointInverseGlobalTransforms[jointIndex];
+    }
+
+    const long GetRootNode() const
+    {
+        assert(rootIndex != -1);
+        return jointIds[rootIndex];
+    }
+
+    const FbxAMatrix &GetInverseBindMatrix(const int jointIndex) const
+    {
+        return inverseBindMatrices[jointIndex];
+    }
+
+    const Vec4i GetVertexIndices(const int controlPointIndex) const
+    {
+        return (!vertexJointIndices.empty()) ?
+               vertexJointIndices[controlPointIndex] : Vec4i(0, 0, 0, 0);
+    }
+
+    const Vec4f GetVertexWeights(const int controlPointIndex) const
+    {
+        return (!vertexJointWeights.empty()) ?
+               vertexJointWeights[controlPointIndex] : Vec4f(0, 0, 0, 0);
+    }
+
+private:
+    int                     rootIndex;
+    std::vector<long>       jointIds;
+    std::vector<FbxNode *>  jointNodes;
+    std::vector<FbxMatrix>  jointSkinningTransforms;
+    std::vector<FbxMatrix>  jointInverseGlobalTransforms;
+    std::vector<FbxAMatrix> inverseBindMatrices;
+    std::vector<Vec4i>      vertexJointIndices;
+    std::vector<Vec4f>      vertexJointWeights;
+};

+ 124 - 0
src/fbx/FbxTraditionalMaterialInfo.cpp

@@ -0,0 +1,124 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include "FbxTraditionalMaterialInfo.hpp"
+
+std::unique_ptr<FbxTraditionalMaterialInfo>
+FbxTraditionalMaterialInfo::From(FbxSurfaceMaterial *fbxMaterial, const std::map<const FbxTexture *, FbxString> &textureLocations)
+{
+    auto getSurfaceScalar = [&](const char *propName) -> std::tuple<FbxDouble, FbxFileTexture *> {
+        const FbxProperty prop = fbxMaterial->FindProperty(propName);
+
+        FbxDouble val(0);
+        FbxFileTexture *tex = prop.GetSrcObject<FbxFileTexture>();
+        if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
+            tex = nullptr;
+        }
+        if (tex == nullptr && prop.IsValid()) {
+            val = prop.Get<FbxDouble>();
+        }
+        return std::make_tuple(val, tex);
+    };
+
+    auto getSurfaceVector = [&](const char *propName) -> std::tuple<FbxDouble3, FbxFileTexture *> {
+        const FbxProperty prop = fbxMaterial->FindProperty(propName);
+
+        FbxDouble3 val(1, 1, 1);
+        FbxFileTexture *tex = prop.GetSrcObject<FbxFileTexture>();
+        if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
+            tex = nullptr;
+        }
+        if (tex == nullptr && prop.IsValid()) {
+            val = prop.Get<FbxDouble3>();
+        }
+        return std::make_tuple(val, tex);
+    };
+
+    auto getSurfaceValues = [&](const char *colName, const char *facName) -> std::tuple<FbxVector4, FbxFileTexture *, FbxFileTexture *> {
+        const FbxProperty colProp = fbxMaterial->FindProperty(colName);
+        const FbxProperty facProp = fbxMaterial->FindProperty(facName);
+
+        FbxDouble3 colorVal(1, 1, 1);
+        FbxDouble  factorVal(1);
+
+        FbxFileTexture *colTex = colProp.GetSrcObject<FbxFileTexture>();
+        if (colTex != nullptr && textureLocations.find(colTex) == textureLocations.end()) {
+            colTex = nullptr;
+        }
+        if (colTex == nullptr && colProp.IsValid()) {
+            colorVal = colProp.Get<FbxDouble3>();
+        }
+        FbxFileTexture *facTex = facProp.GetSrcObject<FbxFileTexture>();
+        if (facTex != nullptr && textureLocations.find(facTex) == textureLocations.end()) {
+            facTex = nullptr;
+        }
+        if (facTex == nullptr && facProp.IsValid()) {
+            factorVal = facProp.Get<FbxDouble>();
+        }
+
+        auto val = FbxVector4(
+            colorVal[0] * factorVal,
+            colorVal[1] * factorVal,
+            colorVal[2] * factorVal,
+            factorVal);
+        return std::make_tuple(val, colTex, facTex);
+    };
+
+    std::string                                 name = fbxMaterial->GetName();
+    std::unique_ptr<FbxTraditionalMaterialInfo> res(new FbxTraditionalMaterialInfo(name.c_str(), fbxMaterial->ShadingModel.Get()));
+
+    // four properties are on the same structure and follow the same rules
+    auto handleBasicProperty = [&](const char *colName, const char *facName) -> std::tuple<FbxVector4, FbxFileTexture *>{
+        FbxFileTexture *colTex, *facTex;
+        FbxVector4     vec;
+
+        std::tie(vec, colTex, facTex) = getSurfaceValues(colName, facName);
+        if (colTex) {
+            if (facTex) {
+                fmt::printf("Warning: Mat [%s]: Can't handle both %s and %s textures; discarding %s.\n", name, colName, facName, facName);
+            }
+            return std::make_tuple(vec, colTex);
+        }
+        return std::make_tuple(vec, facTex);
+    };
+
+    std::tie(res->colAmbient, res->texAmbient)   =
+        handleBasicProperty(FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor);
+    std::tie(res->colSpecular, res->texSpecular) =
+        handleBasicProperty(FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor);
+    std::tie(res->colDiffuse, res->texDiffuse)   =
+        handleBasicProperty(FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor);
+    std::tie(res->colEmissive, res->texEmissive) =
+        handleBasicProperty(FbxSurfaceMaterial::sEmissive, FbxSurfaceMaterial::sEmissiveFactor);
+
+    // the normal map can only ever be a map, ignore everything else
+    tie(std::ignore, res->texNormal) = getSurfaceVector(FbxSurfaceMaterial::sNormalMap);
+
+    // shininess can be a map or a factor; afaict the map is always 'ShininessExponent' and the
+    // value is always found in 'Shininess' but only sometimes in 'ShininessExponent'.
+    tie(std::ignore, res->texShininess) = getSurfaceScalar("ShininessExponent");
+    tie(res->shininess, std::ignore)    = getSurfaceScalar("Shininess");
+
+    // for transparency we just want a constant vector value;
+    FbxVector4 transparency;
+    // extract any existing textures only so we can warn that we're throwing them away
+    FbxFileTexture *colTex, *facTex;
+    std::tie(transparency, colTex, facTex) =
+        getSurfaceValues(FbxSurfaceMaterial::sTransparentColor, FbxSurfaceMaterial::sTransparencyFactor);
+    if (colTex) {
+        fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparentColor);
+    }
+    if (facTex) {
+        fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparencyFactor);
+    }
+    // FBX color is RGB, so we calculate the A channel as the average of the FBX transparency color vector
+    res->colDiffuse[3] = 1.0 - (transparency[0] + transparency[1] + transparency[2])/3.0;
+
+    return res;
+}

+ 44 - 0
src/fbx/FbxTraditionalMaterialInfo.hpp

@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include <fstream>
+#include <string>
+#include <set>
+#include <map>
+#include <unordered_map>
+#include <vector>
+#include <algorithm>
+
+#include "FbxMaterialInfo.hpp"
+
+struct FbxTraditionalMaterialInfo : FbxMaterialInfo {
+    static constexpr const char *FBX_SHADER_LAMBERT = "Lambert";
+    static constexpr const char *FBX_SHADER_BLINN   = "Blinn";
+    static constexpr const char *FBX_SHADER_PHONG   = "Phong";
+
+    FbxTraditionalMaterialInfo(const FbxString &name, const FbxString &shadingModel)
+        : FbxMaterialInfo(name, shadingModel)
+    {}
+
+    FbxFileTexture *texAmbient {};
+    FbxVector4     colAmbient {};
+    FbxFileTexture *texSpecular {};
+    FbxVector4     colSpecular {};
+    FbxFileTexture *texDiffuse {};
+    FbxVector4     colDiffuse {};
+    FbxFileTexture *texEmissive {};
+    FbxVector4     colEmissive {};
+    FbxFileTexture *texNormal {};
+    FbxFileTexture *texShininess {};
+    FbxDouble      shininess {};
+
+    static std::unique_ptr<FbxTraditionalMaterialInfo> From(
+        FbxSurfaceMaterial *fbxMaterial,
+        const std::map<const FbxTexture *, FbxString> &textureLocations);
+};

+ 125 - 0
src/fbx/materials/3dsMaxPhysicalMaterial.cpp

@@ -0,0 +1,125 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include "RoughnessMetallicMaterials.hpp"
+
+std::unique_ptr<FbxRoughMetMaterialInfo> Fbx3dsMaxPhysicalMaterialResolver::resolve() const {
+    const FbxProperty topProp = fbxMaterial->FindProperty("3dsMax");
+    if (topProp.GetPropertyDataType() != FbxCompoundDT) {
+        return nullptr;
+    }
+    const FbxProperty props = fbxMaterial->FindProperty("Parameters");
+
+    FbxString shadingModel = fbxMaterial->ShadingModel.Get();
+    if (!shadingModel.IsEmpty() && shadingModel != "unknown") {
+        ::fmt::printf("Warning: Material %s has surprising shading model: %s\n",
+            fbxMaterial->GetName(), shadingModel);
+    }
+
+    auto getTex = [&](std::string propName) -> const FbxFileTexture * {
+        const FbxFileTexture *ptr = nullptr;
+
+        const FbxProperty useProp = props.FindHierarchical((propName + "_map_on").c_str());
+        if (useProp.IsValid() && useProp.Get<bool>()) {
+            const FbxProperty texProp = useProp.FindHierarchical((propName + "_map").c_str());
+            if (texProp.IsValid()) {
+                ptr = texProp.GetSrcObject<FbxFileTexture>();
+                if (ptr != nullptr && textureLocations.find(ptr) == textureLocations.end()) {
+                    ptr = nullptr;
+                }
+            }
+        } else if (verboseOutput && useProp.IsValid()) {
+            fmt::printf("Note: property '%s' of 3dsMax Physical material '%s' exists, but is flagged as 'off'.\n",
+                propName, fbxMaterial->GetName());
+        }
+        return ptr;
+    };
+
+    int materialMode = getValue(props, "material_mode", 0);
+    fmt::printf("Note: 3dsMax Physical material has material_mode = %d.\n", materialMode);
+
+    // baseWeight && baseColor
+    FbxDouble baseWeight = getValue(props, "base_weight", 1.0);
+    const auto *baseWeightMap = getTex("base_weight");
+    FbxDouble4 baseCol = getValue(props, "base_color", FbxDouble4(0.5, 0.5, 0.5, 1.0));
+    const auto *baseTex = getTex("base_color");
+
+    double emissiveWeight = getValue(props, "emission", 0.0);
+    const auto *emissiveWeightMap = getTex("emission");
+    FbxDouble4 emissiveColor = getValue(props, "emit_color", FbxDouble4(1, 1, 1, 1));
+    const auto *emissiveColorMap = getTex("emit_color");
+    // TODO: emit_luminance, emit_kelvin?
+
+    // roughness & metalness: supported
+    double roughness = getValue(props, "roughness", 0.0);
+    const auto *roughnessMap = getTex("roughness");
+    double metalness = getValue(props, "metalness", 0.0);
+    const auto *metalnessMap = getTex("metalness");
+
+    // TODO: does invertRoughness affect roughness_map too?
+    bool invertRoughness = getValue(props, "inv_roughness", false);
+    if (invertRoughness) {
+        roughness = 1.0f - roughness;
+    }
+
+    // TODO: attempt to bake transparency > 0.0f into the alpha of baseColour?
+    double transparency = getValue(props, "transparency", 0.0);
+    const auto *transparencyMap = getTex("transparency");
+
+    // SSS: not supported
+    double scattering = getValue(props, "scattering", 0.0);
+    const auto *scatteringMap = getTex("scattering");
+
+    // reflectivity: not supported
+    double reflectivityWeight = getValue(props, "reflectivity", 1.);
+    const auto *reflectivityWeightMap = getTex("reflectivity");
+    FbxDouble4 reflectivityColor = getValue(props, "refl_color", FbxDouble4(1, 1, 1, 1));
+    const auto *reflectivityColorMap = getTex("refl_color");
+
+    // coatings: not supported
+    double coating = getValue(props, "coating", 0.0);
+
+    // diffuse roughness: not supported
+    double diffuseRoughness = getValue(props, "diff_roughness", 0.);
+
+    // explicit brdf curve control: not supported
+    bool isBrdfMode = getValue(props, "brdf_mode", false);
+
+    // anisotrophy: not supported
+    double anisotropy = getValue(props, "anisotropy", 1.0);
+
+    // TODO: how the heck do we combine these to generate a normal map?
+    const auto *bumpMap = getTex("bump");
+    const auto *displacementMap = getTex("displacement");
+
+    std::unique_ptr<FbxRoughMetMaterialInfo> res(
+        new FbxRoughMetMaterialInfo(
+            fbxMaterial->GetName(),
+            FbxRoughMetMaterialInfo::FBX_SHADER_METROUGH,
+            baseCol,
+            metalness,
+            roughness
+        )
+    );
+    res->texBaseColor = baseTex;
+    res->baseWeight = baseWeight;
+    res->texBaseWeight = baseWeightMap;
+
+    res->texMetallic = metalnessMap;
+    res->texRoughness = roughnessMap;
+
+    res->texNormal = bumpMap; // TODO LOL NO NONO
+
+    res->emissive = emissiveColor;
+    res->emissiveIntensity = emissiveWeight;
+    res->texEmissive = emissiveColorMap;
+    res->texEmissiveWeight = emissiveWeightMap;
+
+    return res;
+}

+ 78 - 0
src/fbx/materials/FbxMaterials.cpp

@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include "FbxMaterials.hpp"
+#include "RoughnessMetallicMaterials.hpp"
+#include "TraditionalMaterials.hpp"
+
+FbxMaterialsAccess::FbxMaterialsAccess(const FbxMesh *pMesh, const std::map<const FbxTexture *, FbxString> &textureLocations) :
+    mappingMode(FbxGeometryElement::eNone),
+    mesh(nullptr),
+    indices(nullptr)
+{
+    if (pMesh->GetElementMaterialCount() <= 0) {
+        return;
+    }
+
+    const FbxGeometryElement::EMappingMode materialMappingMode = pMesh->GetElementMaterial()->GetMappingMode();
+    if (materialMappingMode != FbxGeometryElement::eByPolygon && materialMappingMode != FbxGeometryElement::eAllSame) {
+        return;
+    }
+
+    const FbxGeometryElement::EReferenceMode materialReferenceMode = pMesh->GetElementMaterial()->GetReferenceMode();
+    if (materialReferenceMode != FbxGeometryElement::eIndexToDirect) {
+        return;
+    }
+
+    mappingMode = materialMappingMode;
+    mesh        = pMesh;
+    indices     = &pMesh->GetElementMaterial()->GetIndexArray();
+
+    for (int ii = 0; ii < indices->GetCount(); ii++) {
+        int materialNum = indices->GetAt(ii);
+        if (materialNum < 0) {
+            continue;
+        }
+        if (materialNum >= summaries.size()) {
+            summaries.resize(materialNum + 1);
+        }
+        auto summary = summaries[materialNum];
+        if (summary == nullptr) {
+            summary = summaries[materialNum] = GetMaterialInfo(
+                mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(materialNum),
+                textureLocations);
+        }
+    }
+}
+
+const std::shared_ptr<FbxMaterialInfo> FbxMaterialsAccess::GetMaterial(const int polygonIndex) const
+{
+    if (mappingMode != FbxGeometryElement::eNone) {
+        const int materialNum = indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0);
+        if (materialNum < 0) {
+            return nullptr;
+        }
+        return summaries.at((unsigned long) materialNum);
+    }
+    return nullptr;
+}
+
+std::unique_ptr<FbxMaterialInfo>
+FbxMaterialsAccess::GetMaterialInfo(FbxSurfaceMaterial *material, const std::map<const FbxTexture *, FbxString> &textureLocations)
+{
+    std::unique_ptr<FbxMaterialInfo> res = FbxStingrayPBSMaterialResolver(material, textureLocations).resolve();
+    if (res == nullptr) {
+        res = Fbx3dsMaxPhysicalMaterialResolver(material, textureLocations).resolve();
+        if (res == nullptr) {
+            res = FbxTraditionalMaterialResolver(material, textureLocations).resolve();
+        }
+    }
+    return res;
+}
+

+ 60 - 0
src/fbx/materials/FbxMaterials.hpp

@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <map>
+#include <vector>
+
+#include "FBX2glTF.h"
+
+class FbxMaterialInfo {
+public:
+    FbxMaterialInfo(const FbxString &name, const FbxString &shadingModel)
+        : name(name)
+        , shadingModel(shadingModel)
+    {}
+
+    const FbxString name;
+    const FbxString shadingModel;
+};
+
+template <class T>
+class FbxMaterialResolver
+{
+public:
+    FbxMaterialResolver(
+        FbxSurfaceMaterial *fbxMaterial,
+        const std::map<const FbxTexture *, FbxString> &textureLocations)
+        : fbxMaterial(fbxMaterial)
+        , textureLocations(textureLocations)
+    {}
+    virtual std::unique_ptr<T> resolve() const = 0;
+
+protected:
+    const FbxSurfaceMaterial *fbxMaterial;
+    const std::map<const FbxTexture *, FbxString> textureLocations;
+};
+
+class FbxMaterialsAccess
+{
+public:
+    FbxMaterialsAccess(const FbxMesh *pMesh, const std::map<const FbxTexture *, FbxString> &textureLocations);
+
+    const std::shared_ptr<FbxMaterialInfo> GetMaterial(const int polygonIndex) const;
+
+    std::unique_ptr<FbxMaterialInfo>
+    GetMaterialInfo(FbxSurfaceMaterial *material, const std::map<const FbxTexture *, FbxString> &textureLocations);
+
+private:
+    FbxGeometryElement::EMappingMode              mappingMode;
+    std::vector<std::shared_ptr<FbxMaterialInfo>> summaries {};
+    const FbxMesh                                 *mesh;
+    const FbxLayerElementArrayTemplate<int>       *indices;
+};

+ 81 - 0
src/fbx/materials/RoughnessMetallicMaterials.hpp

@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <map>
+
+#include "FbxMaterials.hpp"
+
+struct FbxRoughMetMaterialInfo : FbxMaterialInfo {
+    static constexpr const char *FBX_SHADER_METROUGH = "MetallicRoughness";
+
+    static std::unique_ptr<FbxRoughMetMaterialInfo> From(
+        FbxSurfaceMaterial *fbxMaterial,
+        const std::map<const FbxTexture *, FbxString> &textureLocations);
+
+    FbxRoughMetMaterialInfo(
+        const FbxString &name,
+        const FbxString &shadingModel,
+        FbxDouble4 baseColor,
+        FbxDouble metallic,
+        FbxDouble roughness
+    )
+        : FbxMaterialInfo(name, shadingModel)
+        , baseColor(baseColor)
+        , metallic(metallic)
+        , roughness(roughness)
+    {}
+
+    const FbxVector4     baseColor;
+    const FbxDouble      metallic;
+    const FbxDouble      roughness;
+
+    FbxDouble            baseWeight = 1;
+    FbxVector4           emissive = FbxVector4(0, 0, 0, 1);
+    FbxDouble            emissiveIntensity = 1;
+
+    const FbxFileTexture *texNormal = nullptr;
+    const FbxFileTexture *texBaseColor = nullptr;
+    const FbxFileTexture *texBaseWeight = nullptr;
+    const FbxFileTexture *texMetallic = nullptr;
+    const FbxFileTexture *texRoughness = nullptr;
+    const FbxFileTexture *texEmissive = nullptr;
+    const FbxFileTexture *texEmissiveWeight = nullptr;
+    const FbxFileTexture *texAmbientOcclusion = nullptr;
+};
+
+class FbxStingrayPBSMaterialResolver : FbxMaterialResolver<FbxRoughMetMaterialInfo> {
+public:
+    FbxStingrayPBSMaterialResolver(
+        FbxSurfaceMaterial *fbxMaterial,
+        const std::map<const FbxTexture *, FbxString> &textureLocations)
+        : FbxMaterialResolver(fbxMaterial, textureLocations)
+    {}
+
+    virtual std::unique_ptr<FbxRoughMetMaterialInfo> resolve() const;
+};
+
+class Fbx3dsMaxPhysicalMaterialResolver : FbxMaterialResolver<FbxRoughMetMaterialInfo> {
+public:
+    Fbx3dsMaxPhysicalMaterialResolver(
+        FbxSurfaceMaterial *fbxMaterial,
+        const std::map<const FbxTexture *, FbxString> &textureLocations)
+        : FbxMaterialResolver(fbxMaterial, textureLocations)
+    {}
+
+    virtual std::unique_ptr<FbxRoughMetMaterialInfo> resolve() const;
+
+private:
+    template<typename T>
+    T getValue(const FbxProperty &props, std::string propName, const T& default) const {
+        const FbxProperty prop = props.FindHierarchical(propName.c_str());
+        return prop.IsValid() ? prop.Get<T>() : default;
+    }
+};

+ 71 - 0
src/fbx/materials/StingrayPBSMaterial.cpp

@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include "RoughnessMetallicMaterials.hpp"
+
+std::unique_ptr<FbxRoughMetMaterialInfo> FbxStingrayPBSMaterialResolver::resolve() const {
+    const FbxProperty mayaProp = fbxMaterial->FindProperty("Maya");
+    if (mayaProp.GetPropertyDataType() != FbxCompoundDT) {
+        return nullptr;
+    }
+    if (!fbxMaterial->ShadingModel.Get().IsEmpty()) {
+        ::fmt::printf("Warning: Material %s has surprising shading model: %s\n",
+            fbxMaterial->GetName(), fbxMaterial->ShadingModel.Get());
+    }
+
+    auto getTex = [&](std::string propName) {
+        const FbxFileTexture *ptr = nullptr;
+
+        const FbxProperty useProp = mayaProp.FindHierarchical(("use_" + propName + "_map").c_str());
+        if (useProp.IsValid() && useProp.Get<bool>()) {
+            const FbxProperty texProp = mayaProp.FindHierarchical(("TEX_" + propName + "_map").c_str());
+            if (texProp.IsValid()) {
+                ptr = texProp.GetSrcObject<FbxFileTexture>();
+                if (ptr != nullptr && textureLocations.find(ptr) == textureLocations.end()) {
+                    ptr = nullptr;
+                }
+            }
+        } else if (verboseOutput && useProp.IsValid()) {
+            fmt::printf("Note: Property '%s' of Stingray PBS material '%s' exists, but is flagged as 'do not use'.\n",
+                propName, fbxMaterial->GetName());
+        }
+        return ptr;
+    };
+
+    auto getVec = [&](std::string propName) -> FbxDouble3 {
+        const FbxProperty vecProp = mayaProp.FindHierarchical(propName.c_str());
+        return vecProp.IsValid() ? vecProp.Get<FbxDouble3>() : FbxDouble3(1, 1, 1);
+    };
+
+    auto getVal = [&](std::string propName) -> FbxDouble {
+        const FbxProperty vecProp = mayaProp.FindHierarchical(propName .c_str());
+        return vecProp.IsValid() ? vecProp.Get<FbxDouble>() : 0;
+    };
+
+    FbxDouble3 baseColor = getVec("base_color");
+    std::unique_ptr<FbxRoughMetMaterialInfo> res(
+        new FbxRoughMetMaterialInfo(
+            fbxMaterial->GetName(),
+            FbxRoughMetMaterialInfo::FBX_SHADER_METROUGH,
+            FbxDouble4(baseColor[0], baseColor[1], baseColor[2], 1),
+            getVal("metallic"),
+            getVal("roughness")
+        )
+    );
+    res->texNormal = getTex("normal");
+    res->texBaseColor = getTex("color");
+    res->texAmbientOcclusion = getTex("ao");
+    res->texEmissive = getTex("emissive");
+    res->emissive = getVec("emissive");
+    res->emissiveIntensity = getVal("emissive_intensity");
+    res->texMetallic = getTex("metallic");
+    res->texRoughness = getTex("roughness");
+
+    return res;
+};

+ 123 - 0
src/fbx/materials/TraditionalMaterials.cpp

@@ -0,0 +1,123 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include "TraditionalMaterials.hpp"
+
+std::unique_ptr<FbxTraditionalMaterialInfo> FbxTraditionalMaterialResolver::resolve() const
+{
+    auto getSurfaceScalar = [&](const char *propName) -> std::tuple<FbxDouble, FbxFileTexture *> {
+        const FbxProperty prop = fbxMaterial->FindProperty(propName);
+
+        FbxDouble val(0);
+        FbxFileTexture *tex = prop.GetSrcObject<FbxFileTexture>();
+        if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
+            tex = nullptr;
+        }
+        if (tex == nullptr && prop.IsValid()) {
+            val = prop.Get<FbxDouble>();
+        }
+        return std::make_tuple(val, tex);
+    };
+
+    auto getSurfaceVector = [&](const char *propName) -> std::tuple<FbxDouble3, FbxFileTexture *> {
+        const FbxProperty prop = fbxMaterial->FindProperty(propName);
+
+        FbxDouble3 val(1, 1, 1);
+        FbxFileTexture *tex = prop.GetSrcObject<FbxFileTexture>();
+        if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
+            tex = nullptr;
+        }
+        if (tex == nullptr && prop.IsValid()) {
+            val = prop.Get<FbxDouble3>();
+        }
+        return std::make_tuple(val, tex);
+    };
+
+    auto getSurfaceValues = [&](const char *colName, const char *facName) -> std::tuple<FbxVector4, FbxFileTexture *, FbxFileTexture *> {
+        const FbxProperty colProp = fbxMaterial->FindProperty(colName);
+        const FbxProperty facProp = fbxMaterial->FindProperty(facName);
+
+        FbxDouble3 colorVal(1, 1, 1);
+        FbxDouble  factorVal(1);
+
+        FbxFileTexture *colTex = colProp.GetSrcObject<FbxFileTexture>();
+        if (colTex != nullptr && textureLocations.find(colTex) == textureLocations.end()) {
+            colTex = nullptr;
+        }
+        if (colTex == nullptr && colProp.IsValid()) {
+            colorVal = colProp.Get<FbxDouble3>();
+        }
+        FbxFileTexture *facTex = facProp.GetSrcObject<FbxFileTexture>();
+        if (facTex != nullptr && textureLocations.find(facTex) == textureLocations.end()) {
+            facTex = nullptr;
+        }
+        if (facTex == nullptr && facProp.IsValid()) {
+            factorVal = facProp.Get<FbxDouble>();
+        }
+
+        auto val = FbxVector4(
+            colorVal[0] * factorVal,
+            colorVal[1] * factorVal,
+            colorVal[2] * factorVal,
+            factorVal);
+        return std::make_tuple(val, colTex, facTex);
+    };
+
+    std::string name = fbxMaterial->GetName();
+    std::unique_ptr<FbxTraditionalMaterialInfo> res(new FbxTraditionalMaterialInfo(name.c_str(), fbxMaterial->ShadingModel.Get()));
+
+    // four properties are on the same structure and follow the same rules
+    auto handleBasicProperty = [&](const char *colName, const char *facName) -> std::tuple<FbxVector4, FbxFileTexture *>{
+        FbxFileTexture *colTex, *facTex;
+        FbxVector4     vec;
+
+        std::tie(vec, colTex, facTex) = getSurfaceValues(colName, facName);
+        if (colTex) {
+            if (facTex) {
+                fmt::printf("Warning: Mat [%s]: Can't handle both %s and %s textures; discarding %s.\n", name, colName, facName, facName);
+            }
+            return std::make_tuple(vec, colTex);
+        }
+        return std::make_tuple(vec, facTex);
+    };
+
+    std::tie(res->colAmbient, res->texAmbient)   =
+        handleBasicProperty(FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor);
+    std::tie(res->colSpecular, res->texSpecular) =
+        handleBasicProperty(FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor);
+    std::tie(res->colDiffuse, res->texDiffuse)   =
+        handleBasicProperty(FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor);
+    std::tie(res->colEmissive, res->texEmissive) =
+        handleBasicProperty(FbxSurfaceMaterial::sEmissive, FbxSurfaceMaterial::sEmissiveFactor);
+
+    // the normal map can only ever be a map, ignore everything else
+    tie(std::ignore, res->texNormal) = getSurfaceVector(FbxSurfaceMaterial::sNormalMap);
+
+    // shininess can be a map or a factor; afaict the map is always 'ShininessExponent' and the
+    // value is always found in 'Shininess' but only sometimes in 'ShininessExponent'.
+    tie(std::ignore, res->texShininess) = getSurfaceScalar("ShininessExponent");
+    tie(res->shininess, std::ignore)    = getSurfaceScalar("Shininess");
+
+    // for transparency we just want a constant vector value;
+    FbxVector4 transparency;
+    // extract any existing textures only so we can warn that we're throwing them away
+    FbxFileTexture *colTex, *facTex;
+    std::tie(transparency, colTex, facTex) =
+        getSurfaceValues(FbxSurfaceMaterial::sTransparentColor, FbxSurfaceMaterial::sTransparencyFactor);
+    if (colTex) {
+        fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparentColor);
+    }
+    if (facTex) {
+        fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparencyFactor);
+    }
+    // FBX color is RGB, so we calculate the A channel as the average of the FBX transparency color vector
+    res->colDiffuse[3] = 1.0 - (transparency[0] + transparency[1] + transparency[2])/3.0;
+
+    return res;
+}

+ 43 - 0
src/fbx/materials/TraditionalMaterials.hpp

@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#include "FbxMaterials.hpp"
+
+struct FbxTraditionalMaterialInfo : FbxMaterialInfo {
+    static constexpr const char *FBX_SHADER_LAMBERT = "Lambert";
+    static constexpr const char *FBX_SHADER_BLINN   = "Blinn";
+    static constexpr const char *FBX_SHADER_PHONG   = "Phong";
+
+    FbxTraditionalMaterialInfo(const FbxString &name, const FbxString &shadingModel)
+        : FbxMaterialInfo(name, shadingModel)
+    {}
+
+    FbxFileTexture *texAmbient {};
+    FbxVector4     colAmbient {};
+    FbxFileTexture *texSpecular {};
+    FbxVector4     colSpecular {};
+    FbxFileTexture *texDiffuse {};
+    FbxVector4     colDiffuse {};
+    FbxFileTexture *texEmissive {};
+    FbxVector4     colEmissive {};
+    FbxFileTexture *texNormal {};
+    FbxFileTexture *texShininess {};
+    FbxDouble      shininess {};
+};
+
+class FbxTraditionalMaterialResolver : FbxMaterialResolver<FbxTraditionalMaterialInfo> {
+public:
+    FbxTraditionalMaterialResolver(
+        FbxSurfaceMaterial *fbxMaterial,
+        const std::map<const FbxTexture *, FbxString> &textureLocations)
+        : FbxMaterialResolver(fbxMaterial, textureLocations)
+    {}
+
+    virtual std::unique_ptr<FbxTraditionalMaterialInfo> resolve() const;
+};

+ 10 - 0
src/gltf/GltfModel.cpp

@@ -0,0 +1,10 @@
+/**
+* Copyright (c) 2014-present, Facebook, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the BSD-style license found in the
+* LICENSE file in the root directory of this source tree. An additional grant
+* of patent rights can be found in the PATENTS file in the same directory.
+*/
+
+#include "GltfModel.hpp"

+ 189 - 0
src/gltf/GltfModel.hpp

@@ -0,0 +1,189 @@
+/**
+* Copyright (c) 2014-present, Facebook, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the BSD-style license found in the
+* LICENSE file in the root directory of this source tree. An additional grant
+* of patent rights can be found in the PATENTS file in the same directory.
+*/
+
+#pragma once
+
+#include "FBX2glTF.h"
+
+/**
+* glTF 2.0 is based on the idea that data structs within a file are referenced by index; an accessor will
+* point to the n:th buffer view, and so on. The Holder class takes a freshly instantiated class, and then
+* creates, stored, and returns a shared_ptr<T> for it.
+*
+* The idea is that every glTF resource in the file will live as long as the Holder does, and the Holders
+* are all kept in the GLTFData struct. Clients may certainly cnhoose to perpetuate the full shared_ptr<T>
+* reference counting type, but generally speaking we pass around simple T& and T* types because the GLTFData
+* struct will, by design, outlive all other activity that takes place during in a single conversion run.
+*/
+template<typename T>
+struct Holder
+{
+    std::vector<std::shared_ptr<T>> ptrs;
+    std::shared_ptr<T> hold(T *ptr)
+    {
+        ptr->ix = ptrs.size();
+        ptrs.emplace_back(ptr);
+        return ptrs.back();
+    }
+};
+
+struct GltfModel
+{
+    explicit GltfModel(bool _isGlb)
+        : binary(new std::vector<uint8_t>),
+        isGlb(_isGlb)
+    {
+    }
+
+    std::shared_ptr<BufferViewData> GetAlignedBufferView(BufferData &buffer, const BufferViewData::GL_ArrayType target)
+    {
+        unsigned long bufferSize = this->binary->size();
+        if ((bufferSize % 4) > 0) {
+            bufferSize += (4 - (bufferSize % 4));
+            this->binary->resize(bufferSize);
+        }
+        return this->bufferViews.hold(new BufferViewData(buffer, bufferSize, target));
+    }
+
+    // add a bufferview on the fly and copy data into it
+    std::shared_ptr<BufferViewData> AddRawBufferView(BufferData &buffer, const char *source, uint32_t bytes)
+    {
+        auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
+        bufferView->byteLength = bytes;
+
+        // make space for the new bytes (possibly moving the underlying data)
+        unsigned long bufferSize = this->binary->size();
+        this->binary->resize(bufferSize + bytes);
+
+        // and copy them into place
+        memcpy(&(*this->binary)[bufferSize], source, bytes);
+        return bufferView;
+    }
+
+    std::shared_ptr<BufferViewData> AddBufferViewForFile(BufferData &buffer, const std::string &filename)
+    {
+        // see if we've already created a BufferViewData for this precise file
+        auto iter = filenameToBufferView.find(filename);
+        if (iter != filenameToBufferView.end()) {
+            return iter->second;
+        }
+
+        std::shared_ptr<BufferViewData> result;
+        std::ifstream file(filename, std::ios::binary | std::ios::ate);
+        if (file) {
+            std::streamsize size = file.tellg();
+            file.seekg(0, std::ios::beg);
+
+            std::vector<char> fileBuffer(size);
+            if (file.read(fileBuffer.data(), size)) {
+                result = AddRawBufferView(buffer, fileBuffer.data(), size);
+            } else {
+                fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping file.\n", size, filename);
+            }
+        } else {
+            fmt::printf("Warning: Couldn't open file %s, skipping file.\n", filename);
+        }
+        // note that we persist here not only success, but also failure, as nullptr
+        filenameToBufferView[filename] = result;
+        return result;
+    }
+
+
+    template<class T>
+    std::shared_ptr<AccessorData> AddAccessorWithView(
+        BufferViewData &bufferView, const GLType &type, const std::vector<T> &source)
+    {
+        auto accessor = accessors.hold(new AccessorData(bufferView, type));
+        accessor->appendAsBinaryArray(source, *binary);
+        bufferView.byteLength = accessor->byteLength();
+        return accessor;
+    }
+
+    template<class T>
+    std::shared_ptr<AccessorData> AddAccessorAndView(
+        BufferData &buffer, const GLType &type, const std::vector<T> &source)
+    {
+        auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
+        return AddAccessorWithView(*bufferView, type, source);
+    }
+
+    template<class T>
+    std::shared_ptr<AccessorData> AddAttributeToPrimitive(
+        BufferData &buffer, const RawModel &surfaceModel, PrimitiveData &primitive,
+        const AttributeDefinition<T> &attrDef)
+    {
+        // copy attribute data into vector
+        std::vector<T> attribArr;
+        surfaceModel.GetAttributeArray<T>(attribArr, attrDef.rawAttributeIx);
+
+        std::shared_ptr<AccessorData> accessor;
+        if (attrDef.dracoComponentType != draco::DT_INVALID && primitive.dracoMesh != nullptr) {
+            primitive.AddDracoAttrib(attrDef, attribArr);
+
+            accessor = accessors.hold(new AccessorData(attrDef.glType));
+            accessor->count = attribArr.size();
+        } else {
+            auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER);
+            accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr);
+        }
+        primitive.AddAttrib(attrDef.gltfName, *accessor);
+        return accessor;
+    };
+
+    template<class T>
+    void serializeHolder(json &glTFJson, std::string key, const Holder<T> holder)
+    {
+        if (!holder.ptrs.empty()) {
+            std::vector<json> bits;
+            for (const auto &ptr : holder.ptrs) {
+                bits.push_back(ptr->serialize());
+            }
+            glTFJson[key] = bits;
+        }
+    }
+
+    void serializeHolders(json &glTFJson)
+    {
+        serializeHolder(glTFJson, "buffers", buffers);
+        serializeHolder(glTFJson, "bufferViews", bufferViews);
+        serializeHolder(glTFJson, "scenes", scenes);
+        serializeHolder(glTFJson, "accessors", accessors);
+        serializeHolder(glTFJson, "images", images);
+        serializeHolder(glTFJson, "samplers", samplers);
+        serializeHolder(glTFJson, "textures", textures);
+        serializeHolder(glTFJson, "materials", materials);
+        serializeHolder(glTFJson, "meshes", meshes);
+        serializeHolder(glTFJson, "skins", skins);
+        serializeHolder(glTFJson, "animations", animations);
+        serializeHolder(glTFJson, "cameras", cameras);
+        serializeHolder(glTFJson, "nodes", nodes);
+    }
+
+    const bool isGlb;
+
+    // cache BufferViewData instances that've already been created from a given filename
+    std::map<std::string, std::shared_ptr<BufferViewData>> filenameToBufferView;
+
+    std::shared_ptr<std::vector<uint8_t> > binary;
+
+
+    Holder<BufferData>     buffers;
+    Holder<BufferViewData> bufferViews;
+    Holder<AccessorData>   accessors;
+    Holder<ImageData>      images;
+    Holder<SamplerData>    samplers;
+    Holder<TextureData>    textures;
+    Holder<MaterialData>   materials;
+    Holder<MeshData>       meshes;
+    Holder<SkinData>       skins;
+    Holder<AnimationData>  animations;
+    Holder<CameraData>     cameras;
+    Holder<NodeData>       nodes;
+    Holder<SceneData>      scenes;
+};

+ 26 - 28
src/Raw2Gltf.cpp → src/gltf/Raw2Gltf.cpp

@@ -7,6 +7,8 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
+#include "Raw2Gltf.hpp"
+
 #include <cstdint>
 #include <cassert>
 #include <iostream>
@@ -15,33 +17,29 @@
 #include <stb_image.h>
 #include <stb_image_write.h>
 
-#include "FBX2glTF.h"
-#include "utils/String_Utils.h"
-#include "utils/Image_Utils.h"
-#include <utils/File_Utils.h>
-#include "RawModel.h"
-#include "Raw2Gltf.h"
-
-#include "glTF/AccessorData.h"
-#include "glTF/AnimationData.h"
-#include "glTF/BufferData.h"
-#include "glTF/BufferViewData.h"
-#include "glTF/CameraData.h"
-#include "glTF/ImageData.h"
-#include "glTF/MaterialData.h"
-#include "glTF/MeshData.h"
-#include "glTF/NodeData.h"
-#include "glTF/PrimitiveData.h"
-#include "glTF/SamplerData.h"
-#include "glTF/SceneData.h"
-#include "glTF/SkinData.h"
-#include "glTF/TextureData.h"
+#include "utils/String_Utils.hpp"
+#include "utils/Image_Utils.hpp"
+#include <utils/File_Utils.hpp>
+#include "raw/RawModel.hpp"
+
+#include "gltf/properties/AccessorData.hpp"
+#include "gltf/properties/AnimationData.hpp"
+#include "gltf/properties/BufferData.hpp"
+#include "gltf/properties/BufferViewData.hpp"
+#include "gltf/properties/CameraData.hpp"
+#include "gltf/properties/ImageData.hpp"
+#include "gltf/properties/MaterialData.hpp"
+#include "gltf/properties/MeshData.hpp"
+#include "gltf/properties/NodeData.hpp"
+#include "gltf/properties/PrimitiveData.hpp"
+#include "gltf/properties/SamplerData.hpp"
+#include "gltf/properties/SceneData.hpp"
+#include "gltf/properties/SkinData.hpp"
+#include "gltf/properties/TextureData.hpp"
 
 typedef uint32_t TriangleIndex;
 
-extern bool verboseOutput;
-
-const static std::string defaultSceneName = "Root Scene";
+#define DEFAULT_SCENE_NAME "Root Scene"
 
 /**
  * glTF 2.0 is based on the idea that data structs within a file are referenced by index; an accessor will
@@ -585,7 +583,7 @@ ModelData *Raw2Gltf(
                 auto bufferView = gltf->AddBufferViewForFile(buffer, rawTexture.fileLocation);
                 if (bufferView) {
                     std::string suffix = StringUtils::GetFileSuffixString(rawTexture.fileLocation);
-                    image = new ImageData(relativeFilename, *bufferView, suffixToMimeType(suffix));
+                    image = new ImageData(relativeFilename, *bufferView, ImageUtils::suffixToMimeType(suffix));
                 }
 
             } else if (!relativeFilename.empty()) {
@@ -695,7 +693,7 @@ ModelData *Raw2Gltf(
                     aoMetRoughTex = merge3Tex("ao_met_rough",
                         RAW_TEXTURE_USAGE_OCCLUSION, RAW_TEXTURE_USAGE_METALLIC, RAW_TEXTURE_USAGE_ROUGHNESS,
                         [&](const std::vector<const pixel *> pixels) -> pixel {
-                            return { (*pixels[0])[0], (*pixels[2])[0], (*pixels[1])[0], 1 };
+                            return { {(*pixels[0])[0], (*pixels[2])[0], (*pixels[1])[0], 1} };
                         },
                         false);
                     baseColorTex      = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
@@ -738,7 +736,7 @@ ModelData *Raw2Gltf(
                             [&](const std::vector<const pixel *> pixels) -> pixel {
                                 // do not multiply with props->shininess; that doesn't work like the other factors.
                                 float shininess = props->shininess * (*pixels[0])[0];
-                                return { 0, getRoughness(shininess), metallic, 1 };
+                                return { {0, getRoughness(shininess), metallic, 1} };
                             },
                             false);
                         if (aoMetRoughTex != nullptr) {
@@ -1058,7 +1056,7 @@ ModelData *Raw2Gltf(
     }
 
     NodeData        &rootNode  = require(nodesById, raw.GetRootNode());
-    const SceneData &rootScene = *gltf->scenes.hold(new SceneData(defaultSceneName, rootNode));
+    const SceneData &rootScene = *gltf->scenes.hold(new SceneData(DEFAULT_SCENE_NAME, rootNode));
 
     if (options.outputBinary) {
         // note: glTF binary is little-endian

+ 2 - 5
src/Raw2Gltf.h → src/gltf/Raw2Gltf.hpp

@@ -7,8 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef __RAW2GLTF_H__
-#define __RAW2GLTF_H__
+#pragma once
 
 #include <memory>
 #include <string>
@@ -26,7 +25,7 @@ using workaround_fifo_map = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<
 using json = nlohmann::basic_json<workaround_fifo_map>;
 
 #include "FBX2glTF.h"
-#include "RawModel.h"
+#include "raw/RawModel.hpp"
 
 const std::string KHR_DRACO_MESH_COMPRESSION            = "KHR_draco_mesh_compression";
 const std::string KHR_MATERIALS_CMN_UNLIT               = "KHR_materials_unlit";
@@ -193,5 +192,3 @@ ModelData *Raw2Gltf(
     const RawModel &raw,
     const GltfOptions &options
 );
-
-#endif // !__RAW2GLTF_H__

+ 206 - 0
src/gltf/TextureBuilder.cpp

@@ -0,0 +1,206 @@
+/**
+* Copyright (c) 2014-present, Facebook, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the BSD-style license found in the
+* LICENSE file in the root directory of this source tree. An additional grant
+* of patent rights can be found in the PATENTS file in the same directory.
+*/
+
+#include "TextureBuilder.hpp"
+
+#include <stb_image.h>
+#include <stb_image_write.h>
+
+#include <utils/Image_Utils.hpp>
+#include <utils/String_Utils.hpp>
+#include <gltf/properties/ImageData.hpp>
+
+// keep track of some texture data as we load them
+struct TexInfo {
+    explicit TexInfo(int rawTexIx) : rawTexIx(rawTexIx) {}
+
+    const int rawTexIx;
+    int width {};
+    int height {};
+    int channels {};
+    uint8_t *pixels {};
+};
+
+std::shared_ptr<TextureData> TextureBuilder::combine(
+    const std::vector<int> &ixVec,
+    const std::string &tag,
+    const pixel_merger &computePixel)
+{
+    const std::string key = texIndicesKey(ixVec, tag);
+    auto iter = textureByIndicesKey.find(key);
+    if (iter != textureByIndicesKey.end()) {
+        return iter->second;
+    }
+
+    int width = -1, height = -1;
+    std::string mergedFilename = tag;
+    std::vector<TexInfo> texes { };
+    for (const int rawTexIx : ixVec) {
+        TexInfo info(rawTexIx);
+        if (rawTexIx >= 0) {
+            const RawTexture  &rawTex  = raw.GetTexture(rawTexIx);
+            const std::string &fileLoc = rawTex.fileLocation;
+            const std::string &name    = StringUtils::GetFileBaseString(StringUtils::GetFileNameString(fileLoc));
+            if (!fileLoc.empty()) {
+                info.pixels = stbi_load(fileLoc.c_str(), &info.width, &info.height, &info.channels, 0);
+                if (!info.pixels) {
+                    fmt::printf("Warning: merge texture [%d](%s) could not be loaded.\n",
+                        rawTexIx,
+                        name);
+                } else {
+                    if (width < 0) {
+                        width = info.width;
+                        height = info.height;
+                    } else if (width != info.width || height != info.height) {
+                        fmt::printf("Warning: texture %s (%d, %d) can't be merged with previous texture(s) of dimension (%d, %d)\n",
+                            name,
+                            info.width, info.height, width, height);
+                        // this is bad enough that we abort the whole merge
+                        return nullptr;
+                    }
+                    mergedFilename += "_" + name;
+                }
+            }
+        }
+        texes.push_back(info);
+    }
+    // at the moment, the best choice of filename is also the best choice of name
+    const std::string mergedName = mergedFilename;
+
+    if (width < 0) {
+        // no textures to merge; bail
+        return nullptr;
+    }
+    // TODO: which channel combinations make sense in input files?
+
+    // write 3 or 4 channels depending on whether or not we need transparency
+    int channels = transparentOutput ? 4 : 3;
+
+    std::vector<uint8_t> mergedPixels(static_cast<size_t>(channels * width * height));
+    std::vector<pixel> pixels(texes.size());
+    std::vector<const pixel *> pixelPointers(texes.size());
+    for (int xx = 0; xx < width; xx ++) {
+        for (int yy = 0; yy < height; yy ++) {
+            pixels.clear();
+            for (int jj = 0; jj < texes.size(); jj ++) {
+                const TexInfo &tex = texes[jj];
+                // each texture's structure will depend on its channel count
+                int ii = tex.channels * (xx + yy*width);
+                int kk = 0;
+                if (tex.pixels != nullptr) {
+                    for (; kk < tex.channels; kk ++) {
+                        pixels[jj][kk] = tex.pixels[ii++] / 255.0f;
+                    }
+                }
+                for (; kk < pixels[jj].size(); kk ++) {
+                    pixels[jj][kk] = 1.0f;
+                }
+                pixelPointers[jj] = &pixels[jj];
+            }
+            const pixel merged = computePixel(pixelPointers);
+            int ii = channels * (xx + yy*width);
+            for (int jj = 0; jj < channels; jj ++) {
+                mergedPixels[ii + jj] = static_cast<uint8_t>(fmax(0, fmin(255.0f, merged[jj] * 255.0f)));
+            }
+        }
+    }
+
+    // write a .png iff we need transparency in the destination texture
+    bool png = transparentOutput;
+
+    std::vector<char> imgBuffer;
+    int res;
+    if (png) {
+        res = stbi_write_png_to_func(WriteToVectorContext, &imgBuffer,
+            width, height, channels, mergedPixels.data(), width * channels);
+    } else {
+        res = stbi_write_jpg_to_func(WriteToVectorContext, &imgBuffer,
+            width, height, channels, mergedPixels.data(), 80);
+    }
+    if (!res) {
+        fmt::printf("Warning: failed to generate merge texture '%s'.\n", mergedFilename);
+        return nullptr;
+    }
+
+    ImageData *image;
+    if (options.outputBinary) {
+        const auto bufferView = gltf->AddRawBufferView(buffer, imgBuffer.data(), imgBuffer.size());
+        return std::make_unique<ImageData>(mergedName, *bufferView, png ? "image/png" : "image/jpeg");
+    }
+    const std::string imageFilename = mergedFilename + (png ? ".png" : ".jpg");
+    const std::string imagePath = outputFolder + imageFilename;
+    FILE *fp = fopen(imagePath.c_str(), "wb");
+    if (fp == nullptr) {
+        fmt::printf("Warning:: Couldn't write file '%s' for writing.\n", imagePath);
+        return nullptr;
+    }
+
+    if (fwrite(imgBuffer.data(), imgBuffer.size(), 1, fp) != 1) {
+        fmt::printf("Warning: Failed to write %lu bytes to file '%s'.\n", imgBuffer.size(), imagePath);
+        fclose(fp);
+        return nullptr;
+    }
+    fclose(fp);
+    if (verboseOutput) {
+        fmt::printf("Wrote %lu bytes to texture '%s'.\n", imgBuffer.size(), imagePath);
+    }
+    std::shared_ptr<TextureData> texDat = gltf->textures.hold(
+        new TextureData(mergedName, defaultSampler, *image));
+    textureByIndicesKey.insert(std::make_pair(key, texDat));
+
+    return std::make_unique<TextureData>(mergedName, imageFilename);
+}
+
+/** Create a new TextureData for the given RawTexture index, or return a previously created one. */
+std::shared_ptr<TextureData> TextureBuilder::simple(int rawTexIndex, const std::string &tag) {
+    const std::string key = texIndicesKey({ rawTexIndex }, tag);
+    auto iter = textureByIndicesKey.find(key);
+    if (iter != textureByIndicesKey.end()) {
+        return iter->second;
+    }
+
+    const RawTexture  &rawTexture      = raw.GetTexture(rawTexIndex);
+    const std::string textureName      = StringUtils::GetFileBaseString(rawTexture.name);
+    const std::string relativeFilename = StringUtils::GetFileNameString(rawTexture.fileLocation);
+
+    ImageData *image = nullptr;
+    if (options.outputBinary) {
+        auto bufferView = gltf->AddBufferViewForFile(buffer, rawTexture.fileLocation);
+        if (bufferView) {
+            std::string suffix = StringUtils::GetFileSuffixString(rawTexture.fileLocation);
+            image = new ImageData(relativeFilename, *bufferView, ImageUtils::suffixToMimeType(suffix));
+        }
+
+    } else if (!relativeFilename.empty()) {
+        image = new ImageData(relativeFilename, relativeFilename);
+        std::string outputPath = outputFolder + relativeFilename;
+        if (FileUtils::CopyFile(rawTexture.fileLocation, outputPath)) {
+            if (verboseOutput) {
+                fmt::printf("Copied texture '%s' to output folder: %s\n", textureName, outputPath);
+            }
+        } else {
+            // no point commenting further on read/write error; CopyFile() does enough of that, and we
+            // certainly want to to add an image struct to the glTF JSON, with the correct relative path
+            // reference, even if the copy failed.
+        }
+    }
+    if (!image) {
+        // fallback is tiny transparent PNG
+        image = new ImageData(
+            textureName,
+            "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
+        );
+    }
+
+    std::shared_ptr<TextureData> texDat = gltf->textures.hold(
+        new TextureData(textureName, defaultSampler, *gltf->images.hold(image)));
+    textureByIndicesKey.insert(std::make_pair(key, texDat));
+    return texDat;
+
+}

+ 68 - 0
src/gltf/TextureBuilder.hpp

@@ -0,0 +1,68 @@
+/**
+* Copyright (c) 2014-present, Facebook, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the BSD-style license found in the
+* LICENSE file in the root directory of this source tree. An additional grant
+* of patent rights can be found in the PATENTS file in the same directory.
+*/
+
+#pragma once
+
+#include <functional>
+
+#include "FBX2glTF.h"
+
+#include <gltf/properties/ImageData.hpp>
+
+using pixel = std::array<float, 4>; // pixel components are floats in [0, 1]
+using pixel_merger = std::function<pixel(const std::vector<const pixel *>)>;
+
+class TextureBuilder
+{
+public:
+    TextureBuilder(const RawModel &raw, GltfModel &gltf)
+        : raw(raw)
+        , gltf(gltf)
+    {}
+    ~TextureBuilder() {}
+
+    std::shared_ptr<TextureData> combine(
+        const std::vector<int> &ixVec,
+        const std::string &tag,
+        const pixel_merger &mergeFunction
+    );
+
+    std::shared_ptr<TextureData> simple(int rawTexIndex, const std::string &tag);
+
+    static std::string texIndicesKey(const std::vector<int> &ixVec, const std::string &tag) {
+        std::string result = tag;
+        for (int ix : ixVec) {
+            result += "_" + std::to_string(ix);
+        }
+        return result;
+    };
+
+    static std::string describeChannel(int channels) {
+        switch(channels) {
+        case 1: return "G";
+        case 2: return "GA";
+        case 3: return "RGB";
+        case 4: return "RGBA";
+        default:
+            return fmt::format("?%d?", channels);
+        }
+    };
+
+    static void WriteToVectorContext(void *context, void *data, int size) {
+        auto *vec = static_cast<std::vector<char> *>(context);
+        for (int ii = 0; ii < size; ii ++) {
+            vec->push_back(((char *) data)[ii]);
+        }
+    }
+
+private:
+    const RawModel &raw;
+    GltfModel &gltf;
+    std::map<std::string, std::shared_ptr<TextureData>> textureByIndicesKey;
+};

+ 2 - 2
src/glTF/AccessorData.cpp → src/gltf/properties/AccessorData.cpp

@@ -7,8 +7,8 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "AccessorData.h"
-#include "BufferViewData.h"
+#include "AccessorData.hpp"
+#include "BufferViewData.hpp"
 
 AccessorData::AccessorData(const BufferViewData &bufferView, GLType type)
     : Holdable(),

+ 2 - 5
src/glTF/AccessorData.h → src/gltf/properties/AccessorData.hpp

@@ -7,10 +7,9 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_ACCESSORDATA_H
-#define FBX2GLTF_ACCESSORDATA_H
+#pragma once
 
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
 struct AccessorData : Holdable
 {
@@ -44,5 +43,3 @@ struct AccessorData : Holdable
     std::vector<float> min;
     std::vector<float> max;
 };
-
-#endif //FBX2GLTF_ACCESSORDATA_H

+ 3 - 3
src/glTF/AnimationData.cpp → src/gltf/properties/AnimationData.cpp

@@ -7,12 +7,12 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "AnimationData.h"
+#include "AnimationData.hpp"
 
 #include <utility>
 
-#include "AccessorData.h"
-#include "NodeData.h"
+#include "AccessorData.hpp"
+#include "NodeData.hpp"
 
 AnimationData::AnimationData(std::string name, const AccessorData &timeAccessor)
     : Holdable(),

+ 2 - 5
src/glTF/AnimationData.h → src/gltf/properties/AnimationData.hpp

@@ -7,10 +7,9 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_ANIMATIONDATA_H
-#define FBX2GLTF_ANIMATIONDATA_H
+#pragma once
 
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
 struct AnimationData : Holdable
 {
@@ -47,5 +46,3 @@ struct AnimationData : Holdable
 
 void to_json(json &j, const AnimationData::channel_t &data);
 void to_json(json &j, const AnimationData::sampler_t &data);
-
-#endif //FBX2GLTF_ANIMATIONDATA_H

+ 1 - 1
src/glTF/BufferData.cpp → src/gltf/properties/BufferData.cpp

@@ -9,7 +9,7 @@
 
 #include <cppcodec/base64_default_rfc4648.hpp>
 
-#include "BufferData.h"
+#include "BufferData.hpp"
 
 BufferData::BufferData(const std::shared_ptr<const std::vector<uint8_t> > &binData)
     : Holdable(),

+ 2 - 6
src/glTF/BufferData.h → src/gltf/properties/BufferData.hpp

@@ -7,10 +7,9 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_BUFFERDATA_H
-#define FBX2GLTF_BUFFERDATA_H
+#pragma once
 
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
 struct BufferData : Holdable
 {
@@ -24,6 +23,3 @@ struct BufferData : Holdable
     const std::string                                  uri;
     const std::shared_ptr<const std::vector<uint8_t> > binData; // TODO this is just weird
 };
-
-
-#endif //FBX2GLTF_BUFFERDATA_H

+ 2 - 2
src/glTF/BufferViewData.cpp → src/gltf/properties/BufferViewData.cpp

@@ -7,8 +7,8 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "BufferViewData.h"
-#include "BufferData.h"
+#include "BufferViewData.hpp"
+#include "BufferData.hpp"
 
 BufferViewData::BufferViewData(const BufferData &_buffer, const size_t _byteOffset, const GL_ArrayType _target)
     : Holdable(),

+ 2 - 5
src/glTF/BufferViewData.h → src/gltf/properties/BufferViewData.hpp

@@ -7,10 +7,9 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_BUFFERVIEW_H
-#define FBX2GLTF_BUFFERVIEW_H
+#pragma once
 
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
 struct BufferViewData : Holdable
 {
@@ -31,5 +30,3 @@ struct BufferViewData : Holdable
 
     unsigned int byteLength = 0;
 };
-
-#endif //FBX2GLTF_BUFFERVIEW_H

+ 1 - 1
src/glTF/CameraData.cpp → src/gltf/properties/CameraData.cpp

@@ -7,7 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "CameraData.h"
+#include "CameraData.hpp"
 
 CameraData::CameraData()
     : Holdable(),

+ 2 - 5
src/glTF/CameraData.h → src/gltf/properties/CameraData.hpp

@@ -7,10 +7,9 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_CAMERADATA_H
-#define FBX2GLTF_CAMERADATA_H
+#pragma once
 
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
 // TODO: this class needs some work
 struct CameraData : Holdable
@@ -27,5 +26,3 @@ struct CameraData : Holdable
     float       znear;
     float       zfar;
 };
-
-#endif //FBX2GLTF_CAMERADATA_H

+ 2 - 2
src/glTF/ImageData.cpp → src/gltf/properties/ImageData.cpp

@@ -7,11 +7,11 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "ImageData.h"
+#include "ImageData.hpp"
 
 #include <utility>
 
-#include "BufferViewData.h"
+#include "BufferViewData.hpp"
 
 ImageData::ImageData(std::string name, std::string uri)
     : Holdable(),

+ 2 - 5
src/glTF/ImageData.h → src/gltf/properties/ImageData.hpp

@@ -7,10 +7,9 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_IMAGEDATA_H
-#define FBX2GLTF_IMAGEDATA_H
+#pragma once
 
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
 struct ImageData : Holdable
 {
@@ -24,5 +23,3 @@ struct ImageData : Holdable
     const int32_t     bufferView; // non-negative in glb mode
     const std::string mimeType;
 };
-
-#endif //FBX2GLTF_IMAGEDATA_H

+ 2 - 2
src/glTF/MaterialData.cpp → src/gltf/properties/MaterialData.cpp

@@ -7,8 +7,8 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "MaterialData.h"
-#include "TextureData.h"
+#include "MaterialData.hpp"
+#include "TextureData.hpp"
 
 // TODO: retrieve & pass in correct UV set from FBX
 std::unique_ptr<Tex> Tex::ref(const TextureData *tex, uint32_t texCoord)

+ 2 - 5
src/glTF/MaterialData.h → src/gltf/properties/MaterialData.hpp

@@ -7,12 +7,11 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_MATERIALDATA_H
-#define FBX2GLTF_MATERIALDATA_H
+#pragma once
 
 #include <string>
 
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
 struct Tex
 {
@@ -67,5 +66,3 @@ struct MaterialData : Holdable
 void to_json(json &j, const Tex &data);
 void to_json(json &j, const KHRCmnUnlitMaterial &d);
 void to_json(json &j, const PBRMetallicRoughness &d);
-
-#endif //FBX2GLTF_MATERIALDATA_H

+ 2 - 2
src/glTF/MeshData.cpp → src/gltf/properties/MeshData.cpp

@@ -7,8 +7,8 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "MeshData.h"
-#include "PrimitiveData.h"
+#include "MeshData.hpp"
+#include "PrimitiveData.hpp"
 
 MeshData::MeshData(const std::string &name, const std::vector<float> &weights)
     : Holdable(),

+ 3 - 6
src/glTF/MeshData.h → src/gltf/properties/MeshData.hpp

@@ -7,16 +7,15 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_MESHDATA_H
-#define FBX2GLTF_MESHDATA_H
+#pragma once
 
 #include <string>
 
 #include <draco/compression/encode.h>
 
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
-#include "PrimitiveData.h"
+#include "PrimitiveData.hpp"
 
 struct MeshData : Holdable
 {
@@ -33,5 +32,3 @@ struct MeshData : Holdable
     const std::vector<float>                    weights;
     std::vector<std::shared_ptr<PrimitiveData>> primitives;
 };
-
-#endif //FBX2GLTF_MESHDATA_H

+ 2 - 2
src/glTF/NodeData.cpp → src/gltf/properties/NodeData.cpp

@@ -7,7 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "NodeData.h"
+#include "NodeData.hpp"
 
 NodeData::NodeData(
     std::string name, const Vec3f &translation,
@@ -55,7 +55,7 @@ json NodeData::serialize() const
     json result = { { "name", name } };
 
     // if any of the T/R/S have NaN components, just leave them out of the glTF
-    auto maybeAdd = [&](std::string key, std::vector<float> vec) {
+    auto maybeAdd = [&](std::string key, std::vector<float> vec) -> void {
         if (std::none_of(vec.begin(), vec.end(), [&](float n) { return isnan(n); })) {
             result[key] = vec;
         }

+ 2 - 5
src/glTF/NodeData.h → src/gltf/properties/NodeData.hpp

@@ -7,10 +7,9 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_NODEDATA_H
-#define FBX2GLTF_NODEDATA_H
+#pragma once
 
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
 struct NodeData : Holdable
 {
@@ -34,5 +33,3 @@ struct NodeData : Holdable
     int32_t                  skin;
     std::vector<std::string> skeletons;
 };
-
-#endif //FBX2GLTF_NODEDATA_H

+ 6 - 6
src/glTF/PrimitiveData.cpp → src/gltf/properties/PrimitiveData.cpp

@@ -7,11 +7,11 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "PrimitiveData.h"
+#include "PrimitiveData.hpp"
 
-#include "MaterialData.h"
-#include "AccessorData.h"
-#include "BufferViewData.h"
+#include "MaterialData.hpp"
+#include "AccessorData.hpp"
+#include "BufferViewData.hpp"
 
 PrimitiveData::PrimitiveData(const AccessorData &indices, const MaterialData &material, std::shared_ptr<draco::Mesh> dracoMesh)
     : indices(indices.ix),
@@ -43,8 +43,8 @@ void PrimitiveData::AddTarget(const AccessorData *positions, const AccessorData
 {
     targetAccessors.push_back(std::make_tuple(
         positions->ix,
-        normals ? normals->ix : -1,
-        tangents ? tangents ->ix : -1
+        normals != nullptr ? normals->ix : -1,
+        tangents != nullptr ? tangents ->ix : -1
     ));
 }
 

+ 2 - 5
src/glTF/PrimitiveData.h → src/gltf/properties/PrimitiveData.hpp

@@ -7,10 +7,9 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_PRIMITIVEDATA_H
-#define FBX2GLTF_PRIMITIVEDATA_H
+#pragma once
 
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
 struct PrimitiveData
 {
@@ -71,5 +70,3 @@ struct PrimitiveData
 };
 
 void to_json(json &j, const PrimitiveData &d);
-
-#endif //FBX2GLTF_PRIMITIVEDATA_H

+ 3 - 6
src/glTF/SamplerData.h → src/gltf/properties/SamplerData.hpp

@@ -7,10 +7,9 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_SAMPLERDATA_H
-#define FBX2GLTF_SAMPLERDATA_H
+#pragma once
 
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
 struct SamplerData : Holdable
 {
@@ -20,9 +19,7 @@ struct SamplerData : Holdable
     {
     }
 
-    json serialize() const {
+    json serialize() const override {
         return json::object();
     }
 };
-
-#endif //FBX2GLTF_SAMPLERDATA_H

+ 2 - 2
src/glTF/SceneData.cpp → src/gltf/properties/SceneData.cpp

@@ -7,9 +7,9 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "SceneData.h"
+#include "SceneData.hpp"
 
-#include "NodeData.h"
+#include "NodeData.hpp"
 
 SceneData::SceneData(std::string name, const NodeData &rootNode)
     : Holdable(),

+ 1 - 6
src/glTF/SceneData.h → src/gltf/properties/SceneData.hpp

@@ -7,10 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_SCENEDATA_H
-#define FBX2GLTF_SCENEDATA_H
-
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
 struct SceneData : Holdable
 {
@@ -21,5 +18,3 @@ struct SceneData : Holdable
     const std::string     name;
     std::vector<uint32_t> nodes;
 };
-
-#endif //FBX2GLTF_SCENEDATA_H

+ 3 - 3
src/glTF/SkinData.cpp → src/gltf/properties/SkinData.cpp

@@ -7,10 +7,10 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "SkinData.h"
+#include "SkinData.hpp"
 
-#include "AccessorData.h"
-#include "NodeData.h"
+#include "AccessorData.hpp"
+#include "NodeData.hpp"
 
 SkinData::SkinData(
     const std::vector<uint32_t> joints, const AccessorData &inverseBindMatricesAccessor,

+ 1 - 6
src/glTF/SkinData.h → src/gltf/properties/SkinData.hpp

@@ -7,10 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_SKINDATA_H
-#define FBX2GLTF_SKINDATA_H
-
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
 struct SkinData : Holdable
 {
@@ -24,5 +21,3 @@ struct SkinData : Holdable
     const uint32_t              skeletonRootNode;
     const uint32_t              inverseBindMatrices;
 };
-
-#endif //FBX2GLTF_SKINDATA_H

+ 3 - 3
src/glTF/TextureData.cpp → src/gltf/properties/TextureData.cpp

@@ -7,10 +7,10 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "TextureData.h"
+#include "TextureData.hpp"
 
-#include "ImageData.h"
-#include "SamplerData.h"
+#include "ImageData.hpp"
+#include "SamplerData.hpp"
 
 TextureData::TextureData(std::string name, const SamplerData &sampler, const ImageData &source)
     : Holdable(),

+ 1 - 6
src/glTF/TextureData.h → src/gltf/properties/TextureData.hpp

@@ -7,10 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef FBX2GLTF_TEXTUREDATA_H
-#define FBX2GLTF_TEXTUREDATA_H
-
-#include "Raw2Gltf.h"
+#include "gltf/Raw2Gltf.hpp"
 
 struct TextureData : Holdable
 {
@@ -22,5 +19,3 @@ struct TextureData : Holdable
     const uint32_t    sampler;
     const uint32_t    source;
 };
-
-#endif //FBX2GLTF_TEXTUREDATA_H

+ 12 - 14
src/mathfu.h → src/mathfu.hpp

@@ -3,11 +3,11 @@
  * All rights reserved.
  *
  * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant 
+ * LICENSE file in the root directory of this source tree. An additional grant
  * of patent rights can be found in the PATENTS file in the same directory.
  */
-#ifndef FBX2GLTF_MATHFU_H
-#define FBX2GLTF_MATHFU_H
+
+#pragma once
 
 #include <fbxsdk.h>
 
@@ -58,12 +58,12 @@ typedef mathfu::Matrix<float, 4>    Mat4f;
 typedef mathfu::Quaternion<float>   Quatf;
 typedef Bounds<float, 3>            Boundsf;
 
-const Vec3f VEC3F_ONE  = Vec3f {1.0f};
-const Vec3f VEC3F_ZERO = Vec3f {0.0f};
-const Vec4f VEC4F_ONE  = Vec4f {1.0f};
-const Vec4f VEC4F_ZERO = Vec4f {0.0f};
+#define VEC3F_ONE    (Vec3f {1.0f})
+#define VEC3F_ZERO   (Vec3f {0.0f})
+#define VEC4F_ONE    (Vec4f {1.0f})
+#define VEC4F_ZERO   (Vec4f {0.0f})
 
-template<class T, int d> static inline std::vector<T> toStdVec(const mathfu::Vector <T, d> &vec)
+template<class T, int d> inline std::vector<T> toStdVec(const mathfu::Vector <T, d> &vec)
 {
     std::vector<T> result(d);
     for (int ii = 0; ii < d; ii ++) {
@@ -76,15 +76,15 @@ template<class T> std::vector<T> toStdVec(const mathfu::Quaternion<T> &quat) {
     return std::vector<T> { quat.vector()[0], quat.vector()[1], quat.vector()[2], quat.scalar() };
 }
 
-static inline Vec3f toVec3f(const FbxVector4 &v) {
+inline Vec3f toVec3f(const FbxVector4 &v) {
     return Vec3f((float) v[0], (float) v[1], (float) v[2]);
 }
 
-static inline Vec4f toVec4f(const FbxVector4 &v) {
+inline Vec4f toVec4f(const FbxVector4 &v) {
     return Vec4f((float) v[0], (float) v[1], (float) v[2], (float) v[3]);
 }
 
-static inline Mat4f toMat4f(const FbxAMatrix &m) {
+inline Mat4f toMat4f(const FbxAMatrix &m) {
     auto result = Mat4f();
     for (int row = 0; row < 4; row ++) {
         for (int col = 0; col < 4; col ++) {
@@ -94,8 +94,6 @@ static inline Mat4f toMat4f(const FbxAMatrix &m) {
     return result;
 }
 
-static inline Quatf toQuatf(const FbxQuaternion &q) {
+inline Quatf toQuatf(const FbxQuaternion &q) {
     return Quatf((float) q[3], (float) q[0], (float) q[1], (float) q[2]);
 }
-
-#endif //FBX2GLTF_MATHFU_H

+ 8 - 9
src/RawModel.cpp → src/raw/RawModel.cpp

@@ -7,6 +7,8 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
+#include "RawModel.hpp"
+
 #include <vector>
 #include <string>
 #include <unordered_map>
@@ -18,12 +20,8 @@
 #include <algorithm>
 #endif
 
-#include "FBX2glTF.h"
-#include "utils/String_Utils.h"
-#include "utils/Image_Utils.h"
-#include "RawModel.h"
-
-extern bool verboseOutput;
+#include "utils/String_Utils.hpp"
+#include "utils/Image_Utils.hpp"
 
 bool RawVertex::operator==(const RawVertex &other) const
 {
@@ -96,7 +94,8 @@ int RawModel::AddTexture(const std::string &name, const std::string &fileName, c
         }
     }
 
-    const ImageProperties properties = GetImageProperties(!fileLocation.empty() ? fileLocation.c_str() : fileName.c_str());
+    const ImageUtils::ImageProperties
+        properties = ImageUtils::GetImageProperties(!fileLocation.empty() ? fileLocation.c_str() : fileName.c_str());
 
     RawTexture texture;
     texture.name         = name;
@@ -104,7 +103,7 @@ int RawModel::AddTexture(const std::string &name, const std::string &fileName, c
     texture.height       = properties.height;
     texture.mipLevels    = (int) ceilf(log2f(std::max((float) properties.width, (float) properties.height)));
     texture.usage        = usage;
-    texture.occlusion    = (properties.occlusion == IMAGE_TRANSPARENT) ?
+    texture.occlusion    = (properties.occlusion == ImageUtils::IMAGE_TRANSPARENT) ?
                            RAW_TEXTURE_OCCLUSION_TRANSPARENT : RAW_TEXTURE_OCCLUSION_OPAQUE;
     texture.fileName     = fileName;
     texture.fileLocation = fileLocation;
@@ -461,7 +460,7 @@ void RawModel::CreateMaterialModels(
     // Overestimate the number of models that will be created to avoid massive reallocation.
     int discreteCount = 0;
     for (const auto &surface : surfaces) {
-        discreteCount += (surface.discrete != false);
+        discreteCount += surface.discrete ? 1 : 0;
     }
 
     materialModels.clear();

+ 5 - 6
src/RawModel.h → src/raw/RawModel.hpp

@@ -7,13 +7,14 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef __RAWMODEL_H__
-#define __RAWMODEL_H__
+#pragma once
 
 #include <unordered_map>
 #include <functional>
 #include <set>
 
+#include "FBX2glTF.h"
+
 /**
  * The variuos situations in which the user may wish for us to (re-)compute normals for our vertices.
  */
@@ -165,7 +166,7 @@ enum RawShadingModel
     RAW_SHADING_MODEL_MAX
 };
 
-static inline std::string Describe(RawShadingModel model) {
+inline std::string Describe(RawShadingModel model) {
     switch(model) {
         case RAW_SHADING_MODEL_UNKNOWN:         return "<unknown>";
         case RAW_SHADING_MODEL_CONSTANT:        return "Constant";
@@ -194,7 +195,7 @@ enum RawTextureUsage
     RAW_TEXTURE_USAGE_MAX
 };
 
-static inline std::string Describe(RawTextureUsage usage)
+inline std::string Describe(RawTextureUsage usage)
 {
     switch (usage) {
         case RAW_TEXTURE_USAGE_NONE:        return "<none>";
@@ -514,5 +515,3 @@ void RawModel::GetAttributeArray(std::vector<_attrib_type_> &out, const _attrib_
         out[i] = vertices[i].*ptr;
     }
 }
-
-#endif // !__RAWMODEL_H__

+ 6 - 4
src/utils/File_Utils.h → src/utils/File_Utils.hpp

@@ -7,10 +7,13 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#ifndef __FILE_UTILS_H__
-#define __FILE_UTILS_H__
+#pragma once
+
+#include <string>
+#include <vector>
 
 namespace FileUtils {
+
     std::string GetCurrentFolder();
 
     bool FileExists(const std::string &folderPath);
@@ -22,6 +25,5 @@ namespace FileUtils {
     bool CreatePath(const char *path);
 
     bool CopyFile(const std::string &srcFilename, const std::string &dstFilename);
-}
 
-#endif // !__FILE_UTILS_H__
+}

+ 0 - 46
src/utils/Image_Utils.h

@@ -1,46 +0,0 @@
-/**
- * Copyright (c) 2014-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
-
-#ifndef __IMAGE_UTILS_H__
-#define __IMAGE_UTILS_H__
-
-#include <algorithm>
-
-enum ImageOcclusion
-{
-    IMAGE_OPAQUE,
-    IMAGE_TRANSPARENT
-};
-
-struct ImageProperties
-{
-    int            width;
-    int            height;
-    ImageOcclusion occlusion;
-};
-
-ImageProperties GetImageProperties(char const *filePath);
-
-/**
- * Very simple method for mapping filename suffix to mime type. The glTF 2.0 spec only accepts values
- * "image/jpeg" and "image/png" so we don't need to get too fancy.
- */
-inline std::string suffixToMimeType(std::string suffix) {
-    std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower);
-
-    if (suffix == "jpg" || suffix == "jpeg") {
-        return "image/jpeg";
-    }
-    if (suffix == "png") {
-        return "image/png";
-    }
-    return "image/unknown";
-}
-
-#endif // !__IMAGE_UTILS_H__

+ 37 - 0
src/utils/Image_Utils.hpp

@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace ImageUtils {
+
+    enum ImageOcclusion
+    {
+        IMAGE_OPAQUE,
+        IMAGE_TRANSPARENT
+    };
+
+    struct ImageProperties
+    {
+        int            width;
+        int            height;
+        ImageOcclusion occlusion;
+    };
+
+    ImageProperties GetImageProperties(char const *filePath);
+
+    /**
+     * Very simple method for mapping filename suffix to mime type. The glTF 2.0 spec only accepts values
+     * "image/jpeg" and "image/png" so we don't need to get too fancy.
+     */
+    std::string suffixToMimeType(std::string suffix);
+
+}

+ 0 - 91
src/utils/String_Utils.h

@@ -1,91 +0,0 @@
-/**
- * Copyright (c) 2014-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
-
-#ifndef _STRING_UTILS_H__
-#define _STRING_UTILS_H__
-
-#include <string>
-#include <cstdio>
-#include <cstring>
-#include <cstdarg>
-
-#if defined( _MSC_VER )
-#define strncasecmp _strnicmp
-#define strcasecmp _stricmp
-#endif
-
-namespace StringUtils {
-
-    static const unsigned int MAX_PATH_LENGTH = 1024;
-
-    enum PathSeparator
-    {
-        PATH_WIN  = '\\',
-        PATH_UNIX = '/'
-    };
-
-    PathSeparator operator!(const PathSeparator &s);
-
-    inline const std::string GetCleanPathString(const std::string &path, const PathSeparator separator = PATH_WIN)
-    {
-        std::string cleanPath = path;
-        for (size_t s = cleanPath.find(!separator, 0); s != std::string::npos; s = cleanPath.find(!separator, s)) {
-            cleanPath[s] = separator;
-        }
-        return cleanPath;
-    }
-    template<size_t size>
-    inline void GetCleanPath(char (&dest)[size], const char *path, const PathSeparator separator = PATH_WIN)
-    {
-        size_t len = size - 1;
-        strncpy(dest, path, len);
-        char *destPtr = dest;
-        while ((destPtr = strchr(destPtr, !separator)) != nullptr) {
-            *destPtr = separator;
-        }
-    }
-
-    inline const std::string GetFolderString(const std::string &path)
-    {
-        size_t s = path.rfind(PATH_WIN);
-        s = (s != std::string::npos) ? s : path.rfind(PATH_UNIX);
-        return path.substr(0, s + 1);
-    }
-
-    inline const std::string GetFileNameString(const std::string &path)
-    {
-        size_t s = path.rfind(PATH_WIN);
-        s = (s != std::string::npos) ? s : path.rfind(PATH_UNIX);
-        return path.substr(s + 1, std::string::npos);
-    }
-
-    inline const std::string GetFileBaseString(const std::string &path)
-    {
-        const std::string fileName = GetFileNameString(path);
-        return fileName.substr(0, fileName.rfind('.')).c_str();
-    }
-
-    inline const std::string GetFileSuffixString(const std::string &path)
-    {
-        const std::string fileName = GetFileNameString(path);
-        size_t pos = fileName.rfind('.');
-        if (pos == std::string::npos) {
-            return "";
-        }
-        return fileName.substr(++pos);
-    }
-
-    inline int CompareNoCase(const std::string &s1, const std::string &s2)
-    {
-        return strncasecmp(s1.c_str(), s2.c_str(), MAX_PATH_LENGTH);
-    }
-
-} // StringUtils
-#endif // _STRING_UTILS_H__
-

+ 54 - 0
src/utils/String_Utils.hpp

@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#pragma once
+
+#include <string>
+#include <cstdio>
+#include <cstring>
+#include <cstdarg>
+
+#if defined( _MSC_VER )
+#define strncasecmp _strnicmp
+#define strcasecmp _stricmp
+#endif
+
+namespace StringUtils {
+
+    static const unsigned int MAX_PATH_LENGTH = 1024;
+
+    enum PathSeparator
+    {
+        PATH_WIN  = '\\',
+        PATH_UNIX = '/'
+    };
+
+    PathSeparator operator!(const PathSeparator &s);
+
+    const std::string GetCleanPathString(const std::string &path, const PathSeparator separator = PATH_WIN);
+
+    template<size_t size>
+    void GetCleanPath(char (&dest)[size], const char *path, const PathSeparator separator = PATH_WIN)
+    {
+        size_t len = size - 1;
+        strncpy(dest, path, len);
+        char *destPtr = dest;
+        while ((destPtr = strchr(destPtr, !separator)) != nullptr) {
+            *destPtr = separator;
+        }
+    }
+
+    const std::string GetFolderString(const std::string &path);
+    const std::string GetFileNameString(const std::string &path);
+    const std::string GetFileBaseString(const std::string &path);
+    const std::string GetFileSuffixString(const std::string &path);
+
+    int CompareNoCase(const std::string &s1, const std::string &s2);
+
+}