瀏覽代碼

Apply clang-format to all our source. (#149)

Apply clang-format to all our source.
Pär Winzell 6 年之前
父節點
當前提交
5730d1c301
共有 67 個文件被更改,包括 5268 次插入5124 次删除
  1. 1 1
      .clang-format
  2. 22 53
      src/FBX2glTF.cpp
  3. 796 699
      src/fbx/Fbx2Raw.cpp
  4. 2 2
      src/fbx/Fbx2Raw.hpp
  5. 50 42
      src/fbx/FbxBlendShapesAccess.cpp
  6. 81 81
      src/fbx/FbxBlendShapesAccess.hpp
  7. 71 59
      src/fbx/FbxLayerElementAccess.hpp
  8. 5 6
      src/fbx/FbxMaterialInfo.hpp
  9. 74 74
      src/fbx/FbxMaterialsAccess.cpp
  10. 17 16
      src/fbx/FbxMaterialsAccess.hpp
  11. 54 49
      src/fbx/FbxRoughMetMaterialInfo.cpp
  12. 20 21
      src/fbx/FbxRoughMetMaterialInfo.hpp
  13. 72 67
      src/fbx/FbxSkinningAccess.cpp
  14. 61 73
      src/fbx/FbxSkinningAccess.hpp
  15. 122 109
      src/fbx/FbxTraditionalMaterialInfo.cpp
  16. 22 23
      src/fbx/FbxTraditionalMaterialInfo.hpp
  17. 111 110
      src/fbx/materials/3dsMaxPhysicalMaterial.cpp
  18. 54 52
      src/fbx/materials/FbxMaterials.cpp
  19. 31 34
      src/fbx/materials/FbxMaterials.hpp
  20. 46 50
      src/fbx/materials/RoughnessMetallicMaterials.hpp
  21. 52 51
      src/fbx/materials/StingrayPBSMaterial.cpp
  22. 120 108
      src/fbx/materials/TraditionalMaterials.cpp
  23. 22 24
      src/fbx/materials/TraditionalMaterials.hpp
  24. 68 67
      src/gltf/GltfModel.cpp
  25. 142 138
      src/gltf/GltfModel.hpp
  26. 753 670
      src/gltf/Raw2Gltf.cpp
  27. 118 119
      src/gltf/Raw2Gltf.hpp
  28. 186 179
      src/gltf/TextureBuilder.cpp
  29. 56 54
      src/gltf/TextureBuilder.hpp
  30. 20 32
      src/gltf/properties/AccessorData.cpp
  31. 28 28
      src/gltf/properties/AccessorData.hpp
  32. 27 42
      src/gltf/properties/AnimationData.cpp
  33. 23 26
      src/gltf/properties/AnimationData.hpp
  34. 17 26
      src/gltf/properties/BufferData.cpp
  35. 10 8
      src/gltf/properties/BufferData.hpp
  36. 11 18
      src/gltf/properties/BufferViewData.cpp
  37. 12 14
      src/gltf/properties/BufferViewData.hpp
  38. 16 28
      src/gltf/properties/CameraData.cpp
  39. 11 12
      src/gltf/properties/CameraData.hpp
  40. 8 26
      src/gltf/properties/ImageData.cpp
  41. 8 9
      src/gltf/properties/ImageData.hpp
  42. 21 24
      src/gltf/properties/LightData.cpp
  43. 20 16
      src/gltf/properties/LightData.hpp
  44. 80 90
      src/gltf/properties/MaterialData.cpp
  45. 44 41
      src/gltf/properties/MaterialData.hpp
  46. 12 20
      src/gltf/properties/MeshData.cpp
  47. 9 11
      src/gltf/properties/MeshData.hpp
  48. 62 69
      src/gltf/properties/NodeData.cpp
  49. 25 21
      src/gltf/properties/NodeData.hpp
  50. 46 48
      src/gltf/properties/PrimitiveData.cpp
  51. 59 51
      src/gltf/properties/PrimitiveData.hpp
  52. 6 10
      src/gltf/properties/SamplerData.hpp
  53. 5 13
      src/gltf/properties/SceneData.cpp
  54. 5 6
      src/gltf/properties/SceneData.hpp
  55. 8 12
      src/gltf/properties/SkinData.cpp
  56. 9 9
      src/gltf/properties/SkinData.hpp
  57. 4 14
      src/gltf/properties/TextureData.cpp
  58. 6 7
      src/gltf/properties/TextureData.hpp
  59. 63 63
      src/mathfu.hpp
  60. 576 553
      src/raw/RawModel.cpp
  61. 476 417
      src/raw/RawModel.hpp
  62. 158 160
      src/utils/File_Utils.cpp
  63. 11 8
      src/utils/File_Utils.hpp
  64. 43 46
      src/utils/Image_Utils.cpp
  65. 17 22
      src/utils/Image_Utils.hpp
  66. 57 64
      src/utils/String_Utils.cpp
  67. 26 29
      src/utils/String_Utils.hpp

+ 1 - 1
.clang-format

@@ -35,7 +35,7 @@ BreakBeforeTernaryOperators: true
 BreakConstructorInitializersBeforeComma: false
 BreakAfterJavaFieldAnnotations: false
 BreakStringLiterals: false
-ColumnLimit:     80
+ColumnLimit:     100
 CommentPragmas:  '^ IWYU pragma:'
 ConstructorInitializerAllOnOneLineOrOnePerLine: true
 ConstructorInitializerIndentWidth: 4

+ 22 - 53
src/FBX2glTF.cpp

@@ -35,8 +35,7 @@ int main(int argc, char* argv[]) {
 
   CLI::App app{
       fmt::sprintf(
-          "FBX2glTF %s: Generate a glTF 2.0 representation of an FBX model.",
-          FBX2GLTF_VERSION),
+          "FBX2glTF %s: Generate a glTF 2.0 representation of an FBX model.", FBX2GLTF_VERSION),
       "FBX2glTF"};
 
   app.add_flag(
@@ -45,32 +44,22 @@ int main(int argc, char* argv[]) {
       "Include blend shape tangents, if reported present by the FBX SDK.");
 
   app.add_flag_function("-V,--version", [&](size_t count) {
-    fmt::printf(
-        "FBX2glTF version %s\nCopyright (c) 2016-2018 Oculus VR, LLC.\n",
-        FBX2GLTF_VERSION);
+    fmt::printf("FBX2glTF version %s\nCopyright (c) 2016-2018 Oculus VR, LLC.\n", FBX2GLTF_VERSION);
     exit(0);
   });
 
   std::string inputPath;
-  app.add_option("FBX Model", inputPath, "The FBX model to convert.")
-      ->check(CLI::ExistingFile);
-  app.add_option("-i,--input", inputPath, "The FBX model to convert.")
-      ->check(CLI::ExistingFile);
+  app.add_option("FBX Model", inputPath, "The FBX model to convert.")->check(CLI::ExistingFile);
+  app.add_option("-i,--input", inputPath, "The FBX model to convert.")->check(CLI::ExistingFile);
 
   std::string outputPath;
-  app.add_option(
-      "-o,--output",
-      outputPath,
-      "Where to generate the output, without suffix.");
+  app.add_option("-o,--output", outputPath, "Where to generate the output, without suffix.");
 
   app.add_flag(
       "-e,--embed",
       gltfOptions.embedResources,
       "Inline buffers as data:// URIs within generated non-binary glTF.");
-  app.add_flag(
-      "-b,--binary",
-      gltfOptions.outputBinary,
-      "Output a single binary format .glb file.");
+  app.add_flag("-b,--binary", gltfOptions.outputBinary, "Output a single binary format .glb file.");
 
   app.add_option(
          "--long-indices",
@@ -119,8 +108,7 @@ int main(int argc, char* argv[]) {
       "--flip-u",
       [&](size_t count) {
         if (count > 0) {
-          texturesTransforms.emplace_back(
-              [](Vec2f uv) { return Vec2f(1.0f - uv[0], uv[1]); });
+          texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(1.0f - uv[0], uv[1]); });
           if (verboseOutput) {
             fmt::printf("Flipping texture coordinates in the 'U' dimension.\n");
           }
@@ -128,23 +116,20 @@ int main(int argc, char* argv[]) {
       },
       "Flip all U texture coordinates.");
 
-  app.add_flag("--no-flip-u", "Don't flip U texture coordinates.")
-      ->excludes("--flip-u");
+  app.add_flag("--no-flip-u", "Don't flip U texture coordinates.")->excludes("--flip-u");
 
   app.add_flag_function(
       "--no-flip-v",
       [&](size_t count) {
         if (count > 0) {
-          texturesTransforms.emplace_back(
-              [](Vec2f uv) { return Vec2f(uv[0], 1.0f - uv[1]); });
+          texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(uv[0], 1.0f - uv[1]); });
           if (verboseOutput) {
             fmt::printf("NOT flipping texture coordinates in the 'V' dimension.\n");
           }
         }
       },
       "Flip all V texture coordinates.");
-  app.add_flag("--flip-v", "Don't flip U texture coordinates.")
-      ->excludes("--no-flip-v");
+  app.add_flag("--flip-v", "Don't flip U texture coordinates.")->excludes("--no-flip-v");
 
   app.add_flag(
          "--pbr-metallic-rougnness",
@@ -181,8 +166,8 @@ int main(int argc, char* argv[]) {
   app.add_option(
          "-k,--keep-attribute",
          [&](std::vector<std::string> attributes) -> bool {
-           gltfOptions.keepAttribs = RAW_VERTEX_ATTRIBUTE_JOINT_INDICES |
-               RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
+           gltfOptions.keepAttribs =
+               RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
            for (std::string attribute : attributes) {
              if (attribute == "position") {
                gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION;
@@ -212,9 +197,7 @@ int main(int argc, char* argv[]) {
       ->type_name("(position|normal|tangent|binormial|color|uv0|uv1|auto)");
 
   app.add_flag(
-         "-d,--draco",
-         gltfOptions.draco.enabled,
-         "Apply Draco mesh compression to geometries.")
+         "-d,--draco", gltfOptions.draco.enabled, "Apply Draco mesh compression to geometries.")
       ->group("Draco");
 
   app.add_option(
@@ -301,16 +284,12 @@ int main(int argc, char* argv[]) {
 
   } else {
     // in gltf mode, we create a folder and write into that
-    outputFolder = fmt::format(
-        "{}_out{}",
-        outputPath.c_str(),
-        (const char)StringUtils::GetPathSeparator());
-    modelPath =
-        outputFolder + StringUtils::GetFileNameString(outputPath) + ".gltf";
+    outputFolder =
+        fmt::format("{}_out{}", outputPath.c_str(), (const char)StringUtils::GetPathSeparator());
+    modelPath = outputFolder + StringUtils::GetFileNameString(outputPath) + ".gltf";
   }
   if (!FileUtils::CreatePath(modelPath.c_str())) {
-    fmt::fprintf(
-        stderr, "ERROR: Failed to create folder: %s'\n", outputFolder.c_str());
+    fmt::fprintf(stderr, "ERROR: Failed to create folder: %s'\n", outputFolder.c_str());
     return 1;
   }
 
@@ -334,14 +313,9 @@ int main(int argc, char* argv[]) {
   std::ofstream outStream; // note: auto-flushes in destructor
   const auto streamStart = outStream.tellp();
 
-  outStream.open(
-      modelPath,
-      std::ios::trunc | std::ios::ate | std::ios::out | std::ios::binary);
+  outStream.open(modelPath, std::ios::trunc | std::ios::ate | std::ios::out | std::ios::binary);
   if (outStream.fail()) {
-    fmt::fprintf(
-        stderr,
-        "ERROR:: Couldn't open file for writing: %s\n",
-        modelPath.c_str());
+    fmt::fprintf(stderr, "ERROR:: Couldn't open file for writing: %s\n", modelPath.c_str());
     return 1;
   }
   data_render_model = Raw2Gltf(outStream, outputFolder, raw, gltfOptions);
@@ -371,8 +345,7 @@ int main(int argc, char* argv[]) {
   const std::string binaryPath = outputFolder + extBufferFilename;
   FILE* fp = fopen(binaryPath.c_str(), "wb");
   if (fp == nullptr) {
-    fmt::fprintf(
-        stderr, "ERROR:: Couldn't open file '%s' for writing.\n", binaryPath);
+    fmt::fprintf(stderr, "ERROR:: Couldn't open file '%s' for writing.\n", binaryPath);
     return 1;
   }
 
@@ -381,16 +354,12 @@ int main(int argc, char* argv[]) {
     unsigned long binarySize = data_render_model->binary->size();
     if (fwrite(binaryData, binarySize, 1, fp) != 1) {
       fmt::fprintf(
-          stderr,
-          "ERROR: Failed to write %lu bytes to file '%s'.\n",
-          binarySize,
-          binaryPath);
+          stderr, "ERROR: Failed to write %lu bytes to file '%s'.\n", binarySize, binaryPath);
       fclose(fp);
       return 1;
     }
     fclose(fp);
-    fmt::printf(
-        "Wrote %lu bytes of binary data to %s.\n", binarySize, binaryPath);
+    fmt::printf("Wrote %lu bytes of binary data to %s.\n", binarySize, binaryPath);
   }
 
   delete data_render_model;

文件差異過大導致無法顯示
+ 796 - 699
src/fbx/Fbx2Raw.cpp


+ 2 - 2
src/fbx/Fbx2Raw.hpp

@@ -11,6 +11,6 @@
 
 #include "raw/RawModel.hpp"
 
-bool LoadFBXFile(RawModel &raw, const char *fbxFileName, const char *textureExtensions);
+bool LoadFBXFile(RawModel& raw, const char* fbxFileName, const char* textureExtensions);
 
-json TranscribeProperty(FbxProperty &prop);
+json TranscribeProperty(FbxProperty& prop);

+ 50 - 42
src/fbx/FbxBlendShapesAccess.cpp

@@ -9,57 +9,65 @@
 
 #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()))
-{}
+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);
+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, std::string name) : mesh(mesh),
-                                                                          blendShapeIx(blendShapeIx),
-                                                                          channelIx(channelIx),
-                                                                          deformPercent(deformPercent),
-                                                                          targetShapes(targetShapes),
-                                                                          name(name)
-{}
+    FbxMesh* mesh,
+    const unsigned int blendShapeIx,
+    const unsigned int channelIx,
+    const FbxDouble deformPercent,
+    const std::vector<FbxBlendShapesAccess::TargetShape>& targetShapes,
+    std::string name)
+    : mesh(mesh),
+      blendShapeIx(blendShapeIx),
+      channelIx(channelIx),
+      deformPercent(deformPercent),
+      targetShapes(targetShapes),
+      name(name) {}
 
-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 = static_cast<FbxBlendShape *>(mesh->GetDeformer(shapeIx, FbxDeformer::eBlendShape));
+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 =
+        static_cast<FbxBlendShape*>(mesh->GetDeformer(shapeIx, FbxDeformer::eBlendShape));
 
-        for (int channelIx = 0; channelIx < fbxBlendShape->GetBlendShapeChannelCount(); ++channelIx) {
-            FbxBlendShapeChannel *fbxChannel = fbxBlendShape->GetBlendShapeChannel(channelIx);
+    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();
-                std::string name = std::string(fbxChannel->GetName());
+      if (fbxChannel->GetTargetShapeCount() > 0) {
+        std::vector<TargetShape> targetShapes;
+        const double* fullWeights = fbxChannel->GetTargetShapeFullWeights();
+        std::string name = std::string(fbxChannel->GetName());
 
-                if (verboseOutput) {
-                    fmt::printf("\rblendshape channel: %s\n", name);
-                }
+        if (verboseOutput) {
+          fmt::printf("\rblendshape channel: %s\n", name);
+        }
 
-                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, name);
-            }
+        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, name);
+      }
     }
-    return channels;
+  }
+  return channels;
 }

+ 81 - 81
src/fbx/FbxBlendShapesAccess.hpp

@@ -9,97 +9,97 @@
 
 #pragma once
 
+#include <algorithm>
 #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.
+ * 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.
+ * 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.
+ * 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.
+ * 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,
-            const std::string name
-        );
-
-        FbxAnimCurve *ExtractAnimation(unsigned int animIx) const;
-
-        FbxMesh *const mesh;
-
-        const unsigned int                  blendShapeIx;
-        const unsigned int                  channelIx;
-        const std::vector<TargetShape> targetShapes;
-        const std::string name;
-
-        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;
+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,
+        const std::string name);
+
+    FbxAnimCurve* ExtractAnimation(unsigned int animIx) const;
+
+    FbxMesh* const mesh;
+
+    const unsigned int blendShapeIx;
+    const unsigned int channelIx;
+    const std::vector<TargetShape> targetShapes;
+    const std::string name;
+
+    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;
 };

+ 71 - 59
src/fbx/FbxLayerElementAccess.hpp

@@ -9,75 +9,87 @@
 #pragma once
 #include "FBX2glTF.h"
 
-template<typename _type_>
-class FbxLayerElementAccess
-{
-public:
+template <typename _type_>
+class FbxLayerElementAccess {
+ public:
+  FbxLayerElementAccess(const FbxLayerElementTemplate<_type_>* layer, int count);
 
-    FbxLayerElementAccess(const FbxLayerElementTemplate<_type_> *layer, int count);
+  bool LayerPresent() const {
+    return (mappingMode != FbxLayerElement::eNone);
+  }
 
-    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;
+  _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;
+ 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_>
+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_>
+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;
+    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_>
+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;
+    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 defaultValue;
+    return element;
+  }
+  return defaultValue;
 }

+ 5 - 6
src/fbx/FbxMaterialInfo.hpp

@@ -12,11 +12,10 @@
 #include "FBX2glTF.h"
 
 class FbxMaterialInfo {
-public:
-    FbxMaterialInfo(const FbxString &name, const FbxString &shadingModel)
-        : name(name),
-          shadingModel(shadingModel) {}
+ public:
+  FbxMaterialInfo(const FbxString& name, const FbxString& shadingModel)
+      : name(name), shadingModel(shadingModel) {}
 
-    const FbxString name;
-    const FbxString shadingModel;
+  const FbxString name;
+  const FbxString shadingModel;
 };

+ 74 - 74
src/fbx/FbxMaterialsAccess.cpp

@@ -10,95 +10,95 @@
 #include "FbxMaterialsAccess.hpp"
 #include "Fbx2Raw.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;
-    }
+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::EReferenceMode materialReferenceMode = pMesh->GetElementMaterial()->GetReferenceMode();
-    if (materialReferenceMode != FbxGeometryElement::eIndexToDirect) {
-        return;
-    }
+  const FbxGeometryElement::EMappingMode materialMappingMode =
+      pMesh->GetElementMaterial()->GetMappingMode();
+  if (materialMappingMode != FbxGeometryElement::eByPolygon &&
+      materialMappingMode != FbxGeometryElement::eAllSame) {
+    return;
+  }
 
-    mappingMode = materialMappingMode;
-    mesh        = pMesh;
-    indices     = &pMesh->GetElementMaterial()->GetIndexArray();
+  const FbxGeometryElement::EReferenceMode materialReferenceMode =
+      pMesh->GetElementMaterial()->GetReferenceMode();
+  if (materialReferenceMode != FbxGeometryElement::eIndexToDirect) {
+    return;
+  }
 
-    for (int ii = 0; ii < indices->GetCount(); ii++) {
-        int materialNum = indices->GetAt(ii);
-        if (materialNum < 0) {
-            continue;
-        }
+  mappingMode = materialMappingMode;
+  mesh = pMesh;
+  indices = &pMesh->GetElementMaterial()->GetIndexArray();
 
-        FbxSurfaceMaterial* surfaceMaterial = mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(materialNum);
+  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(
-                surfaceMaterial,
-                textureLocations);
-        }
+    FbxSurfaceMaterial* surfaceMaterial =
+        mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(materialNum);
 
-        if (materialNum >= userProperties.size()) {
-            userProperties.resize(materialNum + 1);
-        }
-        if (userProperties[materialNum].empty()) {
-            FbxProperty objectProperty = surfaceMaterial->GetFirstProperty();
-            while (objectProperty.IsValid())
-            {
-                if (objectProperty.GetFlag(FbxPropertyFlags::eUserDefined)) {
-                    userProperties[materialNum].push_back(TranscribeProperty(objectProperty).dump());
-                }
-                objectProperty = surfaceMaterial->GetNextProperty(objectProperty);
-            }
-        }
+    if (materialNum >= summaries.size()) {
+      summaries.resize(materialNum + 1);
+    }
+    auto summary = summaries[materialNum];
+    if (summary == nullptr) {
+      summary = summaries[materialNum] = GetMaterialInfo(surfaceMaterial, 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;
+    if (materialNum >= userProperties.size()) {
+      userProperties.resize(materialNum + 1);
+    }
+    if (userProperties[materialNum].empty()) {
+      FbxProperty objectProperty = surfaceMaterial->GetFirstProperty();
+      while (objectProperty.IsValid()) {
+        if (objectProperty.GetFlag(FbxPropertyFlags::eUserDefined)) {
+          userProperties[materialNum].push_back(TranscribeProperty(objectProperty).dump());
         }
-        return summaries.at((unsigned long) materialNum);
+        objectProperty = surfaceMaterial->GetNextProperty(objectProperty);
+      }
     }
-    return nullptr;
+  }
 }
 
-const std::vector<std::string> FbxMaterialsAccess::GetUserProperties(const int polygonIndex) const
-{
-    if (mappingMode != FbxGeometryElement::eNone) {
-        const int materialNum = indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0);
-        if (materialNum < 0) {
-            return std::vector<std::string>();
-        }
-        return userProperties.at((unsigned long)materialNum);
+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 std::vector<std::string>();
+    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);
+const std::vector<std::string> FbxMaterialsAccess::GetUserProperties(const int polygonIndex) const {
+  if (mappingMode != FbxGeometryElement::eNone) {
+    const int materialNum =
+        indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0);
+    if (materialNum < 0) {
+      return std::vector<std::string>();
     }
-    return res;
+    return userProperties.at((unsigned long)materialNum);
+  }
+  return std::vector<std::string>();
 }
 
+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;
+}

+ 17 - 16
src/fbx/FbxMaterialsAccess.hpp

@@ -11,26 +11,27 @@
 
 #include "Fbx2Raw.hpp"
 #include "FbxMaterialInfo.hpp"
-#include "FbxTraditionalMaterialInfo.hpp"
 #include "FbxRoughMetMaterialInfo.hpp"
+#include "FbxTraditionalMaterialInfo.hpp"
 
-class FbxMaterialsAccess
-{
-public:
-
-    FbxMaterialsAccess(const FbxMesh *pMesh, 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;
+  const std::shared_ptr<FbxMaterialInfo> GetMaterial(const int polygonIndex) const;
 
-	const std::vector<std::string> GetUserProperties(const int polygonIndex) const;
+  const std::vector<std::string> GetUserProperties(const int polygonIndex) const;
 
-    std::unique_ptr<FbxMaterialInfo>
-    GetMaterialInfo(FbxSurfaceMaterial *material, const std::map<const FbxTexture *, FbxString> &textureLocations);
+  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 {};
-	std::vector<std::vector<std::string>>         userProperties;
-    const FbxMesh                                 *mesh;
-    const FbxLayerElementArrayTemplate<int>       *indices;
+ private:
+  FbxGeometryElement::EMappingMode mappingMode;
+  std::vector<std::shared_ptr<FbxMaterialInfo>> summaries{};
+  std::vector<std::vector<std::string>> userProperties;
+  const FbxMesh* mesh;
+  const FbxLayerElementArrayTemplate<int>* indices;
 };

+ 54 - 49
src/fbx/FbxRoughMetMaterialInfo.cpp

@@ -7,62 +7,67 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
- #include "FbxRoughMetMaterialInfo.hpp"
+#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));
+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());
-    }
+  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;
+  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());
+    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;
         }
-        return ptr;
-    };
+      }
+    } 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 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;
-    };
+  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");
+  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;
+  return res;
 }

+ 20 - 21
src/fbx/FbxRoughMetMaterialInfo.hpp

@@ -7,36 +7,35 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
+#include <algorithm>
 #include <fstream>
-#include <string>
-#include <set>
 #include <map>
+#include <set>
+#include <string>
 #include <unordered_map>
 #include <vector>
-#include <algorithm>
 
 #include "FbxMaterialInfo.hpp"
 
 struct FbxRoughMetMaterialInfo : FbxMaterialInfo {
-    static constexpr const char *FBX_SHADER_METROUGH = "MetallicRoughness";
+  static constexpr const char* FBX_SHADER_METROUGH = "MetallicRoughness";
 
-    static std::unique_ptr<FbxRoughMetMaterialInfo> From(
-        FbxSurfaceMaterial *fbxMaterial,
-        const std::map<const FbxTexture *, FbxString> &textureLocations);
+  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)
-    {}
+  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 {};
+  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{};
 };

+ 72 - 67
src/fbx/FbxSkinningAccess.cpp

@@ -9,84 +9,89 @@
 
 #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();
+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));
 
-                assert(cluster->GetLinkMode() == FbxCluster::eNormalize);
+      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();
 
-                // Transform link matrix.
-                FbxAMatrix transformLinkMatrix;
-                cluster->GetTransformLinkMatrix(transformLinkMatrix);
+        assert(cluster->GetLinkMode() == FbxCluster::eNormalize);
 
-                // The transformation of the mesh at binding time
-                FbxAMatrix transformMatrix;
-                cluster->GetTransformMatrix(transformMatrix);
+        // Transform link matrix.
+        FbxAMatrix transformLinkMatrix;
+        cluster->GetTransformLinkMatrix(transformLinkMatrix);
 
-                // Inverse bind matrix.
-                FbxAMatrix globalBindposeInverseMatrix = transformLinkMatrix.Inverse() * transformMatrix;
-                inverseBindMatrices.emplace_back(globalBindposeInverseMatrix);
+        // The transformation of the mesh at binding time
+        FbxAMatrix transformMatrix;
+        cluster->GetTransformMatrix(transformMatrix);
 
-                jointNodes.push_back(cluster->GetLink());
-                jointIds.push_back(cluster->GetLink()->GetUniqueID());
+        // Inverse bind matrix.
+        FbxAMatrix globalBindposeInverseMatrix = transformLinkMatrix.Inverse() * transformMatrix;
+        inverseBindMatrices.emplace_back(globalBindposeInverseMatrix);
 
-                const FbxAMatrix globalNodeTransform = cluster->GetLink()->EvaluateGlobalTransform();
-                jointSkinningTransforms.push_back(FbxMatrix(globalNodeTransform * globalBindposeInverseMatrix));
-                jointInverseGlobalTransforms.push_back(FbxMatrix(globalNodeTransform.Inverse()));
+        jointNodes.push_back(cluster->GetLink());
+        jointIds.push_back(cluster->GetLink()->GetUniqueID());
 
-                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]);
-                    }
-                }
+        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;
             }
-            for (int i = 0; i < controlPointCount; i++) {
-                vertexJointWeights[i] = vertexJointWeights[i].Normalized();
-            }
+            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;
-            }
-        }
+  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;
+      }
     }
+  }
 }

+ 61 - 73
src/fbx/FbxSkinningAccess.hpp

@@ -9,84 +9,72 @@
 
 #pragma once
 
+#include <algorithm>
 #include <fstream>
-#include <string>
-#include <set>
 #include <map>
+#include <set>
+#include <string>
 #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;
+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;
 };

+ 122 - 109
src/fbx/FbxTraditionalMaterialInfo.cpp

@@ -9,116 +9,129 @@
 
 #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);
+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 (facTex) {
-        fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparencyFactor);
+    if (tex == nullptr && prop.IsValid()) {
+      val = prop.Get<FbxDouble3>();
     }
-    // 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 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);
 
-    return res;
+    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;
 }

+ 22 - 23
src/fbx/FbxTraditionalMaterialInfo.hpp

@@ -7,38 +7,37 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
+#include <algorithm>
 #include <fstream>
-#include <string>
-#include <set>
 #include <map>
+#include <set>
+#include <string>
 #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";
+  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)
-    {}
+  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 {};
+  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);
+  static std::unique_ptr<FbxTraditionalMaterialInfo> From(
+      FbxSurfaceMaterial* fbxMaterial,
+      const std::map<const FbxTexture*, FbxString>& textureLocations);
 };

+ 111 - 110
src/fbx/materials/3dsMaxPhysicalMaterial.cpp

@@ -10,116 +10,117 @@
 #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());
+  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;
         }
-        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;
+      }
+    } 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());
     }
-
-    // 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;
+    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;
 }

+ 54 - 52
src/fbx/materials/FbxMaterials.cpp

@@ -11,68 +11,70 @@
 #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;
-    }
+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::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;
-    }
+  const FbxGeometryElement::EReferenceMode materialReferenceMode =
+      pMesh->GetElementMaterial()->GetReferenceMode();
+  if (materialReferenceMode != FbxGeometryElement::eIndexToDirect) {
+    return;
+  }
 
-    mappingMode = materialMappingMode;
-    mesh        = pMesh;
-    indices     = &pMesh->GetElementMaterial()->GetIndexArray();
+  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);
-        }
+  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);
+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 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();
+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 = Fbx3dsMaxPhysicalMaterialResolver(material, textureLocations).resolve();
-        if (res == nullptr) {
-            res = FbxTraditionalMaterialResolver(material, textureLocations).resolve();
-        }
+      res = FbxTraditionalMaterialResolver(material, textureLocations).resolve();
     }
-    return res;
+  }
+  return res;
 }
-

+ 31 - 34
src/fbx/materials/FbxMaterials.hpp

@@ -15,46 +15,43 @@
 #include "FBX2glTF.h"
 
 class FbxMaterialInfo {
-public:
-    FbxMaterialInfo(const FbxString &name, const FbxString &shadingModel)
-        : name(name)
-        , shadingModel(shadingModel)
-    {}
-
-    const FbxString name;
-    const FbxString shadingModel;
+ 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 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);
+class FbxMaterialsAccess {
+ public:
+  FbxMaterialsAccess(
+      const FbxMesh* pMesh,
+      const std::map<const FbxTexture*, FbxString>& textureLocations);
 
-    const std::shared_ptr<FbxMaterialInfo> GetMaterial(const int polygonIndex) const;
+  const std::shared_ptr<FbxMaterialInfo> GetMaterial(const int polygonIndex) const;
 
-    std::unique_ptr<FbxMaterialInfo>
-    GetMaterialInfo(FbxSurfaceMaterial *material, const std::map<const FbxTexture *, FbxString> &textureLocations);
+  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;
+ private:
+  FbxGeometryElement::EMappingMode mappingMode;
+  std::vector<std::shared_ptr<FbxMaterialInfo>> summaries{};
+  const FbxMesh* mesh;
+  const FbxLayerElementArrayTemplate<int>* indices;
 };

+ 46 - 50
src/fbx/materials/RoughnessMetallicMaterials.hpp

@@ -14,68 +14,64 @@
 #include "FbxMaterials.hpp"
 
 struct FbxRoughMetMaterialInfo : FbxMaterialInfo {
-    static constexpr const char *FBX_SHADER_METROUGH = "MetallicRoughness";
+  static constexpr const char* FBX_SHADER_METROUGH = "MetallicRoughness";
 
-    static std::unique_ptr<FbxRoughMetMaterialInfo> From(
-        FbxSurfaceMaterial *fbxMaterial,
-        const std::map<const FbxTexture *, FbxString> &textureLocations);
+  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)
-    {}
+  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;
+  const FbxVector4 baseColor;
+  const FbxDouble metallic;
+  const FbxDouble roughness;
 
-    FbxDouble            baseWeight = 1;
-    FbxVector4           emissive = FbxVector4(0, 0, 0, 1);
-    FbxDouble            emissiveIntensity = 1;
+  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;
+  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)
-    {}
+ public:
+  FbxStingrayPBSMaterialResolver(
+      FbxSurfaceMaterial* fbxMaterial,
+      const std::map<const FbxTexture*, FbxString>& textureLocations)
+      : FbxMaterialResolver(fbxMaterial, textureLocations) {}
 
-    virtual std::unique_ptr<FbxRoughMetMaterialInfo> resolve() const;
+  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)
-    {}
+ public:
+  Fbx3dsMaxPhysicalMaterialResolver(
+      FbxSurfaceMaterial* fbxMaterial,
+      const std::map<const FbxTexture*, FbxString>& textureLocations)
+      : FbxMaterialResolver(fbxMaterial, textureLocations) {}
 
-    virtual std::unique_ptr<FbxRoughMetMaterialInfo> resolve() const;
+  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;
-    }
+ 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;
+  }
 };

+ 52 - 51
src/fbx/materials/StingrayPBSMaterial.cpp

@@ -10,62 +10,63 @@
 #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());
-    }
+  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;
+  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());
+    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;
         }
-        return ptr;
-    };
+      }
+    } 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 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;
-    };
+  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");
+  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;
+  return res;
 };

+ 120 - 108
src/fbx/materials/TraditionalMaterials.cpp

@@ -9,115 +9,127 @@
 
 #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);
+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 (facTex) {
-        fmt::printf("Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", name, FbxSurfaceMaterial::sTransparencyFactor);
+    if (tex == nullptr && prop.IsValid()) {
+      val = prop.Get<FbxDouble3>();
     }
-    // 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 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);
 
-    return res;
+    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;
 }

+ 22 - 24
src/fbx/materials/TraditionalMaterials.hpp

@@ -10,34 +10,32 @@
 #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";
+  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)
-    {}
+  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 {};
+  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)
-    {}
+ public:
+  FbxTraditionalMaterialResolver(
+      FbxSurfaceMaterial* fbxMaterial,
+      const std::map<const FbxTexture*, FbxString>& textureLocations)
+      : FbxMaterialResolver(fbxMaterial, textureLocations) {}
 
-    virtual std::unique_ptr<FbxTraditionalMaterialInfo> resolve() const;
+  virtual std::unique_ptr<FbxTraditionalMaterialInfo> resolve() const;
 };

+ 68 - 67
src/gltf/GltfModel.cpp

@@ -1,85 +1,86 @@
 /**
-* 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.
-*/
+ * 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"
 
-std::shared_ptr<BufferViewData> GltfModel::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));
+std::shared_ptr<BufferViewData> GltfModel::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> GltfModel::AddRawBufferView(BufferData &buffer, const char *source, uint32_t bytes)
-{
-    auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
-    bufferView->byteLength = bytes;
+std::shared_ptr<BufferViewData>
+GltfModel::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);
+  // 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;
+  // and copy them into place
+  memcpy(&(*this->binary)[bufferSize], source, bytes);
+  return bufferView;
 }
 
-std::shared_ptr<BufferViewData> GltfModel::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> GltfModel::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::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);
-        }
+    std::vector<char> fileBuffer(size);
+    if (file.read(fileBuffer.data(), size)) {
+      result = AddRawBufferView(buffer, fileBuffer.data(), size);
     } else {
-        fmt::printf("Warning: Couldn't open file %s, skipping file.\n", filename);
+      fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping file.\n", size, filename);
     }
-    // note that we persist here not only success, but also failure, as nullptr
-    filenameToBufferView[filename] = result;
-    return result;
+  } 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;
 }
 
-void GltfModel::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);
-    if (!lights.ptrs.empty()) {
-        json lightsJson = json::object();
-        serializeHolder(lightsJson, "lights", lights);
-        glTFJson["extensions"][KHR_LIGHTS_PUNCTUAL] = lightsJson;
-    }
+void GltfModel::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);
+  if (!lights.ptrs.empty()) {
+    json lightsJson = json::object();
+    serializeHolder(lightsJson, "lights", lights);
+    glTFJson["extensions"][KHR_LIGHTS_PUNCTUAL] = lightsJson;
+  }
 }

+ 142 - 138
src/gltf/GltfModel.hpp

@@ -1,11 +1,11 @@
 /**
-* 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.
-*/
+ * 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
 
@@ -30,139 +30,143 @@
 #include "gltf/properties/TextureData.hpp"
 
 /**
-* 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>
-class Holder
-{
-public:
-    std::shared_ptr<T> hold(T *ptr)
-    {
-        ptr->ix = ptrs.size();
-        ptrs.emplace_back(ptr);
-        return ptrs.back();
-    }
-    std::vector<std::shared_ptr<T>> ptrs;
+ * 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>
+class Holder {
+ public:
+  std::shared_ptr<T> hold(T* ptr) {
+    ptr->ix = ptrs.size();
+    ptrs.emplace_back(ptr);
+    return ptrs.back();
+  }
+  std::vector<std::shared_ptr<T>> ptrs;
 };
 
-class GltfModel
-{
-public:
-    explicit GltfModel(const GltfOptions &options)
-        : binary(new std::vector<uint8_t>)
-        , isGlb(options.outputBinary)
-        , defaultSampler(nullptr)
-        , defaultBuffer(buffers.hold(buildDefaultBuffer(options)))
-    {
-        defaultSampler = samplers.hold(buildDefaultSampler());
-    }
-
-    std::shared_ptr<BufferViewData> GetAlignedBufferView(BufferData &buffer, const BufferViewData::GL_ArrayType target);
-    std::shared_ptr<BufferViewData> AddRawBufferView(BufferData &buffer, const char *source, uint32_t bytes);
-    std::shared_ptr<BufferViewData> AddBufferViewForFile(BufferData &buffer, const std::string &filename);
-
-    template<class T>
-    std::shared_ptr<AccessorData> AddAccessorWithView(
-        BufferViewData &bufferView, const GLType &type, const std::vector<T> &source, std::string name)
-    {
-        auto accessor = accessors.hold(new AccessorData(bufferView, type, name));
-        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, std::string(""));
-    }
-
-    template<class T>
-    std::shared_ptr<AccessorData> AddAccessorAndView(
-        BufferData &buffer, const GLType &type, const std::vector<T> &source, std::string name)
-    {
-        auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
-        return AddAccessorWithView(*bufferView, type, source, name);
-    }
-
-    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, std::string(""));
-        }
-        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);
-
-    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;
-    Holder<LightData>      lights;
-
-    std::shared_ptr<SamplerData> defaultSampler;
-    std::shared_ptr<BufferData> defaultBuffer;
-
-private:
-    SamplerData *buildDefaultSampler() {
-        return new SamplerData();
+class GltfModel {
+ public:
+  explicit GltfModel(const GltfOptions& options)
+      : binary(new std::vector<uint8_t>),
+        isGlb(options.outputBinary),
+        defaultSampler(nullptr),
+        defaultBuffer(buffers.hold(buildDefaultBuffer(options))) {
+    defaultSampler = samplers.hold(buildDefaultSampler());
+  }
+
+  std::shared_ptr<BufferViewData> GetAlignedBufferView(
+      BufferData& buffer,
+      const BufferViewData::GL_ArrayType target);
+  std::shared_ptr<BufferViewData>
+  AddRawBufferView(BufferData& buffer, const char* source, uint32_t bytes);
+  std::shared_ptr<BufferViewData> AddBufferViewForFile(
+      BufferData& buffer,
+      const std::string& filename);
+
+  template <class T>
+  std::shared_ptr<AccessorData> AddAccessorWithView(
+      BufferViewData& bufferView,
+      const GLType& type,
+      const std::vector<T>& source,
+      std::string name) {
+    auto accessor = accessors.hold(new AccessorData(bufferView, type, name));
+    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, std::string(""));
+  }
+
+  template <class T>
+  std::shared_ptr<AccessorData> AddAccessorAndView(
+      BufferData& buffer,
+      const GLType& type,
+      const std::vector<T>& source,
+      std::string name) {
+    auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
+    return AddAccessorWithView(*bufferView, type, source, name);
+  }
+
+  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, std::string(""));
     }
-    BufferData *buildDefaultBuffer(const GltfOptions &options) {
-        return options.outputBinary ?
-            new BufferData(binary) :
-            new BufferData(extBufferFilename, binary, options.embedResources);
+    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);
+
+  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;
+  Holder<LightData> lights;
+
+  std::shared_ptr<SamplerData> defaultSampler;
+  std::shared_ptr<BufferData> defaultBuffer;
+
+ private:
+  SamplerData* buildDefaultSampler() {
+    return new SamplerData();
+  }
+  BufferData* buildDefaultBuffer(const GltfOptions& options) {
+    return options.outputBinary ? new BufferData(binary)
+                                : new BufferData(extBufferFilename, binary, options.embedResources);
+  }
 };

+ 753 - 670
src/gltf/Raw2Gltf.cpp

@@ -9,17 +9,17 @@
 
 #include "Raw2Gltf.hpp"
 
-#include <cstdint>
 #include <cassert>
-#include <iostream>
+#include <cstdint>
 #include <fstream>
+#include <iostream>
 
 #include <stb_image.h>
 #include <stb_image_write.h>
 
-#include "utils/String_Utils.hpp"
-#include "utils/Image_Utils.hpp"
 #include <utils/File_Utils.hpp>
+#include "utils/Image_Utils.hpp"
+#include "utils/String_Utils.hpp"
 
 #include "raw/RawModel.hpp"
 
@@ -38,8 +38,8 @@
 #include "gltf/properties/SkinData.hpp"
 #include "gltf/properties/TextureData.hpp"
 
-#include "TextureBuilder.hpp"
 #include "GltfModel.hpp"
+#include "TextureBuilder.hpp"
 
 typedef uint32_t TriangleIndex;
 
@@ -50,727 +50,810 @@ typedef uint32_t TriangleIndex;
  * registered under that name. This is safe in the context of this tool, where all such data
  * classes are guaranteed to stick around for the duration of the process.
  */
-template<typename T>
-T &require(std::map<std::string, std::shared_ptr<T>> map, const std::string &key)
-{
-    auto iter = map.find(key);
-    assert(iter != map.end());
-    T &result = *iter->second;
-    return result;
+template <typename T>
+T& require(std::map<std::string, std::shared_ptr<T>> map, const std::string& key) {
+  auto iter = map.find(key);
+  assert(iter != map.end());
+  T& result = *iter->second;
+  return result;
 }
 
-template<typename T>
-T &require(std::map<long, std::shared_ptr<T>> map, long key)
-{
-    auto iter = map.find(key);
-    assert(iter != map.end());
-    T &result = *iter->second;
-    return result;
+template <typename T>
+T& require(std::map<long, std::shared_ptr<T>> map, long key) {
+  auto iter = map.find(key);
+  assert(iter != map.end());
+  T& result = *iter->second;
+  return result;
 }
 
-static const std::vector<TriangleIndex> getIndexArray(const RawModel &raw)
-{
-    std::vector<TriangleIndex> result;
+static const std::vector<TriangleIndex> getIndexArray(const RawModel& raw) {
+  std::vector<TriangleIndex> result;
 
-    for (int i = 0; i < raw.GetTriangleCount(); i++) {
-        result.push_back((TriangleIndex) raw.GetTriangle(i).verts[0]);
-        result.push_back((TriangleIndex) raw.GetTriangle(i).verts[1]);
-        result.push_back((TriangleIndex) raw.GetTriangle(i).verts[2]);
-    }
-    return result;
+  for (int i = 0; i < raw.GetTriangleCount(); i++) {
+    result.push_back((TriangleIndex)raw.GetTriangle(i).verts[0]);
+    result.push_back((TriangleIndex)raw.GetTriangle(i).verts[1]);
+    result.push_back((TriangleIndex)raw.GetTriangle(i).verts[2]);
+  }
+  return result;
 }
 
 // TODO: replace with a proper MaterialHasher class
-static const std::string materialHash(const RawMaterial &m) {
-    return m.name + "_" + std::to_string(m.type);
+static const std::string materialHash(const RawMaterial& m) {
+  return m.name + "_" + std::to_string(m.type);
 }
 
-ModelData *Raw2Gltf(
-    std::ofstream &gltfOutStream,
-    const std::string &outputFolder,
-    const RawModel &raw,
-    const GltfOptions &options
-)
-{
-    if (verboseOutput) {
-        fmt::printf("Building render model...\n");
-        for (int i = 0; i < raw.GetMaterialCount(); i++) {
-            fmt::printf(
-                "Material %d: %s [shading: %s]\n", i, raw.GetMaterial(i).name.c_str(),
-                Describe(raw.GetMaterial(i).info->shadingModel));
-        }
-        if (raw.GetVertexCount() > 2 * raw.GetTriangleCount()) {
-            fmt::printf(
-                "Warning: High vertex count. Make sure there are no unnecessary vertex attributes. (see -keepAttribute cmd-line option)");
-        }
+ModelData* Raw2Gltf(
+    std::ofstream& gltfOutStream,
+    const std::string& outputFolder,
+    const RawModel& raw,
+    const GltfOptions& options) {
+  if (verboseOutput) {
+    fmt::printf("Building render model...\n");
+    for (int i = 0; i < raw.GetMaterialCount(); i++) {
+      fmt::printf(
+          "Material %d: %s [shading: %s]\n",
+          i,
+          raw.GetMaterial(i).name.c_str(),
+          Describe(raw.GetMaterial(i).info->shadingModel));
     }
-
-    std::vector<RawModel> materialModels;
-    raw.CreateMaterialModels(
-        materialModels,
-        options.useLongIndices == UseLongIndicesOptions::NEVER,
-        options.keepAttribs,
-        true);
-
-    if (verboseOutput) {
-        fmt::printf("%7d vertices\n", raw.GetVertexCount());
-        fmt::printf("%7d triangles\n", raw.GetTriangleCount());
-        fmt::printf("%7d textures\n", raw.GetTextureCount());
-        fmt::printf("%7d nodes\n", raw.GetNodeCount());
-        fmt::printf("%7d surfaces\n", (int) materialModels.size());
-        fmt::printf("%7d animations\n", raw.GetAnimationCount());
-        fmt::printf("%7d cameras\n", raw.GetCameraCount());
-        fmt::printf("%7d lights\n", raw.GetLightCount());
+    if (raw.GetVertexCount() > 2 * raw.GetTriangleCount()) {
+      fmt::printf(
+          "Warning: High vertex count. Make sure there are no unnecessary vertex attributes. (see -keepAttribute cmd-line option)");
+    }
+  }
+
+  std::vector<RawModel> materialModels;
+  raw.CreateMaterialModels(
+      materialModels,
+      options.useLongIndices == UseLongIndicesOptions::NEVER,
+      options.keepAttribs,
+      true);
+
+  if (verboseOutput) {
+    fmt::printf("%7d vertices\n", raw.GetVertexCount());
+    fmt::printf("%7d triangles\n", raw.GetTriangleCount());
+    fmt::printf("%7d textures\n", raw.GetTextureCount());
+    fmt::printf("%7d nodes\n", raw.GetNodeCount());
+    fmt::printf("%7d surfaces\n", (int)materialModels.size());
+    fmt::printf("%7d animations\n", raw.GetAnimationCount());
+    fmt::printf("%7d cameras\n", raw.GetCameraCount());
+    fmt::printf("%7d lights\n", raw.GetLightCount());
+  }
+
+  std::unique_ptr<GltfModel> gltf(new GltfModel(options));
+
+  std::map<long, std::shared_ptr<NodeData>> nodesById;
+  std::map<std::string, std::shared_ptr<MaterialData>> materialsByName;
+  std::map<std::string, std::shared_ptr<TextureData>> textureByIndicesKey;
+  std::map<long, std::shared_ptr<MeshData>> meshBySurfaceId;
+
+  // for now, we only have one buffer; data->binary points to the same vector as that BufferData
+  // does.
+  BufferData& buffer = *gltf->defaultBuffer;
+  {
+    //
+    // nodes
+    //
+
+    for (int i = 0; i < raw.GetNodeCount(); i++) {
+      // assumption: RawNode index == NodeData index
+      const RawNode& node = raw.GetNode(i);
+
+      auto nodeData = gltf->nodes.hold(
+          new NodeData(node.name, node.translation, node.rotation, node.scale, node.isJoint));
+
+      if (options.enableUserProperties) {
+        nodeData->userProperties = node.userProperties;
+      }
+
+      for (const auto& childId : node.childIds) {
+        int childIx = raw.GetNodeById(childId);
+        assert(childIx >= 0);
+        nodeData->AddChildNode(childIx);
+      }
+
+      nodesById.insert(std::make_pair(node.id, nodeData));
     }
 
-    std::unique_ptr<GltfModel> gltf(new GltfModel(options));
-
-    std::map<long, std::shared_ptr<NodeData>>            nodesById;
-    std::map<std::string, std::shared_ptr<MaterialData>> materialsByName;
-    std::map<std::string, std::shared_ptr<TextureData>>  textureByIndicesKey;
-    std::map<long, std::shared_ptr<MeshData>>            meshBySurfaceId;
-
-    // for now, we only have one buffer; data->binary points to the same vector as that BufferData does.
-    BufferData &buffer = *gltf->defaultBuffer;
-    {
-        //
-        // nodes
-        //
-
-        for (int i = 0; i < raw.GetNodeCount(); i++) {
-            // assumption: RawNode index == NodeData index
-            const RawNode &node = raw.GetNode(i);
-
-            auto nodeData = gltf->nodes.hold(
-                new NodeData(node.name, node.translation, node.rotation, node.scale, node.isJoint));
-
-            if (options.enableUserProperties) {
-                nodeData->userProperties = node.userProperties;
-            }
-
-            for (const auto &childId : node.childIds) {
-                int childIx = raw.GetNodeById(childId);
-                assert(childIx >= 0);
-                nodeData->AddChildNode(childIx);
-            }
-            
-            nodesById.insert(std::make_pair(node.id, nodeData));
+    //
+    // animations
+    //
+
+    for (int i = 0; i < raw.GetAnimationCount(); i++) {
+      const RawAnimation& animation = raw.GetAnimation(i);
+
+      if (animation.channels.size() == 0) {
+        fmt::printf(
+            "Warning: animation '%s' has zero channels. Skipping.\n", animation.name.c_str());
+        continue;
+      }
+
+      auto accessor = gltf->AddAccessorAndView(buffer, GLT_FLOAT, animation.times);
+      accessor->min = {*std::min_element(std::begin(animation.times), std::end(animation.times))};
+      accessor->max = {*std::max_element(std::begin(animation.times), std::end(animation.times))};
+
+      AnimationData& aDat = *gltf->animations.hold(new AnimationData(animation.name, *accessor));
+      if (verboseOutput) {
+        fmt::printf(
+            "Animation '%s' has %lu channels:\n",
+            animation.name.c_str(),
+            animation.channels.size());
+      }
+
+      for (size_t channelIx = 0; channelIx < animation.channels.size(); channelIx++) {
+        const RawChannel& channel = animation.channels[channelIx];
+        const RawNode& node = raw.GetNode(channel.nodeIndex);
+
+        if (verboseOutput) {
+          fmt::printf(
+              "  Channel %lu (%s) has translations/rotations/scales/weights: [%lu, %lu, %lu, %lu]\n",
+              channelIx,
+              node.name.c_str(),
+              channel.translations.size(),
+              channel.rotations.size(),
+              channel.scales.size(),
+              channel.weights.size());
         }
 
-        //
-        // animations
-        //
-
-        for (int i = 0; i < raw.GetAnimationCount(); i++) {
-            const RawAnimation &animation = raw.GetAnimation(i);
+        NodeData& nDat = require(nodesById, node.id);
+        if (!channel.translations.empty()) {
+          aDat.AddNodeChannel(
+              nDat,
+              *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.translations),
+              "translation");
+        }
+        if (!channel.rotations.empty()) {
+          aDat.AddNodeChannel(
+              nDat, *gltf->AddAccessorAndView(buffer, GLT_QUATF, channel.rotations), "rotation");
+        }
+        if (!channel.scales.empty()) {
+          aDat.AddNodeChannel(
+              nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.scales), "scale");
+        }
+        if (!channel.weights.empty()) {
+          aDat.AddNodeChannel(
+              nDat,
+              *gltf->AddAccessorAndView(buffer, {CT_FLOAT, 1, "SCALAR"}, channel.weights),
+              "weights");
+        }
+      }
+    }
 
-            if (animation.channels.size() == 0) {
-                fmt::printf("Warning: animation '%s' has zero channels. Skipping.\n", animation.name.c_str());
-                continue;
+    //
+    // samplers
+    //
+
+    // textures
+    //
+
+    TextureBuilder textureBuilder(raw, options, outputFolder, *gltf);
+
+    //
+    // materials
+    //
+
+    for (int materialIndex = 0; materialIndex < raw.GetMaterialCount(); materialIndex++) {
+      const RawMaterial& material = raw.GetMaterial(materialIndex);
+      const bool isTransparent = material.type == RAW_MATERIAL_TYPE_TRANSPARENT ||
+          material.type == RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT;
+
+      Vec3f emissiveFactor;
+      float emissiveIntensity;
+
+      // acquire the texture of a specific RawTextureUsage as *TextData, or nullptr if none exists
+      auto simpleTex = [&](RawTextureUsage usage) -> std::shared_ptr<TextureData> {
+        return (material.textures[usage] >= 0)
+            ? textureBuilder.simple(material.textures[usage], "simple")
+            : nullptr;
+      };
+
+      TextureData* normalTexture = simpleTex(RAW_TEXTURE_USAGE_NORMAL).get();
+      TextureData* emissiveTexture = simpleTex(RAW_TEXTURE_USAGE_EMISSIVE).get();
+      TextureData* occlusionTexture = nullptr;
+
+      std::shared_ptr<PBRMetallicRoughness> pbrMetRough;
+      if (options.usePBRMetRough) {
+        // albedo is a basic texture, no merging needed
+        std::shared_ptr<TextureData> baseColorTex, aoMetRoughTex;
+
+        Vec4f diffuseFactor;
+        float metallic, roughness;
+        if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) {
+          /**
+           * PBR FBX Material -> PBR Met/Rough glTF.
+           *
+           * METALLIC and ROUGHNESS textures are packed in G and B channels of a rough/met texture.
+           * Other values translate directly.
+           */
+          RawMetRoughMatProps* props = (RawMetRoughMatProps*)material.info.get();
+          // merge metallic into the blue channel and roughness into the green, of a new combinatory
+          // texture
+          aoMetRoughTex = textureBuilder.combine(
+              {
+                  material.textures[RAW_TEXTURE_USAGE_OCCLUSION],
+                  material.textures[RAW_TEXTURE_USAGE_METALLIC],
+                  material.textures[RAW_TEXTURE_USAGE_ROUGHNESS],
+              },
+              "ao_met_rough",
+              [&](const std::vector<const TextureBuilder::pixel*> pixels) -> TextureBuilder::pixel {
+                return {{(*pixels[0])[0], (*pixels[2])[0], (*pixels[1])[0], 1}};
+              },
+              false);
+
+          baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
+          diffuseFactor = props->diffuseFactor;
+          metallic = props->metallic;
+          roughness = props->roughness;
+          emissiveFactor = props->emissiveFactor;
+          emissiveIntensity = props->emissiveIntensity;
+          // add the occlusion texture only if actual occlusion pixels exist in the aoNetRough
+          // texture.
+          if (material.textures[RAW_TEXTURE_USAGE_OCCLUSION] >= 0) {
+            occlusionTexture = aoMetRoughTex.get();
+          }
+        } else {
+          /**
+           * Traditional FBX Material -> PBR Met/Rough glTF.
+           *
+           * Diffuse channel is used as base colour. Simple constants for metallic and roughness.
+           */
+          const RawTraditionalMatProps* props = ((RawTraditionalMatProps*)material.info.get());
+          diffuseFactor = props->diffuseFactor;
+
+          if (material.info->shadingModel == RAW_SHADING_MODEL_BLINN ||
+              material.info->shadingModel == RAW_SHADING_MODEL_PHONG) {
+            // blinn/phong hardcoded to 0.4 metallic
+            metallic = 0.4f;
+
+            // fairly arbitrary conversion equation, with properties:
+            //   shininess 0 -> roughness 1
+            //   shininess 2 -> roughness ~0.7
+            //   shininess 6 -> roughness 0.5
+            //   shininess 16 -> roughness ~0.33
+            //   as shininess ==> oo, roughness ==> 0
+            auto getRoughness = [&](float shininess) { return sqrtf(2.0f / (2.0f + shininess)); };
+
+            aoMetRoughTex = textureBuilder.combine(
+                {
+                    material.textures[RAW_TEXTURE_USAGE_SHININESS],
+                },
+                "ao_met_rough",
+                [&](const std::vector<const TextureBuilder::pixel*> pixels)
+                    -> TextureBuilder::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}};
+                },
+                false);
+
+            if (aoMetRoughTex != nullptr) {
+              // if we successfully built a texture, factors are just multiplicative identity
+              metallic = roughness = 1.0f;
+            } else {
+              // no shininess texture,
+              roughness = getRoughness(props->shininess);
             }
 
-            auto accessor = gltf->AddAccessorAndView(buffer, GLT_FLOAT, animation.times);
-            accessor->min = { *std::min_element(std::begin(animation.times), std::end(animation.times)) };
-            accessor->max = { *std::max_element(std::begin(animation.times), std::end(animation.times)) };
+          } else {
+            metallic = 0.2f;
+            roughness = 0.8f;
+          }
 
-            AnimationData &aDat = *gltf->animations.hold(new AnimationData(animation.name, *accessor));
-            if (verboseOutput) {
-                fmt::printf("Animation '%s' has %lu channels:\n", animation.name.c_str(), animation.channels.size());
-            }
+          baseColorTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE);
 
-            for (size_t channelIx = 0; channelIx < animation.channels.size(); channelIx++) {
-                const RawChannel &channel = animation.channels[channelIx];
-                const RawNode    &node    = raw.GetNode(channel.nodeIndex);
-
-                if (verboseOutput) {
-                    fmt::printf(
-                        "  Channel %lu (%s) has translations/rotations/scales/weights: [%lu, %lu, %lu, %lu]\n",
-                        channelIx, node.name.c_str(), channel.translations.size(), channel.rotations.size(),
-                        channel.scales.size(), channel.weights.size());
-                }
-
-                NodeData &nDat = require(nodesById, node.id);
-                if (!channel.translations.empty()) {
-                    aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.translations), "translation");
-                }
-                if (!channel.rotations.empty()) {
-                    aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_QUATF, channel.rotations), "rotation");
-                }
-                if (!channel.scales.empty()) {
-                    aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.scales), "scale");
-                }
-                if (!channel.weights.empty()) {
-                    aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, {CT_FLOAT, 1, "SCALAR"}, channel.weights), "weights");
-                }
-            }
+          emissiveFactor = props->emissiveFactor;
+          emissiveIntensity = 1.0f;
+        }
+        pbrMetRough.reset(new PBRMetallicRoughness(
+            baseColorTex.get(), aoMetRoughTex.get(), diffuseFactor, metallic, roughness));
+      }
+
+      std::shared_ptr<KHRCmnUnlitMaterial> khrCmnUnlitMat;
+      if (options.useKHRMatUnlit) {
+        normalTexture = nullptr;
+
+        emissiveTexture = nullptr;
+        emissiveFactor = Vec3f(0.00f, 0.00f, 0.00f);
+
+        Vec4f diffuseFactor;
+        std::shared_ptr<TextureData> baseColorTex;
+
+        if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) {
+          RawMetRoughMatProps* props = (RawMetRoughMatProps*)material.info.get();
+          diffuseFactor = props->diffuseFactor;
+          baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
+        } else {
+          RawTraditionalMatProps* props = ((RawTraditionalMatProps*)material.info.get());
+          diffuseFactor = props->diffuseFactor;
+          baseColorTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE);
         }
 
-        //
-        // samplers
-        //
-
-        // textures
-        //
-
-        TextureBuilder textureBuilder(raw, options, outputFolder, *gltf);
-
-        //
-        // materials
-        //
-
-        for (int materialIndex = 0; materialIndex < raw.GetMaterialCount(); materialIndex++) {
-            const RawMaterial &material = raw.GetMaterial(materialIndex);
-            const bool isTransparent =
-                           material.type == RAW_MATERIAL_TYPE_TRANSPARENT ||
-                           material.type == RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT;
-
-            Vec3f emissiveFactor;
-            float emissiveIntensity;
-
-            // acquire the texture of a specific RawTextureUsage as *TextData, or nullptr if none exists
-            auto simpleTex = [&](RawTextureUsage usage) -> std::shared_ptr<TextureData> {
-                return (material.textures[usage] >= 0) ? textureBuilder.simple(material.textures[usage], "simple") : nullptr;
-            };
-
-            TextureData *normalTexture   = simpleTex(RAW_TEXTURE_USAGE_NORMAL).get();
-            TextureData *emissiveTexture = simpleTex(RAW_TEXTURE_USAGE_EMISSIVE).get();
-            TextureData *occlusionTexture = nullptr;
-
-            std::shared_ptr<PBRMetallicRoughness> pbrMetRough;
-            if (options.usePBRMetRough) {
-                // albedo is a basic texture, no merging needed
-                std::shared_ptr<TextureData> baseColorTex, aoMetRoughTex;
-
-                Vec4f diffuseFactor;
-                float metallic, roughness;
-                if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) {
-                    /**
-                     * PBR FBX Material -> PBR Met/Rough glTF.
-                     *
-                     * METALLIC and ROUGHNESS textures are packed in G and B channels of a rough/met texture.
-                     * Other values translate directly.
-                     */
-                    RawMetRoughMatProps *props = (RawMetRoughMatProps *) material.info.get();
-                    // merge metallic into the blue channel and roughness into the green, of a new combinatory texture
-                    aoMetRoughTex = textureBuilder.combine(
-                        {
-                            material.textures[RAW_TEXTURE_USAGE_OCCLUSION],
-                            material.textures[RAW_TEXTURE_USAGE_METALLIC],
-                            material.textures[RAW_TEXTURE_USAGE_ROUGHNESS],
-                        },
-                        "ao_met_rough",
-                        [&](const std::vector<const TextureBuilder::pixel *> pixels) -> TextureBuilder::pixel {
-                            return { {(*pixels[0])[0], (*pixels[2])[0], (*pixels[1])[0], 1} };
-                        },
-                        false);
-
-                    baseColorTex      = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
-                    diffuseFactor     = props->diffuseFactor;
-                    metallic          = props->metallic;
-                    roughness         = props->roughness;
-                    emissiveFactor    = props->emissiveFactor;
-                    emissiveIntensity = props->emissiveIntensity;
-                    // add the occlusion texture only if actual occlusion pixels exist in the aoNetRough texture.
-                    if (material.textures[RAW_TEXTURE_USAGE_OCCLUSION] >= 0) {
-                       occlusionTexture  = aoMetRoughTex.get();
-                    }
-                } else {
-                    /**
-                     * Traditional FBX Material -> PBR Met/Rough glTF.
-                     *
-                     * Diffuse channel is used as base colour. Simple constants for metallic and roughness.
-                     */
-                    const RawTraditionalMatProps *props = ((RawTraditionalMatProps *) material.info.get());
-                    diffuseFactor = props->diffuseFactor;
-
-                    if (material.info->shadingModel == RAW_SHADING_MODEL_BLINN ||
-                        material.info->shadingModel == RAW_SHADING_MODEL_PHONG)
-                    {
-                        // blinn/phong hardcoded to 0.4 metallic
-                        metallic  = 0.4f;
-
-                        // fairly arbitrary conversion equation, with properties:
-                        //   shininess 0 -> roughness 1
-                        //   shininess 2 -> roughness ~0.7
-                        //   shininess 6 -> roughness 0.5
-                        //   shininess 16 -> roughness ~0.33
-                        //   as shininess ==> oo, roughness ==> 0
-                        auto getRoughness = [&](float shininess) {
-                            return sqrtf(2.0f / (2.0f + shininess));
-                        };
-
-                        aoMetRoughTex = textureBuilder.combine(
-                            { material.textures[RAW_TEXTURE_USAGE_SHININESS], },
-                            "ao_met_rough",
-                            [&](const std::vector<const TextureBuilder::pixel *> pixels) -> TextureBuilder::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} };
-                            },
-                            false);
-
-                        if (aoMetRoughTex != nullptr) {
-                            // if we successfully built a texture, factors are just multiplicative identity
-                            metallic = roughness = 1.0f;
-                        } else {
-                            // no shininess texture,
-                            roughness = getRoughness(props->shininess);
-                        }
-
-                    } else {
-                        metallic  = 0.2f;
-                        roughness = 0.8f;
-                    }
-
-                    baseColorTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE);
-
-                    emissiveFactor    = props->emissiveFactor;
-                    emissiveIntensity = 1.0f;
-                }
-                pbrMetRough.reset(new PBRMetallicRoughness(baseColorTex.get(), aoMetRoughTex.get(), diffuseFactor, metallic, roughness));
-            }
+        pbrMetRough.reset(
+            new PBRMetallicRoughness(baseColorTex.get(), nullptr, diffuseFactor, 0.0f, 1.0f));
+
+        khrCmnUnlitMat.reset(new KHRCmnUnlitMaterial());
+      }
+      if (!occlusionTexture) {
+        occlusionTexture = simpleTex(RAW_TEXTURE_USAGE_OCCLUSION).get();
+      }
+
+      std::shared_ptr<MaterialData> mData = gltf->materials.hold(new MaterialData(
+          material.name,
+          isTransparent,
+          material.info->shadingModel,
+          normalTexture,
+          occlusionTexture,
+          emissiveTexture,
+          emissiveFactor * emissiveIntensity,
+          khrCmnUnlitMat,
+          pbrMetRough));
+      materialsByName[materialHash(material)] = mData;
+
+      if (options.enableUserProperties) {
+        mData->userProperties = material.userProperties;
+      }
+    }
 
-            std::shared_ptr<KHRCmnUnlitMaterial> khrCmnUnlitMat;
-            if (options.useKHRMatUnlit) {
-                normalTexture = nullptr;
+    for (const auto& surfaceModel : materialModels) {
+      assert(surfaceModel.GetSurfaceCount() == 1);
+      const RawSurface& rawSurface = surfaceModel.GetSurface(0);
+      const long surfaceId = rawSurface.id;
 
-                emissiveTexture = nullptr;
-                emissiveFactor = Vec3f(0.00f, 0.00f, 0.00f);
+      const RawMaterial& rawMaterial =
+          surfaceModel.GetMaterial(surfaceModel.GetTriangle(0).materialIndex);
+      const MaterialData& mData = require(materialsByName, materialHash(rawMaterial));
 
-                Vec4f diffuseFactor;
-                std::shared_ptr<TextureData> baseColorTex;
+      MeshData* mesh = nullptr;
+      auto meshIter = meshBySurfaceId.find(surfaceId);
+      if (meshIter != meshBySurfaceId.end()) {
+        mesh = meshIter->second.get();
 
-                if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) {
-                    RawMetRoughMatProps *props = (RawMetRoughMatProps *) material.info.get();
-                    diffuseFactor = props->diffuseFactor;
-                    baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
-                } else {
-                    RawTraditionalMatProps *props = ((RawTraditionalMatProps *) material.info.get());
-                    diffuseFactor = props->diffuseFactor;
-                    baseColorTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE);
-                }
+      } else {
+        std::vector<float> defaultDeforms;
+        for (const auto& channel : rawSurface.blendChannels) {
+          defaultDeforms.push_back(channel.defaultDeform);
+        }
+        auto meshPtr = gltf->meshes.hold(new MeshData(rawSurface.name, defaultDeforms));
+        meshBySurfaceId[surfaceId] = meshPtr;
+        mesh = meshPtr.get();
+      }
+
+      bool useLongIndices = (options.useLongIndices == UseLongIndicesOptions::ALWAYS) ||
+          (options.useLongIndices == UseLongIndicesOptions::AUTO &&
+           surfaceModel.GetVertexCount() > 65535);
+
+      std::shared_ptr<PrimitiveData> primitive;
+      if (options.draco.enabled) {
+        int triangleCount = surfaceModel.GetTriangleCount();
+
+        // initialize Draco mesh with vertex index information
+        auto dracoMesh(std::make_shared<draco::Mesh>());
+        dracoMesh->SetNumFaces(static_cast<size_t>(triangleCount));
+
+        for (uint32_t ii = 0; ii < triangleCount; ii++) {
+          draco::Mesh::Face face;
+          face[0] = surfaceModel.GetTriangle(ii).verts[0];
+          face[1] = surfaceModel.GetTriangle(ii).verts[1];
+          face[2] = surfaceModel.GetTriangle(ii).verts[2];
+          dracoMesh->SetFace(draco::FaceIndex(ii), face);
+        }
 
-                pbrMetRough.reset(new PBRMetallicRoughness(baseColorTex.get(), nullptr, diffuseFactor, 0.0f, 1.0f));
+        AccessorData& indexes =
+            *gltf->accessors.hold(new AccessorData(useLongIndices ? GLT_UINT : GLT_USHORT));
+        indexes.count = 3 * triangleCount;
+        primitive.reset(new PrimitiveData(indexes, mData, dracoMesh));
+      } else {
+        const AccessorData& indexes = *gltf->AddAccessorWithView(
+            *gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ELEMENT_ARRAY_BUFFER),
+            useLongIndices ? GLT_UINT : GLT_USHORT,
+            getIndexArray(surfaceModel),
+            std::string(""));
+        primitive.reset(new PrimitiveData(indexes, mData));
+      };
+
+      //
+      // surface vertices
+      //
+      {
+        if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_POSITION) != 0) {
+          const AttributeDefinition<Vec3f> ATTR_POSITION(
+              "POSITION",
+              &RawVertex::position,
+              GLT_VEC3F,
+              draco::GeometryAttribute::POSITION,
+              draco::DT_FLOAT32);
+          auto accessor =
+              gltf->AddAttributeToPrimitive<Vec3f>(buffer, surfaceModel, *primitive, ATTR_POSITION);
+
+          accessor->min = toStdVec(rawSurface.bounds.min);
+          accessor->max = toStdVec(rawSurface.bounds.max);
+        }
+        if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_NORMAL) != 0) {
+          const AttributeDefinition<Vec3f> ATTR_NORMAL(
+              "NORMAL",
+              &RawVertex::normal,
+              GLT_VEC3F,
+              draco::GeometryAttribute::NORMAL,
+              draco::DT_FLOAT32);
+          gltf->AddAttributeToPrimitive<Vec3f>(buffer, surfaceModel, *primitive, ATTR_NORMAL);
+        }
+        if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_TANGENT) != 0) {
+          const AttributeDefinition<Vec4f> ATTR_TANGENT("TANGENT", &RawVertex::tangent, GLT_VEC4F);
+          gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_TANGENT);
+        }
+        if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) != 0) {
+          const AttributeDefinition<Vec4f> ATTR_COLOR(
+              "COLOR_0",
+              &RawVertex::color,
+              GLT_VEC4F,
+              draco::GeometryAttribute::COLOR,
+              draco::DT_FLOAT32);
+          gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_COLOR);
+        }
+        if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV0) != 0) {
+          const AttributeDefinition<Vec2f> ATTR_TEXCOORD_0(
+              "TEXCOORD_0",
+              &RawVertex::uv0,
+              GLT_VEC2F,
+              draco::GeometryAttribute::TEX_COORD,
+              draco::DT_FLOAT32);
+          gltf->AddAttributeToPrimitive<Vec2f>(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_0);
+        }
+        if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV1) != 0) {
+          const AttributeDefinition<Vec2f> ATTR_TEXCOORD_1(
+              "TEXCOORD_1",
+              &RawVertex::uv1,
+              GLT_VEC2F,
+              draco::GeometryAttribute::TEX_COORD,
+              draco::DT_FLOAT32);
+          gltf->AddAttributeToPrimitive<Vec2f>(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1);
+        }
+        if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) != 0) {
+          const AttributeDefinition<Vec4i> ATTR_JOINTS(
+              "JOINTS_0",
+              &RawVertex::jointIndices,
+              GLT_VEC4I,
+              draco::GeometryAttribute::GENERIC,
+              draco::DT_UINT16);
+          gltf->AddAttributeToPrimitive<Vec4i>(buffer, surfaceModel, *primitive, ATTR_JOINTS);
+        }
+        if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) != 0) {
+          const AttributeDefinition<Vec4f> ATTR_WEIGHTS(
+              "WEIGHTS_0",
+              &RawVertex::jointWeights,
+              GLT_VEC4F,
+              draco::GeometryAttribute::GENERIC,
+              draco::DT_FLOAT32);
+          gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_WEIGHTS);
+        }
 
-                khrCmnUnlitMat.reset(new KHRCmnUnlitMaterial());
-            }
-            if (!occlusionTexture) {
-                occlusionTexture = simpleTex(RAW_TEXTURE_USAGE_OCCLUSION).get();
+        // each channel present in the mesh always ends up a target in the primitive
+        for (int channelIx = 0; channelIx < rawSurface.blendChannels.size(); channelIx++) {
+          const auto& channel = rawSurface.blendChannels[channelIx];
+
+          // track the bounds of each shape channel
+          Bounds<float, 3> shapeBounds;
+
+          std::vector<Vec3f> positions, normals;
+          std::vector<Vec4f> tangents;
+          for (int jj = 0; jj < surfaceModel.GetVertexCount(); jj++) {
+            auto blendVertex = surfaceModel.GetVertex(jj).blends[channelIx];
+            shapeBounds.AddPoint(blendVertex.position);
+            positions.push_back(blendVertex.position);
+            if (options.useBlendShapeTangents && channel.hasNormals) {
+              normals.push_back(blendVertex.normal);
             }
-
-            std::shared_ptr<MaterialData> mData = gltf->materials.hold(
-                new MaterialData(
-                    material.name, isTransparent, material.info->shadingModel,
-                    normalTexture, occlusionTexture, emissiveTexture,
-                    emissiveFactor * emissiveIntensity, khrCmnUnlitMat, pbrMetRough));
-            materialsByName[materialHash(material)] = mData;
-
-            if (options.enableUserProperties) {
-                mData->userProperties = material.userProperties;
+            if (options.useBlendShapeTangents && channel.hasTangents) {
+              tangents.push_back(blendVertex.tangent);
             }
+          }
+          std::shared_ptr<AccessorData> pAcc = gltf->AddAccessorWithView(
+              *gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
+              GLT_VEC3F,
+              positions,
+              channel.name);
+          pAcc->min = toStdVec(shapeBounds.min);
+          pAcc->max = toStdVec(shapeBounds.max);
+
+          std::shared_ptr<AccessorData> nAcc;
+          if (!normals.empty()) {
+            nAcc = gltf->AddAccessorWithView(
+                *gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
+                GLT_VEC3F,
+                normals,
+                channel.name);
+          }
+
+          std::shared_ptr<AccessorData> tAcc;
+          if (!tangents.empty()) {
+            nAcc = gltf->AddAccessorWithView(
+                *gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
+                GLT_VEC4F,
+                tangents,
+                channel.name);
+          }
+
+          primitive->AddTarget(pAcc.get(), nAcc.get(), tAcc.get());
+        }
+      }
+      if (options.draco.enabled) {
+        // Set up the encoder.
+        draco::Encoder encoder;
+
+        if (options.draco.compressionLevel != -1) {
+          int dracoSpeed = 10 - options.draco.compressionLevel;
+          encoder.SetSpeedOptions(dracoSpeed, dracoSpeed);
+        }
+        if (options.draco.quantBitsPosition != -1) {
+          encoder.SetAttributeQuantization(
+              draco::GeometryAttribute::POSITION, options.draco.quantBitsPosition);
+        }
+        if (options.draco.quantBitsTexCoord != -1) {
+          encoder.SetAttributeQuantization(
+              draco::GeometryAttribute::TEX_COORD, options.draco.quantBitsTexCoord);
+        }
+        if (options.draco.quantBitsNormal != -1) {
+          encoder.SetAttributeQuantization(
+              draco::GeometryAttribute::NORMAL, options.draco.quantBitsNormal);
+        }
+        if (options.draco.quantBitsColor != -1) {
+          encoder.SetAttributeQuantization(
+              draco::GeometryAttribute::COLOR, options.draco.quantBitsColor);
+        }
+        if (options.draco.quantBitsGeneric != -1) {
+          encoder.SetAttributeQuantization(
+              draco::GeometryAttribute::GENERIC, options.draco.quantBitsGeneric);
         }
 
-        for (const auto &surfaceModel : materialModels) {
-            assert(surfaceModel.GetSurfaceCount() == 1);
-            const RawSurface &rawSurface = surfaceModel.GetSurface(0);
-            const long surfaceId = rawSurface.id;
-
-            const RawMaterial &rawMaterial = surfaceModel.GetMaterial(surfaceModel.GetTriangle(0).materialIndex);
-            const MaterialData &mData = require(materialsByName, materialHash(rawMaterial));
+        draco::EncoderBuffer dracoBuffer;
+        draco::Status status = encoder.EncodeMeshToBuffer(*primitive->dracoMesh, &dracoBuffer);
+        assert(status.code() == draco::Status::OK);
 
-            MeshData *mesh = nullptr;
-            auto meshIter = meshBySurfaceId.find(surfaceId);
-            if (meshIter != meshBySurfaceId.end()) {
-                mesh = meshIter->second.get();
+        auto view = gltf->AddRawBufferView(buffer, dracoBuffer.data(), dracoBuffer.size());
+        primitive->NoteDracoBuffer(*view);
+      }
+      mesh->AddPrimitive(primitive);
+    }
 
-            } else {
-                std::vector<float> defaultDeforms;
-                for (const auto &channel : rawSurface.blendChannels) {
-                    defaultDeforms.push_back(channel.defaultDeform);
-                }
-                auto meshPtr = gltf->meshes.hold(new MeshData(rawSurface.name, defaultDeforms));
-                meshBySurfaceId[surfaceId] = meshPtr;
-                mesh = meshPtr.get();
-            }
+    //
+    // Assign meshes to node
+    //
 
-            bool useLongIndices =
-                (options.useLongIndices == UseLongIndicesOptions::ALWAYS)
-                || (options.useLongIndices == UseLongIndicesOptions::AUTO
-                    && surfaceModel.GetVertexCount() > 65535);
-
-            std::shared_ptr<PrimitiveData> primitive;
-            if (options.draco.enabled) {
-                int triangleCount = surfaceModel.GetTriangleCount();
-
-                // initialize Draco mesh with vertex index information
-                auto dracoMesh(std::make_shared<draco::Mesh>());
-                dracoMesh->SetNumFaces(static_cast<size_t>(triangleCount));
-
-                for (uint32_t ii = 0; ii < triangleCount; ii++) {
-                    draco::Mesh::Face face;
-                    face[0] = surfaceModel.GetTriangle(ii).verts[0];
-                    face[1] = surfaceModel.GetTriangle(ii).verts[1];
-                    face[2] = surfaceModel.GetTriangle(ii).verts[2];
-                    dracoMesh->SetFace(draco::FaceIndex(ii), face);
-                }
-
-                AccessorData &indexes = *gltf->accessors.hold(new AccessorData(useLongIndices ? GLT_UINT : GLT_USHORT));
-                indexes.count = 3 * triangleCount;
-                primitive.reset(new PrimitiveData(indexes, mData, dracoMesh));
-            } else {
-                const AccessorData &indexes = *gltf->AddAccessorWithView(
-                    *gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ELEMENT_ARRAY_BUFFER),
-                    useLongIndices ? GLT_UINT : GLT_USHORT, getIndexArray(surfaceModel), std::string(""));
-                primitive.reset(new PrimitiveData(indexes, mData));
-            };
-
-            //
-            // surface vertices
-            //
-            {
-                if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_POSITION) != 0) {
-                    const AttributeDefinition<Vec3f> ATTR_POSITION("POSITION", &RawVertex::position,
-                        GLT_VEC3F, draco::GeometryAttribute::POSITION, draco::DT_FLOAT32);
-                    auto accessor = gltf->AddAttributeToPrimitive<Vec3f>(
-                        buffer, surfaceModel, *primitive, ATTR_POSITION);
-
-                    accessor->min = toStdVec(rawSurface.bounds.min);
-                    accessor->max = toStdVec(rawSurface.bounds.max);
-                }
-                if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_NORMAL) != 0) {
-                    const AttributeDefinition<Vec3f> ATTR_NORMAL("NORMAL", &RawVertex::normal,
-                        GLT_VEC3F, draco::GeometryAttribute::NORMAL, draco::DT_FLOAT32);
-                    gltf->AddAttributeToPrimitive<Vec3f>(buffer, surfaceModel, *primitive, ATTR_NORMAL);
-                }
-                if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_TANGENT) != 0) {
-                    const AttributeDefinition<Vec4f> ATTR_TANGENT("TANGENT", &RawVertex::tangent, GLT_VEC4F);
-                    gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_TANGENT);
-                }
-                if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) != 0) {
-                    const AttributeDefinition<Vec4f> ATTR_COLOR("COLOR_0", &RawVertex::color, GLT_VEC4F,
-                        draco::GeometryAttribute::COLOR, draco::DT_FLOAT32);
-                    gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_COLOR);
-                }
-                if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV0) != 0) {
-                    const AttributeDefinition<Vec2f> ATTR_TEXCOORD_0("TEXCOORD_0", &RawVertex::uv0,
-                        GLT_VEC2F, draco::GeometryAttribute::TEX_COORD, draco::DT_FLOAT32);
-                    gltf->AddAttributeToPrimitive<Vec2f>(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_0);
-                }
-                if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV1) != 0) {
-                    const AttributeDefinition<Vec2f> ATTR_TEXCOORD_1("TEXCOORD_1", &RawVertex::uv1,
-                        GLT_VEC2F, draco::GeometryAttribute::TEX_COORD, draco::DT_FLOAT32);
-                    gltf->AddAttributeToPrimitive<Vec2f>(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1);
-                }
-                if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) != 0) {
-                    const AttributeDefinition<Vec4i> ATTR_JOINTS("JOINTS_0", &RawVertex::jointIndices,
-                        GLT_VEC4I, draco::GeometryAttribute::GENERIC, draco::DT_UINT16);
-                    gltf->AddAttributeToPrimitive<Vec4i>(buffer, surfaceModel, *primitive, ATTR_JOINTS);
-                }
-                if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) != 0) {
-                    const AttributeDefinition<Vec4f> ATTR_WEIGHTS("WEIGHTS_0", &RawVertex::jointWeights,
-                        GLT_VEC4F, draco::GeometryAttribute::GENERIC, draco::DT_FLOAT32);
-                    gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_WEIGHTS);
-                }
-
-                // each channel present in the mesh always ends up a target in the primitive
-                for (int channelIx = 0; channelIx < rawSurface.blendChannels.size(); channelIx ++) {
-                    const auto &channel = rawSurface.blendChannels[channelIx];
-
-                    // track the bounds of each shape channel
-                    Bounds<float, 3> shapeBounds;
-
-                    std::vector<Vec3f> positions, normals;
-                    std::vector<Vec4f> tangents;
-                    for (int jj = 0; jj < surfaceModel.GetVertexCount(); jj ++) {
-                        auto blendVertex = surfaceModel.GetVertex(jj).blends[channelIx];
-                        shapeBounds.AddPoint(blendVertex.position);
-                        positions.push_back(blendVertex.position);
-                        if (options.useBlendShapeTangents && channel.hasNormals) {
-                            normals.push_back(blendVertex.normal);
-                        }
-                        if (options.useBlendShapeTangents && channel.hasTangents) {
-                            tangents.push_back(blendVertex.tangent);
-                        }
-                    }
-                    std::shared_ptr<AccessorData> pAcc = gltf->AddAccessorWithView(
-                        *gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
-                        GLT_VEC3F, positions, channel.name);
-                    pAcc->min = toStdVec(shapeBounds.min);
-                    pAcc->max = toStdVec(shapeBounds.max);
-
-                    std::shared_ptr<AccessorData> nAcc;
-                    if (!normals.empty()) {
-                        nAcc = gltf->AddAccessorWithView(
-                            *gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
-                            GLT_VEC3F, normals, channel.name);
-                    }
-
-                    std::shared_ptr<AccessorData> tAcc;
-                    if (!tangents.empty()) {
-                        nAcc = gltf->AddAccessorWithView(
-                            *gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
-                            GLT_VEC4F, tangents, channel.name);
-                    }
-
-                    primitive->AddTarget(pAcc.get(), nAcc.get(), tAcc.get());
-                }
-            }
-            if (options.draco.enabled) {
-                // Set up the encoder.
-                draco::Encoder encoder;
-
-                if (options.draco.compressionLevel != -1) {
-                    int dracoSpeed = 10 - options.draco.compressionLevel;
-                    encoder.SetSpeedOptions(dracoSpeed, dracoSpeed);
-                }
-                if (options.draco.quantBitsPosition != -1) {
-                    encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, options.draco.quantBitsPosition);
-                }
-                if (options.draco.quantBitsTexCoord != -1) {
-                    encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, options.draco.quantBitsTexCoord);
-                }
-                if (options.draco.quantBitsNormal != -1) {
-                    encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, options.draco.quantBitsNormal);
-                }
-                if (options.draco.quantBitsColor != -1) {
-                    encoder.SetAttributeQuantization(draco::GeometryAttribute::COLOR, options.draco.quantBitsColor);
-                }
-                if (options.draco.quantBitsGeneric != -1) {
-                    encoder.SetAttributeQuantization(draco::GeometryAttribute::GENERIC, options.draco.quantBitsGeneric);
-                }
-
-                draco::EncoderBuffer dracoBuffer;
-                draco::Status        status = encoder.EncodeMeshToBuffer(*primitive->dracoMesh, &dracoBuffer);
-                assert(status.code() == draco::Status::OK);
-
-                auto view = gltf->AddRawBufferView(buffer, dracoBuffer.data(), dracoBuffer.size());
-                primitive->NoteDracoBuffer(*view);
-            }
-            mesh->AddPrimitive(primitive);
-        }
+    for (int i = 0; i < raw.GetNodeCount(); i++) {
+      const RawNode& node = raw.GetNode(i);
+      auto nodeData = gltf->nodes.ptrs[i];
 
-        //
-        // Assign meshes to node
-        //
+      //
+      // Assign mesh to node
+      //
+      if (node.surfaceId > 0) {
+        int surfaceIndex = raw.GetSurfaceById(node.surfaceId);
+        const RawSurface& rawSurface = raw.GetSurface(surfaceIndex);
 
-        for (int i = 0; i < raw.GetNodeCount(); i++) {
-            const RawNode &node = raw.GetNode(i);
-            auto nodeData = gltf->nodes.ptrs[i];
-
-            //
-            // Assign mesh to node
-            //
-            if (node.surfaceId > 0)
-            {
-                int surfaceIndex = raw.GetSurfaceById(node.surfaceId);
-                const RawSurface &rawSurface = raw.GetSurface(surfaceIndex);
-
-                MeshData &meshData = require(meshBySurfaceId, rawSurface.id);
-                nodeData->SetMesh(meshData.ix);
-
-                //
-                // surface skin
-                //
-                if (!rawSurface.jointIds.empty()) {
-                    if (nodeData->skin == -1) {
-                        // glTF uses column-major matrices
-                        std::vector<Mat4f> inverseBindMatrices;
-                        for (const auto &inverseBindMatrice : rawSurface.inverseBindMatrices) {
-                            inverseBindMatrices.push_back(inverseBindMatrice.Transpose());
-                        }
-
-                        std::vector<uint32_t> jointIndexes;
-                        for (const auto &jointId : rawSurface.jointIds) {
-                            jointIndexes.push_back(require(nodesById, jointId).ix);
-                        }
-
-                        // Write out inverseBindMatrices
-                        auto accIBM = gltf->AddAccessorAndView(buffer, GLT_MAT4F, inverseBindMatrices);
-
-                        auto skeletonRoot = require(nodesById, rawSurface.skeletonRootId);
-                        auto skin = *gltf->skins.hold(new SkinData(jointIndexes, *accIBM, skeletonRoot));
-                        nodeData->SetSkin(skin.ix);
-                    }
-                }
-            }
-        }
+        MeshData& meshData = require(meshBySurfaceId, rawSurface.id);
+        nodeData->SetMesh(meshData.ix);
 
         //
-        // cameras
+        // surface skin
         //
-
-        for (int i = 0; i < raw.GetCameraCount(); i++) {
-            const RawCamera &cam    = raw.GetCamera(i);
-            CameraData      &camera = *gltf->cameras.hold(new CameraData());
-            camera.name = cam.name;
-
-            if (cam.mode == RawCamera::CAMERA_MODE_PERSPECTIVE) {
-                camera.type        = "perspective";
-                camera.aspectRatio = cam.perspective.aspectRatio;
-                camera.yfov        = cam.perspective.fovDegreesY * ((float) M_PI / 180.0f);
-                camera.znear       = cam.perspective.nearZ;
-                camera.zfar        = cam.perspective.farZ;
-            } else {
-                camera.type  = "orthographic";
-                camera.xmag  = cam.orthographic.magX;
-                camera.ymag  = cam.orthographic.magY;
-                camera.znear = cam.orthographic.nearZ;
-                camera.zfar  = cam.orthographic.farZ;
+        if (!rawSurface.jointIds.empty()) {
+          if (nodeData->skin == -1) {
+            // glTF uses column-major matrices
+            std::vector<Mat4f> inverseBindMatrices;
+            for (const auto& inverseBindMatrice : rawSurface.inverseBindMatrices) {
+              inverseBindMatrices.push_back(inverseBindMatrice.Transpose());
             }
-            // Add the camera to the node hierarchy.
 
-            auto iter = nodesById.find(cam.nodeId);
-            if (iter == nodesById.end()) {
-                fmt::printf("Warning: Camera node id %lu does not exist.\n", cam.nodeId);
-                continue;
+            std::vector<uint32_t> jointIndexes;
+            for (const auto& jointId : rawSurface.jointIds) {
+              jointIndexes.push_back(require(nodesById, jointId).ix);
             }
-            iter->second->SetCamera(camera.ix);
-        }
 
-        //
-        // lights
-        //
-        std::vector<json> khrPunctualLights;
-        if (options.useKHRLightsPunctual) {
-            for (int i = 0; i < raw.GetLightCount(); i ++) {
-                const RawLight &light = raw.GetLight(i);
-                LightData::Type type;
-                switch(light.type) {
-                    case RAW_LIGHT_TYPE_DIRECTIONAL:
-                        type = LightData::Type::Directional;
-                        break;
-                    case RAW_LIGHT_TYPE_POINT:
-                        type = LightData::Type::Point;
-                        break;
-                    case RAW_LIGHT_TYPE_SPOT:
-                        type = LightData::Type::Spot;
-                        break;
-                }
-                gltf->lights.hold(new LightData(
-                    light.name,
-                    type,
-                    light.color,
-                    // FBX intensity defaults to 100, so let's call that 1.0;
-                    // but caveat: I find nothing in the documentation to suggest
-                    // what unit the FBX value is meant to be measured in...
-                    light.intensity / 100,
-                    light.innerConeAngle,
-                    light.outerConeAngle));
-            }
-        }
-        for (int i = 0; i < raw.GetNodeCount(); i++) {
-            const RawNode &node = raw.GetNode(i);
-            const auto nodeData = gltf->nodes.ptrs[i];
+            // Write out inverseBindMatrices
+            auto accIBM = gltf->AddAccessorAndView(buffer, GLT_MAT4F, inverseBindMatrices);
 
-            if (node.lightIx >= 0) {
-                // we lean on the fact that in this simple case, raw and gltf indexing are aligned
-                nodeData->SetLight(node.lightIx);
-            }
+            auto skeletonRoot = require(nodesById, rawSurface.skeletonRootId);
+            auto skin = *gltf->skins.hold(new SkinData(jointIndexes, *accIBM, skeletonRoot));
+            nodeData->SetSkin(skin.ix);
+          }
         }
+      }
     }
 
-    NodeData        &rootNode  = require(nodesById, raw.GetRootNode());
-    const SceneData &rootScene = *gltf->scenes.hold(new SceneData(DEFAULT_SCENE_NAME, rootNode));
-
-    if (options.outputBinary) {
-        // note: glTF binary is little-endian
-        const char glbHeader[] = {
-            'g', 'l', 'T', 'F',        // magic
-            0x02, 0x00, 0x00, 0x00,        // version
-            0x00, 0x00, 0x00, 0x00,        // total length: written in later
-        };
-        gltfOutStream.write(glbHeader, 12);
-
-        // binary glTF 2.0 has a sub-header for each of the JSON and BIN chunks
-        const char glb2JsonHeader[] = {
-            0x00, 0x00, 0x00, 0x00, // chunk length: written in later
-            'J', 'S', 'O', 'N',     // chunk type: 0x4E4F534A aka JSON
-        };
-        gltfOutStream.write(glb2JsonHeader, 8);
+    //
+    // cameras
+    //
+
+    for (int i = 0; i < raw.GetCameraCount(); i++) {
+      const RawCamera& cam = raw.GetCamera(i);
+      CameraData& camera = *gltf->cameras.hold(new CameraData());
+      camera.name = cam.name;
+
+      if (cam.mode == RawCamera::CAMERA_MODE_PERSPECTIVE) {
+        camera.type = "perspective";
+        camera.aspectRatio = cam.perspective.aspectRatio;
+        camera.yfov = cam.perspective.fovDegreesY * ((float)M_PI / 180.0f);
+        camera.znear = cam.perspective.nearZ;
+        camera.zfar = cam.perspective.farZ;
+      } else {
+        camera.type = "orthographic";
+        camera.xmag = cam.orthographic.magX;
+        camera.ymag = cam.orthographic.magY;
+        camera.znear = cam.orthographic.nearZ;
+        camera.zfar = cam.orthographic.farZ;
+      }
+      // Add the camera to the node hierarchy.
+
+      auto iter = nodesById.find(cam.nodeId);
+      if (iter == nodesById.end()) {
+        fmt::printf("Warning: Camera node id %lu does not exist.\n", cam.nodeId);
+        continue;
+      }
+      iter->second->SetCamera(camera.ix);
     }
 
-    {
-        std::vector<std::string> extensionsUsed, extensionsRequired;
-        if (options.useKHRMatUnlit) {
-            extensionsUsed.push_back(KHR_MATERIALS_CMN_UNLIT);
-        }
-        if (!gltf->lights.ptrs.empty()) {
-            extensionsUsed.push_back(KHR_LIGHTS_PUNCTUAL);
-        }
-        if (options.draco.enabled) {
-            extensionsUsed.push_back(KHR_DRACO_MESH_COMPRESSION);
-            extensionsRequired.push_back(KHR_DRACO_MESH_COMPRESSION);
+    //
+    // lights
+    //
+    std::vector<json> khrPunctualLights;
+    if (options.useKHRLightsPunctual) {
+      for (int i = 0; i < raw.GetLightCount(); i++) {
+        const RawLight& light = raw.GetLight(i);
+        LightData::Type type;
+        switch (light.type) {
+          case RAW_LIGHT_TYPE_DIRECTIONAL:
+            type = LightData::Type::Directional;
+            break;
+          case RAW_LIGHT_TYPE_POINT:
+            type = LightData::Type::Point;
+            break;
+          case RAW_LIGHT_TYPE_SPOT:
+            type = LightData::Type::Spot;
+            break;
         }
+        gltf->lights.hold(new LightData(
+            light.name,
+            type,
+            light.color,
+            // FBX intensity defaults to 100, so let's call that 1.0;
+            // but caveat: I find nothing in the documentation to suggest
+            // what unit the FBX value is meant to be measured in...
+            light.intensity / 100,
+            light.innerConeAngle,
+            light.outerConeAngle));
+      }
+    }
+    for (int i = 0; i < raw.GetNodeCount(); i++) {
+      const RawNode& node = raw.GetNode(i);
+      const auto nodeData = gltf->nodes.ptrs[i];
+
+      if (node.lightIx >= 0) {
+        // we lean on the fact that in this simple case, raw and gltf indexing are aligned
+        nodeData->SetLight(node.lightIx);
+      }
+    }
+  }
+
+  NodeData& rootNode = require(nodesById, raw.GetRootNode());
+  const SceneData& rootScene = *gltf->scenes.hold(new SceneData(DEFAULT_SCENE_NAME, rootNode));
+
+  if (options.outputBinary) {
+    // note: glTF binary is little-endian
+    const char glbHeader[] = {
+        'g',
+        'l',
+        'T',
+        'F', // magic
+        0x02,
+        0x00,
+        0x00,
+        0x00, // version
+        0x00,
+        0x00,
+        0x00,
+        0x00, // total length: written in later
+    };
+    gltfOutStream.write(glbHeader, 12);
+
+    // binary glTF 2.0 has a sub-header for each of the JSON and BIN chunks
+    const char glb2JsonHeader[] = {
+        0x00,
+        0x00,
+        0x00,
+        0x00, // chunk length: written in later
+        'J',
+        'S',
+        'O',
+        'N', // chunk type: 0x4E4F534A aka JSON
+    };
+    gltfOutStream.write(glb2JsonHeader, 8);
+  }
+
+  {
+    std::vector<std::string> extensionsUsed, extensionsRequired;
+    if (options.useKHRMatUnlit) {
+      extensionsUsed.push_back(KHR_MATERIALS_CMN_UNLIT);
+    }
+    if (!gltf->lights.ptrs.empty()) {
+      extensionsUsed.push_back(KHR_LIGHTS_PUNCTUAL);
+    }
+    if (options.draco.enabled) {
+      extensionsUsed.push_back(KHR_DRACO_MESH_COMPRESSION);
+      extensionsRequired.push_back(KHR_DRACO_MESH_COMPRESSION);
+    }
 
-        json glTFJson {
-          { "asset", {
-              { "generator", "FBX2glTF v" + FBX2GLTF_VERSION },
-              { "version", "2.0" }}},
-          { "scene", rootScene.ix }
-        };
-        if (!extensionsUsed.empty()) {
-            glTFJson["extensionsUsed"] = extensionsUsed;
-        }
-        if (!extensionsRequired.empty()) {
-            glTFJson["extensionsRequired"] = extensionsRequired;
-        }
+    json glTFJson{{"asset", {{"generator", "FBX2glTF v" + FBX2GLTF_VERSION}, {"version", "2.0"}}},
+                  {"scene", rootScene.ix}};
+    if (!extensionsUsed.empty()) {
+      glTFJson["extensionsUsed"] = extensionsUsed;
+    }
+    if (!extensionsRequired.empty()) {
+      glTFJson["extensionsRequired"] = extensionsRequired;
+    }
 
-        gltf->serializeHolders(glTFJson);
+    gltf->serializeHolders(glTFJson);
 
-        gltfOutStream << glTFJson.dump(options.outputBinary ? 0 : 4);
+    gltfOutStream << glTFJson.dump(options.outputBinary ? 0 : 4);
+  }
+  if (options.outputBinary) {
+    uint32_t jsonLength = (uint32_t)gltfOutStream.tellp() - 20;
+    // the binary body must begin on a 4-aligned address, so pad json with spaces if necessary
+    while ((jsonLength % 4) != 0) {
+      gltfOutStream.put(' ');
+      jsonLength++;
     }
-    if (options.outputBinary) {
-        uint32_t jsonLength = (uint32_t) gltfOutStream.tellp() - 20;
-        // the binary body must begin on a 4-aligned address, so pad json with spaces if necessary
-        while ((jsonLength % 4) != 0) {
-            gltfOutStream.put(' ');
-            jsonLength++;
-        }
 
-        uint32_t binHeader = (uint32_t) gltfOutStream.tellp();
-        // binary glTF 2.0 has a sub-header for each of the JSON and BIN chunks
-        const char glb2BinaryHeader[] = {
-            0x00, 0x00, 0x00, 0x00, // chunk length: written in later
-            'B', 'I', 'N', 0x00,    // chunk type: 0x004E4942 aka BIN
-        };
-        gltfOutStream.write(glb2BinaryHeader, 8);
-
-        // append binary buffer directly to .glb file
-        uint32_t binaryLength = gltf->binary->size();
-        gltfOutStream.write((const char *) &(*gltf->binary)[0], binaryLength);
-        while ((binaryLength % 4) != 0) {
-            gltfOutStream.put('\0');
-            binaryLength++;
-        }
-        uint32_t totalLength = (uint32_t) gltfOutStream.tellp();
-
-        // seek back to sub-header for json chunk
-        gltfOutStream.seekp(8);
-
-        // write total length, little-endian
-        gltfOutStream.put((totalLength >> 0) & 0xFF);
-        gltfOutStream.put((totalLength >> 8) & 0xFF);
-        gltfOutStream.put((totalLength >> 16) & 0xFF);
-        gltfOutStream.put((totalLength >> 24) & 0xFF);
-
-        // write JSON length, little-endian
-        gltfOutStream.put((jsonLength >> 0) & 0xFF);
-        gltfOutStream.put((jsonLength >> 8) & 0xFF);
-        gltfOutStream.put((jsonLength >> 16) & 0xFF);
-        gltfOutStream.put((jsonLength >> 24) & 0xFF);
-
-        // seek back to the gltf 2.0 binary chunk header
-        gltfOutStream.seekp(binHeader);
-
-        // write total length, little-endian
-        gltfOutStream.put((binaryLength >> 0) & 0xFF);
-        gltfOutStream.put((binaryLength >> 8) & 0xFF);
-        gltfOutStream.put((binaryLength >> 16) & 0xFF);
-        gltfOutStream.put((binaryLength >> 24) & 0xFF);
-
-        // be tidy and return write pointer to end-of-file
-        gltfOutStream.seekp(0, std::ios::end);
+    uint32_t binHeader = (uint32_t)gltfOutStream.tellp();
+    // binary glTF 2.0 has a sub-header for each of the JSON and BIN chunks
+    const char glb2BinaryHeader[] = {
+        0x00,
+        0x00,
+        0x00,
+        0x00, // chunk length: written in later
+        'B',
+        'I',
+        'N',
+        0x00, // chunk type: 0x004E4942 aka BIN
+    };
+    gltfOutStream.write(glb2BinaryHeader, 8);
+
+    // append binary buffer directly to .glb file
+    uint32_t binaryLength = gltf->binary->size();
+    gltfOutStream.write((const char*)&(*gltf->binary)[0], binaryLength);
+    while ((binaryLength % 4) != 0) {
+      gltfOutStream.put('\0');
+      binaryLength++;
     }
+    uint32_t totalLength = (uint32_t)gltfOutStream.tellp();
+
+    // seek back to sub-header for json chunk
+    gltfOutStream.seekp(8);
+
+    // write total length, little-endian
+    gltfOutStream.put((totalLength >> 0) & 0xFF);
+    gltfOutStream.put((totalLength >> 8) & 0xFF);
+    gltfOutStream.put((totalLength >> 16) & 0xFF);
+    gltfOutStream.put((totalLength >> 24) & 0xFF);
+
+    // write JSON length, little-endian
+    gltfOutStream.put((jsonLength >> 0) & 0xFF);
+    gltfOutStream.put((jsonLength >> 8) & 0xFF);
+    gltfOutStream.put((jsonLength >> 16) & 0xFF);
+    gltfOutStream.put((jsonLength >> 24) & 0xFF);
+
+    // seek back to the gltf 2.0 binary chunk header
+    gltfOutStream.seekp(binHeader);
+
+    // write total length, little-endian
+    gltfOutStream.put((binaryLength >> 0) & 0xFF);
+    gltfOutStream.put((binaryLength >> 8) & 0xFF);
+    gltfOutStream.put((binaryLength >> 16) & 0xFF);
+    gltfOutStream.put((binaryLength >> 24) & 0xFF);
+
+    // be tidy and return write pointer to end-of-file
+    gltfOutStream.seekp(0, std::ios::end);
+  }
 
-    return new ModelData(gltf->binary);
+  return new ModelData(gltf->binary);
 }

+ 118 - 119
src/gltf/Raw2Gltf.hpp

@@ -19,138 +19,141 @@
 #include "FBX2glTF.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";
-const std::string KHR_LIGHTS_PUNCTUAL                   = "KHR_lights_punctual";
+const std::string KHR_DRACO_MESH_COMPRESSION = "KHR_draco_mesh_compression";
+const std::string KHR_MATERIALS_CMN_UNLIT = "KHR_materials_unlit";
+const std::string KHR_LIGHTS_PUNCTUAL = "KHR_lights_punctual";
 
 const std::string extBufferFilename = "buffer.bin";
 
 struct ComponentType {
-    // OpenGL Datatype enums
-    enum GL_DataType
-    {
-        GL_BYTE = 5120,
-        GL_UNSIGNED_BYTE,
-        GL_SHORT,
-        GL_UNSIGNED_SHORT,
-        GL_INT,
-        GL_UNSIGNED_INT,
-        GL_FLOAT
-    };
-
-    const GL_DataType glType;
-    const unsigned int size;
+  // OpenGL Datatype enums
+  enum GL_DataType {
+    GL_BYTE = 5120,
+    GL_UNSIGNED_BYTE,
+    GL_SHORT,
+    GL_UNSIGNED_SHORT,
+    GL_INT,
+    GL_UNSIGNED_INT,
+    GL_FLOAT
+  };
+
+  const GL_DataType glType;
+  const unsigned int size;
 };
 
 const ComponentType CT_USHORT = {ComponentType::GL_UNSIGNED_SHORT, 2};
-const ComponentType CT_UINT   = {ComponentType::GL_UNSIGNED_INT, 4};
-const ComponentType CT_FLOAT  = {ComponentType::GL_FLOAT, 4};
+const ComponentType CT_UINT = {ComponentType::GL_UNSIGNED_INT, 4};
+const ComponentType CT_FLOAT = {ComponentType::GL_FLOAT, 4};
 
 // Map our low-level data types for glTF output
 struct GLType {
-    GLType(const ComponentType &componentType, unsigned int count, const std::string dataType)
-        : componentType(componentType),
-          count(count),
-          dataType(dataType)
-    {}
-
-    unsigned int byteStride() const { return componentType.size * count; }
-
-    void write(uint8_t *buf, const float scalar) const    { *((float *) buf)    = scalar; }
-    void write(uint8_t *buf, const uint32_t scalar) const {
-        switch(componentType.size) {
-            case 1:
-                *buf = (uint8_t)scalar;
-                break;
-            case 2:
-                *((uint16_t *) buf) = (uint16_t)scalar;
-                break;
-            case 4:
-                *((uint32_t *) buf) = scalar;
-                break;
-        }
+  GLType(const ComponentType& componentType, unsigned int count, const std::string dataType)
+      : componentType(componentType), count(count), dataType(dataType) {}
+
+  unsigned int byteStride() const {
+    return componentType.size * count;
+  }
+
+  void write(uint8_t* buf, const float scalar) const {
+    *((float*)buf) = scalar;
+  }
+  void write(uint8_t* buf, const uint32_t scalar) const {
+    switch (componentType.size) {
+      case 1:
+        *buf = (uint8_t)scalar;
+        break;
+      case 2:
+        *((uint16_t*)buf) = (uint16_t)scalar;
+        break;
+      case 4:
+        *((uint32_t*)buf) = scalar;
+        break;
     }
+  }
 
-    template<class T, int d>
-    void write(uint8_t *buf, const mathfu::Vector<T, d> &vector) const {
-        for (int ii = 0; ii < d; ii ++) {
-            ((T *)buf)[ii] = vector(ii);
-        }
+  template <class T, int d>
+  void write(uint8_t* buf, const mathfu::Vector<T, d>& vector) const {
+    for (int ii = 0; ii < d; ii++) {
+      ((T*)buf)[ii] = vector(ii);
     }
-    template<class T, int d>
-    void write(uint8_t *buf, const mathfu::Matrix<T, d> &matrix) const {
-        // three matrix types require special alignment considerations that we don't handle
-        // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
-        assert(!(sizeof(T) == 1 && d == 2));
-        assert(!(sizeof(T) == 1 && d == 3));
-        assert(!(sizeof(T) == 2 && d == 2));
-        for (int col = 0; col < d; col ++) {
-            for (int row = 0; row < d; row ++) {
-                // glTF matrices are column-major
-                ((T *)buf)[col * d + row] = matrix(row, col);
-            }
-        }
+  }
+  template <class T, int d>
+  void write(uint8_t* buf, const mathfu::Matrix<T, d>& matrix) const {
+    // three matrix types require special alignment considerations that we don't handle
+    // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
+    assert(!(sizeof(T) == 1 && d == 2));
+    assert(!(sizeof(T) == 1 && d == 3));
+    assert(!(sizeof(T) == 2 && d == 2));
+    for (int col = 0; col < d; col++) {
+      for (int row = 0; row < d; row++) {
+        // glTF matrices are column-major
+        ((T*)buf)[col * d + row] = matrix(row, col);
+      }
     }
-    template<class T>
-    void write(uint8_t *buf, const mathfu::Quaternion<T> &quaternion) const {
-        for (int ii = 0; ii < 3; ii++) {
-            ((T *)buf)[ii] = quaternion.vector()(ii);
-        }
-        ((T *)buf)[3] = quaternion.scalar();
+  }
+  template <class T>
+  void write(uint8_t* buf, const mathfu::Quaternion<T>& quaternion) const {
+    for (int ii = 0; ii < 3; ii++) {
+      ((T*)buf)[ii] = quaternion.vector()(ii);
     }
+    ((T*)buf)[3] = quaternion.scalar();
+  }
 
-    const ComponentType componentType;
-    const uint8_t       count;
-    const std::string   dataType;
+  const ComponentType componentType;
+  const uint8_t count;
+  const std::string dataType;
 };
 
-const GLType GLT_FLOAT  = {CT_FLOAT, 1, "SCALAR"};
+const GLType GLT_FLOAT = {CT_FLOAT, 1, "SCALAR"};
 const GLType GLT_USHORT = {CT_USHORT, 1, "SCALAR"};
-const GLType GLT_UINT   = {CT_UINT, 1, "SCALAR"};
-const GLType GLT_VEC2F  = {CT_FLOAT, 2, "VEC2"};
-const GLType GLT_VEC3F  = {CT_FLOAT, 3, "VEC3"};
-const GLType GLT_VEC4F  = {CT_FLOAT, 4, "VEC4"};
-const GLType GLT_VEC4I  = {CT_USHORT, 4, "VEC4"};
-const GLType GLT_MAT2F  = {CT_USHORT, 4, "MAT2"};
-const GLType GLT_MAT3F  = {CT_USHORT, 9, "MAT3"};
-const GLType GLT_MAT4F  = {CT_FLOAT, 16, "MAT4"};
-const GLType GLT_QUATF  = {CT_FLOAT, 4, "VEC4"};
+const GLType GLT_UINT = {CT_UINT, 1, "SCALAR"};
+const GLType GLT_VEC2F = {CT_FLOAT, 2, "VEC2"};
+const GLType GLT_VEC3F = {CT_FLOAT, 3, "VEC3"};
+const GLType GLT_VEC4F = {CT_FLOAT, 4, "VEC4"};
+const GLType GLT_VEC4I = {CT_USHORT, 4, "VEC4"};
+const GLType GLT_MAT2F = {CT_USHORT, 4, "MAT2"};
+const GLType GLT_MAT3F = {CT_USHORT, 9, "MAT3"};
+const GLType GLT_MAT4F = {CT_FLOAT, 16, "MAT4"};
+const GLType GLT_QUATF = {CT_FLOAT, 4, "VEC4"};
 
 /**
  * The base of any indexed glTF entity.
  */
-struct Holdable
-{
-    uint32_t ix;
+struct Holdable {
+  uint32_t ix;
 
-    virtual json serialize() const = 0;
+  virtual json serialize() const = 0;
 };
 
-template<class T>
-struct AttributeDefinition
-{
-    const std::string                    gltfName;
-    const T RawVertex::*                 rawAttributeIx;
-    const GLType                         glType;
-    const draco::GeometryAttribute::Type dracoAttribute;
-    const draco::DataType                dracoComponentType;
-
-    AttributeDefinition(
-        const std::string gltfName, const T RawVertex::*rawAttributeIx, const GLType &_glType,
-        const draco::GeometryAttribute::Type dracoAttribute, const draco::DataType dracoComponentType)
-        : gltfName(gltfName),
-          rawAttributeIx(rawAttributeIx),
-          glType(_glType),
-          dracoAttribute(dracoAttribute),
-          dracoComponentType(dracoComponentType) {}
-
-    AttributeDefinition(
-        const std::string gltfName, const T RawVertex::*rawAttributeIx, const GLType &_glType)
-        : gltfName(gltfName),
-          rawAttributeIx(rawAttributeIx),
-          glType(_glType),
-          dracoAttribute(draco::GeometryAttribute::INVALID),
-          dracoComponentType(draco::DataType::DT_INVALID) {}
+template <class T>
+struct AttributeDefinition {
+  const std::string gltfName;
+  const T RawVertex::*rawAttributeIx;
+  const GLType glType;
+  const draco::GeometryAttribute::Type dracoAttribute;
+  const draco::DataType dracoComponentType;
+
+  AttributeDefinition(
+      const std::string gltfName,
+      const T RawVertex::*rawAttributeIx,
+      const GLType& _glType,
+      const draco::GeometryAttribute::Type dracoAttribute,
+      const draco::DataType dracoComponentType)
+      : gltfName(gltfName),
+        rawAttributeIx(rawAttributeIx),
+        glType(_glType),
+        dracoAttribute(dracoAttribute),
+        dracoComponentType(dracoComponentType) {}
+
+  AttributeDefinition(
+      const std::string gltfName,
+      const T RawVertex::*rawAttributeIx,
+      const GLType& _glType)
+      : gltfName(gltfName),
+        rawAttributeIx(rawAttributeIx),
+        glType(_glType),
+        dracoAttribute(draco::GeometryAttribute::INVALID),
+        dracoComponentType(draco::DataType::DT_INVALID) {}
 };
 
 struct AccessorData;
@@ -169,19 +172,15 @@ struct SceneData;
 struct SkinData;
 struct TextureData;
 
-struct ModelData
-{
-    explicit ModelData(std::shared_ptr<const std::vector<uint8_t> > const &_binary)
-        : binary(_binary)
-    {
-    }
+struct ModelData {
+  explicit ModelData(std::shared_ptr<const std::vector<uint8_t>> const& _binary)
+      : binary(_binary) {}
 
-    std::shared_ptr<const std::vector<uint8_t> > const binary;
+  std::shared_ptr<const std::vector<uint8_t>> const binary;
 };
 
-ModelData *Raw2Gltf(
-    std::ofstream &gltfOutStream,
-    const std::string &outputFolder,
-    const RawModel &raw,
-    const GltfOptions &options
-);
+ModelData* Raw2Gltf(
+    std::ofstream& gltfOutStream,
+    const std::string& outputFolder,
+    const RawModel& raw,
+    const GltfOptions& options);

+ 186 - 179
src/gltf/TextureBuilder.cpp

@@ -1,212 +1,219 @@
 /**
-* 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.
-*/
+ * 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/File_Utils.hpp>
 #include <utils/Image_Utils.hpp>
 #include <utils/String_Utils.hpp>
-#include <utils/File_Utils.hpp>
 
 #include <gltf/properties/ImageData.hpp>
 #include <gltf/properties/TextureData.hpp>
 
 // keep track of some texture data as we load them
 struct TexInfo {
-    explicit TexInfo(int rawTexIx) : rawTexIx(rawTexIx) {}
+  explicit TexInfo(int rawTexIx) : rawTexIx(rawTexIx) {}
 
-    const int rawTexIx;
-    int width {};
-    int height {};
-    int channels {};
-    uint8_t *pixels {};
+  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,
-    bool includeAlphaChannel)
-{
-    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;
-                }
-            }
+    const std::vector<int>& ixVec,
+    const std::string& tag,
+    const pixel_merger& computePixel,
+    bool includeAlphaChannel) {
+  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 = includeAlphaChannel ? 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)));
-            }
+    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 = includeAlphaChannel ? 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 = includeAlphaChannel;
-
-    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;
+  }
+
+  // write a .png iff we need transparency in the destination texture
+  bool png = includeAlphaChannel;
+
+  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(*gltf.defaultBuffer, imgBuffer.data(), imgBuffer.size());
+    image = new ImageData(mergedName, *bufferView, png ? "image/png" : "image/jpeg");
+  } else {
+    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;
     }
 
-    ImageData *image;
-    if (options.outputBinary) {
-        const auto bufferView = gltf.AddRawBufferView(*gltf.defaultBuffer, imgBuffer.data(), imgBuffer.size());
-        image = new ImageData(mergedName, *bufferView, png ? "image/png" : "image/jpeg");
-    } else {
-        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);
-        }
-        image = new ImageData(mergedName, imageFilename);
+    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, *gltf.defaultSampler, *gltf.images.hold(image)));
-    textureByIndicesKey.insert(std::make_pair(key, texDat));
-    return texDat;
+    image = new ImageData(mergedName, imageFilename);
+  }
+  std::shared_ptr<TextureData> texDat = gltf.textures.hold(
+      new TextureData(mergedName, *gltf.defaultSampler, *gltf.images.hold(image)));
+  textureByIndicesKey.insert(std::make_pair(key, texDat));
+  return texDat;
 }
 
 /** 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;
+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(*gltf.defaultBuffer, rawTexture.fileLocation);
+    if (bufferView) {
+      std::string suffix = StringUtils::GetFileSuffixString(rawTexture.fileLocation);
+      image = new ImageData(relativeFilename, *bufferView, ImageUtils::suffixToMimeType(suffix));
     }
 
-    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(*gltf.defaultBuffer, 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 + StringUtils::NormalizePath(relativeFilename);
-        if (FileUtils::CopyFile(rawTexture.fileLocation, outputPath, true))
-        {
-            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=="
-        );
+  } else if (!relativeFilename.empty()) {
+    image = new ImageData(relativeFilename, relativeFilename);
+    std::string outputPath = outputFolder + StringUtils::NormalizePath(relativeFilename);
+    if (FileUtils::CopyFile(rawTexture.fileLocation, outputPath, true)) {
+      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.
     }
-
-    std::shared_ptr<TextureData> texDat = gltf.textures.hold(
-        new TextureData(textureName, *gltf.defaultSampler, *gltf.images.hold(image)));
-    textureByIndicesKey.insert(std::make_pair(key, texDat));
-    return texDat;
-
+  }
+  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, *gltf.defaultSampler, *gltf.images.hold(image)));
+  textureByIndicesKey.insert(std::make_pair(key, texDat));
+  return texDat;
 }

+ 56 - 54
src/gltf/TextureBuilder.hpp

@@ -1,11 +1,11 @@
 /**
-* 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.
-*/
+ * 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
 
@@ -17,60 +17,62 @@
 
 #include "GltfModel.hpp"
 
-class TextureBuilder
-{
-public:
-    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:
+  using pixel = std::array<float, 4>; // pixel components are floats in [0, 1]
+  using pixel_merger = std::function<pixel(const std::vector<const pixel*>)>;
 
-    TextureBuilder(const RawModel &raw, const GltfOptions &options, const std::string &outputFolder, GltfModel &gltf)
-        : raw(raw)
-        , options(options)
-        , outputFolder(outputFolder)
-        , gltf(gltf)
-    {}
-    ~TextureBuilder() {}
+  TextureBuilder(
+      const RawModel& raw,
+      const GltfOptions& options,
+      const std::string& outputFolder,
+      GltfModel& gltf)
+      : raw(raw), options(options), outputFolder(outputFolder), gltf(gltf) {}
+  ~TextureBuilder() {}
 
-    std::shared_ptr<TextureData> combine(
-        const std::vector<int> &ixVec,
-        const std::string &tag,
-        const pixel_merger &mergeFunction,
-        bool transparency
-    );
+  std::shared_ptr<TextureData> combine(
+      const std::vector<int>& ixVec,
+      const std::string& tag,
+      const pixel_merger& mergeFunction,
+      bool transparency);
 
-    std::shared_ptr<TextureData> simple(int rawTexIndex, const std::string &tag);
+  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 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 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]);
-        }
+  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;
-    const GltfOptions &options;
-    const std::string outputFolder;
-    GltfModel &gltf;
+ private:
+  const RawModel& raw;
+  const GltfOptions& options;
+  const std::string outputFolder;
+  GltfModel& gltf;
 
-    std::map<std::string, std::shared_ptr<TextureData>> textureByIndicesKey;
+  std::map<std::string, std::shared_ptr<TextureData>> textureByIndicesKey;
 };

+ 20 - 32
src/gltf/properties/AccessorData.cpp

@@ -10,44 +10,32 @@
 #include "AccessorData.hpp"
 #include "BufferViewData.hpp"
 
-AccessorData::AccessorData(const BufferViewData &bufferView, GLType type, std::string name)
+AccessorData::AccessorData(const BufferViewData& bufferView, GLType type, std::string name)
     : Holdable(),
       bufferView(bufferView.ix),
       type(std::move(type)),
       byteOffset(0),
       count(0),
-      name(name)
-{
-}
+      name(name) {}
 
 AccessorData::AccessorData(GLType type)
-    : Holdable(),
-      bufferView(-1),
-      type(std::move(type)),
-      byteOffset(0),
-      count(0)
-{
-}
+    : Holdable(), bufferView(-1), type(std::move(type)), byteOffset(0), count(0) {}
 
-json AccessorData::serialize() const
-{
-    json result {
-        { "componentType", type.componentType.glType },
-        { "type", type.dataType },
-        { "count", count }
-    };
-    if (bufferView >= 0) {
-        result["bufferView"] = bufferView;
-        result["byteOffset"] = byteOffset;
-    }
-    if (!min.empty()) {
-        result["min"] = min;
-    }
-    if (!max.empty()) {
-        result["max"] = max;
-    }
-    if (name.length() > 0) {
-        result["name"] = name;
-    }
-    return result;
+json AccessorData::serialize() const {
+  json result{
+      {"componentType", type.componentType.glType}, {"type", type.dataType}, {"count", count}};
+  if (bufferView >= 0) {
+    result["bufferView"] = bufferView;
+    result["byteOffset"] = byteOffset;
+  }
+  if (!min.empty()) {
+    result["min"] = min;
+  }
+  if (!max.empty()) {
+    result["max"] = max;
+  }
+  if (name.length() > 0) {
+    result["name"] = name;
+  }
+  return result;
 }

+ 28 - 28
src/gltf/properties/AccessorData.hpp

@@ -11,36 +11,36 @@
 
 #include "gltf/Raw2Gltf.hpp"
 
-struct AccessorData : Holdable
-{
-    AccessorData(const BufferViewData &bufferView, GLType type, std::string name);
-    explicit AccessorData(GLType type);
-
-    json serialize() const override;
-
-    template<class T>
-    void appendAsBinaryArray(const std::vector<T> &in, std::vector<uint8_t> &out)
-    {
-        const unsigned int stride = type.byteStride();
-        const size_t       offset = out.size();
-        const size_t       count  = in.size();
-
-        this->count = (unsigned int) count;
-
-        out.resize(offset + count * stride);
-        for (int ii = 0; ii < count; ii ++) {
-            type.write(&out[offset + ii * stride], in[ii]);
-        }
+struct AccessorData : Holdable {
+  AccessorData(const BufferViewData& bufferView, GLType type, std::string name);
+  explicit AccessorData(GLType type);
+
+  json serialize() const override;
+
+  template <class T>
+  void appendAsBinaryArray(const std::vector<T>& in, std::vector<uint8_t>& out) {
+    const unsigned int stride = type.byteStride();
+    const size_t offset = out.size();
+    const size_t count = in.size();
+
+    this->count = (unsigned int)count;
+
+    out.resize(offset + count * stride);
+    for (int ii = 0; ii < count; ii++) {
+      type.write(&out[offset + ii * stride], in[ii]);
     }
+  }
 
-    unsigned int byteLength() const { return type.byteStride() * count; }
+  unsigned int byteLength() const {
+    return type.byteStride() * count;
+  }
 
-    const int          bufferView;
-    const GLType       type;
+  const int bufferView;
+  const GLType type;
 
-    unsigned int       byteOffset;
-    unsigned int       count;
-    std::vector<float> min;
-    std::vector<float> max;
-    std::string        name;
+  unsigned int byteOffset;
+  unsigned int count;
+  std::vector<float> min;
+  std::vector<float> max;
+  std::string name;
 };

+ 27 - 42
src/gltf/properties/AnimationData.cpp

@@ -14,57 +14,42 @@
 #include "AccessorData.hpp"
 #include "NodeData.hpp"
 
-AnimationData::AnimationData(std::string name, const AccessorData &timeAccessor)
-    : Holdable(),
-      name(std::move(name)),
-      timeAccessor(timeAccessor.ix) {}
+AnimationData::AnimationData(std::string name, const AccessorData& timeAccessor)
+    : Holdable(), name(std::move(name)), timeAccessor(timeAccessor.ix) {}
 
 // assumption: 1-to-1 relationship between channels and samplers; this is a simplification on what
 // glTF can express, but it means we can rely on samplerIx == channelIx throughout an animation
-void AnimationData::AddNodeChannel(const NodeData &node, const AccessorData &accessor, std::string path)
-{
-    assert(channels.size() == samplers.size());
-    uint32_t ix = channels.size();
-    channels.emplace_back(channel_t(ix, node, std::move(path)));
-    samplers.emplace_back(sampler_t(timeAccessor, accessor.ix));
+void AnimationData::AddNodeChannel(
+    const NodeData& node,
+    const AccessorData& accessor,
+    std::string path) {
+  assert(channels.size() == samplers.size());
+  uint32_t ix = channels.size();
+  channels.emplace_back(channel_t(ix, node, std::move(path)));
+  samplers.emplace_back(sampler_t(timeAccessor, accessor.ix));
 }
 
-json AnimationData::serialize() const
-{
-    return {
-        { "name", name },
-        { "channels", channels },
-        { "samplers", samplers }
-    };
+json AnimationData::serialize() const {
+  return {{"name", name}, {"channels", channels}, {"samplers", samplers}};
 }
 
-AnimationData::channel_t::channel_t(uint32_t ix, const NodeData &node, std::string path)
-    : ix(ix),
-      node(node.ix),
-      path(std::move(path))
-{
-}
+AnimationData::channel_t::channel_t(uint32_t ix, const NodeData& node, std::string path)
+    : ix(ix), node(node.ix), path(std::move(path)) {}
 
-AnimationData::sampler_t::sampler_t(uint32_t time, uint32_t output)
-    : time(time),
-      output(output)
-{
-}
+AnimationData::sampler_t::sampler_t(uint32_t time, uint32_t output) : time(time), output(output) {}
 
-void to_json(json &j, const AnimationData::channel_t &data) {
-    j = json {
-        { "sampler", data.ix },
-        { "target", {
-            { "node", data.node },
-            { "path", data.path }},
-        }
-    };
+void to_json(json& j, const AnimationData::channel_t& data) {
+  j = json{{"sampler", data.ix},
+           {
+               "target",
+               {{"node", data.node}, {"path", data.path}},
+           }};
 }
 
-void to_json(json &j, const AnimationData::sampler_t &data) {
-    j = json {
-        { "input", data.time },
-        { "interpolation", "LINEAR" },
-        { "output", data.output },
-    };
+void to_json(json& j, const AnimationData::sampler_t& data) {
+  j = json{
+      {"input", data.time},
+      {"interpolation", "LINEAR"},
+      {"output", data.output},
+  };
 }

+ 23 - 26
src/gltf/properties/AnimationData.hpp

@@ -11,38 +11,35 @@
 
 #include "gltf/Raw2Gltf.hpp"
 
-struct AnimationData : Holdable
-{
-    AnimationData(std::string name, const AccessorData &timeAccessor);
+struct AnimationData : Holdable {
+  AnimationData(std::string name, const AccessorData& timeAccessor);
 
-    // assumption: 1-to-1 relationship between channels and samplers; this is a simplification on what
-    // glTF can express, but it means we can rely on samplerIx == channelIx throughout an animation
-    void AddNodeChannel(const NodeData &node, const AccessorData &accessor, std::string path);
+  // assumption: 1-to-1 relationship between channels and samplers; this is a simplification on what
+  // glTF can express, but it means we can rely on samplerIx == channelIx throughout an animation
+  void AddNodeChannel(const NodeData& node, const AccessorData& accessor, std::string path);
 
-    json serialize() const override;
+  json serialize() const override;
 
-    struct channel_t
-    {
-        channel_t(uint32_t _ix, const NodeData &node, std::string path);
+  struct channel_t {
+    channel_t(uint32_t _ix, const NodeData& node, std::string path);
 
-        const uint32_t    ix;
-        const uint32_t    node;
-        const std::string path;
-    };
+    const uint32_t ix;
+    const uint32_t node;
+    const std::string path;
+  };
 
-    struct sampler_t
-    {
-        sampler_t(uint32_t time, uint32_t output);
+  struct sampler_t {
+    sampler_t(uint32_t time, uint32_t output);
 
-        const uint32_t time;
-        const uint32_t output;
-    };
+    const uint32_t time;
+    const uint32_t output;
+  };
 
-    const std::string      name;
-    const uint32_t         timeAccessor;
-    std::vector<channel_t> channels;
-    std::vector<sampler_t> samplers;
+  const std::string name;
+  const uint32_t timeAccessor;
+  std::vector<channel_t> channels;
+  std::vector<sampler_t> samplers;
 };
 
-void to_json(json &j, const AnimationData::channel_t &data);
-void to_json(json &j, const AnimationData::sampler_t &data);
+void to_json(json& j, const AnimationData::channel_t& data);
+void to_json(json& j, const AnimationData::sampler_t& data);

+ 17 - 26
src/gltf/properties/BufferData.cpp

@@ -11,33 +11,24 @@
 
 #include "BufferData.hpp"
 
-BufferData::BufferData(const std::shared_ptr<const std::vector<uint8_t> > &binData)
-    : Holdable(),
-      isGlb(true),
-      binData(binData)
-{
-}
+BufferData::BufferData(const std::shared_ptr<const std::vector<uint8_t>>& binData)
+    : Holdable(), isGlb(true), binData(binData) {}
 
-BufferData::BufferData(std::string uri, const std::shared_ptr<const std::vector<uint8_t> > &binData, bool isEmbedded)
-    : Holdable(),
-      isGlb(false),
-      uri(isEmbedded ? "" : std::move(uri)),
-      binData(binData)
-{
-}
+BufferData::BufferData(
+    std::string uri,
+    const std::shared_ptr<const std::vector<uint8_t>>& binData,
+    bool isEmbedded)
+    : Holdable(), isGlb(false), uri(isEmbedded ? "" : std::move(uri)), binData(binData) {}
 
-json BufferData::serialize() const
-{
-    json result{
-        {"byteLength", binData->size()}
-    };
-    if (!isGlb) {
-        if (!uri.empty()) {
-            result["uri"] = uri;
-        } else {
-            std::string encoded = base64::encode(*binData);
-            result["uri"] = "data:application/octet-stream;base64," + encoded;
-        }
+json BufferData::serialize() const {
+  json result{{"byteLength", binData->size()}};
+  if (!isGlb) {
+    if (!uri.empty()) {
+      result["uri"] = uri;
+    } else {
+      std::string encoded = base64::encode(*binData);
+      result["uri"] = "data:application/octet-stream;base64," + encoded;
     }
-    return result;
+  }
+  return result;
 }

+ 10 - 8
src/gltf/properties/BufferData.hpp

@@ -11,15 +11,17 @@
 
 #include "gltf/Raw2Gltf.hpp"
 
-struct BufferData : Holdable
-{
-    explicit BufferData(const std::shared_ptr<const std::vector<uint8_t> > &binData);
+struct BufferData : Holdable {
+  explicit BufferData(const std::shared_ptr<const std::vector<uint8_t>>& binData);
 
-    BufferData(std::string uri, const std::shared_ptr<const std::vector<uint8_t> > &binData, bool isEmbedded = false);
+  BufferData(
+      std::string uri,
+      const std::shared_ptr<const std::vector<uint8_t>>& binData,
+      bool isEmbedded = false);
 
-    json serialize() const override;
+  json serialize() const override;
 
-    const bool                                         isGlb;
-    const std::string                                  uri;
-    const std::shared_ptr<const std::vector<uint8_t> > binData; // TODO this is just weird
+  const bool isGlb;
+  const std::string uri;
+  const std::shared_ptr<const std::vector<uint8_t>> binData; // TODO this is just weird
 };

+ 11 - 18
src/gltf/properties/BufferViewData.cpp

@@ -10,23 +10,16 @@
 #include "BufferViewData.hpp"
 #include "BufferData.hpp"
 
-BufferViewData::BufferViewData(const BufferData &_buffer, const size_t _byteOffset, const GL_ArrayType _target)
-    : Holdable(),
-      buffer(_buffer.ix),
-      byteOffset((unsigned int) _byteOffset),
-      target(_target)
-{
-}
+BufferViewData::BufferViewData(
+    const BufferData& _buffer,
+    const size_t _byteOffset,
+    const GL_ArrayType _target)
+    : Holdable(), buffer(_buffer.ix), byteOffset((unsigned int)_byteOffset), target(_target) {}
 
-json BufferViewData::serialize() const
-{
-    json result {
-        { "buffer", buffer },
-        { "byteLength", byteLength },
-        { "byteOffset", byteOffset }
-    };
-    if (target != GL_ARRAY_NONE) {
-        result["target"] = target;
-    }
-    return result;
+json BufferViewData::serialize() const {
+  json result{{"buffer", buffer}, {"byteLength", byteLength}, {"byteOffset", byteOffset}};
+  if (target != GL_ARRAY_NONE) {
+    result["target"] = target;
+  }
+  return result;
 }

+ 12 - 14
src/gltf/properties/BufferViewData.hpp

@@ -11,22 +11,20 @@
 
 #include "gltf/Raw2Gltf.hpp"
 
-struct BufferViewData : Holdable
-{
-    enum GL_ArrayType
-    {
-        GL_ARRAY_NONE           = 0, // no GL buffer is being set
-        GL_ARRAY_BUFFER         = 34962,
-        GL_ELEMENT_ARRAY_BUFFER = 34963
-    };
+struct BufferViewData : Holdable {
+  enum GL_ArrayType {
+    GL_ARRAY_NONE = 0, // no GL buffer is being set
+    GL_ARRAY_BUFFER = 34962,
+    GL_ELEMENT_ARRAY_BUFFER = 34963
+  };
 
-    BufferViewData(const BufferData &_buffer, const size_t _byteOffset, const GL_ArrayType _target);
+  BufferViewData(const BufferData& _buffer, const size_t _byteOffset, const GL_ArrayType _target);
 
-    json serialize() const override;
+  json serialize() const override;
 
-    const unsigned int buffer;
-    const unsigned int byteOffset;
-    const GL_ArrayType target;
+  const unsigned int buffer;
+  const unsigned int byteOffset;
+  const GL_ArrayType target;
 
-    unsigned int byteLength = 0;
+  unsigned int byteLength = 0;
 };

+ 16 - 28
src/gltf/properties/CameraData.cpp

@@ -10,33 +10,21 @@
 #include "CameraData.hpp"
 
 CameraData::CameraData()
-    : Holdable(),
-      aspectRatio(0.0f),
-      yfov(0.0f),
-      xmag(0.0f),
-      ymag(0.0f),
-      znear(0.0f),
-      zfar(0.0f)
-{
-}
+    : Holdable(), aspectRatio(0.0f), yfov(0.0f), xmag(0.0f), ymag(0.0f), znear(0.0f), zfar(0.0f) {}
 
-json CameraData::serialize() const
-{
-    json result {
-        { "name", name },
-        { "type", type },
-    };
-    json subResult {
-        { "znear", znear },
-        { "zfar", zfar }
-    };
-    if (type == "perspective") {
-        subResult["aspectRatio"] = aspectRatio;
-        subResult["yfov"] = yfov;
-    } else {
-        subResult["xmag"] = xmag;
-        subResult["ymag"] = ymag;
-    }
-    result[type] = subResult;
-    return result;
+json CameraData::serialize() const {
+  json result{
+      {"name", name},
+      {"type", type},
+  };
+  json subResult{{"znear", znear}, {"zfar", zfar}};
+  if (type == "perspective") {
+    subResult["aspectRatio"] = aspectRatio;
+    subResult["yfov"] = yfov;
+  } else {
+    subResult["xmag"] = xmag;
+    subResult["ymag"] = ymag;
+  }
+  result[type] = subResult;
+  return result;
 }

+ 11 - 12
src/gltf/properties/CameraData.hpp

@@ -12,17 +12,16 @@
 #include "gltf/Raw2Gltf.hpp"
 
 // TODO: this class needs some work
-struct CameraData : Holdable
-{
-    CameraData();
-    json serialize() const override;
+struct CameraData : Holdable {
+  CameraData();
+  json serialize() const override;
 
-    std::string name;
-    std::string type;
-    float       aspectRatio;
-    float       yfov;
-    float       xmag;
-    float       ymag;
-    float       znear;
-    float       zfar;
+  std::string name;
+  std::string type;
+  float aspectRatio;
+  float yfov;
+  float xmag;
+  float ymag;
+  float znear;
+  float zfar;
 };

+ 8 - 26
src/gltf/properties/ImageData.cpp

@@ -14,32 +14,14 @@
 #include "BufferViewData.hpp"
 
 ImageData::ImageData(std::string name, std::string uri)
-    : Holdable(),
-      name(std::move(name)),
-      uri(std::move(uri)),
-      bufferView(-1)
-{
-}
+    : Holdable(), name(std::move(name)), uri(std::move(uri)), bufferView(-1) {}
 
-ImageData::ImageData(std::string name, const BufferViewData &bufferView, std::string mimeType)
-    : Holdable(),
-      name(std::move(name)),
-      bufferView(bufferView.ix),
-      mimeType(std::move(mimeType))
-{
-}
+ImageData::ImageData(std::string name, const BufferViewData& bufferView, std::string mimeType)
+    : Holdable(), name(std::move(name)), bufferView(bufferView.ix), mimeType(std::move(mimeType)) {}
 
-json ImageData::serialize() const
-{
-    if (bufferView < 0) {
-        return {
-            { "name", name },
-            { "uri", uri }
-        };
-    }
-    return {
-        { "name", name },
-        { "bufferView", bufferView },
-        { "mimeType", mimeType }
-    };
+json ImageData::serialize() const {
+  if (bufferView < 0) {
+    return {{"name", name}, {"uri", uri}};
+  }
+  return {{"name", name}, {"bufferView", bufferView}, {"mimeType", mimeType}};
 }

+ 8 - 9
src/gltf/properties/ImageData.hpp

@@ -11,15 +11,14 @@
 
 #include "gltf/Raw2Gltf.hpp"
 
-struct ImageData : Holdable
-{
-    ImageData(std::string name, std::string uri);
-    ImageData(std::string name, const BufferViewData &bufferView, std::string mimeType);
+struct ImageData : Holdable {
+  ImageData(std::string name, std::string uri);
+  ImageData(std::string name, const BufferViewData& bufferView, std::string mimeType);
 
-    json serialize() const override;
+  json serialize() const override;
 
-    const std::string name;
-    const std::string uri; // non-empty in gltf mode
-    const int32_t     bufferView; // non-negative in glb mode
-    const std::string mimeType;
+  const std::string name;
+  const std::string uri; // non-empty in gltf mode
+  const int32_t bufferView; // non-negative in glb mode
+  const std::string mimeType;
 };

+ 21 - 24
src/gltf/properties/LightData.cpp

@@ -10,34 +10,31 @@
 #include "LightData.hpp"
 
 LightData::LightData(
-    std::string name, Type type, Vec3f color, float intensity,
-    float innerConeAngle, float outerConeAngle)
+    std::string name,
+    Type type,
+    Vec3f color,
+    float intensity,
+    float innerConeAngle,
+    float outerConeAngle)
     : Holdable(),
       type(type),
       color(color),
       intensity(intensity),
       innerConeAngle(innerConeAngle),
-      outerConeAngle(outerConeAngle)
-{
-}
+      outerConeAngle(outerConeAngle) {}
 
-json LightData::serialize() const
-{
-    json result {
-        { "name", name },
-        { "color", toStdVec(color) },
-        { "intensity", intensity }
-    };
-    switch(type) {
-        case Directional:
-            result["type"] = "directional";
-            break;
-        case Point:
-            result["type"] = "point";
-            break;
-        case Spot:
-            result["type"] = "spot";
-            break;
-    }
-    return result;
+json LightData::serialize() const {
+  json result{{"name", name}, {"color", toStdVec(color)}, {"intensity", intensity}};
+  switch (type) {
+    case Directional:
+      result["type"] = "directional";
+      break;
+    case Point:
+      result["type"] = "point";
+      break;
+    case Spot:
+      result["type"] = "spot";
+      break;
+  }
+  return result;
 }

+ 20 - 16
src/gltf/properties/LightData.hpp

@@ -11,23 +11,27 @@
 
 #include "gltf/Raw2Gltf.hpp"
 
-struct LightData : Holdable
-{
-    enum Type {
-        Directional,
-        Point,
-        Spot,
-    };
+struct LightData : Holdable {
+  enum Type {
+    Directional,
+    Point,
+    Spot,
+  };
 
-    LightData(std::string name, Type type, Vec3f color, float intensity,
-              float innerConeAngle, float outerConeAngle);
+  LightData(
+      std::string name,
+      Type type,
+      Vec3f color,
+      float intensity,
+      float innerConeAngle,
+      float outerConeAngle);
 
-    json serialize() const override;
+  json serialize() const override;
 
-    const std::string name;
-    const Type type;
-    const Vec3f color;
-    const float intensity;
-    const float innerConeAngle;
-    const float outerConeAngle;
+  const std::string name;
+  const Type type;
+  const Vec3f color;
+  const float intensity;
+  const float innerConeAngle;
+  const float outerConeAngle;
 };

+ 80 - 90
src/gltf/properties/MaterialData.cpp

@@ -11,77 +11,74 @@
 #include "TextureData.hpp"
 
 // TODO: retrieve & pass in correct UV set from FBX
-std::unique_ptr<Tex> Tex::ref(const TextureData *tex, uint32_t texCoord)
-{
-    return std::unique_ptr<Tex> { (tex != nullptr) ? new Tex(tex->ix, texCoord) : nullptr };
+std::unique_ptr<Tex> Tex::ref(const TextureData* tex, uint32_t texCoord) {
+  return std::unique_ptr<Tex>{(tex != nullptr) ? new Tex(tex->ix, texCoord) : nullptr};
 }
 
-Tex::Tex(uint32_t texRef, uint32_t texCoord)
-    : texRef(texRef),
-      texCoord(texCoord) {}
+Tex::Tex(uint32_t texRef, uint32_t texCoord) : texRef(texRef), texCoord(texCoord) {}
 
-void to_json(json &j, const Tex &data) {
-    j = json {
-        { "index", data.texRef },
-        { "texCoord", data.texCoord }
-    };
+void to_json(json& j, const Tex& data) {
+  j = json{{"index", data.texRef}, {"texCoord", data.texCoord}};
 }
 
-KHRCmnUnlitMaterial::KHRCmnUnlitMaterial()
-{
-}
+KHRCmnUnlitMaterial::KHRCmnUnlitMaterial() {}
 
-void to_json(json &j, const KHRCmnUnlitMaterial &d)
-{
-    j = json({});
+void to_json(json& j, const KHRCmnUnlitMaterial& d) {
+  j = json({});
 }
 
 inline float clamp(float d, float bottom = 0, float top = 1) {
-    return std::max(bottom, std::min(top, d));
+  return std::max(bottom, std::min(top, d));
 }
-inline Vec3f clamp(const Vec3f &vec, const Vec3f &bottom = VEC3F_ZERO, const Vec3f &top = VEC3F_ONE) {
-    return Vec3f::Max(bottom, Vec3f::Min(top, vec));
+inline Vec3f
+clamp(const Vec3f& vec, const Vec3f& bottom = VEC3F_ZERO, const Vec3f& top = VEC3F_ONE) {
+  return Vec3f::Max(bottom, Vec3f::Min(top, vec));
 }
-inline Vec4f clamp(const Vec4f &vec, const Vec4f &bottom = VEC4F_ZERO, const Vec4f &top = VEC4F_ONE) {
-    return Vec4f::Max(bottom, Vec4f::Min(top, vec));
+inline Vec4f
+clamp(const Vec4f& vec, const Vec4f& bottom = VEC4F_ZERO, const Vec4f& top = VEC4F_ONE) {
+  return Vec4f::Max(bottom, Vec4f::Min(top, vec));
 }
 
 PBRMetallicRoughness::PBRMetallicRoughness(
-    const TextureData *baseColorTexture, const TextureData *metRoughTexture,
-    const Vec4f &baseColorFactor, float metallic, float roughness)
+    const TextureData* baseColorTexture,
+    const TextureData* metRoughTexture,
+    const Vec4f& baseColorFactor,
+    float metallic,
+    float roughness)
     : baseColorTexture(Tex::ref(baseColorTexture)),
       metRoughTexture(Tex::ref(metRoughTexture)),
       baseColorFactor(clamp(baseColorFactor)),
       metallic(clamp(metallic)),
-      roughness(clamp(roughness))
-{
-}
+      roughness(clamp(roughness)) {}
 
-void to_json(json &j, const PBRMetallicRoughness &d)
-{
-    j = { };
-    if (d.baseColorTexture != nullptr) {
-        j["baseColorTexture"] = *d.baseColorTexture;
-    }
-    if (d.baseColorFactor.LengthSquared() > 0) {
-        j["baseColorFactor"] = toStdVec(d.baseColorFactor);
-    }
-    if (d.metRoughTexture != nullptr) {
-        j["metallicRoughnessTexture"] = *d.metRoughTexture;
-        // if a texture is provided, throw away metallic/roughness values
-        j["roughnessFactor"] = 1.0f;
-        j["metallicFactor"] = 1.0f;
-    } else {
-        // without a texture, however, use metallic/roughness as constants
-        j["metallicFactor"] = d.metallic;
-        j["roughnessFactor"] = d.roughness;
-    }
+void to_json(json& j, const PBRMetallicRoughness& d) {
+  j = {};
+  if (d.baseColorTexture != nullptr) {
+    j["baseColorTexture"] = *d.baseColorTexture;
+  }
+  if (d.baseColorFactor.LengthSquared() > 0) {
+    j["baseColorFactor"] = toStdVec(d.baseColorFactor);
+  }
+  if (d.metRoughTexture != nullptr) {
+    j["metallicRoughnessTexture"] = *d.metRoughTexture;
+    // if a texture is provided, throw away metallic/roughness values
+    j["roughnessFactor"] = 1.0f;
+    j["metallicFactor"] = 1.0f;
+  } else {
+    // without a texture, however, use metallic/roughness as constants
+    j["metallicFactor"] = d.metallic;
+    j["roughnessFactor"] = d.roughness;
+  }
 }
 
 MaterialData::MaterialData(
-    std::string name, bool isTransparent, const RawShadingModel shadingModel, 
-    const TextureData *normalTexture, const TextureData *occlusionTexture,
-    const TextureData *emissiveTexture, const Vec3f & emissiveFactor,
+    std::string name,
+    bool isTransparent,
+    const RawShadingModel shadingModel,
+    const TextureData* normalTexture,
+    const TextureData* occlusionTexture,
+    const TextureData* emissiveTexture,
+    const Vec3f& emissiveFactor,
     std::shared_ptr<KHRCmnUnlitMaterial> const khrCmnConstantMaterial,
     std::shared_ptr<PBRMetallicRoughness> const pbrMetallicRoughness)
     : Holdable(),
@@ -95,50 +92,43 @@ MaterialData::MaterialData(
       khrCmnConstantMaterial(khrCmnConstantMaterial),
       pbrMetallicRoughness(pbrMetallicRoughness) {}
 
-json MaterialData::serialize() const
-{
-    json result = {
-        { "name", name },
-        { "alphaMode", isTransparent ? "BLEND" : "OPAQUE" },
-        { "extras", {
-            { "fromFBX", {
-                { "shadingModel", Describe(shadingModel) },
-                { "isTruePBR", shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH }
-            }}
-        }}
-    };
+json MaterialData::serialize() const {
+  json result = {{"name", name},
+                 {"alphaMode", isTransparent ? "BLEND" : "OPAQUE"},
+                 {"extras",
+                  {{"fromFBX",
+                    {{"shadingModel", Describe(shadingModel)},
+                     {"isTruePBR", shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH}}}}}};
 
-    if (normalTexture != nullptr) {
-        result["normalTexture"] = *normalTexture;
-    }
-    if (occlusionTexture != nullptr) {
-        result["occlusionTexture"] = *occlusionTexture;
-    }
-    if (emissiveTexture != nullptr) {
-        result["emissiveTexture"] = *emissiveTexture;
-    }
-    if (emissiveFactor.LengthSquared() > 0) {
-        result["emissiveFactor"] = toStdVec(emissiveFactor);
-    }
-    if (pbrMetallicRoughness != nullptr) {
-        result["pbrMetallicRoughness"] = *pbrMetallicRoughness;
-    }
-    if (khrCmnConstantMaterial != nullptr) {
-        json extensions = { };
-        extensions[KHR_MATERIALS_CMN_UNLIT] = *khrCmnConstantMaterial;
-        result["extensions"] = extensions;
-    }
+  if (normalTexture != nullptr) {
+    result["normalTexture"] = *normalTexture;
+  }
+  if (occlusionTexture != nullptr) {
+    result["occlusionTexture"] = *occlusionTexture;
+  }
+  if (emissiveTexture != nullptr) {
+    result["emissiveTexture"] = *emissiveTexture;
+  }
+  if (emissiveFactor.LengthSquared() > 0) {
+    result["emissiveFactor"] = toStdVec(emissiveFactor);
+  }
+  if (pbrMetallicRoughness != nullptr) {
+    result["pbrMetallicRoughness"] = *pbrMetallicRoughness;
+  }
+  if (khrCmnConstantMaterial != nullptr) {
+    json extensions = {};
+    extensions[KHR_MATERIALS_CMN_UNLIT] = *khrCmnConstantMaterial;
+    result["extensions"] = extensions;
+  }
 
-    for (const auto& i : userProperties)
-    {
-        auto& prop_map = result["extras"]["fromFBX"]["userProperties"];
+  for (const auto& i : userProperties) {
+    auto& prop_map = result["extras"]["fromFBX"]["userProperties"];
 
-        json j = json::parse(i);
-        for (const auto& k : json::iterator_wrapper(j))
-        {
-            prop_map[k.key()] = k.value();
-        }
+    json j = json::parse(i);
+    for (const auto& k : json::iterator_wrapper(j)) {
+      prop_map[k.key()] = k.value();
     }
+  }
 
-    return result;
+  return result;
 }

+ 44 - 41
src/gltf/properties/MaterialData.hpp

@@ -13,58 +13,61 @@
 
 #include "gltf/Raw2Gltf.hpp"
 
-struct Tex
-{
-    static std::unique_ptr<Tex> ref(const TextureData *tex, uint32_t texCoord = 0);
-    explicit Tex(uint32_t texRef, uint32_t texCoord);
+struct Tex {
+  static std::unique_ptr<Tex> ref(const TextureData* tex, uint32_t texCoord = 0);
+  explicit Tex(uint32_t texRef, uint32_t texCoord);
 
-    const uint32_t texRef;
-    const uint32_t texCoord;
+  const uint32_t texRef;
+  const uint32_t texCoord;
 };
 
-struct KHRCmnUnlitMaterial
-{
-    KHRCmnUnlitMaterial();
+struct KHRCmnUnlitMaterial {
+  KHRCmnUnlitMaterial();
 };
 
-struct PBRMetallicRoughness
-{
-    PBRMetallicRoughness(
-        const TextureData *baseColorTexture, const TextureData *metRoughTexture,
-        const Vec4f &baseColorFactor, float metallic = 0.1f, float roughness = 0.6f);
+struct PBRMetallicRoughness {
+  PBRMetallicRoughness(
+      const TextureData* baseColorTexture,
+      const TextureData* metRoughTexture,
+      const Vec4f& baseColorFactor,
+      float metallic = 0.1f,
+      float roughness = 0.6f);
 
-    std::unique_ptr<Tex> baseColorTexture;
-    std::unique_ptr<Tex> metRoughTexture;
-    const Vec4f          baseColorFactor;
-    const float          metallic;
-    const float          roughness;
+  std::unique_ptr<Tex> baseColorTexture;
+  std::unique_ptr<Tex> metRoughTexture;
+  const Vec4f baseColorFactor;
+  const float metallic;
+  const float roughness;
 };
 
-struct MaterialData : Holdable
-{
-    MaterialData(
-        std::string name, bool isTransparent, RawShadingModel shadingModel,
-        const TextureData *normalTexture, const TextureData *occlusionTexture,
-        const TextureData *emissiveTexture, const Vec3f &emissiveFactor,
-        std::shared_ptr<KHRCmnUnlitMaterial> const khrCmnConstantMaterial,
-        std::shared_ptr<PBRMetallicRoughness> const pbrMetallicRoughness);
+struct MaterialData : Holdable {
+  MaterialData(
+      std::string name,
+      bool isTransparent,
+      RawShadingModel shadingModel,
+      const TextureData* normalTexture,
+      const TextureData* occlusionTexture,
+      const TextureData* emissiveTexture,
+      const Vec3f& emissiveFactor,
+      std::shared_ptr<KHRCmnUnlitMaterial> const khrCmnConstantMaterial,
+      std::shared_ptr<PBRMetallicRoughness> const pbrMetallicRoughness);
 
-    json serialize() const override;
+  json serialize() const override;
 
-    const std::string                name;
-    const RawShadingModel            shadingModel;
-    const bool                       isTransparent;
-    const std::unique_ptr<const Tex> normalTexture;
-    const std::unique_ptr<const Tex> occlusionTexture;
-    const std::unique_ptr<const Tex> emissiveTexture;
-    const Vec3f                      emissiveFactor;
+  const std::string name;
+  const RawShadingModel shadingModel;
+  const bool isTransparent;
+  const std::unique_ptr<const Tex> normalTexture;
+  const std::unique_ptr<const Tex> occlusionTexture;
+  const std::unique_ptr<const Tex> emissiveTexture;
+  const Vec3f emissiveFactor;
 
-    const std::shared_ptr<const KHRCmnUnlitMaterial>    khrCmnConstantMaterial;
-    const std::shared_ptr<const PBRMetallicRoughness>   pbrMetallicRoughness;
+  const std::shared_ptr<const KHRCmnUnlitMaterial> khrCmnConstantMaterial;
+  const std::shared_ptr<const PBRMetallicRoughness> pbrMetallicRoughness;
 
-    std::vector<std::string> userProperties;
+  std::vector<std::string> userProperties;
 };
 
-void to_json(json &j, const Tex &data);
-void to_json(json &j, const KHRCmnUnlitMaterial &d);
-void to_json(json &j, const PBRMetallicRoughness &d);
+void to_json(json& j, const Tex& data);
+void to_json(json& j, const KHRCmnUnlitMaterial& d);
+void to_json(json& j, const PBRMetallicRoughness& d);

+ 12 - 20
src/gltf/properties/MeshData.cpp

@@ -10,25 +10,17 @@
 #include "MeshData.hpp"
 #include "PrimitiveData.hpp"
 
-MeshData::MeshData(const std::string &name, const std::vector<float> &weights)
-    : Holdable(),
-      name(name),
-      weights(weights)
-{
-}
+MeshData::MeshData(const std::string& name, const std::vector<float>& weights)
+    : Holdable(), name(name), weights(weights) {}
 
-json MeshData::serialize() const
-{
-    json jsonPrimitivesArray = json::array();
-    for (const auto &primitive : primitives) {
-        jsonPrimitivesArray.push_back(*primitive);
-    }
-    json result = {
-        { "name", name },
-        { "primitives", jsonPrimitivesArray }
-    };
-    if (!weights.empty()) {
-        result["weights"] = weights;
-    }
-    return result;
+json MeshData::serialize() const {
+  json jsonPrimitivesArray = json::array();
+  for (const auto& primitive : primitives) {
+    jsonPrimitivesArray.push_back(*primitive);
+  }
+  json result = {{"name", name}, {"primitives", jsonPrimitivesArray}};
+  if (!weights.empty()) {
+    result["weights"] = weights;
+  }
+  return result;
 }

+ 9 - 11
src/gltf/properties/MeshData.hpp

@@ -17,18 +17,16 @@
 
 #include "PrimitiveData.hpp"
 
-struct MeshData : Holdable
-{
-    MeshData(const std::string &name, const std::vector<float> &weights);
+struct MeshData : Holdable {
+  MeshData(const std::string& name, const std::vector<float>& weights);
 
-    void AddPrimitive(std::shared_ptr<PrimitiveData> primitive)
-    {
-        primitives.push_back(std::move(primitive));
-    }
+  void AddPrimitive(std::shared_ptr<PrimitiveData> primitive) {
+    primitives.push_back(std::move(primitive));
+  }
 
-    json serialize() const override;
+  json serialize() const override;
 
-    const std::string                           name;
-    const std::vector<float>                    weights;
-    std::vector<std::shared_ptr<PrimitiveData>> primitives;
+  const std::string name;
+  const std::vector<float> weights;
+  std::vector<std::shared_ptr<PrimitiveData>> primitives;
 };

+ 62 - 69
src/gltf/properties/NodeData.cpp

@@ -10,8 +10,11 @@
 #include "NodeData.hpp"
 
 NodeData::NodeData(
-    std::string name, const Vec3f &translation,
-    const Quatf &rotation, const Vec3f &scale, bool isJoint)
+    std::string name,
+    const Vec3f& translation,
+    const Quatf& rotation,
+    const Vec3f& scale,
+    bool isJoint)
     : Holdable(),
       name(std::move(name)),
       isJoint(isJoint),
@@ -22,90 +25,80 @@ NodeData::NodeData(
       mesh(-1),
       camera(-1),
       light(-1),
-      skin(-1)
-{
-}
+      skin(-1) {}
 
-void NodeData::AddChildNode(uint32_t childIx)
-{
-    children.push_back(childIx);
+void NodeData::AddChildNode(uint32_t childIx) {
+  children.push_back(childIx);
 }
 
-void NodeData::SetMesh(uint32_t meshIx)
-{
-    assert(mesh < 0);
-    assert(!isJoint);
-    mesh = meshIx;
+void NodeData::SetMesh(uint32_t meshIx) {
+  assert(mesh < 0);
+  assert(!isJoint);
+  mesh = meshIx;
 }
 
-void NodeData::SetSkin(uint32_t skinIx)
-{
-    assert(skin < 0);
-    assert(!isJoint);
-    skin = skinIx;
+void NodeData::SetSkin(uint32_t skinIx) {
+  assert(skin < 0);
+  assert(!isJoint);
+  skin = skinIx;
 }
 
-void NodeData::SetCamera(uint32_t cameraIndex)
-{
-    assert(!isJoint);
-    camera = cameraIndex;
+void NodeData::SetCamera(uint32_t cameraIndex) {
+  assert(!isJoint);
+  camera = cameraIndex;
 }
 
-void NodeData::SetLight(uint32_t lightIndex)
-{
-    assert(!isJoint);
-    light = lightIndex;
+void NodeData::SetLight(uint32_t lightIndex) {
+  assert(!isJoint);
+  light = lightIndex;
 }
 
-json NodeData::serialize() const
-{
-    json result = { { "name", name } };
+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) -> void {
-        if (std::none_of(vec.begin(), vec.end(), [&](float n) { return std::isnan(n); })) {
-            result[key] = vec;
-        }
-    };
-    maybeAdd("translation", toStdVec(translation));
-    maybeAdd("rotation", toStdVec(rotation));
-    maybeAdd("scale", toStdVec(scale));
+  // 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) -> void {
+    if (std::none_of(vec.begin(), vec.end(), [&](float n) { return std::isnan(n); })) {
+      result[key] = vec;
+    }
+  };
+  maybeAdd("translation", toStdVec(translation));
+  maybeAdd("rotation", toStdVec(rotation));
+  maybeAdd("scale", toStdVec(scale));
 
-    if (!children.empty()) {
-        result["children"] = children;
+  if (!children.empty()) {
+    result["children"] = children;
+  }
+  if (isJoint) {
+    // sanity-check joint node
+    assert(mesh < 0 && skin < 0);
+  } else {
+    // non-joint node
+    if (mesh >= 0) {
+      result["mesh"] = mesh;
+    }
+    if (!skeletons.empty()) {
+      result["skeletons"] = skeletons;
+    }
+    if (skin >= 0) {
+      result["skin"] = skin;
+    }
+    if (camera >= 0) {
+      result["camera"] = camera;
     }
-    if (isJoint) {
-        // sanity-check joint node
-        assert(mesh < 0 && skin < 0);
-    } else {
-        // non-joint node
-        if (mesh >= 0) {
-            result["mesh"] = mesh;
-        }
-        if (!skeletons.empty()) {
-            result["skeletons"] = skeletons;
-        }
-        if (skin >= 0) {
-            result["skin"] = skin;
-        }
-        if (camera >= 0) {
-            result["camera"] = camera;
-        }
-        if (light >= 0) {
-            result["extensions"][KHR_LIGHTS_PUNCTUAL]["light"] = light;
-        }
+    if (light >= 0) {
+      result["extensions"][KHR_LIGHTS_PUNCTUAL]["light"] = light;
     }
+  }
 
-    for (const auto& i : userProperties)
-    {
-        auto& prop_map = result["extras"]["fromFBX"]["userProperties"];
+  for (const auto& i : userProperties) {
+    auto& prop_map = result["extras"]["fromFBX"]["userProperties"];
 
-        json j = json::parse(i);
-        for (const auto& k : json::iterator_wrapper(j))
-        {
-            prop_map[k.key()] = k.value();
-        }
+    json j = json::parse(i);
+    for (const auto& k : json::iterator_wrapper(j)) {
+      prop_map[k.key()] = k.value();
     }
+  }
 
-    return result;
+  return result;
 }

+ 25 - 21
src/gltf/properties/NodeData.hpp

@@ -11,28 +11,32 @@
 
 #include "gltf/Raw2Gltf.hpp"
 
-struct NodeData : Holdable
-{
-    NodeData(std::string name, const Vec3f &translation, const Quatf &rotation, const Vec3f &scale, bool isJoint);
+struct NodeData : Holdable {
+  NodeData(
+      std::string name,
+      const Vec3f& translation,
+      const Quatf& rotation,
+      const Vec3f& scale,
+      bool isJoint);
 
-    void AddChildNode(uint32_t childIx);
-    void SetMesh(uint32_t meshIx);
-    void SetSkin(uint32_t skinIx);
-    void SetCamera(uint32_t camera);
-    void SetLight(uint32_t light);
+  void AddChildNode(uint32_t childIx);
+  void SetMesh(uint32_t meshIx);
+  void SetSkin(uint32_t skinIx);
+  void SetCamera(uint32_t camera);
+  void SetLight(uint32_t light);
 
-    json serialize() const override;
+  json serialize() const override;
 
-    const std::string        name;
-    const bool               isJoint;
-    Vec3f                    translation;
-    Quatf                    rotation;
-    Vec3f                    scale;
-    std::vector<uint32_t>    children;
-    int32_t                  mesh;
-    int32_t                  camera;
-    int32_t                  light;
-    int32_t                  skin;
-    std::vector<std::string> skeletons;
-    std::vector<std::string> userProperties;
+  const std::string name;
+  const bool isJoint;
+  Vec3f translation;
+  Quatf rotation;
+  Vec3f scale;
+  std::vector<uint32_t> children;
+  int32_t mesh;
+  int32_t camera;
+  int32_t light;
+  int32_t skin;
+  std::vector<std::string> skeletons;
+  std::vector<std::string> userProperties;
 };

+ 46 - 48
src/gltf/properties/PrimitiveData.cpp

@@ -9,73 +9,71 @@
 
 #include "PrimitiveData.hpp"
 
-#include "MaterialData.hpp"
 #include "AccessorData.hpp"
 #include "BufferViewData.hpp"
+#include "MaterialData.hpp"
 
-PrimitiveData::PrimitiveData(const AccessorData &indices, const MaterialData &material, std::shared_ptr<draco::Mesh> dracoMesh)
+PrimitiveData::PrimitiveData(
+    const AccessorData& indices,
+    const MaterialData& material,
+    std::shared_ptr<draco::Mesh> dracoMesh)
     : indices(indices.ix),
       material(material.ix),
       mode(TRIANGLES),
       dracoMesh(dracoMesh),
       dracoBufferView(-1) {}
 
-PrimitiveData::PrimitiveData(const AccessorData &indices, const MaterialData &material)
+PrimitiveData::PrimitiveData(const AccessorData& indices, const MaterialData& material)
     : indices(indices.ix),
       material(material.ix),
       mode(TRIANGLES),
       dracoMesh(nullptr),
-      dracoBufferView(-1)
-{
-}
+      dracoBufferView(-1) {}
 
-void PrimitiveData::AddAttrib(std::string name, const AccessorData &accessor)
-{
-    attributes[name] = accessor.ix;
+void PrimitiveData::AddAttrib(std::string name, const AccessorData& accessor) {
+  attributes[name] = accessor.ix;
 }
 
-void PrimitiveData::NoteDracoBuffer(const BufferViewData &data)
-{
-    dracoBufferView = data.ix;
+void PrimitiveData::NoteDracoBuffer(const BufferViewData& data) {
+  dracoBufferView = data.ix;
 }
 
-void PrimitiveData::AddTarget(const AccessorData *positions, const AccessorData *normals, const AccessorData *tangents)
-{
-    targetAccessors.push_back(std::make_tuple(
-        positions->ix,
-        normals != nullptr ? normals->ix : -1,
-        tangents != nullptr ? tangents ->ix : -1
-    ));
+void PrimitiveData::AddTarget(
+    const AccessorData* positions,
+    const AccessorData* normals,
+    const AccessorData* tangents) {
+  targetAccessors.push_back(std::make_tuple(
+      positions->ix,
+      normals != nullptr ? normals->ix : -1,
+      tangents != nullptr ? tangents->ix : -1));
 }
 
-void to_json(json &j, const PrimitiveData &d) {
-    j = {
-        { "material", d.material },
-        { "mode", d.mode },
-        { "attributes", d.attributes }
-    };
-    if (d.indices >= 0) {
-        j["indices"] = d.indices;
-    }
-    if (!d.targetAccessors.empty()) {
-        json targets {};
-        int pIx, nIx, tIx;
-        for (auto accessor : d.targetAccessors) {
-            std::tie(pIx, nIx, tIx) = accessor;
-            json target {};
-            if (pIx >= 0) { target["POSITION"] = pIx; }
-            if (nIx >= 0) { target["NORMAL"] = nIx; }
-            if (tIx >= 0) { target["TANGENT"] = tIx; }
-            targets.push_back(target);
-        }
-        j["targets"] = targets;
-    }
-    if (!d.dracoAttributes.empty()) {
-        j["extensions"] = {
-            { KHR_DRACO_MESH_COMPRESSION, {
-                { "bufferView", d.dracoBufferView },
-                { "attributes", d.dracoAttributes }
-            }}
-        };
+void to_json(json& j, const PrimitiveData& d) {
+  j = {{"material", d.material}, {"mode", d.mode}, {"attributes", d.attributes}};
+  if (d.indices >= 0) {
+    j["indices"] = d.indices;
+  }
+  if (!d.targetAccessors.empty()) {
+    json targets{};
+    int pIx, nIx, tIx;
+    for (auto accessor : d.targetAccessors) {
+      std::tie(pIx, nIx, tIx) = accessor;
+      json target{};
+      if (pIx >= 0) {
+        target["POSITION"] = pIx;
+      }
+      if (nIx >= 0) {
+        target["NORMAL"] = nIx;
+      }
+      if (tIx >= 0) {
+        target["TANGENT"] = tIx;
+      }
+      targets.push_back(target);
     }
+    j["targets"] = targets;
+  }
+  if (!d.dracoAttributes.empty()) {
+    j["extensions"] = {{KHR_DRACO_MESH_COMPRESSION,
+                        {{"bufferView", d.dracoBufferView}, {"attributes", d.dracoAttributes}}}};
+  }
 }

+ 59 - 51
src/gltf/properties/PrimitiveData.hpp

@@ -11,62 +11,70 @@
 
 #include "gltf/Raw2Gltf.hpp"
 
-struct PrimitiveData
-{
-    enum MeshMode
-    {
-        POINTS = 0,
-        LINES,
-        LINE_LOOP,
-        LINE_STRIP,
-        TRIANGLES,
-        TRIANGLE_STRIP,
-        TRIANGLE_FAN
-    };
-
-    PrimitiveData(const AccessorData &indices, const MaterialData &material, std::shared_ptr<draco::Mesh> dracoMesh);
-
-    PrimitiveData(const AccessorData &indices, const MaterialData &material);
-
-    void AddAttrib(std::string name, const AccessorData &accessor);
-
-    void AddTarget(const AccessorData *positions, const AccessorData *normals, const AccessorData *tangents);
-
-    template<class T>
-    void AddDracoAttrib(const AttributeDefinition<T> attribute, const std::vector<T> &attribArr)
-    {
-        draco::PointAttribute att;
-        int8_t componentCount = attribute.glType.count;
-        att.Init(
-            attribute.dracoAttribute, nullptr, componentCount, attribute.dracoComponentType,
-            false, componentCount * draco::DataTypeLength(attribute.dracoComponentType), 0);
-
-        const int dracoAttId = dracoMesh->AddAttribute(att, true, attribArr.size());
-        draco::PointAttribute *attPtr = dracoMesh->attribute(dracoAttId);
-
-        std::vector<uint8_t> buf(sizeof(T));
-        for (uint32_t ii = 0; ii < attribArr.size(); ii++) {
-            uint8_t *ptr = &buf[0];
-            attribute.glType.write(ptr, attribArr[ii]);
-            attPtr->SetAttributeValue(attPtr->mapped_index(draco::PointIndex(ii)), ptr);
-        }
-
-        dracoAttributes[attribute.gltfName] = dracoAttId;
+struct PrimitiveData {
+  enum MeshMode {
+    POINTS = 0,
+    LINES,
+    LINE_LOOP,
+    LINE_STRIP,
+    TRIANGLES,
+    TRIANGLE_STRIP,
+    TRIANGLE_FAN
+  };
+
+  PrimitiveData(
+      const AccessorData& indices,
+      const MaterialData& material,
+      std::shared_ptr<draco::Mesh> dracoMesh);
+
+  PrimitiveData(const AccessorData& indices, const MaterialData& material);
+
+  void AddAttrib(std::string name, const AccessorData& accessor);
+
+  void AddTarget(
+      const AccessorData* positions,
+      const AccessorData* normals,
+      const AccessorData* tangents);
+
+  template <class T>
+  void AddDracoAttrib(const AttributeDefinition<T> attribute, const std::vector<T>& attribArr) {
+    draco::PointAttribute att;
+    int8_t componentCount = attribute.glType.count;
+    att.Init(
+        attribute.dracoAttribute,
+        nullptr,
+        componentCount,
+        attribute.dracoComponentType,
+        false,
+        componentCount * draco::DataTypeLength(attribute.dracoComponentType),
+        0);
+
+    const int dracoAttId = dracoMesh->AddAttribute(att, true, attribArr.size());
+    draco::PointAttribute* attPtr = dracoMesh->attribute(dracoAttId);
+
+    std::vector<uint8_t> buf(sizeof(T));
+    for (uint32_t ii = 0; ii < attribArr.size(); ii++) {
+      uint8_t* ptr = &buf[0];
+      attribute.glType.write(ptr, attribArr[ii]);
+      attPtr->SetAttributeValue(attPtr->mapped_index(draco::PointIndex(ii)), ptr);
     }
 
-    void NoteDracoBuffer(const BufferViewData &data);
+    dracoAttributes[attribute.gltfName] = dracoAttId;
+  }
 
-    const int          indices;
-    const unsigned int material;
-    const MeshMode     mode;
+  void NoteDracoBuffer(const BufferViewData& data);
 
-    std::vector<std::tuple<int, int, int>> targetAccessors {};
+  const int indices;
+  const unsigned int material;
+  const MeshMode mode;
 
-    std::map<std::string, int> attributes;
-    std::map<std::string, int> dracoAttributes;
+  std::vector<std::tuple<int, int, int>> targetAccessors{};
 
-    std::shared_ptr<draco::Mesh> dracoMesh;
-    int                          dracoBufferView;
+  std::map<std::string, int> attributes;
+  std::map<std::string, int> dracoAttributes;
+
+  std::shared_ptr<draco::Mesh> dracoMesh;
+  int dracoBufferView;
 };
 
-void to_json(json &j, const PrimitiveData &d);
+void to_json(json& j, const PrimitiveData& d);

+ 6 - 10
src/gltf/properties/SamplerData.hpp

@@ -11,15 +11,11 @@
 
 #include "gltf/Raw2Gltf.hpp"
 
-struct SamplerData : Holdable
-{
-    // this is where magFilter, minFilter, wrapS and wrapT would go, should we want it
-    SamplerData()
-        : Holdable()
-    {
-    }
+struct SamplerData : Holdable {
+  // this is where magFilter, minFilter, wrapS and wrapT would go, should we want it
+  SamplerData() : Holdable() {}
 
-    json serialize() const override {
-        return json::object();
-    }
+  json serialize() const override {
+    return json::object();
+  }
 };

+ 5 - 13
src/gltf/properties/SceneData.cpp

@@ -11,18 +11,10 @@
 
 #include "NodeData.hpp"
 
-SceneData::SceneData(std::string name, const NodeData &rootNode)
-    : Holdable(),
-      name(std::move(name)),
-      nodes({rootNode.ix})
-{
-}
+SceneData::SceneData(std::string name, const NodeData& rootNode)
+    : Holdable(), name(std::move(name)), nodes({rootNode.ix}) {}
 
-json SceneData::serialize() const
-{
-    assert(nodes.size() <= 1);
-    return {
-        { "name", name },
-        { "nodes", nodes }
-    };
+json SceneData::serialize() const {
+  assert(nodes.size() <= 1);
+  return {{"name", name}, {"nodes", nodes}};
 }

+ 5 - 6
src/gltf/properties/SceneData.hpp

@@ -11,12 +11,11 @@
 
 #include "gltf/Raw2Gltf.hpp"
 
-struct SceneData : Holdable
-{
-    SceneData(std::string name, const NodeData &rootNode);
+struct SceneData : Holdable {
+  SceneData(std::string name, const NodeData& rootNode);
 
-    json serialize() const override;
+  json serialize() const override;
 
-    const std::string     name;
-    std::vector<uint32_t> nodes;
+  const std::string name;
+  std::vector<uint32_t> nodes;
 };

+ 8 - 12
src/gltf/properties/SkinData.cpp

@@ -13,20 +13,16 @@
 #include "NodeData.hpp"
 
 SkinData::SkinData(
-    const std::vector<uint32_t> joints, const AccessorData &inverseBindMatricesAccessor,
-    const NodeData &skeletonRootNode)
+    const std::vector<uint32_t> joints,
+    const AccessorData& inverseBindMatricesAccessor,
+    const NodeData& skeletonRootNode)
     : Holdable(),
       joints(joints),
       inverseBindMatrices(inverseBindMatricesAccessor.ix),
-      skeletonRootNode(skeletonRootNode.ix)
-{
-}
+      skeletonRootNode(skeletonRootNode.ix) {}
 
-json SkinData::serialize() const
-{
-    return {
-        { "joints", joints },
-        { "inverseBindMatrices", inverseBindMatrices },
-        { "skeleton", skeletonRootNode }
-    };
+json SkinData::serialize() const {
+  return {{"joints", joints},
+          {"inverseBindMatrices", inverseBindMatrices},
+          {"skeleton", skeletonRootNode}};
 }

+ 9 - 9
src/gltf/properties/SkinData.hpp

@@ -11,15 +11,15 @@
 
 #include "gltf/Raw2Gltf.hpp"
 
-struct SkinData : Holdable
-{
-    SkinData(
-        const std::vector<uint32_t> joints, const AccessorData &inverseBindMatricesAccessor,
-        const NodeData &skeletonRootNode);
+struct SkinData : Holdable {
+  SkinData(
+      const std::vector<uint32_t> joints,
+      const AccessorData& inverseBindMatricesAccessor,
+      const NodeData& skeletonRootNode);
 
-    json serialize() const override;
+  json serialize() const override;
 
-    const std::vector<uint32_t> joints;
-    const uint32_t              skeletonRootNode;
-    const uint32_t              inverseBindMatrices;
+  const std::vector<uint32_t> joints;
+  const uint32_t skeletonRootNode;
+  const uint32_t inverseBindMatrices;
 };

+ 4 - 14
src/gltf/properties/TextureData.cpp

@@ -12,19 +12,9 @@
 #include "ImageData.hpp"
 #include "SamplerData.hpp"
 
-TextureData::TextureData(std::string name, const SamplerData &sampler, const ImageData &source)
-    : Holdable(),
-      name(std::move(name)),
-      sampler(sampler.ix),
-      source(source.ix)
-{
-}
+TextureData::TextureData(std::string name, const SamplerData& sampler, const ImageData& source)
+    : Holdable(), name(std::move(name)), sampler(sampler.ix), source(source.ix) {}
 
-json TextureData::serialize() const
-{
-    return {
-        { "name", name },
-        { "sampler", sampler },
-        { "source", source }
-    };
+json TextureData::serialize() const {
+  return {{"name", name}, {"sampler", sampler}, {"source", source}};
 }

+ 6 - 7
src/gltf/properties/TextureData.hpp

@@ -11,13 +11,12 @@
 
 #include "gltf/Raw2Gltf.hpp"
 
-struct TextureData : Holdable
-{
-    TextureData(std::string name, const SamplerData &sampler, const ImageData &source);
+struct TextureData : Holdable {
+  TextureData(std::string name, const SamplerData& sampler, const ImageData& source);
 
-    json serialize() const override;
+  json serialize() const override;
 
-    const std::string name;
-    const uint32_t    sampler;
-    const uint32_t    source;
+  const std::string name;
+  const uint32_t sampler;
+  const uint32_t source;
 };

+ 63 - 63
src/mathfu.hpp

@@ -11,93 +11,93 @@
 
 #include <fbxsdk.h>
 
-#include <mathfu/vector.h>
 #include <mathfu/matrix.h>
 #include <mathfu/quaternion.h>
 #include <mathfu/rect.h>
+#include <mathfu/vector.h>
 
 /**
  * All the mathfu:: implementations of our core data types.
  */
 
-template<class T, int d>
-struct Bounds
-{
-    mathfu::Vector<T, d> min;
-    mathfu::Vector<T, d> max;
-    bool initialized = false;
-
-    void Clear() {
-        min = mathfu::Vector<T, d>();
-        max = mathfu::Vector<T, d>();
-        initialized = false;
-    }
-
-    void AddPoint(const mathfu::Vector<T, d> &p) {
-        if (initialized) {
-            for (int ii = 0; ii < d; ii ++) {
-                min(ii) = std::min(min(ii), p(ii));
-                max(ii) = std::max(max(ii), p(ii));
-            }
-        } else {
-            min = p;
-            max = p;
-            initialized = true;
-        }
+template <class T, int d>
+struct Bounds {
+  mathfu::Vector<T, d> min;
+  mathfu::Vector<T, d> max;
+  bool initialized = false;
+
+  void Clear() {
+    min = mathfu::Vector<T, d>();
+    max = mathfu::Vector<T, d>();
+    initialized = false;
+  }
+
+  void AddPoint(const mathfu::Vector<T, d>& p) {
+    if (initialized) {
+      for (int ii = 0; ii < d; ii++) {
+        min(ii) = std::min(min(ii), p(ii));
+        max(ii) = std::max(max(ii), p(ii));
+      }
+    } else {
+      min = p;
+      max = p;
+      initialized = true;
     }
+  }
 };
 
 typedef mathfu::Vector<uint16_t, 4> Vec4i;
 typedef mathfu::Matrix<uint16_t, 4> Mat4i;
-typedef mathfu::Vector<float, 2>    Vec2f;
-typedef mathfu::Vector<float, 3>    Vec3f;
-typedef mathfu::Vector<float, 4>    Vec4f;
-typedef mathfu::Matrix<float, 2>    Mat2f;
-typedef mathfu::Matrix<float, 3>    Mat3f;
-typedef mathfu::Matrix<float, 4>    Mat4f;
-typedef mathfu::Quaternion<float>   Quatf;
-typedef Bounds<float, 3>            Boundsf;
-
-#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> inline std::vector<T> toStdVec(const mathfu::Vector <T, d> &vec)
-{
-    std::vector<T> result(d);
-    for (int ii = 0; ii < d; ii ++) {
-        result[ii] = vec[ii];
-    }
-    return result;
+typedef mathfu::Vector<float, 2> Vec2f;
+typedef mathfu::Vector<float, 3> Vec3f;
+typedef mathfu::Vector<float, 4> Vec4f;
+typedef mathfu::Matrix<float, 2> Mat2f;
+typedef mathfu::Matrix<float, 3> Mat3f;
+typedef mathfu::Matrix<float, 4> Mat4f;
+typedef mathfu::Quaternion<float> Quatf;
+typedef Bounds<float, 3> Boundsf;
+
+#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>
+inline std::vector<T> toStdVec(const mathfu::Vector<T, d>& vec) {
+  std::vector<T> result(d);
+  for (int ii = 0; ii < d; ii++) {
+    result[ii] = vec[ii];
+  }
+  return result;
 }
 
-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() };
+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()};
 }
 
-inline Vec3f toVec3f(const FbxDouble3 &v) {
-    return Vec3f((float) v[0], (float) v[1], (float) v[2]);
+inline Vec3f toVec3f(const FbxDouble3& v) {
+  return Vec3f((float)v[0], (float)v[1], (float)v[2]);
 }
 
-inline Vec3f toVec3f(const FbxVector4 &v) {
-    return Vec3f((float) v[0], (float) v[1], (float) v[2]);
+inline Vec3f toVec3f(const FbxVector4& v) {
+  return Vec3f((float)v[0], (float)v[1], (float)v[2]);
 }
 
-inline Vec4f toVec4f(const FbxVector4 &v) {
-    return Vec4f((float) v[0], (float) v[1], (float) v[2], (float) v[3]);
+inline Vec4f toVec4f(const FbxVector4& v) {
+  return Vec4f((float)v[0], (float)v[1], (float)v[2], (float)v[3]);
 }
 
-inline Mat4f toMat4f(const FbxAMatrix &m) {
-    auto result = Mat4f();
-    for (int row = 0; row < 4; row ++) {
-        for (int col = 0; col < 4; col ++) {
-            result(row, col) = (float) m[row][col];
-        }
+inline Mat4f toMat4f(const FbxAMatrix& m) {
+  auto result = Mat4f();
+  for (int row = 0; row < 4; row++) {
+    for (int col = 0; col < 4; col++) {
+      result(row, col) = (float)m[row][col];
     }
-    return result;
+  }
+  return result;
 }
 
-inline Quatf toQuatf(const FbxQuaternion &q) {
-    return Quatf((float) q[3], (float) q[0], (float) q[1], (float) q[2]);
+inline Quatf toQuatf(const FbxQuaternion& q) {
+  return Quatf((float)q[3], (float)q[0], (float)q[1], (float)q[2]);
 }

+ 576 - 553
src/raw/RawModel.cpp

@@ -9,684 +9,707 @@
 
 #include "RawModel.hpp"
 
-#include <vector>
-#include <string>
-#include <unordered_map>
 #include <cmath>
 #include <map>
 #include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
 
-#if defined( __unix__ )
+#if defined(__unix__)
 #include <algorithm>
 #endif
 
-#include "utils/String_Utils.hpp"
 #include "utils/Image_Utils.hpp"
+#include "utils/String_Utils.hpp"
 
-bool RawVertex::operator==(const RawVertex &other) const
-{
-    return (position == other.position) &&
-           (normal == other.normal) &&
-           (tangent == other.tangent) &&
-           (binormal == other.binormal) &&
-           (color == other.color) &&
-           (uv0 == other.uv0) &&
-           (uv1 == other.uv1) &&
-           (jointIndices == other.jointIndices) &&
-           (jointWeights == other.jointWeights) &&
-           (polarityUv0 == other.polarityUv0) &&
-           (blendSurfaceIx == other.blendSurfaceIx) &&
-           (blends == other.blends);
+bool RawVertex::operator==(const RawVertex& other) const {
+  return (position == other.position) && (normal == other.normal) && (tangent == other.tangent) &&
+      (binormal == other.binormal) && (color == other.color) && (uv0 == other.uv0) &&
+      (uv1 == other.uv1) && (jointIndices == other.jointIndices) &&
+      (jointWeights == other.jointWeights) && (polarityUv0 == other.polarityUv0) &&
+      (blendSurfaceIx == other.blendSurfaceIx) && (blends == other.blends);
 }
 
-size_t RawVertex::Difference(const RawVertex &other) const
-{
-    size_t attributes = 0;
-    if (position != other.position) { attributes |= RAW_VERTEX_ATTRIBUTE_POSITION; }
-    if (normal != other.normal) { attributes |= RAW_VERTEX_ATTRIBUTE_NORMAL; }
-    if (tangent != other.tangent) { attributes |= RAW_VERTEX_ATTRIBUTE_TANGENT; }
-    if (binormal != other.binormal) { attributes |= RAW_VERTEX_ATTRIBUTE_BINORMAL; }
-    if (color != other.color) { attributes |= RAW_VERTEX_ATTRIBUTE_COLOR; }
-    if (uv0 != other.uv0) { attributes |= RAW_VERTEX_ATTRIBUTE_UV0; }
-    if (uv1 != other.uv1) { attributes |= RAW_VERTEX_ATTRIBUTE_UV1; }
-    // Always need both or neither.
-    if (jointIndices != other.jointIndices) { attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; }
-    if (jointWeights != other.jointWeights) { attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; }
-    return attributes;
+size_t RawVertex::Difference(const RawVertex& other) const {
+  size_t attributes = 0;
+  if (position != other.position) {
+    attributes |= RAW_VERTEX_ATTRIBUTE_POSITION;
+  }
+  if (normal != other.normal) {
+    attributes |= RAW_VERTEX_ATTRIBUTE_NORMAL;
+  }
+  if (tangent != other.tangent) {
+    attributes |= RAW_VERTEX_ATTRIBUTE_TANGENT;
+  }
+  if (binormal != other.binormal) {
+    attributes |= RAW_VERTEX_ATTRIBUTE_BINORMAL;
+  }
+  if (color != other.color) {
+    attributes |= RAW_VERTEX_ATTRIBUTE_COLOR;
+  }
+  if (uv0 != other.uv0) {
+    attributes |= RAW_VERTEX_ATTRIBUTE_UV0;
+  }
+  if (uv1 != other.uv1) {
+    attributes |= RAW_VERTEX_ATTRIBUTE_UV1;
+  }
+  // Always need both or neither.
+  if (jointIndices != other.jointIndices) {
+    attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
+  }
+  if (jointWeights != other.jointWeights) {
+    attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
+  }
+  return attributes;
 }
 
-RawModel::RawModel()
-    : vertexAttributes(0)
-{
-}
+RawModel::RawModel() : vertexAttributes(0) {}
 
-void RawModel::AddVertexAttribute(const RawVertexAttribute attrib)
-{
-    vertexAttributes |= attrib;
+void RawModel::AddVertexAttribute(const RawVertexAttribute attrib) {
+  vertexAttributes |= attrib;
 }
 
-int RawModel::AddVertex(const RawVertex &vertex)
-{
-    auto it = vertexHash.find(vertex);
-    if (it != vertexHash.end()) {
-        return it->second;
-    }
-    vertexHash.emplace(vertex, (int) vertices.size());
-    vertices.push_back(vertex);
-    return (int) vertices.size() - 1;
+int RawModel::AddVertex(const RawVertex& vertex) {
+  auto it = vertexHash.find(vertex);
+  if (it != vertexHash.end()) {
+    return it->second;
+  }
+  vertexHash.emplace(vertex, (int)vertices.size());
+  vertices.push_back(vertex);
+  return (int)vertices.size() - 1;
 }
 
-int RawModel::AddTriangle(const int v0, const int v1, const int v2, const int materialIndex, const int surfaceIndex)
-{
-    const RawTriangle triangle = {{v0, v1, v2}, materialIndex, surfaceIndex};
-    triangles.push_back(triangle);
-    return (int) triangles.size() - 1;
+int RawModel::AddTriangle(
+    const int v0,
+    const int v1,
+    const int v2,
+    const int materialIndex,
+    const int surfaceIndex) {
+  const RawTriangle triangle = {{v0, v1, v2}, materialIndex, surfaceIndex};
+  triangles.push_back(triangle);
+  return (int)triangles.size() - 1;
 }
 
-int RawModel::AddTexture(const std::string &name, const std::string &fileName, const std::string &fileLocation, RawTextureUsage usage)
-{
-    if (name.empty()) {
-        return -1;
-    }
-    for (size_t i = 0; i < textures.size(); i++) {
-        if (StringUtils::CompareNoCase(textures[i].name, name) == 0 && textures[i].usage == usage) {
-            return (int) i;
-        }
+int RawModel::AddTexture(
+    const std::string& name,
+    const std::string& fileName,
+    const std::string& fileLocation,
+    RawTextureUsage usage) {
+  if (name.empty()) {
+    return -1;
+  }
+  for (size_t i = 0; i < textures.size(); i++) {
+    if (StringUtils::CompareNoCase(textures[i].name, name) == 0 && textures[i].usage == usage) {
+      return (int)i;
     }
-
-    const ImageUtils::ImageProperties
-        properties = ImageUtils::GetImageProperties(!fileLocation.empty() ? fileLocation.c_str() : fileName.c_str());
-
-    RawTexture texture;
-    texture.name         = name;
-    texture.width        = properties.width;
-    texture.height       = properties.height;
-    texture.mipLevels    = (int) ceilf(log2f(std::max((float) properties.width, (float) properties.height)));
-    texture.usage        = usage;
-    texture.occlusion    = (properties.occlusion == ImageUtils::IMAGE_TRANSPARENT) ?
-                           RAW_TEXTURE_OCCLUSION_TRANSPARENT : RAW_TEXTURE_OCCLUSION_OPAQUE;
-    texture.fileName     = fileName;
-    texture.fileLocation = fileLocation;
-    textures.emplace_back(texture);
-    return (int) textures.size() - 1;
+  }
+
+  const ImageUtils::ImageProperties properties = ImageUtils::GetImageProperties(
+      !fileLocation.empty() ? fileLocation.c_str() : fileName.c_str());
+
+  RawTexture texture;
+  texture.name = name;
+  texture.width = properties.width;
+  texture.height = properties.height;
+  texture.mipLevels =
+      (int)ceilf(log2f(std::max((float)properties.width, (float)properties.height)));
+  texture.usage = usage;
+  texture.occlusion = (properties.occlusion == ImageUtils::IMAGE_TRANSPARENT)
+      ? RAW_TEXTURE_OCCLUSION_TRANSPARENT
+      : RAW_TEXTURE_OCCLUSION_OPAQUE;
+  texture.fileName = fileName;
+  texture.fileLocation = fileLocation;
+  textures.emplace_back(texture);
+  return (int)textures.size() - 1;
 }
 
-int RawModel::AddMaterial(const RawMaterial &material)
-{
-    return AddMaterial(material.name.c_str(), material.type, material.textures, material.info, material.userProperties);
+int RawModel::AddMaterial(const RawMaterial& material) {
+  return AddMaterial(
+      material.name.c_str(),
+      material.type,
+      material.textures,
+      material.info,
+      material.userProperties);
 }
 
 int RawModel::AddMaterial(
-    const char *name,
+    const char* name,
     const RawMaterialType materialType,
     const int textures[RAW_TEXTURE_USAGE_MAX],
     std::shared_ptr<RawMatProps> materialInfo,
-    const std::vector<std::string>& userProperties)
-{
-    for (size_t i = 0; i < materials.size(); i++) {
-        if (materials[i].name != name) {
-            continue;
-        }
-        if (materials[i].type != materialType) {
-            continue;
-        }
-        if (*(materials[i].info) != *materialInfo) {
-            continue;
-        }
-        bool match = true;
-        for (int j = 0; match && j < RAW_TEXTURE_USAGE_MAX; j++) {
-            match = match && (materials[i].textures[j] == textures[j]);
-        }
-        if (materials[i].userProperties.size() != userProperties.size()) {
-            match = false;
-        }
-        else {
-            for (int j = 0; match && j < userProperties.size(); j++) {
-                match = match && (materials[i].userProperties[j] == userProperties[j]);
-            }
-        }
-        if (match) {
-            return (int) i;
-        }
+    const std::vector<std::string>& userProperties) {
+  for (size_t i = 0; i < materials.size(); i++) {
+    if (materials[i].name != name) {
+      continue;
+    }
+    if (materials[i].type != materialType) {
+      continue;
+    }
+    if (*(materials[i].info) != *materialInfo) {
+      continue;
     }
+    bool match = true;
+    for (int j = 0; match && j < RAW_TEXTURE_USAGE_MAX; j++) {
+      match = match && (materials[i].textures[j] == textures[j]);
+    }
+    if (materials[i].userProperties.size() != userProperties.size()) {
+      match = false;
+    } else {
+      for (int j = 0; match && j < userProperties.size(); j++) {
+        match = match && (materials[i].userProperties[j] == userProperties[j]);
+      }
+    }
+    if (match) {
+      return (int)i;
+    }
+  }
 
-    RawMaterial material;
-    material.name = name;
-    material.type = materialType;
-    material.info = materialInfo;
-    material.userProperties = userProperties;
+  RawMaterial material;
+  material.name = name;
+  material.type = materialType;
+  material.info = materialInfo;
+  material.userProperties = userProperties;
 
-    for (int i = 0; i < RAW_TEXTURE_USAGE_MAX; i++) {
-        material.textures[i] = textures[i];
-    }
+  for (int i = 0; i < RAW_TEXTURE_USAGE_MAX; i++) {
+    material.textures[i] = textures[i];
+  }
 
-    materials.emplace_back(material);
+  materials.emplace_back(material);
 
-    return (int) materials.size() - 1;
+  return (int)materials.size() - 1;
 }
 
 int RawModel::AddLight(
-    const char *name,
+    const char* name,
     const RawLightType lightType,
     const Vec3f color,
     const float intensity,
     const float innerConeAngle,
-    const float outerConeAngle)
-{
-    for (size_t i = 0; i < lights.size(); i ++) {
-        if (lights[i].name != name || lights[i].type != lightType) {
-            continue;
-        }
-        // only care about cone angles for spot
-        if (lights[i].type == RAW_LIGHT_TYPE_SPOT) {
-            if (lights[i].innerConeAngle != innerConeAngle ||
-                lights[i].outerConeAngle != outerConeAngle) {
-                continue;
-            }
-        }
-        return (int) i;
-    }
-    RawLight light {
-        name,
-        lightType,
-        color,
-        intensity,
-        innerConeAngle,
-        outerConeAngle,
-    };
-    lights.push_back(light);
-    return (int) lights.size() - 1;
+    const float outerConeAngle) {
+  for (size_t i = 0; i < lights.size(); i++) {
+    if (lights[i].name != name || lights[i].type != lightType) {
+      continue;
+    }
+    // only care about cone angles for spot
+    if (lights[i].type == RAW_LIGHT_TYPE_SPOT) {
+      if (lights[i].innerConeAngle != innerConeAngle ||
+          lights[i].outerConeAngle != outerConeAngle) {
+        continue;
+      }
+    }
+    return (int)i;
+  }
+  RawLight light{
+      name,
+      lightType,
+      color,
+      intensity,
+      innerConeAngle,
+      outerConeAngle,
+  };
+  lights.push_back(light);
+  return (int)lights.size() - 1;
 }
 
-
-int RawModel::AddSurface(const RawSurface &surface)
-{
-    for (size_t i = 0; i < surfaces.size(); i++) {
-        if (StringUtils::CompareNoCase(surfaces[i].name, surface.name) == 0) {
-            return (int) i;
-        }
+int RawModel::AddSurface(const RawSurface& surface) {
+  for (size_t i = 0; i < surfaces.size(); i++) {
+    if (StringUtils::CompareNoCase(surfaces[i].name, surface.name) == 0) {
+      return (int)i;
     }
+  }
 
-    surfaces.emplace_back(surface);
-    return (int) (surfaces.size() - 1);
+  surfaces.emplace_back(surface);
+  return (int)(surfaces.size() - 1);
 }
 
-int RawModel::AddSurface(const char *name, const long surfaceId)
-{
-    assert(name[0] != '\0');
+int RawModel::AddSurface(const char* name, const long surfaceId) {
+  assert(name[0] != '\0');
 
-    for (size_t i = 0; i < surfaces.size(); i++) {
-        if (surfaces[i].id == surfaceId) {
-            return (int) i;
-        }
+  for (size_t i = 0; i < surfaces.size(); i++) {
+    if (surfaces[i].id == surfaceId) {
+      return (int)i;
     }
-    RawSurface  surface;
-    surface.id = surfaceId;
-    surface.name     = name;
-    surface.bounds.Clear();
-    surface.discrete  = false;
-
-    surfaces.emplace_back(surface);
-    return (int) (surfaces.size() - 1);
+  }
+  RawSurface surface;
+  surface.id = surfaceId;
+  surface.name = name;
+  surface.bounds.Clear();
+  surface.discrete = false;
+
+  surfaces.emplace_back(surface);
+  return (int)(surfaces.size() - 1);
 }
 
-int RawModel::AddAnimation(const RawAnimation &animation)
-{
-    animations.emplace_back(animation);
-    return (int) (animations.size() - 1);
+int RawModel::AddAnimation(const RawAnimation& animation) {
+  animations.emplace_back(animation);
+  return (int)(animations.size() - 1);
 }
 
-int RawModel::AddNode(const RawNode &node)
-{
-    for (size_t i = 0; i < nodes.size(); i++) {
-        if (nodes[i].id == node.id) {
-            return (int)i;
-        }
+int RawModel::AddNode(const RawNode& node) {
+  for (size_t i = 0; i < nodes.size(); i++) {
+    if (nodes[i].id == node.id) {
+      return (int)i;
     }
+  }
 
-    nodes.emplace_back(node);
-    return (int) nodes.size() - 1;
+  nodes.emplace_back(node);
+  return (int)nodes.size() - 1;
 }
 
 int RawModel::AddCameraPerspective(
-    const char *name, const long nodeId, const float aspectRatio, const float fovDegreesX, const float fovDegreesY, const float nearZ,
-    const float farZ)
-{
-    RawCamera camera;
-    camera.name                    = name;
-    camera.nodeId                  = nodeId;
-    camera.mode                    = RawCamera::CAMERA_MODE_PERSPECTIVE;
-    camera.perspective.aspectRatio = aspectRatio;
-    camera.perspective.fovDegreesX = fovDegreesX;
-    camera.perspective.fovDegreesY = fovDegreesY;
-    camera.perspective.nearZ       = nearZ;
-    camera.perspective.farZ        = farZ;
-    cameras.emplace_back(camera);
-    return (int) cameras.size() - 1;
+    const char* name,
+    const long nodeId,
+    const float aspectRatio,
+    const float fovDegreesX,
+    const float fovDegreesY,
+    const float nearZ,
+    const float farZ) {
+  RawCamera camera;
+  camera.name = name;
+  camera.nodeId = nodeId;
+  camera.mode = RawCamera::CAMERA_MODE_PERSPECTIVE;
+  camera.perspective.aspectRatio = aspectRatio;
+  camera.perspective.fovDegreesX = fovDegreesX;
+  camera.perspective.fovDegreesY = fovDegreesY;
+  camera.perspective.nearZ = nearZ;
+  camera.perspective.farZ = farZ;
+  cameras.emplace_back(camera);
+  return (int)cameras.size() - 1;
 }
 
 int RawModel::AddCameraOrthographic(
-    const char *name, const long nodeId, const float magX, const float magY, const float nearZ, const float farZ)
-{
-    RawCamera camera;
-    camera.name               = name;
-    camera.nodeId             = nodeId;
-    camera.mode               = RawCamera::CAMERA_MODE_ORTHOGRAPHIC;
-    camera.orthographic.magX  = magX;
-    camera.orthographic.magY  = magY;
-    camera.orthographic.nearZ = nearZ;
-    camera.orthographic.farZ  = farZ;
-    cameras.emplace_back(camera);
-    return (int) cameras.size() - 1;
+    const char* name,
+    const long nodeId,
+    const float magX,
+    const float magY,
+    const float nearZ,
+    const float farZ) {
+  RawCamera camera;
+  camera.name = name;
+  camera.nodeId = nodeId;
+  camera.mode = RawCamera::CAMERA_MODE_ORTHOGRAPHIC;
+  camera.orthographic.magX = magX;
+  camera.orthographic.magY = magY;
+  camera.orthographic.nearZ = nearZ;
+  camera.orthographic.farZ = farZ;
+  cameras.emplace_back(camera);
+  return (int)cameras.size() - 1;
 }
 
-int RawModel::AddNode(const long id, const char *name, const long parentId)
-{
-    assert(name[0] != '\0');
+int RawModel::AddNode(const long id, const char* name, const long parentId) {
+  assert(name[0] != '\0');
 
-    for (size_t i = 0; i < nodes.size(); i++) {
-        if (nodes[i].id == id ) {
-            return (int) i;
-        }
+  for (size_t i = 0; i < nodes.size(); i++) {
+    if (nodes[i].id == id) {
+      return (int)i;
     }
+  }
+
+  RawNode joint;
+  joint.isJoint = false;
+  joint.id = id;
+  joint.name = name;
+  joint.parentId = parentId;
+  joint.surfaceId = 0;
+  joint.lightIx = -1;
+  joint.translation = Vec3f(0, 0, 0);
+  joint.rotation = Quatf(0, 0, 0, 1);
+  joint.scale = Vec3f(1, 1, 1);
+
+  nodes.emplace_back(joint);
+  return (int)nodes.size() - 1;
+}
 
-    RawNode joint;
-    joint.isJoint     = false;
-    joint.id          = id;
-    joint.name        = name;
-    joint.parentId    = parentId;
-    joint.surfaceId   = 0;
-    joint.lightIx     = -1;
-    joint.translation = Vec3f(0, 0, 0);
-    joint.rotation    = Quatf(0, 0, 0, 1);
-    joint.scale       = Vec3f(1, 1, 1);
+void RawModel::Condense() {
+  // Only keep surfaces that are referenced by one or more triangles.
+  {
+    std::vector<RawSurface> oldSurfaces = surfaces;
 
-    nodes.emplace_back(joint);
-    return (int) nodes.size() - 1;
-}
+    surfaces.clear();
 
-void RawModel::Condense()
-{
-    // Only keep surfaces that are referenced by one or more triangles.
-    {
-        std::vector<RawSurface> oldSurfaces = surfaces;
-
-        surfaces.clear();
-
-        std::set<int> survivingSurfaceIds;
-        for (auto &triangle : triangles) {
-            const RawSurface &surface = oldSurfaces[triangle.surfaceIndex];
-            const int surfaceIndex = AddSurface(surface.name.c_str(), surface.id);
-            surfaces[surfaceIndex] = surface;
-            triangle.surfaceIndex = surfaceIndex;
-            survivingSurfaceIds.emplace(surface.id);
-        }
-        // clear out references to meshes that no longer exist
-        for (auto &node : nodes) {
-            if (node.surfaceId != 0 && survivingSurfaceIds.find(node.surfaceId) == survivingSurfaceIds.end()) {
-                node.surfaceId = 0;
-            }
-        }
+    std::set<int> survivingSurfaceIds;
+    for (auto& triangle : triangles) {
+      const RawSurface& surface = oldSurfaces[triangle.surfaceIndex];
+      const int surfaceIndex = AddSurface(surface.name.c_str(), surface.id);
+      surfaces[surfaceIndex] = surface;
+      triangle.surfaceIndex = surfaceIndex;
+      survivingSurfaceIds.emplace(surface.id);
+    }
+    // clear out references to meshes that no longer exist
+    for (auto& node : nodes) {
+      if (node.surfaceId != 0 &&
+          survivingSurfaceIds.find(node.surfaceId) == survivingSurfaceIds.end()) {
+        node.surfaceId = 0;
+      }
     }
+  }
 
-    // Only keep materials that are referenced by one or more triangles.
-    {
-        std::vector<RawMaterial> oldMaterials = materials;
+  // Only keep materials that are referenced by one or more triangles.
+  {
+    std::vector<RawMaterial> oldMaterials = materials;
 
-        materials.clear();
+    materials.clear();
 
-        for (auto &triangle : triangles) {
-            const RawMaterial &material = oldMaterials[triangle.materialIndex];
-            const int materialIndex = AddMaterial(material);
-            materials[materialIndex] = material;
-            triangle.materialIndex = materialIndex;
-        }
+    for (auto& triangle : triangles) {
+      const RawMaterial& material = oldMaterials[triangle.materialIndex];
+      const int materialIndex = AddMaterial(material);
+      materials[materialIndex] = material;
+      triangle.materialIndex = materialIndex;
     }
+  }
 
-    // Only keep textures that are referenced by one or more materials.
-    {
-        std::vector<RawTexture> oldTextures = textures;
+  // Only keep textures that are referenced by one or more materials.
+  {
+    std::vector<RawTexture> oldTextures = textures;
 
-        textures.clear();
+    textures.clear();
 
-        for (auto &material : materials) {
-            for (int j = 0; j < RAW_TEXTURE_USAGE_MAX; j++) {
-                if (material.textures[j] >= 0) {
-                    const RawTexture &texture = oldTextures[material.textures[j]];
-                    const int textureIndex = AddTexture(texture.name, texture.fileName, texture.fileLocation, texture.usage);
-                    textures[textureIndex] = texture;
-                    material.textures[j] = textureIndex;
-                }
-            }
+    for (auto& material : materials) {
+      for (int j = 0; j < RAW_TEXTURE_USAGE_MAX; j++) {
+        if (material.textures[j] >= 0) {
+          const RawTexture& texture = oldTextures[material.textures[j]];
+          const int textureIndex =
+              AddTexture(texture.name, texture.fileName, texture.fileLocation, texture.usage);
+          textures[textureIndex] = texture;
+          material.textures[j] = textureIndex;
         }
+      }
     }
+  }
 
-    // Only keep vertices that are referenced by one or more triangles.
-    {
-        std::vector<RawVertex> oldVertices = vertices;
+  // Only keep vertices that are referenced by one or more triangles.
+  {
+    std::vector<RawVertex> oldVertices = vertices;
 
-        vertexHash.clear();
-        vertices.clear();
+    vertexHash.clear();
+    vertices.clear();
 
-        for (auto &triangle : triangles) {
-            for (int j = 0; j < 3; j++) {
-                triangle.verts[j] = AddVertex(oldVertices[triangle.verts[j]]);
-            }
-        }
+    for (auto& triangle : triangles) {
+      for (int j = 0; j < 3; j++) {
+        triangle.verts[j] = AddVertex(oldVertices[triangle.verts[j]]);
+      }
     }
+  }
 }
 
-void RawModel::TransformGeometry(ComputeNormalsOption normals)
-{
-    switch(normals) {
-        case ComputeNormalsOption::NEVER:
-            break;
-        case ComputeNormalsOption::MISSING:
-            if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_NORMAL) != 0) {
-                break;
-            }
-            // otherwise fall through
-        case ComputeNormalsOption::BROKEN:
-        case ComputeNormalsOption::ALWAYS:
-            size_t computedNormalsCount = this->CalculateNormals(normals == ComputeNormalsOption::BROKEN);
-            vertexAttributes |= RAW_VERTEX_ATTRIBUTE_NORMAL;
-
-            if (verboseOutput) {
-                if (normals == ComputeNormalsOption::BROKEN) {
-                    if (computedNormalsCount > 0) {
-                        fmt::printf("Repaired %lu empty normals.\n", computedNormalsCount);
-                    }
-                } else {
-                    fmt::printf("Computed %lu normals.\n", computedNormalsCount);
-                }
-            }
-            break;
-    }
+void RawModel::TransformGeometry(ComputeNormalsOption normals) {
+  switch (normals) {
+    case ComputeNormalsOption::NEVER:
+      break;
+    case ComputeNormalsOption::MISSING:
+      if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_NORMAL) != 0) {
+        break;
+      }
+      // otherwise fall through
+    case ComputeNormalsOption::BROKEN:
+    case ComputeNormalsOption::ALWAYS:
+      size_t computedNormalsCount = this->CalculateNormals(normals == ComputeNormalsOption::BROKEN);
+      vertexAttributes |= RAW_VERTEX_ATTRIBUTE_NORMAL;
+
+      if (verboseOutput) {
+        if (normals == ComputeNormalsOption::BROKEN) {
+          if (computedNormalsCount > 0) {
+            fmt::printf("Repaired %lu empty normals.\n", computedNormalsCount);
+          }
+        } else {
+          fmt::printf("Computed %lu normals.\n", computedNormalsCount);
+        }
+      }
+      break;
+  }
 }
 
-void RawModel::TransformTextures(const std::vector<std::function<Vec2f(Vec2f)>> &transforms)
-{
-    for (auto &vertice : vertices) {
-        if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_UV0) != 0) {
-            for (const auto &fun : transforms) {
-                vertice.uv0 = fun(vertice.uv0);
-            }
-        }
-        if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_UV1) != 0) {
-            for (const auto &fun : transforms) {
-                vertice.uv1 = fun(vertice.uv1);
-            }
-        }
+void RawModel::TransformTextures(const std::vector<std::function<Vec2f(Vec2f)>>& transforms) {
+  for (auto& vertice : vertices) {
+    if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_UV0) != 0) {
+      for (const auto& fun : transforms) {
+        vertice.uv0 = fun(vertice.uv0);
+      }
     }
+    if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_UV1) != 0) {
+      for (const auto& fun : transforms) {
+        vertice.uv1 = fun(vertice.uv1);
+      }
+    }
+  }
 }
 
-struct TriangleModelSortPos
-{
-    static bool Compare(const RawTriangle &a, const RawTriangle &b)
-    {
-        if (a.materialIndex != b.materialIndex) {
-            return a.materialIndex < b.materialIndex;
-        }
-        if (a.surfaceIndex != b.surfaceIndex) {
-            return a.surfaceIndex < b.surfaceIndex;
-        }
-        return a.verts[0] < b.verts[0];
+struct TriangleModelSortPos {
+  static bool Compare(const RawTriangle& a, const RawTriangle& b) {
+    if (a.materialIndex != b.materialIndex) {
+      return a.materialIndex < b.materialIndex;
     }
+    if (a.surfaceIndex != b.surfaceIndex) {
+      return a.surfaceIndex < b.surfaceIndex;
+    }
+    return a.verts[0] < b.verts[0];
+  }
 };
 
-struct TriangleModelSortNeg
-{
-    static bool Compare(const RawTriangle &a, const RawTriangle &b)
-    {
-        if (a.materialIndex != b.materialIndex) {
-            return a.materialIndex < b.materialIndex;
-        }
-        if (a.surfaceIndex != b.surfaceIndex) {
-            return a.surfaceIndex < b.surfaceIndex;
-        }
-        return a.verts[0] > b.verts[0];
+struct TriangleModelSortNeg {
+  static bool Compare(const RawTriangle& a, const RawTriangle& b) {
+    if (a.materialIndex != b.materialIndex) {
+      return a.materialIndex < b.materialIndex;
     }
+    if (a.surfaceIndex != b.surfaceIndex) {
+      return a.surfaceIndex < b.surfaceIndex;
+    }
+    return a.verts[0] > b.verts[0];
+  }
 };
 
 void RawModel::CreateMaterialModels(
-    std::vector<RawModel> &materialModels, bool shortIndices, const int keepAttribs, const bool forceDiscrete) const
-{
-    // Sort all triangles based on material first, then surface, then first vertex index.
-    std::vector<RawTriangle> sortedTriangles;
-
-    bool invertedTransparencySort = true;
-    if (invertedTransparencySort) {
-        // Split the triangles into opaque and transparent triangles.
-        std::vector<RawTriangle> opaqueTriangles;
-        std::vector<RawTriangle> transparentTriangles;
-        for (const auto &triangle : triangles) {
-            const int materialIndex = triangle.materialIndex;
-            if (materialIndex < 0) {
-                opaqueTriangles.push_back(triangle);
-                continue;
-            }
-            const int textureIndex = materials[materialIndex].textures[RAW_TEXTURE_USAGE_DIFFUSE];
-            if (textureIndex < 0) {
-                if (vertices[triangle.verts[0]].color.w < 1.0f ||
-                    vertices[triangle.verts[1]].color.w < 1.0f ||
-                    vertices[triangle.verts[2]].color.w < 1.0f) {
-                    transparentTriangles.push_back(triangle);
-                    continue;
-                }
-                opaqueTriangles.push_back(triangle);
-                continue;
-            }
-            if (textures[textureIndex].occlusion == RAW_TEXTURE_OCCLUSION_TRANSPARENT) {
-                transparentTriangles.push_back(triangle);
-            } else {
-                opaqueTriangles.push_back(triangle);
-            }
-        }
+    std::vector<RawModel>& materialModels,
+    bool shortIndices,
+    const int keepAttribs,
+    const bool forceDiscrete) const {
+  // Sort all triangles based on material first, then surface, then first vertex index.
+  std::vector<RawTriangle> sortedTriangles;
+
+  bool invertedTransparencySort = true;
+  if (invertedTransparencySort) {
+    // Split the triangles into opaque and transparent triangles.
+    std::vector<RawTriangle> opaqueTriangles;
+    std::vector<RawTriangle> transparentTriangles;
+    for (const auto& triangle : triangles) {
+      const int materialIndex = triangle.materialIndex;
+      if (materialIndex < 0) {
+        opaqueTriangles.push_back(triangle);
+        continue;
+      }
+      const int textureIndex = materials[materialIndex].textures[RAW_TEXTURE_USAGE_DIFFUSE];
+      if (textureIndex < 0) {
+        if (vertices[triangle.verts[0]].color.w < 1.0f ||
+            vertices[triangle.verts[1]].color.w < 1.0f ||
+            vertices[triangle.verts[2]].color.w < 1.0f) {
+          transparentTriangles.push_back(triangle);
+          continue;
+        }
+        opaqueTriangles.push_back(triangle);
+        continue;
+      }
+      if (textures[textureIndex].occlusion == RAW_TEXTURE_OCCLUSION_TRANSPARENT) {
+        transparentTriangles.push_back(triangle);
+      } else {
+        opaqueTriangles.push_back(triangle);
+      }
+    }
 
-        // Sort the opaque triangles.
-        std::sort(opaqueTriangles.begin(), opaqueTriangles.end(), TriangleModelSortPos::Compare);
+    // Sort the opaque triangles.
+    std::sort(opaqueTriangles.begin(), opaqueTriangles.end(), TriangleModelSortPos::Compare);
 
-        // Sort the transparent triangles in the reverse direction.
-        std::sort(transparentTriangles.begin(), transparentTriangles.end(), TriangleModelSortNeg::Compare);
+    // Sort the transparent triangles in the reverse direction.
+    std::sort(
+        transparentTriangles.begin(), transparentTriangles.end(), TriangleModelSortNeg::Compare);
 
-        // Add the triangles to the sorted list.
-        for (const auto &opaqueTriangle : opaqueTriangles) {
-            sortedTriangles.push_back(opaqueTriangle);
-        }
-        for (const auto &transparentTriangle : transparentTriangles) {
-            sortedTriangles.push_back(transparentTriangle);
-        }
-    } else {
-        sortedTriangles = triangles;
-        std::sort(sortedTriangles.begin(), sortedTriangles.end(), TriangleModelSortPos::Compare);
+    // Add the triangles to the sorted list.
+    for (const auto& opaqueTriangle : opaqueTriangles) {
+      sortedTriangles.push_back(opaqueTriangle);
     }
-
-    // Overestimate the number of models that will be created to avoid massive reallocation.
-    int discreteCount = 0;
-    for (const auto &surface : surfaces) {
-        discreteCount += surface.discrete ? 1 : 0;
+    for (const auto& transparentTriangle : transparentTriangles) {
+      sortedTriangles.push_back(transparentTriangle);
+    }
+  } else {
+    sortedTriangles = triangles;
+    std::sort(sortedTriangles.begin(), sortedTriangles.end(), TriangleModelSortPos::Compare);
+  }
+
+  // Overestimate the number of models that will be created to avoid massive reallocation.
+  int discreteCount = 0;
+  for (const auto& surface : surfaces) {
+    discreteCount += surface.discrete ? 1 : 0;
+  }
+
+  materialModels.clear();
+  materialModels.reserve(materials.size() + discreteCount);
+
+  const RawVertex defaultVertex;
+
+  // Create a separate model for each material.
+  RawModel* model;
+  for (size_t i = 0; i < sortedTriangles.size(); i++) {
+    if (sortedTriangles[i].materialIndex < 0 || sortedTriangles[i].surfaceIndex < 0) {
+      continue;
     }
 
-    materialModels.clear();
-    materialModels.reserve(materials.size() + discreteCount);
+    if (i == 0 || (shortIndices && model->GetVertexCount() >= 0xFFFE) ||
+        sortedTriangles[i].materialIndex != sortedTriangles[i - 1].materialIndex ||
+        (sortedTriangles[i].surfaceIndex != sortedTriangles[i - 1].surfaceIndex &&
+         (forceDiscrete || surfaces[sortedTriangles[i].surfaceIndex].discrete ||
+          surfaces[sortedTriangles[i - 1].surfaceIndex].discrete))) {
+      materialModels.resize(materialModels.size() + 1);
+      model = &materialModels[materialModels.size() - 1];
+    }
 
-    const RawVertex defaultVertex;
+    // FIXME: will have to unlink from the nodes, transform both surfaces into a
+    // common space, and reparent to a new node with appropriate transform.
+
+    const int prevSurfaceCount = model->GetSurfaceCount();
+    const int materialIndex = model->AddMaterial(materials[sortedTriangles[i].materialIndex]);
+    const int surfaceIndex = model->AddSurface(surfaces[sortedTriangles[i].surfaceIndex]);
+    RawSurface& rawSurface = model->GetSurface(surfaceIndex);
+
+    if (model->GetSurfaceCount() > prevSurfaceCount) {
+      const std::vector<long>& jointIds = surfaces[sortedTriangles[i].surfaceIndex].jointIds;
+      for (const auto& jointId : jointIds) {
+        const int nodeIndex = GetNodeById(jointId);
+        assert(nodeIndex != -1);
+        model->AddNode(GetNode(nodeIndex));
+      }
+      rawSurface.bounds.Clear();
+    }
 
-    // Create a separate model for each material.
-    RawModel *model;
-    for (size_t i = 0; i < sortedTriangles.size(); i++) {
+    int verts[3];
+    for (int j = 0; j < 3; j++) {
+      RawVertex vertex = vertices[sortedTriangles[i].verts[j]];
 
-        if (sortedTriangles[i].materialIndex < 0 || sortedTriangles[i].surfaceIndex < 0) {
-            continue;
+      if (keepAttribs != -1) {
+        int keep = keepAttribs;
+        if ((keepAttribs & RAW_VERTEX_ATTRIBUTE_POSITION) != 0) {
+          keep |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
         }
+        if ((keepAttribs & RAW_VERTEX_ATTRIBUTE_AUTO) != 0) {
+          keep |= RAW_VERTEX_ATTRIBUTE_POSITION;
 
-        if (i == 0 ||
-            (shortIndices && model->GetVertexCount() >= 0xFFFE) ||
-            sortedTriangles[i].materialIndex != sortedTriangles[i - 1].materialIndex ||
-            (sortedTriangles[i].surfaceIndex != sortedTriangles[i - 1].surfaceIndex &&
-                (forceDiscrete || surfaces[sortedTriangles[i].surfaceIndex].discrete ||
-                    surfaces[sortedTriangles[i - 1].surfaceIndex].discrete))) {
-            materialModels.resize(materialModels.size() + 1);
-            model = &materialModels[materialModels.size() - 1];
+          const RawMaterial& mat = model->GetMaterial(materialIndex);
+          if (mat.textures[RAW_TEXTURE_USAGE_DIFFUSE] != -1) {
+            keep |= RAW_VERTEX_ATTRIBUTE_UV0;
+          }
+          if (mat.textures[RAW_TEXTURE_USAGE_NORMAL] != -1) {
+            keep |= RAW_VERTEX_ATTRIBUTE_NORMAL | RAW_VERTEX_ATTRIBUTE_TANGENT |
+                RAW_VERTEX_ATTRIBUTE_BINORMAL | RAW_VERTEX_ATTRIBUTE_UV0;
+          }
+          if (mat.textures[RAW_TEXTURE_USAGE_SPECULAR] != -1) {
+            keep |= RAW_VERTEX_ATTRIBUTE_NORMAL | RAW_VERTEX_ATTRIBUTE_UV0;
+          }
+          if (mat.textures[RAW_TEXTURE_USAGE_EMISSIVE] != -1) {
+            keep |= RAW_VERTEX_ATTRIBUTE_UV1;
+          }
         }
-
-        // FIXME: will have to unlink from the nodes, transform both surfaces into a
-        // common space, and reparent to a new node with appropriate transform.
-
-        const int prevSurfaceCount = model->GetSurfaceCount();
-        const int materialIndex    = model->AddMaterial(materials[sortedTriangles[i].materialIndex]);
-        const int surfaceIndex     = model->AddSurface(surfaces[sortedTriangles[i].surfaceIndex]);
-        RawSurface &rawSurface = model->GetSurface(surfaceIndex);
-
-        if (model->GetSurfaceCount() > prevSurfaceCount) {
-            const std::vector<long> &jointIds = surfaces[sortedTriangles[i].surfaceIndex].jointIds;
-            for (const auto &jointId : jointIds) {
-                const int nodeIndex = GetNodeById(jointId);
-                assert(nodeIndex != -1);
-                model->AddNode(GetNode(nodeIndex));
-            }
-            rawSurface.bounds.Clear();
+        if ((keep & RAW_VERTEX_ATTRIBUTE_POSITION) == 0) {
+          vertex.position = defaultVertex.position;
         }
-
-        int verts[3];
-        for (int j = 0; j < 3; j++) {
-            RawVertex vertex = vertices[sortedTriangles[i].verts[j]];
-
-            if (keepAttribs != -1) {
-                int keep = keepAttribs;
-                if ((keepAttribs & RAW_VERTEX_ATTRIBUTE_POSITION) != 0) {
-                    keep |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
-                }
-                if ((keepAttribs & RAW_VERTEX_ATTRIBUTE_AUTO) != 0) {
-                    keep |= RAW_VERTEX_ATTRIBUTE_POSITION;
-
-                    const RawMaterial &mat = model->GetMaterial(materialIndex);
-                    if (mat.textures[RAW_TEXTURE_USAGE_DIFFUSE] != -1) {
-                        keep |= RAW_VERTEX_ATTRIBUTE_UV0;
-                    }
-                    if (mat.textures[RAW_TEXTURE_USAGE_NORMAL] != -1) {
-                        keep |= RAW_VERTEX_ATTRIBUTE_NORMAL |
-                                RAW_VERTEX_ATTRIBUTE_TANGENT |
-                                RAW_VERTEX_ATTRIBUTE_BINORMAL |
-                                RAW_VERTEX_ATTRIBUTE_UV0;
-                    }
-                    if (mat.textures[RAW_TEXTURE_USAGE_SPECULAR] != -1) {
-                        keep |= RAW_VERTEX_ATTRIBUTE_NORMAL |
-                                RAW_VERTEX_ATTRIBUTE_UV0;
-                    }
-                    if (mat.textures[RAW_TEXTURE_USAGE_EMISSIVE] != -1) {
-                        keep |= RAW_VERTEX_ATTRIBUTE_UV1;
-                    }
-                }
-                if ((keep & RAW_VERTEX_ATTRIBUTE_POSITION) == 0) { vertex.position = defaultVertex.position; }
-                if ((keep & RAW_VERTEX_ATTRIBUTE_NORMAL) == 0) { vertex.normal = defaultVertex.normal; }
-                if ((keep & RAW_VERTEX_ATTRIBUTE_TANGENT) == 0) { vertex.tangent = defaultVertex.tangent; }
-                if ((keep & RAW_VERTEX_ATTRIBUTE_BINORMAL) == 0) { vertex.binormal = defaultVertex.binormal; }
-                if ((keep & RAW_VERTEX_ATTRIBUTE_COLOR) == 0) { vertex.color = defaultVertex.color; }
-                if ((keep & RAW_VERTEX_ATTRIBUTE_UV0) == 0) { vertex.uv0 = defaultVertex.uv0; }
-                if ((keep & RAW_VERTEX_ATTRIBUTE_UV1) == 0) { vertex.uv1 = defaultVertex.uv1; }
-                if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) == 0) { vertex.jointIndices = defaultVertex.jointIndices; }
-                if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) == 0) { vertex.jointWeights = defaultVertex.jointWeights; }
-            }
-
-            verts[j] = model->AddVertex(vertex);
-            model->vertexAttributes |= vertex.Difference(defaultVertex);
-
-            rawSurface.bounds.AddPoint(vertex.position);
+        if ((keep & RAW_VERTEX_ATTRIBUTE_NORMAL) == 0) {
+          vertex.normal = defaultVertex.normal;
+        }
+        if ((keep & RAW_VERTEX_ATTRIBUTE_TANGENT) == 0) {
+          vertex.tangent = defaultVertex.tangent;
+        }
+        if ((keep & RAW_VERTEX_ATTRIBUTE_BINORMAL) == 0) {
+          vertex.binormal = defaultVertex.binormal;
         }
+        if ((keep & RAW_VERTEX_ATTRIBUTE_COLOR) == 0) {
+          vertex.color = defaultVertex.color;
+        }
+        if ((keep & RAW_VERTEX_ATTRIBUTE_UV0) == 0) {
+          vertex.uv0 = defaultVertex.uv0;
+        }
+        if ((keep & RAW_VERTEX_ATTRIBUTE_UV1) == 0) {
+          vertex.uv1 = defaultVertex.uv1;
+        }
+        if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) == 0) {
+          vertex.jointIndices = defaultVertex.jointIndices;
+        }
+        if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) == 0) {
+          vertex.jointWeights = defaultVertex.jointWeights;
+        }
+      }
+
+      verts[j] = model->AddVertex(vertex);
+      model->vertexAttributes |= vertex.Difference(defaultVertex);
 
-        model->AddTriangle(verts[0], verts[1], verts[2], materialIndex, surfaceIndex);
+      rawSurface.bounds.AddPoint(vertex.position);
     }
+
+    model->AddTriangle(verts[0], verts[1], verts[2], materialIndex, surfaceIndex);
+  }
 }
 
-int RawModel::GetNodeById(const long nodeId) const
-{
-    for (size_t i = 0; i < nodes.size(); i++) {
-        if (nodes[i].id == nodeId) {
-            return (int) i;
-        }
+int RawModel::GetNodeById(const long nodeId) const {
+  for (size_t i = 0; i < nodes.size(); i++) {
+    if (nodes[i].id == nodeId) {
+      return (int)i;
     }
-    return -1;
+  }
+  return -1;
 }
 
-int RawModel::GetSurfaceById(const long surfaceId) const
-{
-    for (size_t i = 0; i < surfaces.size(); i++) {
-        if (surfaces[i].id == surfaceId) {
-            return (int)i;
-        }
+int RawModel::GetSurfaceById(const long surfaceId) const {
+  for (size_t i = 0; i < surfaces.size(); i++) {
+    if (surfaces[i].id == surfaceId) {
+      return (int)i;
     }
-    return -1;
+  }
+  return -1;
 }
 
-Vec3f RawModel::getFaceNormal(int verts[3]) const
-{
-    const float l0 = (vertices[verts[1]].position - vertices[verts[0]].position ).LengthSquared();
-    const float l1 = (vertices[verts[2]].position - vertices[verts[1]].position ).LengthSquared();
-    const float l2 = (vertices[verts[0]].position - vertices[verts[2]].position ).LengthSquared();
-    const int index = ( l0 > l1 ) ? ( l0 > l2 ? 2 : 1 ) : ( l1 > l2 ? 0 : 1 );
-
-    const Vec3f e0 = vertices[verts[(index + 1) % 3]].position - vertices[verts[index]].position;
-    const Vec3f e1 = vertices[verts[(index + 2) % 3]].position - vertices[verts[index]].position;
-    if (e0.LengthSquared() < FLT_MIN || e1.LengthSquared() < FLT_MIN) {
-        return Vec3f { 0.0f };
-    }
-    auto result = Vec3f::CrossProduct(e0, e1);
-    auto resultLengthSquared = result.LengthSquared();
-    if (resultLengthSquared < FLT_MIN) {
-        return Vec3f { 0.0f };
-    }
-    float edgeDot = std::max(-1.0f, std::min(1.0f, Vec3f::DotProduct(e0, e1)));
-    float angle = acos(edgeDot);
-    float area = resultLengthSquared / 2.0f;
-    return result.Normalized() * angle * area;
+Vec3f RawModel::getFaceNormal(int verts[3]) const {
+  const float l0 = (vertices[verts[1]].position - vertices[verts[0]].position).LengthSquared();
+  const float l1 = (vertices[verts[2]].position - vertices[verts[1]].position).LengthSquared();
+  const float l2 = (vertices[verts[0]].position - vertices[verts[2]].position).LengthSquared();
+  const int index = (l0 > l1) ? (l0 > l2 ? 2 : 1) : (l1 > l2 ? 0 : 1);
+
+  const Vec3f e0 = vertices[verts[(index + 1) % 3]].position - vertices[verts[index]].position;
+  const Vec3f e1 = vertices[verts[(index + 2) % 3]].position - vertices[verts[index]].position;
+  if (e0.LengthSquared() < FLT_MIN || e1.LengthSquared() < FLT_MIN) {
+    return Vec3f{0.0f};
+  }
+  auto result = Vec3f::CrossProduct(e0, e1);
+  auto resultLengthSquared = result.LengthSquared();
+  if (resultLengthSquared < FLT_MIN) {
+    return Vec3f{0.0f};
+  }
+  float edgeDot = std::max(-1.0f, std::min(1.0f, Vec3f::DotProduct(e0, e1)));
+  float angle = acos(edgeDot);
+  float area = resultLengthSquared / 2.0f;
+  return result.Normalized() * angle * area;
 }
 
-size_t RawModel::CalculateNormals(bool onlyBroken)
-{
-    Vec3f averagePos = Vec3f { 0.0f };
-    std::set<int> brokenVerts;
-    for (int vertIx = 0; vertIx < vertices.size(); vertIx ++) {
-        RawVertex &vertex = vertices[vertIx];
-        averagePos += (vertex.position / (float)vertices.size());
-        if (onlyBroken && (vertex.normal.LengthSquared() >= FLT_MIN)) {
-            continue;
-        }
-        vertex.normal = Vec3f { 0.0f };
-        if (onlyBroken) {
-            brokenVerts.emplace(vertIx);
-        }
+size_t RawModel::CalculateNormals(bool onlyBroken) {
+  Vec3f averagePos = Vec3f{0.0f};
+  std::set<int> brokenVerts;
+  for (int vertIx = 0; vertIx < vertices.size(); vertIx++) {
+    RawVertex& vertex = vertices[vertIx];
+    averagePos += (vertex.position / (float)vertices.size());
+    if (onlyBroken && (vertex.normal.LengthSquared() >= FLT_MIN)) {
+      continue;
+    }
+    vertex.normal = Vec3f{0.0f};
+    if (onlyBroken) {
+      brokenVerts.emplace(vertIx);
     }
+  }
 
-    for (auto &triangle : triangles) {
-        bool relevant = false;
-        for (int vertIx : triangle.verts) {
-            relevant |= (brokenVerts.count(vertIx) > 0);
-        }
-        if (!relevant) {
-            continue;
-        }
-        Vec3f faceNormal = this->getFaceNormal(triangle.verts);
-        for (int vertIx : triangle.verts) {
-            if (!onlyBroken || brokenVerts.count(vertIx) > 0) {
-                vertices[vertIx].normal += faceNormal;
-            }
-        }
+  for (auto& triangle : triangles) {
+    bool relevant = false;
+    for (int vertIx : triangle.verts) {
+      relevant |= (brokenVerts.count(vertIx) > 0);
     }
+    if (!relevant) {
+      continue;
+    }
+    Vec3f faceNormal = this->getFaceNormal(triangle.verts);
+    for (int vertIx : triangle.verts) {
+      if (!onlyBroken || brokenVerts.count(vertIx) > 0) {
+        vertices[vertIx].normal += faceNormal;
+      }
+    }
+  }
 
-    for (int vertIx = 0; vertIx < vertices.size(); vertIx ++) {
-        if (onlyBroken && brokenVerts.count(vertIx) == 0) {
-            continue;
-        }
-        RawVertex &vertex = vertices[vertIx];
-        if (vertex.normal.LengthSquared() < FLT_MIN) {
-            vertex.normal = vertex.position - averagePos;
-            if (vertex.normal.LengthSquared() < FLT_MIN) {
-                vertex.normal = Vec3f { 0.0f, 1.0f, 0.0f };
-                continue;
-            }
-        }
-        vertex.normal.Normalize();
+  for (int vertIx = 0; vertIx < vertices.size(); vertIx++) {
+    if (onlyBroken && brokenVerts.count(vertIx) == 0) {
+      continue;
+    }
+    RawVertex& vertex = vertices[vertIx];
+    if (vertex.normal.LengthSquared() < FLT_MIN) {
+      vertex.normal = vertex.position - averagePos;
+      if (vertex.normal.LengthSquared() < FLT_MIN) {
+        vertex.normal = Vec3f{0.0f, 1.0f, 0.0f};
+        continue;
+      }
     }
-    return onlyBroken ? brokenVerts.size() : vertices.size();
+    vertex.normal.Normalize();
+  }
+  return onlyBroken ? brokenVerts.size() : vertices.size();
 }

+ 476 - 417
src/raw/RawModel.hpp

@@ -9,481 +9,540 @@
 
 #pragma once
 
-#include <unordered_map>
 #include <functional>
 #include <set>
+#include <unordered_map>
 
 #include "FBX2glTF.h"
 
-
-enum RawVertexAttribute
-{
-    RAW_VERTEX_ATTRIBUTE_POSITION      = 1 << 0,
-    RAW_VERTEX_ATTRIBUTE_NORMAL        = 1 << 1,
-    RAW_VERTEX_ATTRIBUTE_TANGENT       = 1 << 2,
-    RAW_VERTEX_ATTRIBUTE_BINORMAL      = 1 << 3,
-    RAW_VERTEX_ATTRIBUTE_COLOR         = 1 << 4,
-    RAW_VERTEX_ATTRIBUTE_UV0           = 1 << 5,
-    RAW_VERTEX_ATTRIBUTE_UV1           = 1 << 6,
-    RAW_VERTEX_ATTRIBUTE_JOINT_INDICES = 1 << 7,
-    RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS = 1 << 8,
-
-    RAW_VERTEX_ATTRIBUTE_AUTO = 1 << 31
+enum RawVertexAttribute {
+  RAW_VERTEX_ATTRIBUTE_POSITION = 1 << 0,
+  RAW_VERTEX_ATTRIBUTE_NORMAL = 1 << 1,
+  RAW_VERTEX_ATTRIBUTE_TANGENT = 1 << 2,
+  RAW_VERTEX_ATTRIBUTE_BINORMAL = 1 << 3,
+  RAW_VERTEX_ATTRIBUTE_COLOR = 1 << 4,
+  RAW_VERTEX_ATTRIBUTE_UV0 = 1 << 5,
+  RAW_VERTEX_ATTRIBUTE_UV1 = 1 << 6,
+  RAW_VERTEX_ATTRIBUTE_JOINT_INDICES = 1 << 7,
+  RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS = 1 << 8,
+
+  RAW_VERTEX_ATTRIBUTE_AUTO = 1 << 31
 };
 
-struct RawBlendVertex
-{
-    Vec3f position {};
-    Vec3f normal {};
-    Vec4f tangent {};
+struct RawBlendVertex {
+  Vec3f position{};
+  Vec3f normal{};
+  Vec4f tangent{};
 
-    bool operator==(const RawBlendVertex &other) const {
-        return position == other.position &&
-               normal == other.normal &&
-               tangent == other.tangent;
-    }
+  bool operator==(const RawBlendVertex& other) const {
+    return position == other.position && normal == other.normal && tangent == other.tangent;
+  }
 };
 
-struct RawVertex
-{
-    RawVertex() :
-        polarityUv0(false),
-        pad1(false),
-        pad2(false),
-        pad3(false) {}
-
-    Vec3f position { 0.0f };
-    Vec3f normal { 0.0f };
-    Vec3f binormal { 0.0f };
-    Vec4f tangent { 0.0f };
-    Vec4f color { 0.0f };
-    Vec2f uv0 { 0.0f };
-    Vec2f uv1 { 0.0f };
-    Vec4i jointIndices { 0, 0, 0, 0 };
-    Vec4f jointWeights { 0.0f };
-    // end of members that directly correspond to vertex attributes
-
-    // if this vertex participates in a blend shape setup, the surfaceIx of its dedicated mesh; otherwise, -1
-    int blendSurfaceIx = -1;
-    // the size of this vector is always identical to the size of the corresponding RawSurface.blendChannels
-    std::vector<RawBlendVertex> blends { };
-
-    bool polarityUv0;
-    bool pad1;
-    bool pad2;
-    bool pad3;
-
-    bool operator==(const RawVertex &other) const;
-    size_t Difference(const RawVertex &other) const;
+struct RawVertex {
+  RawVertex() : polarityUv0(false), pad1(false), pad2(false), pad3(false) {}
+
+  Vec3f position{0.0f};
+  Vec3f normal{0.0f};
+  Vec3f binormal{0.0f};
+  Vec4f tangent{0.0f};
+  Vec4f color{0.0f};
+  Vec2f uv0{0.0f};
+  Vec2f uv1{0.0f};
+  Vec4i jointIndices{0, 0, 0, 0};
+  Vec4f jointWeights{0.0f};
+  // end of members that directly correspond to vertex attributes
+
+  // if this vertex participates in a blend shape setup, the surfaceIx of its dedicated mesh;
+  // otherwise, -1
+  int blendSurfaceIx = -1;
+  // the size of this vector is always identical to the size of the corresponding
+  // RawSurface.blendChannels
+  std::vector<RawBlendVertex> blends{};
+
+  bool polarityUv0;
+  bool pad1;
+  bool pad2;
+  bool pad3;
+
+  bool operator==(const RawVertex& other) const;
+  size_t Difference(const RawVertex& other) const;
 };
 
-class VertexHasher
-{
-public:
-    size_t operator()(const RawVertex &v) const
-    {
-        size_t seed = 5381;
-        const auto hasher = std::hash<float>{};
-        seed ^= hasher(v.position[0]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
-        seed ^= hasher(v.position[1]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
-        seed ^= hasher(v.position[2]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
-        return seed;
-    }
+class VertexHasher {
+ public:
+  size_t operator()(const RawVertex& v) const {
+    size_t seed = 5381;
+    const auto hasher = std::hash<float>{};
+    seed ^= hasher(v.position[0]) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+    seed ^= hasher(v.position[1]) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+    seed ^= hasher(v.position[2]) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+    return seed;
+  }
 };
 
-struct RawTriangle
-{
-    int verts[3];
-    int materialIndex;
-    int surfaceIndex;
+struct RawTriangle {
+  int verts[3];
+  int materialIndex;
+  int surfaceIndex;
 };
 
-enum RawShadingModel
-{
-    RAW_SHADING_MODEL_UNKNOWN = -1,
-    RAW_SHADING_MODEL_CONSTANT,
-    RAW_SHADING_MODEL_LAMBERT,
-    RAW_SHADING_MODEL_BLINN,
-    RAW_SHADING_MODEL_PHONG,
-    RAW_SHADING_MODEL_PBR_MET_ROUGH,
-    RAW_SHADING_MODEL_MAX
+enum RawShadingModel {
+  RAW_SHADING_MODEL_UNKNOWN = -1,
+  RAW_SHADING_MODEL_CONSTANT,
+  RAW_SHADING_MODEL_LAMBERT,
+  RAW_SHADING_MODEL_BLINN,
+  RAW_SHADING_MODEL_PHONG,
+  RAW_SHADING_MODEL_PBR_MET_ROUGH,
+  RAW_SHADING_MODEL_MAX
 };
 
 inline std::string Describe(RawShadingModel model) {
-    switch(model) {
-        case RAW_SHADING_MODEL_UNKNOWN:         return "<unknown>";
-        case RAW_SHADING_MODEL_CONSTANT:        return "Constant";
-        case RAW_SHADING_MODEL_LAMBERT:         return "Lambert";
-        case RAW_SHADING_MODEL_BLINN:           return "Blinn";
-        case RAW_SHADING_MODEL_PHONG:           return "Phong";
-        case RAW_SHADING_MODEL_PBR_MET_ROUGH:   return "Metallic/Roughness";
-        case RAW_SHADING_MODEL_MAX: default:    return "<unknown>";
-    }
+  switch (model) {
+    case RAW_SHADING_MODEL_UNKNOWN:
+      return "<unknown>";
+    case RAW_SHADING_MODEL_CONSTANT:
+      return "Constant";
+    case RAW_SHADING_MODEL_LAMBERT:
+      return "Lambert";
+    case RAW_SHADING_MODEL_BLINN:
+      return "Blinn";
+    case RAW_SHADING_MODEL_PHONG:
+      return "Phong";
+    case RAW_SHADING_MODEL_PBR_MET_ROUGH:
+      return "Metallic/Roughness";
+    case RAW_SHADING_MODEL_MAX:
+    default:
+      return "<unknown>";
+  }
 }
 
-enum RawTextureUsage
-{
-    RAW_TEXTURE_USAGE_NONE = -1,
-    RAW_TEXTURE_USAGE_AMBIENT,
-    RAW_TEXTURE_USAGE_DIFFUSE,
-    RAW_TEXTURE_USAGE_NORMAL,
-    RAW_TEXTURE_USAGE_SPECULAR,
-    RAW_TEXTURE_USAGE_SHININESS,
-    RAW_TEXTURE_USAGE_EMISSIVE,
-    RAW_TEXTURE_USAGE_REFLECTION,
-    RAW_TEXTURE_USAGE_ALBEDO,
-    RAW_TEXTURE_USAGE_OCCLUSION,
-    RAW_TEXTURE_USAGE_ROUGHNESS,
-    RAW_TEXTURE_USAGE_METALLIC,
-    RAW_TEXTURE_USAGE_MAX
+enum RawTextureUsage {
+  RAW_TEXTURE_USAGE_NONE = -1,
+  RAW_TEXTURE_USAGE_AMBIENT,
+  RAW_TEXTURE_USAGE_DIFFUSE,
+  RAW_TEXTURE_USAGE_NORMAL,
+  RAW_TEXTURE_USAGE_SPECULAR,
+  RAW_TEXTURE_USAGE_SHININESS,
+  RAW_TEXTURE_USAGE_EMISSIVE,
+  RAW_TEXTURE_USAGE_REFLECTION,
+  RAW_TEXTURE_USAGE_ALBEDO,
+  RAW_TEXTURE_USAGE_OCCLUSION,
+  RAW_TEXTURE_USAGE_ROUGHNESS,
+  RAW_TEXTURE_USAGE_METALLIC,
+  RAW_TEXTURE_USAGE_MAX
 };
 
-inline std::string Describe(RawTextureUsage usage)
-{
-    switch (usage) {
-        case RAW_TEXTURE_USAGE_NONE:        return "<none>";
-        case RAW_TEXTURE_USAGE_AMBIENT:     return "ambient";
-        case RAW_TEXTURE_USAGE_DIFFUSE:     return "diffuse";
-        case RAW_TEXTURE_USAGE_NORMAL:      return "normal";
-        case RAW_TEXTURE_USAGE_SPECULAR:    return "specular";
-        case RAW_TEXTURE_USAGE_SHININESS:   return "shininess";
-        case RAW_TEXTURE_USAGE_EMISSIVE:    return "emissive";
-        case RAW_TEXTURE_USAGE_REFLECTION:  return "reflection";
-        case RAW_TEXTURE_USAGE_OCCLUSION:   return "occlusion";
-        case RAW_TEXTURE_USAGE_ROUGHNESS:   return "roughness";
-        case RAW_TEXTURE_USAGE_METALLIC:    return "metallic";
-        case RAW_TEXTURE_USAGE_MAX:default: return "unknown";
-    }
-};
-
-enum RawTextureOcclusion
-{
-    RAW_TEXTURE_OCCLUSION_OPAQUE,
-    RAW_TEXTURE_OCCLUSION_TRANSPARENT
+inline std::string Describe(RawTextureUsage usage) {
+  switch (usage) {
+    case RAW_TEXTURE_USAGE_NONE:
+      return "<none>";
+    case RAW_TEXTURE_USAGE_AMBIENT:
+      return "ambient";
+    case RAW_TEXTURE_USAGE_DIFFUSE:
+      return "diffuse";
+    case RAW_TEXTURE_USAGE_NORMAL:
+      return "normal";
+    case RAW_TEXTURE_USAGE_SPECULAR:
+      return "specular";
+    case RAW_TEXTURE_USAGE_SHININESS:
+      return "shininess";
+    case RAW_TEXTURE_USAGE_EMISSIVE:
+      return "emissive";
+    case RAW_TEXTURE_USAGE_REFLECTION:
+      return "reflection";
+    case RAW_TEXTURE_USAGE_OCCLUSION:
+      return "occlusion";
+    case RAW_TEXTURE_USAGE_ROUGHNESS:
+      return "roughness";
+    case RAW_TEXTURE_USAGE_METALLIC:
+      return "metallic";
+    case RAW_TEXTURE_USAGE_MAX:
+    default:
+      return "unknown";
+  }
 };
 
-struct RawTexture
-{
-    std::string         name;           // logical name in FBX file
-    int                 width;
-    int                 height;
-    int                 mipLevels;
-    RawTextureUsage     usage;
-    RawTextureOcclusion occlusion;
-    std::string         fileName;       // original filename in FBX file
-    std::string         fileLocation;   // inferred path in local filesystem, or ""
+enum RawTextureOcclusion { RAW_TEXTURE_OCCLUSION_OPAQUE, RAW_TEXTURE_OCCLUSION_TRANSPARENT };
+
+struct RawTexture {
+  std::string name; // logical name in FBX file
+  int width;
+  int height;
+  int mipLevels;
+  RawTextureUsage usage;
+  RawTextureOcclusion occlusion;
+  std::string fileName; // original filename in FBX file
+  std::string fileLocation; // inferred path in local filesystem, or ""
 };
 
-enum RawMaterialType
-{
-    RAW_MATERIAL_TYPE_OPAQUE,
-    RAW_MATERIAL_TYPE_TRANSPARENT,
-    RAW_MATERIAL_TYPE_SKINNED_OPAQUE,
-    RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT,
+enum RawMaterialType {
+  RAW_MATERIAL_TYPE_OPAQUE,
+  RAW_MATERIAL_TYPE_TRANSPARENT,
+  RAW_MATERIAL_TYPE_SKINNED_OPAQUE,
+  RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT,
 };
 
 struct RawMatProps {
-    explicit RawMatProps(RawShadingModel shadingModel)
-        : shadingModel(shadingModel)
-    {}
-    const RawShadingModel shadingModel;
-
-    virtual bool operator!=(const RawMatProps &other) const { return !(*this == other); }
-    virtual bool operator==(const RawMatProps &other) const { return shadingModel == other.shadingModel; };
+  explicit RawMatProps(RawShadingModel shadingModel) : shadingModel(shadingModel) {}
+  const RawShadingModel shadingModel;
+
+  virtual bool operator!=(const RawMatProps& other) const {
+    return !(*this == other);
+  }
+  virtual bool operator==(const RawMatProps& other) const {
+    return shadingModel == other.shadingModel;
+  };
 };
 
 struct RawTraditionalMatProps : RawMatProps {
-    RawTraditionalMatProps(
-        RawShadingModel shadingModel,
-        const Vec3f &&ambientFactor,
-        const Vec4f &&diffuseFactor,
-        const Vec3f &&emissiveFactor,
-        const Vec3f &&specularFactor,
-        const float shininess
-    ) : RawMatProps(shadingModel),
-          ambientFactor(ambientFactor),
-          diffuseFactor(diffuseFactor),
-          emissiveFactor(emissiveFactor),
-          specularFactor(specularFactor),
-          shininess(shininess)
-    {}
-
-    const Vec3f ambientFactor;
-    const Vec4f diffuseFactor;
-    const Vec3f emissiveFactor;
-    const Vec3f specularFactor;
-    const float shininess;
-
-    bool operator==(const RawMatProps &other) const override {
-        if (RawMatProps::operator==(other)) {
-            const auto &typed = (RawTraditionalMatProps &) other;
-            return ambientFactor == typed.ambientFactor &&
-                diffuseFactor == typed.diffuseFactor &&
-                specularFactor == typed.specularFactor &&
-                emissiveFactor == typed.emissiveFactor &&
-                shininess == typed.shininess;
-        }
-        return false;
+  RawTraditionalMatProps(
+      RawShadingModel shadingModel,
+      const Vec3f&& ambientFactor,
+      const Vec4f&& diffuseFactor,
+      const Vec3f&& emissiveFactor,
+      const Vec3f&& specularFactor,
+      const float shininess)
+      : RawMatProps(shadingModel),
+        ambientFactor(ambientFactor),
+        diffuseFactor(diffuseFactor),
+        emissiveFactor(emissiveFactor),
+        specularFactor(specularFactor),
+        shininess(shininess) {}
+
+  const Vec3f ambientFactor;
+  const Vec4f diffuseFactor;
+  const Vec3f emissiveFactor;
+  const Vec3f specularFactor;
+  const float shininess;
+
+  bool operator==(const RawMatProps& other) const override {
+    if (RawMatProps::operator==(other)) {
+      const auto& typed = (RawTraditionalMatProps&)other;
+      return ambientFactor == typed.ambientFactor && diffuseFactor == typed.diffuseFactor &&
+          specularFactor == typed.specularFactor && emissiveFactor == typed.emissiveFactor &&
+          shininess == typed.shininess;
     }
+    return false;
+  }
 };
 
 struct RawMetRoughMatProps : RawMatProps {
-    RawMetRoughMatProps(
-        RawShadingModel shadingModel,
-        const Vec4f &&diffuseFactor,
-        const Vec3f &&emissiveFactor,
-        float emissiveIntensity,
-        float metallic,
-        float roughness
-    ) : RawMatProps(shadingModel),
-      diffuseFactor(diffuseFactor),
-      emissiveFactor(emissiveFactor),
-      emissiveIntensity(emissiveIntensity),
-      metallic(metallic),
-      roughness(roughness)
-    {}
-    const Vec4f diffuseFactor;
-    const Vec3f emissiveFactor;
-    const float emissiveIntensity;
-    const float metallic;
-    const float roughness;
-
-    bool operator==(const RawMatProps &other) const override {
-        if (RawMatProps::operator==(other)) {
-            const auto &typed = (RawMetRoughMatProps &) other;
-            return diffuseFactor == typed.diffuseFactor &&
-            emissiveFactor == typed.emissiveFactor &&
-            emissiveIntensity == typed.emissiveIntensity &&
-            metallic == typed.metallic &&
-            roughness == typed.roughness;
-        }
-        return false;
+  RawMetRoughMatProps(
+      RawShadingModel shadingModel,
+      const Vec4f&& diffuseFactor,
+      const Vec3f&& emissiveFactor,
+      float emissiveIntensity,
+      float metallic,
+      float roughness)
+      : RawMatProps(shadingModel),
+        diffuseFactor(diffuseFactor),
+        emissiveFactor(emissiveFactor),
+        emissiveIntensity(emissiveIntensity),
+        metallic(metallic),
+        roughness(roughness) {}
+  const Vec4f diffuseFactor;
+  const Vec3f emissiveFactor;
+  const float emissiveIntensity;
+  const float metallic;
+  const float roughness;
+
+  bool operator==(const RawMatProps& other) const override {
+    if (RawMatProps::operator==(other)) {
+      const auto& typed = (RawMetRoughMatProps&)other;
+      return diffuseFactor == typed.diffuseFactor && emissiveFactor == typed.emissiveFactor &&
+          emissiveIntensity == typed.emissiveIntensity && metallic == typed.metallic &&
+          roughness == typed.roughness;
     }
+    return false;
+  }
 };
 
-
-struct RawMaterial
-{
-    std::string                  name;
-    RawMaterialType              type;
-    std::shared_ptr<RawMatProps> info;
-    int                          textures[RAW_TEXTURE_USAGE_MAX];
-    std::vector<std::string>     userProperties;
+struct RawMaterial {
+  std::string name;
+  RawMaterialType type;
+  std::shared_ptr<RawMatProps> info;
+  int textures[RAW_TEXTURE_USAGE_MAX];
+  std::vector<std::string> userProperties;
 };
 
-enum RawLightType
-{
-    RAW_LIGHT_TYPE_DIRECTIONAL,
-    RAW_LIGHT_TYPE_POINT,
-    RAW_LIGHT_TYPE_SPOT,
+enum RawLightType {
+  RAW_LIGHT_TYPE_DIRECTIONAL,
+  RAW_LIGHT_TYPE_POINT,
+  RAW_LIGHT_TYPE_SPOT,
 };
 
-struct RawLight
-{
-    std::string     name;
-    RawLightType    type;
-    Vec3f color;
-    float intensity;
-    float           innerConeAngle;     // only meaningful for spot
-    float           outerConeAngle;     // only meaningful for spot
+struct RawLight {
+  std::string name;
+  RawLightType type;
+  Vec3f color;
+  float intensity;
+  float innerConeAngle; // only meaningful for spot
+  float outerConeAngle; // only meaningful for spot
 };
 
-struct RawBlendChannel
-{
-    float defaultDeform;
-    bool hasNormals;
-    bool hasTangents;
-    std::string name;
+struct RawBlendChannel {
+  float defaultDeform;
+  bool hasNormals;
+  bool hasTangents;
+  std::string name;
 };
 
-struct RawSurface
-{
-    long                         id;
-    std::string                  name;                            // The name of this surface
-    long                         skeletonRootId;                  // The id of the root node of the skeleton.
-    Bounds<float, 3>             bounds;
-    std::vector<long>            jointIds;
-    std::vector<Vec3f>           jointGeometryMins;
-    std::vector<Vec3f>           jointGeometryMaxs;
-    std::vector<Mat4f>           inverseBindMatrices;
-    std::vector<RawBlendChannel> blendChannels;
-    bool                         discrete;
+struct RawSurface {
+  long id;
+  std::string name; // The name of this surface
+  long skeletonRootId; // The id of the root node of the skeleton.
+  Bounds<float, 3> bounds;
+  std::vector<long> jointIds;
+  std::vector<Vec3f> jointGeometryMins;
+  std::vector<Vec3f> jointGeometryMaxs;
+  std::vector<Mat4f> inverseBindMatrices;
+  std::vector<RawBlendChannel> blendChannels;
+  bool discrete;
 };
 
-struct RawChannel
-{
-    int                nodeIndex;
-    std::vector<Vec3f> translations;
-    std::vector<Quatf> rotations;
-    std::vector<Vec3f> scales;
-    std::vector<float> weights;
+struct RawChannel {
+  int nodeIndex;
+  std::vector<Vec3f> translations;
+  std::vector<Quatf> rotations;
+  std::vector<Vec3f> scales;
+  std::vector<float> weights;
 };
 
-struct RawAnimation
-{
-    std::string             name;
-    std::vector<float>      times;
-    std::vector<RawChannel> channels;
+struct RawAnimation {
+  std::string name;
+  std::vector<float> times;
+  std::vector<RawChannel> channels;
 };
 
-struct RawCamera
-{
-    std::string name;
-    long        nodeId;
-
-    enum
-    {
-        CAMERA_MODE_PERSPECTIVE,
-        CAMERA_MODE_ORTHOGRAPHIC
-    } mode;
-
-    struct
-    {
-        float aspectRatio;
-        float fovDegreesX;
-        float fovDegreesY;
-        float nearZ;
-        float farZ;
-    } perspective;
-
-    struct
-    {
-        float magX;
-        float magY;
-        float nearZ;
-        float farZ;
-    } orthographic;
+struct RawCamera {
+  std::string name;
+  long nodeId;
+
+  enum { CAMERA_MODE_PERSPECTIVE, CAMERA_MODE_ORTHOGRAPHIC } mode;
+
+  struct {
+    float aspectRatio;
+    float fovDegreesX;
+    float fovDegreesY;
+    float nearZ;
+    float farZ;
+  } perspective;
+
+  struct {
+    float magX;
+    float magY;
+    float nearZ;
+    float farZ;
+  } orthographic;
 };
 
-struct RawNode
-{
-    bool                     isJoint;
-    long                     id;
-    std::string              name;
-    long                     parentId;
-    std::vector<long>        childIds;
-    Vec3f                    translation;
-    Quatf                    rotation;
-    Vec3f                    scale;
-    long                     surfaceId;
-    long                     lightIx;
-    std::vector<std::string> userProperties;
+struct RawNode {
+  bool isJoint;
+  long id;
+  std::string name;
+  long parentId;
+  std::vector<long> childIds;
+  Vec3f translation;
+  Quatf rotation;
+  Vec3f scale;
+  long surfaceId;
+  long lightIx;
+  std::vector<std::string> userProperties;
 };
 
-class RawModel
-{
-public:
-    RawModel();
-
-    // Add geometry.
-    void AddVertexAttribute(const RawVertexAttribute attrib);
-    int AddVertex(const RawVertex &vertex);
-    int AddTriangle(const int v0, const int v1, const int v2, const int materialIndex, const int surfaceIndex);
-    int AddTexture(const std::string &name, const std::string &fileName, const std::string &fileLocation, RawTextureUsage usage);
-    int AddMaterial(const RawMaterial &material);
-    int AddMaterial(
-        const char *name, const RawMaterialType materialType, const int textures[RAW_TEXTURE_USAGE_MAX],
-        std::shared_ptr<RawMatProps> materialInfo, const std::vector<std::string>& userProperties);
-    int AddLight(const char *name, RawLightType lightType, Vec3f color, float intensity,
-                 float innerConeAngle, float outerConeAngle);
-    int AddSurface(const RawSurface &suface);
-    int AddSurface(const char *name, long surfaceId);
-    int AddAnimation(const RawAnimation &animation);
-    int AddCameraPerspective(
-        const char *name, const long nodeId, const float aspectRatio, const float fovDegreesX, const float fovDegreesY,
-        const float nearZ, const float farZ);
-    int
-    AddCameraOrthographic(const char *name, const long nodeId, const float magX, const float magY, const float nearZ, const float farZ);
-    int AddNode(const RawNode &node);
-    int AddNode(const long id, const char *name, const long parentId);
-    void SetRootNode(const long nodeId) { rootNodeId = nodeId; }
-    const long GetRootNode() const { return rootNodeId; }
-
-    // Remove unused vertices, textures or materials after removing vertex attributes, textures, materials or surfaces.
-    void Condense();
-
-    void TransformGeometry(ComputeNormalsOption);
-
-    void TransformTextures(const std::vector<std::function<Vec2f(Vec2f)>> &transforms);
-
-    size_t CalculateNormals(bool);
-
-    // Get the attributes stored per vertex.
-    int GetVertexAttributes() const { return vertexAttributes; }
-
-    // Iterate over the vertices.
-    int GetVertexCount() const { return (int) vertices.size(); }
-    const RawVertex &GetVertex(const int index) const { return vertices[index]; }
-
-    // Iterate over the triangles.
-    int GetTriangleCount() const { return (int) triangles.size(); }
-    const RawTriangle &GetTriangle(const int index) const { return triangles[index]; }
-
-    // Iterate over the textures.
-    int GetTextureCount() const { return (int) textures.size(); }
-    const RawTexture &GetTexture(const int index) const { return textures[index]; }
-
-    // Iterate over the materials.
-    int GetMaterialCount() const { return (int) materials.size(); }
-    const RawMaterial &GetMaterial(const int index) const { return materials[index]; }
-
-    // Iterate over the surfaces.
-    int GetSurfaceCount() const { return (int) surfaces.size(); }
-    const RawSurface &GetSurface(const int index) const { return surfaces[index]; }
-    RawSurface &GetSurface(const int index) { return surfaces[index]; }
-    int GetSurfaceById(const long id) const;
-
-    // Iterate over the animations.
-    int GetAnimationCount() const { return (int) animations.size(); }
-    const RawAnimation &GetAnimation(const int index) const { return animations[index]; }
-
-    // Iterate over the cameras.
-    int GetCameraCount() const { return (int) cameras.size(); }
-    const RawCamera &GetCamera(const int index) const { return cameras[index]; }
-
-    // Iterate over the lights.
-    int GetLightCount() const { return (int) lights.size(); }
-    const RawLight &GetLight(const int index) const { return lights[index]; }
-
-    // Iterate over the nodes.
-    int GetNodeCount() const { return (int) nodes.size(); }
-    const RawNode &GetNode(const int index) const { return nodes[index]; }
-    RawNode &GetNode(const int index) { return nodes[index]; }
-    int GetNodeById(const long nodeId) const;
-
-    // Create individual attribute arrays.
-    // Returns true if the vertices store the particular attribute.
-    template<typename _attrib_type_>
-    void GetAttributeArray(std::vector<_attrib_type_> &out, const _attrib_type_ RawVertex::* ptr) const;
-
-    // Create an array with a raw model for each material.
-    // Multiple surfaces with the same material will turn into a single model.
-    // However, surfaces that are marked as 'discrete' will turn into separate models.
-    void CreateMaterialModels(
-        std::vector<RawModel> &materialModels, bool shortIndices, const int keepAttribs, const bool forceDiscrete) const;
-
-private:
-    Vec3f getFaceNormal(int verts[3]) const;
-
-    long                                             rootNodeId;
-    int                                              vertexAttributes;
-    std::unordered_map<RawVertex, int, VertexHasher> vertexHash;
-    std::vector<RawVertex>                           vertices;
-    std::vector<RawTriangle>                         triangles;
-    std::vector<RawTexture>                          textures;
-    std::vector<RawMaterial>                         materials;
-    std::vector<RawLight>                            lights;
-    std::vector<RawSurface>                          surfaces;
-    std::vector<RawAnimation>                        animations;
-    std::vector<RawCamera>                           cameras;
-    std::vector<RawNode>                             nodes;
+class RawModel {
+ public:
+  RawModel();
+
+  // Add geometry.
+  void AddVertexAttribute(const RawVertexAttribute attrib);
+  int AddVertex(const RawVertex& vertex);
+  int AddTriangle(
+      const int v0,
+      const int v1,
+      const int v2,
+      const int materialIndex,
+      const int surfaceIndex);
+  int AddTexture(
+      const std::string& name,
+      const std::string& fileName,
+      const std::string& fileLocation,
+      RawTextureUsage usage);
+  int AddMaterial(const RawMaterial& material);
+  int AddMaterial(
+      const char* name,
+      const RawMaterialType materialType,
+      const int textures[RAW_TEXTURE_USAGE_MAX],
+      std::shared_ptr<RawMatProps> materialInfo,
+      const std::vector<std::string>& userProperties);
+  int AddLight(
+      const char* name,
+      RawLightType lightType,
+      Vec3f color,
+      float intensity,
+      float innerConeAngle,
+      float outerConeAngle);
+  int AddSurface(const RawSurface& suface);
+  int AddSurface(const char* name, long surfaceId);
+  int AddAnimation(const RawAnimation& animation);
+  int AddCameraPerspective(
+      const char* name,
+      const long nodeId,
+      const float aspectRatio,
+      const float fovDegreesX,
+      const float fovDegreesY,
+      const float nearZ,
+      const float farZ);
+  int AddCameraOrthographic(
+      const char* name,
+      const long nodeId,
+      const float magX,
+      const float magY,
+      const float nearZ,
+      const float farZ);
+  int AddNode(const RawNode& node);
+  int AddNode(const long id, const char* name, const long parentId);
+  void SetRootNode(const long nodeId) {
+    rootNodeId = nodeId;
+  }
+  const long GetRootNode() const {
+    return rootNodeId;
+  }
+
+  // Remove unused vertices, textures or materials after removing vertex attributes, textures,
+  // materials or surfaces.
+  void Condense();
+
+  void TransformGeometry(ComputeNormalsOption);
+
+  void TransformTextures(const std::vector<std::function<Vec2f(Vec2f)>>& transforms);
+
+  size_t CalculateNormals(bool);
+
+  // Get the attributes stored per vertex.
+  int GetVertexAttributes() const {
+    return vertexAttributes;
+  }
+
+  // Iterate over the vertices.
+  int GetVertexCount() const {
+    return (int)vertices.size();
+  }
+  const RawVertex& GetVertex(const int index) const {
+    return vertices[index];
+  }
+
+  // Iterate over the triangles.
+  int GetTriangleCount() const {
+    return (int)triangles.size();
+  }
+  const RawTriangle& GetTriangle(const int index) const {
+    return triangles[index];
+  }
+
+  // Iterate over the textures.
+  int GetTextureCount() const {
+    return (int)textures.size();
+  }
+  const RawTexture& GetTexture(const int index) const {
+    return textures[index];
+  }
+
+  // Iterate over the materials.
+  int GetMaterialCount() const {
+    return (int)materials.size();
+  }
+  const RawMaterial& GetMaterial(const int index) const {
+    return materials[index];
+  }
+
+  // Iterate over the surfaces.
+  int GetSurfaceCount() const {
+    return (int)surfaces.size();
+  }
+  const RawSurface& GetSurface(const int index) const {
+    return surfaces[index];
+  }
+  RawSurface& GetSurface(const int index) {
+    return surfaces[index];
+  }
+  int GetSurfaceById(const long id) const;
+
+  // Iterate over the animations.
+  int GetAnimationCount() const {
+    return (int)animations.size();
+  }
+  const RawAnimation& GetAnimation(const int index) const {
+    return animations[index];
+  }
+
+  // Iterate over the cameras.
+  int GetCameraCount() const {
+    return (int)cameras.size();
+  }
+  const RawCamera& GetCamera(const int index) const {
+    return cameras[index];
+  }
+
+  // Iterate over the lights.
+  int GetLightCount() const {
+    return (int)lights.size();
+  }
+  const RawLight& GetLight(const int index) const {
+    return lights[index];
+  }
+
+  // Iterate over the nodes.
+  int GetNodeCount() const {
+    return (int)nodes.size();
+  }
+  const RawNode& GetNode(const int index) const {
+    return nodes[index];
+  }
+  RawNode& GetNode(const int index) {
+    return nodes[index];
+  }
+  int GetNodeById(const long nodeId) const;
+
+  // Create individual attribute arrays.
+  // Returns true if the vertices store the particular attribute.
+  template <typename _attrib_type_>
+  void GetAttributeArray(std::vector<_attrib_type_>& out, const _attrib_type_ RawVertex::*ptr)
+      const;
+
+  // Create an array with a raw model for each material.
+  // Multiple surfaces with the same material will turn into a single model.
+  // However, surfaces that are marked as 'discrete' will turn into separate models.
+  void CreateMaterialModels(
+      std::vector<RawModel>& materialModels,
+      bool shortIndices,
+      const int keepAttribs,
+      const bool forceDiscrete) const;
+
+ private:
+  Vec3f getFaceNormal(int verts[3]) const;
+
+  long rootNodeId;
+  int vertexAttributes;
+  std::unordered_map<RawVertex, int, VertexHasher> vertexHash;
+  std::vector<RawVertex> vertices;
+  std::vector<RawTriangle> triangles;
+  std::vector<RawTexture> textures;
+  std::vector<RawMaterial> materials;
+  std::vector<RawLight> lights;
+  std::vector<RawSurface> surfaces;
+  std::vector<RawAnimation> animations;
+  std::vector<RawCamera> cameras;
+  std::vector<RawNode> nodes;
 };
 
-template<typename _attrib_type_>
-void RawModel::GetAttributeArray(std::vector<_attrib_type_> &out, const _attrib_type_ RawVertex::* ptr) const
-{
-    out.resize(vertices.size());
-    for (size_t i = 0; i < vertices.size(); i++) {
-        out[i] = vertices[i].*ptr;
-    }
+template <typename _attrib_type_>
+void RawModel::GetAttributeArray(
+    std::vector<_attrib_type_>& out,
+    const _attrib_type_ RawVertex::*ptr) const {
+  out.resize(vertices.size());
+  for (size_t i = 0; i < vertices.size(); i++) {
+    out[i] = vertices[i].*ptr;
+  }
 }

+ 158 - 160
src/utils/File_Utils.cpp

@@ -9,24 +9,24 @@
 
 #include "File_Utils.hpp"
 
+#include <fstream>
 #include <string>
 #include <vector>
-#include <fstream>
 
 #include <stdint.h>
 #include <stdio.h>
 
-#if defined( __unix__ ) || defined ( __APPLE__ )
+#if defined(__unix__) || defined(__APPLE__)
 
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/types.h>
 #include <dirent.h>
 #include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #define _getcwd getcwd
 #define _mkdir(a) mkdir(a, 0777)
-#elif defined( _WIN32 )
+#elif defined(_WIN32)
 #include <direct.h>
 #include <process.h>
 #else
@@ -41,176 +41,174 @@
 
 namespace FileUtils {
 
-    std::string GetCurrentFolder()
-    {
-        char cwd[StringUtils::MAX_PATH_LENGTH];
-        if (!_getcwd(cwd, sizeof(cwd))) {
-            return std::string();
-        }
-        cwd[sizeof(cwd) - 1] = '\0';
-        StringUtils::GetCleanPath(cwd, cwd, StringUtils::PATH_UNIX);
-        const size_t length = strlen(cwd);
-        if (cwd[length - 1] != '/' && length < StringUtils::MAX_PATH_LENGTH - 1) {
-            cwd[length + 0] = '/';
-            cwd[length + 1] = '\0';
-        }
-        return std::string(cwd);
-    }
+std::string GetCurrentFolder() {
+  char cwd[StringUtils::MAX_PATH_LENGTH];
+  if (!_getcwd(cwd, sizeof(cwd))) {
+    return std::string();
+  }
+  cwd[sizeof(cwd) - 1] = '\0';
+  StringUtils::GetCleanPath(cwd, cwd, StringUtils::PATH_UNIX);
+  const size_t length = strlen(cwd);
+  if (cwd[length - 1] != '/' && length < StringUtils::MAX_PATH_LENGTH - 1) {
+    cwd[length + 0] = '/';
+    cwd[length + 1] = '\0';
+  }
+  return std::string(cwd);
+}
 
-    bool FileExists(const std::string &filePath)
-    {
-        std::ifstream stream(filePath);
-        return stream.good();
-    }
+bool FileExists(const std::string& filePath) {
+  std::ifstream stream(filePath);
+  return stream.good();
+}
 
-    bool FolderExists(const std::string &folderPath)
-    {
-#if defined( __unix__ ) || defined( __APPLE__ )
-        DIR *dir = opendir(folderPath.c_str());
-        if (dir) {
-            closedir(dir);
-            return true;
-        }
-        return false;
+bool FolderExists(const std::string& folderPath) {
+#if defined(__unix__) || defined(__APPLE__)
+  DIR* dir = opendir(folderPath.c_str());
+  if (dir) {
+    closedir(dir);
+    return true;
+  }
+  return false;
 #else
-        const DWORD ftyp = GetFileAttributesA( folderPath.c_str() );
-        if ( ftyp == INVALID_FILE_ATTRIBUTES )
-        {
-            return false;  // bad path
-        }
-        return ( ftyp & FILE_ATTRIBUTE_DIRECTORY ) != 0;
+  const DWORD ftyp = GetFileAttributesA(folderPath.c_str());
+  if (ftyp == INVALID_FILE_ATTRIBUTES) {
+    return false; // bad path
+  }
+  return (ftyp & FILE_ATTRIBUTE_DIRECTORY) != 0;
 #endif
-    }
+}
 
-    bool MatchExtension(const char *fileExtension, const char *matchExtensions)
-    {
-        if (matchExtensions[0] == '\0') {
-            return true;
-        }
-        if (fileExtension[0] == '.') {
-            fileExtension++;
-        }
-        for (const char *end = matchExtensions; end[0] != '\0';) {
-            for (; end[0] == ';'; end++) {}
-            const char *ext = end;
-            for (; end[0] != ';' && end[0] != '\0'; end++) {}
-#if defined( __unix__ ) || defined( __APPLE__ )
-            if (strncasecmp(fileExtension, ext, end - ext) == 0)
+bool MatchExtension(const char* fileExtension, const char* matchExtensions) {
+  if (matchExtensions[0] == '\0') {
+    return true;
+  }
+  if (fileExtension[0] == '.') {
+    fileExtension++;
+  }
+  for (const char* end = matchExtensions; end[0] != '\0';) {
+    for (; end[0] == ';'; end++) {
+    }
+    const char* ext = end;
+    for (; end[0] != ';' && end[0] != '\0'; end++) {
+    }
+#if defined(__unix__) || defined(__APPLE__)
+    if (strncasecmp(fileExtension, ext, end - ext) == 0)
 #else
-                if ( _strnicmp( fileExtension, ext, end - ext ) == 0 )
+    if (_strnicmp(fileExtension, ext, end - ext) == 0)
 #endif
-            {
-                return true;
-            }
-        }
-        return false;
+    {
+      return true;
     }
+  }
+  return false;
+}
 
-    std::vector<std::string> ListFolderFiles(const char *folder, const char *matchExtensions)
-    {
-        std::vector<std::string> fileList;
-#if defined( __unix__ ) || defined( __APPLE__ )
-        DIR *dir = opendir(strlen(folder) > 0 ? folder : ".");
-        if (dir != nullptr) {
-            for (;;) {
-                struct dirent *dp = readdir(dir);
-                if (dp == nullptr) {
-                    break;
-                }
-
-                if (dp->d_type == DT_DIR) {
-                    continue;
-                }
-
-                const char *fileName = dp->d_name;
-                const char *fileExt  = strrchr(fileName, '.');
-
-                if (!fileExt || !MatchExtension(fileExt, matchExtensions)) {
-                    continue;
-                }
-
-                fileList.emplace_back(fileName);
-            }
-
-            closedir(dir);
-        }
+std::vector<std::string> ListFolderFiles(const char* folder, const char* matchExtensions) {
+  std::vector<std::string> fileList;
+#if defined(__unix__) || defined(__APPLE__)
+  DIR* dir = opendir(strlen(folder) > 0 ? folder : ".");
+  if (dir != nullptr) {
+    for (;;) {
+      struct dirent* dp = readdir(dir);
+      if (dp == nullptr) {
+        break;
+      }
+
+      if (dp->d_type == DT_DIR) {
+        continue;
+      }
+
+      const char* fileName = dp->d_name;
+      const char* fileExt = strrchr(fileName, '.');
+
+      if (!fileExt || !MatchExtension(fileExt, matchExtensions)) {
+        continue;
+      }
+
+      fileList.emplace_back(fileName);
+    }
+
+    closedir(dir);
+  }
 #else
-        std::string pathStr = folder;
-        pathStr += "*";
-
-        WIN32_FIND_DATA FindFileData;
-        HANDLE hFind = FindFirstFile( pathStr.c_str(), &FindFileData );
-        if ( hFind != INVALID_HANDLE_VALUE )
-        {
-            do
-            {
-                if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
-                    std::string fileName = FindFileData.cFileName;
-                    std::string::size_type extPos = fileName.rfind('.');
-                    if (extPos != std::string::npos &&
-                        MatchExtension(fileName.substr(extPos + 1).c_str(), matchExtensions)) {
-                        fileList.push_back(fileName);
-                    }
-                }
-            } while ( FindNextFile( hFind, &FindFileData ) );
-
-            FindClose( hFind );
+  std::string pathStr = folder;
+  pathStr += "*";
+
+  WIN32_FIND_DATA FindFileData;
+  HANDLE hFind = FindFirstFile(pathStr.c_str(), &FindFileData);
+  if (hFind != INVALID_HANDLE_VALUE) {
+    do {
+      if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
+        std::string fileName = FindFileData.cFileName;
+        std::string::size_type extPos = fileName.rfind('.');
+        if (extPos != std::string::npos &&
+            MatchExtension(fileName.substr(extPos + 1).c_str(), matchExtensions)) {
+          fileList.push_back(fileName);
         }
+      }
+    } while (FindNextFile(hFind, &FindFileData));
+
+    FindClose(hFind);
+  }
 #endif
-        return fileList;
-    }
+  return fileList;
+}
 
-    bool CreatePath(const char *path)
-    {
-#if defined( __unix__ ) || defined( __APPLE__ )
-        StringUtils::PathSeparator separator = StringUtils::PATH_UNIX;
+bool CreatePath(const char* path) {
+#if defined(__unix__) || defined(__APPLE__)
+  StringUtils::PathSeparator separator = StringUtils::PATH_UNIX;
 #else
-        StringUtils::PathSeparator separator = StringUtils::PATH_WIN;
+  StringUtils::PathSeparator separator = StringUtils::PATH_WIN;
 #endif
-        std::string folder = StringUtils::GetFolderString(path);
-        std::string clean = StringUtils::GetCleanPathString(folder, separator);
-        std::string build = clean;
-        for (int i = 0; i < clean.length(); i ++) {
-            if (clean[i] == separator && i > 0) {
-                build[i] = '\0';
-                if (i > 1 || build[1] != ':') {
-                    if (_mkdir(build.c_str()) != 0 && errno != EEXIST) {
-                        return false;
-                    }
-                }
-            }
-            build[i] = clean[i];
+  std::string folder = StringUtils::GetFolderString(path);
+  std::string clean = StringUtils::GetCleanPathString(folder, separator);
+  std::string build = clean;
+  for (int i = 0; i < clean.length(); i++) {
+    if (clean[i] == separator && i > 0) {
+      build[i] = '\0';
+      if (i > 1 || build[1] != ':') {
+        if (_mkdir(build.c_str()) != 0 && errno != EEXIST) {
+          return false;
         }
-        return true;
+      }
     }
+    build[i] = clean[i];
+  }
+  return true;
+}
 
-    bool CopyFile(const std::string &srcFilename, const std::string &dstFilename, bool createPath) {
-        std::ifstream srcFile(srcFilename, std::ios::binary);
-        if (!srcFile) {
-            fmt::printf("Warning: Couldn't open file %s for reading.\n", srcFilename);
-            return false;
-        }
-        // find source file length
-        srcFile.seekg(0, std::ios::end);
-        std::streamsize srcSize = srcFile.tellg();
-        srcFile.seekg(0, std::ios::beg);
-
-        if (createPath && !CreatePath(dstFilename.c_str())) {
-            fmt::printf("Warning: Couldn't create directory %s.\n", dstFilename);
-            return false;
-        }
-
-        std::ofstream dstFile(dstFilename, std::ios::binary | std::ios::trunc);
-        if (!dstFile) {
-            fmt::printf("Warning: Couldn't open file %s for writing.\n", dstFilename);
-            return false;
-        }
-        dstFile << srcFile.rdbuf();
-        std::streamsize dstSize = dstFile.tellp();
-        if (srcSize == dstSize) {
-            return true;
-        }
-        fmt::printf("Warning: Only copied %lu bytes to %s, when %s is %lu bytes long.\n", dstSize, dstFilename, srcFilename, srcSize);
-        return false;
-    }
+bool CopyFile(const std::string& srcFilename, const std::string& dstFilename, bool createPath) {
+  std::ifstream srcFile(srcFilename, std::ios::binary);
+  if (!srcFile) {
+    fmt::printf("Warning: Couldn't open file %s for reading.\n", srcFilename);
+    return false;
+  }
+  // find source file length
+  srcFile.seekg(0, std::ios::end);
+  std::streamsize srcSize = srcFile.tellg();
+  srcFile.seekg(0, std::ios::beg);
+
+  if (createPath && !CreatePath(dstFilename.c_str())) {
+    fmt::printf("Warning: Couldn't create directory %s.\n", dstFilename);
+    return false;
+  }
+
+  std::ofstream dstFile(dstFilename, std::ios::binary | std::ios::trunc);
+  if (!dstFile) {
+    fmt::printf("Warning: Couldn't open file %s for writing.\n", dstFilename);
+    return false;
+  }
+  dstFile << srcFile.rdbuf();
+  std::streamsize dstSize = dstFile.tellp();
+  if (srcSize == dstSize) {
+    return true;
+  }
+  fmt::printf(
+      "Warning: Only copied %lu bytes to %s, when %s is %lu bytes long.\n",
+      dstSize,
+      dstFilename,
+      srcFilename,
+      srcSize);
+  return false;
 }
+} // namespace FileUtils

+ 11 - 8
src/utils/File_Utils.hpp

@@ -14,15 +14,18 @@
 
 namespace FileUtils {
 
-    std::string GetCurrentFolder();
+std::string GetCurrentFolder();
 
-    bool FileExists(const std::string &folderPath);
-    bool FolderExists(const std::string &folderPath);
+bool FileExists(const std::string& folderPath);
+bool FolderExists(const std::string& folderPath);
 
-    bool MatchExtension(const char *fileExtension, const char *matchExtensions);
-    std::vector<std::string> ListFolderFiles(const char *folder, const char *matchExtensions);
+bool MatchExtension(const char* fileExtension, const char* matchExtensions);
+std::vector<std::string> ListFolderFiles(const char* folder, const char* matchExtensions);
 
-    bool CreatePath(const char *path);
+bool CreatePath(const char* path);
 
-    bool CopyFile(const std::string &srcFilename, const std::string &dstFilename, bool createPath = false);
-}
+bool CopyFile(
+    const std::string& srcFilename,
+    const std::string& dstFilename,
+    bool createPath = false);
+} // namespace FileUtils

+ 43 - 46
src/utils/Image_Utils.cpp

@@ -9,8 +9,8 @@
 
 #include "Image_Utils.hpp"
 
-#include <string>
 #include <algorithm>
+#include <string>
 
 #define STB_IMAGE_IMPLEMENTATION
 
@@ -22,56 +22,53 @@
 
 namespace ImageUtils {
 
-    static bool imageHasTransparentPixels(FILE *f)
-    {
-        int     width, height, channels;
-        // RGBA: we have to load the pixels to figure out if the image is fully opaque
-        uint8_t *pixels = stbi_load_from_file(f, &width, &height, &channels, 0);
-        if (pixels != nullptr) {
-            int      pixelCount = width * height;
-            for (int ix         = 0; ix < pixelCount; ix++) {
-                // test fourth byte (alpha); 255 is 1.0
-                if (pixels[4 * ix + 3] != 255) {
-                    return true;
-                }
-            }
-        }
-        return false;
+static bool imageHasTransparentPixels(FILE* f) {
+  int width, height, channels;
+  // RGBA: we have to load the pixels to figure out if the image is fully opaque
+  uint8_t* pixels = stbi_load_from_file(f, &width, &height, &channels, 0);
+  if (pixels != nullptr) {
+    int pixelCount = width * height;
+    for (int ix = 0; ix < pixelCount; ix++) {
+      // test fourth byte (alpha); 255 is 1.0
+      if (pixels[4 * ix + 3] != 255) {
+        return true;
+      }
     }
+  }
+  return false;
+}
 
-    ImageProperties GetImageProperties(char const *filePath)
-    {
-        ImageProperties result = {
-            1,
-            1,
-            IMAGE_OPAQUE,
-        };
-
-        FILE *f     = fopen(filePath, "rb");
-        if (f == nullptr) {
-            return result;
-        }
+ImageProperties GetImageProperties(char const* filePath) {
+  ImageProperties result = {
+      1,
+      1,
+      IMAGE_OPAQUE,
+  };
 
-        int channels;
-        int success = stbi_info_from_file(f, &result.width, &result.height, &channels);
+  FILE* f = fopen(filePath, "rb");
+  if (f == nullptr) {
+    return result;
+  }
 
-        if (success && channels == 4 && imageHasTransparentPixels(f)) {
-            result.occlusion = IMAGE_TRANSPARENT;
-        }
-        return result;
-    }
+  int channels;
+  int success = stbi_info_from_file(f, &result.width, &result.height, &channels);
 
-    std::string suffixToMimeType(std::string suffix)
-    {
-        std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower);
+  if (success && channels == 4 && imageHasTransparentPixels(f)) {
+    result.occlusion = IMAGE_TRANSPARENT;
+  }
+  return result;
+}
 
-        if (suffix == "jpg" || suffix == "jpeg") {
-            return "image/jpeg";
-        }
-        if (suffix == "png") {
-            return "image/png";
-        }
-        return "image/unknown";
-    }
+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";
 }
+
+} // namespace ImageUtils

+ 17 - 22
src/utils/Image_Utils.hpp

@@ -13,25 +13,20 @@
 
 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);
-
-}
+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);
+
+} // namespace ImageUtils

+ 57 - 64
src/utils/String_Utils.cpp

@@ -11,77 +11,70 @@
 
 namespace StringUtils {
 
-    PathSeparator operator!(const PathSeparator &s)
-    {
-        return (s == PATH_WIN) ? PATH_UNIX : PATH_WIN;
-    }
+PathSeparator operator!(const PathSeparator& s) {
+  return (s == PATH_WIN) ? PATH_UNIX : PATH_WIN;
+}
 
-    PathSeparator GetPathSeparator() {
-#if defined( __unix__ ) || defined( __APPLE__ )
-        return PATH_UNIX;
+PathSeparator GetPathSeparator() {
+#if defined(__unix__) || defined(__APPLE__)
+  return PATH_UNIX;
 #else
-        return PATH_WIN;
+  return PATH_WIN;
 #endif
-    }
-    const std::string NormalizePath(const std::string &path)
-    {
-        PathSeparator separator = GetPathSeparator();
-        char replace;
-        if (separator == PATH_WIN) {
-            replace = PATH_UNIX;
-        }
-        else {
-            replace = PATH_WIN;
-        }
-        std::string normalizedPath = path;
-        for (size_t s = normalizedPath.find(replace, 0); s != std::string::npos; s = normalizedPath.find(replace, s)) {
-            normalizedPath[s] = separator;
-        }
-        return normalizedPath;
-    }
-
-    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);
-    }
+}
+const std::string NormalizePath(const std::string& path) {
+  PathSeparator separator = GetPathSeparator();
+  char replace;
+  if (separator == PATH_WIN) {
+    replace = PATH_UNIX;
+  } else {
+    replace = PATH_WIN;
+  }
+  std::string normalizedPath = path;
+  for (size_t s = normalizedPath.find(replace, 0); s != std::string::npos;
+       s = normalizedPath.find(replace, s)) {
+    normalizedPath[s] = separator;
+  }
+  return normalizedPath;
+}
 
-    const std::string GetCleanPathString(const std::string &path, const PathSeparator separator)
-    {
-        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;
-    }
+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);
+}
 
-    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);
-    }
+const std::string GetCleanPathString(const std::string& path, const PathSeparator separator) {
+  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;
+}
 
-    const std::string GetFileBaseString(const std::string &path)
-    {
-        const std::string fileName = GetFileNameString(path);
-        return fileName.substr(0, fileName.rfind('.')).c_str();
-    }
+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);
+}
 
-    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);
-    }
+const std::string GetFileBaseString(const std::string& path) {
+  const std::string fileName = GetFileNameString(path);
+  return fileName.substr(0, fileName.rfind('.')).c_str();
+}
 
-    int CompareNoCase(const std::string &s1, const std::string &s2)
-    {
-        return strncasecmp(s1.c_str(), s2.c_str(), MAX_PATH_LENGTH);
-    }
+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);
+}
 
+int CompareNoCase(const std::string& s1, const std::string& s2) {
+  return strncasecmp(s1.c_str(), s2.c_str(), MAX_PATH_LENGTH);
 }
+
+} // namespace StringUtils

+ 26 - 29
src/utils/String_Utils.hpp

@@ -9,49 +9,46 @@
 
 #pragma once
 
-#include <string>
+#include <cstdarg>
 #include <cstdio>
 #include <cstring>
-#include <cstdarg>
+#include <string>
 
-#if defined( _MSC_VER )
+#if defined(_MSC_VER)
 #define strncasecmp _strnicmp
 #define strcasecmp _stricmp
 #endif
 
 namespace StringUtils {
 
-    static const unsigned int MAX_PATH_LENGTH = 1024;
+static const unsigned int MAX_PATH_LENGTH = 1024;
 
-    enum PathSeparator
-    {
-        PATH_WIN  = '\\',
-        PATH_UNIX = '/'
-    };
+enum PathSeparator { PATH_WIN = '\\', PATH_UNIX = '/' };
 
-    PathSeparator operator!(const PathSeparator &s);
+PathSeparator operator!(const PathSeparator& s);
 
-    PathSeparator GetPathSeparator();
-    const std::string NormalizePath(const std::string &path);
+PathSeparator GetPathSeparator();
+const std::string NormalizePath(const std::string& path);
 
-    const std::string GetCleanPathString(const std::string &path, const PathSeparator separator = PATH_WIN);
+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;
-        }
-    }
+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);
+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);
+int CompareNoCase(const std::string& s1, const std::string& s2);
 
-}
+} // namespace StringUtils

部分文件因文件數量過多而無法顯示