text: /*
   * Copyright (C) 2015 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  #define GL_NEAREST                        0x2600
  #define GL_LINEAR                         0x2601
  #define GL_NEAREST_MIPMAP_NEAREST         0x2700
  #define GL_LINEAR_MIPMAP_NEAREST          0x2701
  #define GL_NEAREST_MIPMAP_LINEAR          0x2702
  #define GL_LINEAR_MIPMAP_LINEAR           0x2703
  #define GL_TEXTURE_MAG_FILTER             0x2800
  #define GL_TEXTURE_MIN_FILTER             0x2801
  #define GL_TEXTURE_WRAP_S                 0x2802
  #define GL_TEXTURE_WRAP_T                 0x2803
  
  #include "MeshAssimp.h"
  
  #include <stdlib.h>
  #include <string.h>
  
  #include <array>
  #include <iostream>
  
  #include <filament/Color.h>
  #include <filament/VertexBuffer.h>
  #include <filament/Engine.h>
  #include <filament/IndexBuffer.h>
  #include <filament/Material.h>
  #include <filament/Renderer.h>
  #include <filament/Scene.h>
  #include <filament/RenderableManager.h>
  #include <filament/TransformManager.h>
  
  #include <math/norm.h>
  #include <math/vec3.h>
  #include <math/TVecHelpers.h>
  
  #include <assimp/Importer.hpp>
  #include <assimp/postprocess.h>
  #include <assimp/cimport.h>
  #include <assimp/scene.h>
  #include <assimp/pbrmaterial.h>
  
  #include <stb_image.h>
  
  #include <backend/DriverEnums.h>
  
  #include "generated/resources/resources.h"
  
  using namespace filament;
  using namespace filamat;
  using namespace filament::math;
  using namespace utils;
  
  enum class AlphaMode : uint8_t {
      OPAQUE,
      MASKED,
      TRANSPARENT
  };
  
  struct MaterialConfig {
      bool doubleSided = false;
      bool unlit = false;
      bool hasVertexColors = false;
      AlphaMode alphaMode = AlphaMode::OPAQUE;
      float maskThreshold = 0.5f;
      uint8_t baseColorUV = 0;
      uint8_t metallicRoughnessUV = 0;
      uint8_t emissiveUV = 0;
      uint8_t aoUV = 0;
      uint8_t normalUV = 0;
  
      uint8_t maxUVIndex() {
          return std::max({baseColorUV, metallicRoughnessUV, emissiveUV, aoUV, normalUV});
      }
  };
  
  void appendBooleanToBitMask(uint64_t &bitmask, bool b) {
      bitmask <<= 1;
      bitmask |= b;
  }
  
  uint64_t hashMaterialConfig(MaterialConfig config) {
      uint64_t bitmask = 0;
      memcpy(&bitmask, &config.maskThreshold, sizeof(config.maskThreshold));
      appendBooleanToBitMask(bitmask, config.doubleSided);
      appendBooleanToBitMask(bitmask, config.unlit);
      appendBooleanToBitMask(bitmask, config.hasVertexColors);
      appendBooleanToBitMask(bitmask, config.alphaMode == AlphaMode::OPAQUE);
      appendBooleanToBitMask(bitmask, config.alphaMode == AlphaMode::MASKED);
      appendBooleanToBitMask(bitmask, config.alphaMode == AlphaMode::TRANSPARENT);
      appendBooleanToBitMask(bitmask, config.baseColorUV == 0);
      appendBooleanToBitMask(bitmask, config.metallicRoughnessUV == 0);
      appendBooleanToBitMask(bitmask, config.emissiveUV == 0);
      appendBooleanToBitMask(bitmask, config.aoUV == 0);
      appendBooleanToBitMask(bitmask, config.normalUV == 0);
      return bitmask;
  }
  
  std::string shaderFromConfig(MaterialConfig config) {
      std::string shader = R"SHADER(
          void material(inout MaterialInputs material) {
      )SHADER";
  
      shader += "float2 normalUV = getUV" + std::to_string(config.normalUV) + "();\n";
      shader += "float2 baseColorUV = getUV" + std::to_string(config.baseColorUV) + "();\n";
      shader += "float2 metallicRoughnessUV = getUV" + std::to_string(config.metallicRoughnessUV) + "();\n";
      shader += "float2 aoUV = getUV" + std::to_string(config.aoUV) + "();\n";
      shader += "float2 emissiveUV = getUV" + std::to_string(config.emissiveUV) + "();\n";
  
      if (!config.unlit) {
          shader += R"SHADER(
              material.normal = texture(materialParams_normalMap, normalUV).xyz * 2.0 - 1.0;
              material.normal.y = -material.normal.y;
          )SHADER";
      }
  
      shader += R"SHADER(
          prepareMaterial(material);
          material.baseColor = texture(materialParams_baseColorMap, baseColorUV);
          material.baseColor *= materialParams.baseColorFactor;
      )SHADER";
  
      if (config.alphaMode == AlphaMode::TRANSPARENT) {
          shader += R"SHADER(
              material.baseColor.rgb *= material.baseColor.a;
          )SHADER";
      }
  
      if (!config.unlit) {
          shader += R"SHADER(
              vec4 metallicRoughness = texture(materialParams_metallicRoughnessMap, metallicRoughnessUV);
              material.roughness = materialParams.roughnessFactor * metallicRoughness.g;
              material.metallic = materialParams.metallicFactor * metallicRoughness.b;
              material.ambientOcclusion = texture(materialParams_aoMap, aoUV).r;
              material.emissive.rgb = texture(materialParams_emissiveMap, emissiveUV).rgb;
              material.emissive.rgb *= materialParams.emissiveFactor.rgb;
  
              // The opinionated lighting model specified by glTF does not account for energy
              // compensation, using this value basically disables it:
              material.emissive.a = 3.0;
          )SHADER";
      }
  
      shader += "}\n";
      return shader;
  }
  
  Material* createMaterialFromConfig(Engine& engine, MaterialConfig config ) {
      std::string shader = shaderFromConfig(config);
      MaterialBuilder::init();
      MaterialBuilder builder = MaterialBuilder()
              .name("material")
              .material(shader.c_str())
              .doubleSided(config.doubleSided)
              .require(VertexAttribute::UV0)
              .parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "baseColorMap")
              .parameter(MaterialBuilder::UniformType::FLOAT4, "baseColorFactor")
              .parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "metallicRoughnessMap")
              .parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "aoMap")
              .parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "emissiveMap")
              .parameter(MaterialBuilder::SamplerType::SAMPLER_2D, "normalMap")
              .parameter(MaterialBuilder::UniformType::FLOAT, "metallicFactor")
              .parameter(MaterialBuilder::UniformType::FLOAT, "roughnessFactor")
              .parameter(MaterialBuilder::UniformType::FLOAT, "normalScale")
              .parameter(MaterialBuilder::UniformType::FLOAT, "aoStrength")
              .parameter(MaterialBuilder::UniformType::FLOAT3, "emissiveFactor");
  
      if (config.maxUVIndex() > 0) {
          builder.require(VertexAttribute::UV1);
      }
  
      switch(config.alphaMode) {
          case AlphaMode::MASKED : builder.blending(MaterialBuilder::BlendingMode::MASKED);
              builder.maskThreshold(config.maskThreshold);
              break;
          case AlphaMode::TRANSPARENT : builder.blending(MaterialBuilder::BlendingMode::TRANSPARENT);
              break;
          default : builder.blending(MaterialBuilder::BlendingMode::OPAQUE);
      }
  
      builder.shading(config.unlit ? Shading::UNLIT : Shading::LIT);
  
      Package pkg = builder.build();
      return Material::Builder().package(pkg.getData(), pkg.getSize()).build(engine);
  }
  
  Texture* MeshAssimp::createOneByOneTexture(uint32_t pixel) {
      uint32_t *textureData = (uint32_t *) malloc(sizeof(uint32_t));
      *textureData = pixel;
  
      Texture *texturePtr = Texture::Builder()
              .width(uint32_t(1))
              .height(uint32_t(1))
              .levels(0xff)
              .format(Texture::InternalFormat::RGBA8)
              .build(mEngine);
  
      Texture::PixelBufferDescriptor defaultNormalBuffer(textureData,
              size_t(1 * 1 * 4),
              Texture::Format::RGBA,
              Texture::Type::UBYTE,
              (Texture::PixelBufferDescriptor::Callback) &free);
  
      texturePtr->setImage(mEngine, 0, std::move(defaultNormalBuffer));
      texturePtr->generateMipmaps(mEngine);
  
      return texturePtr;
  }
  
  void getMinMaxUV(const aiScene *scene, const aiNode* node, float2 &minUV,
          float2 &maxUV, uint32_t uvIndex) {
      for (size_t i = 0; i < node->mNumMeshes; ++i) {
          const aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
          if (!mesh->HasTextureCoords(uvIndex)) {
              continue;
          }
          const float3* uv = reinterpret_cast<const float3*>(mesh->mTextureCoords[uvIndex]);
          const size_t numVertices = mesh->mNumVertices;
          const size_t numFaces = mesh->mNumFaces;
          if (numVertices == 0 || numFaces == 0) {
              continue;
          }
          if (uv) {
              for (size_t j = 0; j < numVertices; j++) {
                  minUV = min(uv[j].xy, minUV);
                  maxUV = max(uv[j].xy, maxUV);
              }
          }
      }
      for (size_t i = 0 ; i < node->mNumChildren ; ++i) {
          getMinMaxUV(scene, node->mChildren[i], minUV, maxUV, uvIndex);
      }
  }
  
  template<bool SNORMUVS>
  static ushort2 convertUV(float2 uv) {
      if (SNORMUVS) {
          short2 uvshort(packSnorm16(uv));
          return bit_cast<ushort2>(uvshort);
      } else {
          half2 uvhalf(uv);
          return bit_cast<ushort2>(uvhalf);
      }
  }
  
  MeshAssimp::MeshAssimp(Engine& engine) : mEngine(engine) {
      mDefaultMap = createOneByOneTexture(0xffffffff);
      mDefaultNormalMap = createOneByOneTexture(0xffff8080);
  
      mDefaultColorMaterial = Material::Builder()
              .package(RESOURCES_AIDEFAULTMAT_DATA, RESOURCES_AIDEFAULTMAT_SIZE)
              .build(mEngine);
  
      mDefaultColorMaterial->setDefaultParameter("baseColor",   RgbType::LINEAR, float3{0.8});
      mDefaultColorMaterial->setDefaultParameter("metallic",    0.0f);
      mDefaultColorMaterial->setDefaultParameter("roughness",   0.4f);
      mDefaultColorMaterial->setDefaultParameter("reflectance", 0.5f);
  
      mDefaultTransparentColorMaterial = Material::Builder()
              .package(RESOURCES_AIDEFAULTTRANS_DATA, RESOURCES_AIDEFAULTTRANS_SIZE)
              .build(mEngine);
  
      mDefaultTransparentColorMaterial->setDefaultParameter("baseColor", RgbType::LINEAR, float3{0.8});
      mDefaultTransparentColorMaterial->setDefaultParameter("metallic",  0.0f);
      mDefaultTransparentColorMaterial->setDefaultParameter("roughness", 0.4f);
  }
  
  MeshAssimp::~MeshAssimp() {
      mEngine.destroy(mVertexBuffer);
      mEngine.destroy(mIndexBuffer);
      mEngine.destroy(mDefaultColorMaterial);
      mEngine.destroy(mDefaultTransparentColorMaterial);
      mEngine.destroy(mDefaultNormalMap);
      mEngine.destroy(mDefaultMap);
  
      for (auto& item : mGltfMaterialCache) {
          auto material = item.second;
          mEngine.destroy(material);
      }
  
      for (Entity renderable : mRenderables) {
          mEngine.destroy(renderable);
      }
  
      for (Texture* texture : mTextures) {
          mEngine.destroy(texture);
      }
  
      // destroy the Entities itself
      EntityManager::get().destroy(mRenderables.size(), mRenderables.data());
  }
  
  template<typename T>
  struct State {
      std::vector<T> state;
      explicit State(std::vector<T>&& state) : state(std::move(state)) { }
      static void free(void* buffer, size_t size, void* user) {
          auto* const that = static_cast<State<T>*>(user);
          delete that;
      }
      size_t size() const { return state.size() * sizeof(T); }
      T const * data() const { return state.data(); }
  };
  
  //TODO: Remove redundant method from sample_full_pbr
  static void loadTexture(Engine *engine, const std::string &filePath, Texture **map,
          bool sRGB, bool hasAlpha) {
  
      if (!filePath.empty()) {
          Path path(filePath);
          if (path.exists()) {
              int w, h, n;
              int numChannels = hasAlpha ? 4 : 3;
  
              Texture::InternalFormat inputFormat;
              if (sRGB) {
                  inputFormat = hasAlpha ? Texture::InternalFormat::SRGB8_A8 : Texture::InternalFormat::SRGB8;
              } else {
                  inputFormat = hasAlpha ? Texture::InternalFormat::RGBA8 : Texture::InternalFormat::RGB8;
              }
  
              Texture::Format outputFormat = hasAlpha ? Texture::Format::RGBA : Texture::Format::RGB;
  
              uint8_t *data = stbi_load(path.getAbsolutePath().c_str(), &w, &h, &n, numChannels);
              if (data != nullptr) {
                  *map = Texture::Builder()
                          .width(uint32_t(w))
                          .height(uint32_t(h))
                          .levels(0xff)
                          .format(inputFormat)
                          .build(*engine);
  
                  Texture::PixelBufferDescriptor buffer(data,
                          size_t(w * h * numChannels),
                          outputFormat,
                          Texture::Type::UBYTE,
                          (Texture::PixelBufferDescriptor::Callback) &stbi_image_free);
  
                  (*map)->setImage(*engine, 0, std::move(buffer));
                  (*map)->generateMipmaps(*engine);
              } else {
                  std::cout << "The texture " << path << " could not be loaded" << std::endl;
              }
          } else {
              std::cout << "The texture " << path << " does not exist" << std::endl;
          }
      }
  }
  
  void loadEmbeddedTexture(Engine *engine, aiTexture *embeddedTexture, Texture **map,
          bool sRGB, bool hasAlpha) {
  
      int w, h, n;
      int numChannels = hasAlpha ? 4 : 3;
  
      Texture::InternalFormat inputFormat;
      if (sRGB) {
          inputFormat = hasAlpha ? Texture::InternalFormat::SRGB8_A8 : Texture::InternalFormat::SRGB8;
      } else {
          inputFormat = hasAlpha ? Texture::InternalFormat::RGBA8 : Texture::InternalFormat::RGB8;
      }
  
      Texture::Format outputFormat = hasAlpha ? Texture::Format::RGBA : Texture::Format::RGB;
  
      uint8_t *data = stbi_load_from_memory((unsigned char *) embeddedTexture->pcData,
              embeddedTexture->mWidth, &w, &h, &n, numChannels);
  
      *map = Texture::Builder()
              .width(uint32_t(w))
              .height(uint32_t(h))
              .levels(0xff)
              .format(inputFormat)
              .build(*engine);
  
      Texture::PixelBufferDescriptor defaultBuffer(data,
              size_t(w * h * numChannels),
              outputFormat,
              Texture::Type::UBYTE,
              (Texture::PixelBufferDescriptor::Callback) &free);
  
      (*map)->setImage(*engine, 0, std::move(defaultBuffer));
      (*map)->generateMipmaps(*engine);
  }
  
  // Takes a texture filename and returns the index of the embedded texture,
  // -1 if the texture is not embedded
  int32_t getEmbeddedTextureId(const aiString& path) {
      const char *pathStr = path.C_Str();
      if (path.length >= 2 && pathStr[0] == '*') {
          for (int i = 1; i < path.length; i++) {
              if (!isdigit(pathStr[i])) {
                  return -1;
              }
          }
          return std::atoi(pathStr + 1); // NOLINT
      }
      return -1;
  }
  
  TextureSampler::WrapMode aiToFilamentMapMode(aiTextureMapMode mapMode) {
      switch(mapMode) {
          case aiTextureMapMode_Clamp :
              return TextureSampler::WrapMode::CLAMP_TO_EDGE;
          case aiTextureMapMode_Mirror :
              return TextureSampler::WrapMode::MIRRORED_REPEAT;
          default:
              return TextureSampler::WrapMode::REPEAT;
      }
  }
  
  TextureSampler::MinFilter aiMinFilterToFilament(unsigned int aiMinFilter) {
      switch(aiMinFilter) {
          case GL_NEAREST: return TextureSampler::MinFilter::NEAREST;
          case GL_LINEAR: return TextureSampler::MinFilter::LINEAR;
          case GL_NEAREST_MIPMAP_NEAREST: return TextureSampler::MinFilter::NEAREST_MIPMAP_NEAREST;
          case GL_LINEAR_MIPMAP_NEAREST: return TextureSampler::MinFilter::LINEAR_MIPMAP_NEAREST;
          case GL_NEAREST_MIPMAP_LINEAR: return TextureSampler::MinFilter::NEAREST_MIPMAP_LINEAR;
          case GL_LINEAR_MIPMAP_LINEAR: return TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR;
          default: return TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR;
      }
  }
  
  TextureSampler::MagFilter aiMagFilterToFilament(unsigned int aiMagFilter) {
      switch(aiMagFilter) {
          case GL_NEAREST: return TextureSampler::MagFilter::NEAREST;
          default: return TextureSampler::MagFilter::LINEAR;
      }
  }
  
  // TODO: Change this to a member function (requires some alteration of cmakelsts.txt)
  void setTextureFromPath(const aiScene *scene, Engine *engine,
          std::vector<filament::Texture*> textures, const aiString &textureFile,
          const std::string &materialName, const std::string &textureDirectory,
          aiTextureMapMode *mapMode, const char *parameterName,
          std::map<std::string, MaterialInstance *> &outMaterials,
          unsigned int aiMinFilterType=0, unsigned int aiMagFilterType=0) {
  
      TextureSampler::MinFilter minFilterType = aiMinFilterToFilament(aiMinFilterType);
      TextureSampler::MagFilter magFilterType = aiMagFilterToFilament(aiMagFilterType);
  
      TextureSampler sampler;
      if (mapMode) {
          sampler = TextureSampler(
                  minFilterType,
                  magFilterType,
                  aiToFilamentMapMode(mapMode[0]),
                  aiToFilamentMapMode(mapMode[1]),
                  aiToFilamentMapMode(mapMode[2]));
      } else {
          sampler = TextureSampler(
                  minFilterType,
                  magFilterType,
                  TextureSampler::WrapMode::REPEAT);
      }
  
      Texture* textureMap = nullptr;
      int32_t embeddedId = getEmbeddedTextureId(textureFile);
  
      // TODO: change this in refactor
      bool isSRGB = strcmp(parameterName, "baseColorMap") == 0 || strcmp(parameterName, "emissiveMap") == 0;
      bool hasAlpha = strcmp(parameterName, "baseColorMap") == 0;
  
      if (embeddedId != -1) {
          loadEmbeddedTexture(engine, scene->mTextures[embeddedId], &textureMap, isSRGB, hasAlpha);
      } else {
          loadTexture(engine, textureDirectory + textureFile.C_Str(), &textureMap, isSRGB, hasAlpha);
      }
  
      textures.push_back(textureMap);
  
      if (textureMap != nullptr) {
          outMaterials[materialName]->setParameter(parameterName, textureMap, sampler);
      }
  }
  
  template<typename VECTOR, typename INDEX>
  Box computeTransformedAABB(VECTOR const* vertices, INDEX const* indices, size_t count,
          const mat4f& transform) noexcept {
      size_t stride = sizeof(VECTOR);
      filament::math::float3 bmin(std::numeric_limits<float>::max());
      filament::math::float3 bmax(std::numeric_limits<float>::lowest());
      for (size_t i = 0; i < count; ++i) {
          VECTOR const* p = reinterpret_cast<VECTOR const*>(
                  (char const*) vertices + indices[i] * stride);
          const filament::math::float3 v(p->x, p->y, p->z);
          float3 tv = (transform * float4(v, 1.0f)).xyz;
          bmin = min(bmin, tv);
          bmax = max(bmax, tv);
      }
      return Box().set(bmin, bmax);
  }
  
  void MeshAssimp::addFromFile(const Path& path,
          std::map<std::string, MaterialInstance*>& materials, bool overrideMaterial) {
  
      Asset asset;
      asset.file = path;
  
      { // This scope to make sure we're not using std::move()'d objects later
  
          // TODO: if we had a way to allocate temporary buffers from the engine with a
          // "command buffer" lifetime, we wouldn't need to have to deal with freeing the
          // std::vectors here.
  
          //TODO: a lot of these method arguments should probably be class or global variables
          if (!setFromFile(asset, materials)) {
              return;
          }
  
          VertexBuffer::Builder vertexBufferBuilder = VertexBuffer::Builder()
                  .vertexCount((uint32_t)asset.positions.size())
                  .bufferCount(4)
                  .attribute(VertexAttribute::POSITION,     0, VertexBuffer::AttributeType::HALF4)
                  .attribute(VertexAttribute::TANGENTS,     1, VertexBuffer::AttributeType::SHORT4)
                  .normalized(VertexAttribute::TANGENTS);
  
          if (asset.snormUV0) {
              vertexBufferBuilder.attribute(VertexAttribute::UV0, 2, VertexBuffer::AttributeType::SHORT2)
                  .normalized(VertexAttribute::UV0);
          } else {
              vertexBufferBuilder.attribute(VertexAttribute::UV0, 2, VertexBuffer::AttributeType::HALF2);
          }
  
          if (asset.snormUV1) {
              vertexBufferBuilder.attribute(VertexAttribute::UV1, 3, VertexBuffer::AttributeType::SHORT2)
                      .normalized(VertexAttribute::UV1);
          } else {
              vertexBufferBuilder.attribute(VertexAttribute::UV1, 3, VertexBuffer::AttributeType::HALF2);
          }
  
          mVertexBuffer = vertexBufferBuilder.build(mEngine);
  
          auto ps = new State<half4>(std::move(asset.positions));
          auto ns = new State<short4>(std::move(asset.tangents));
          auto t0s = new State<ushort2>(std::move(asset.texCoords0));
          auto t1s = new State<ushort2>(std::move(asset.texCoords1));
          auto is = new State<uint32_t>(std::move(asset.indices));
  
          mVertexBuffer->setBufferAt(mEngine, 0,
                  VertexBuffer::BufferDescriptor(ps->data(), ps->size(), State<half4>::free, ps));
  
          mVertexBuffer->setBufferAt(mEngine, 1,
                  VertexBuffer::BufferDescriptor(ns->data(), ns->size(), State<short4>::free, ns));
  
          mVertexBuffer->setBufferAt(mEngine, 2,
                  VertexBuffer::BufferDescriptor(t0s->data(), t0s->size(), State<ushort2>::free, t0s));
  
          mVertexBuffer->setBufferAt(mEngine, 3,
                  VertexBuffer::BufferDescriptor(t1s->data(), t1s->size(), State<ushort2>::free, t1s));
  
          mIndexBuffer = IndexBuffer::Builder().indexCount(uint32_t(is->size())).build(mEngine);
          mIndexBuffer->setBuffer(mEngine,
                  IndexBuffer::BufferDescriptor(is->data(), is->size(), State<uint32_t>::free, is));
      }
  
      // always add the DefaultMaterial (with its default parameters), so we don't pick-up
      // whatever defaults is used in mesh
      if (materials.find(AI_DEFAULT_MATERIAL_NAME) == materials.end()) {
          materials[AI_DEFAULT_MATERIAL_NAME] = mDefaultColorMaterial->createInstance();
      }
  
      size_t startIndex = mRenderables.size();
      mRenderables.resize(startIndex + asset.meshes.size());
      EntityManager::get().create(asset.meshes.size(), mRenderables.data() + startIndex);
      EntityManager::get().create(1, &rootEntity);
  
      TransformManager& tcm = mEngine.getTransformManager();
      //Add root instance
      tcm.create(rootEntity, TransformManager::Instance{}, mat4f());
  
      for (auto& mesh : asset.meshes) {
          RenderableManager::Builder builder(mesh.parts.size());
          builder.boundingBox(mesh.aabb);
  
          size_t partIndex = 0;
          for (auto& part : mesh.parts) {
              builder.geometry(partIndex, RenderableManager::PrimitiveType::TRIANGLES,
                      mVertexBuffer, mIndexBuffer, part.offset, part.count);
  
              if (overrideMaterial) {
                  builder.material(partIndex, materials[AI_DEFAULT_MATERIAL_NAME]);
              } else {
                  auto pos = materials.find(part.material);
  
                  if (pos != materials.end()) {
                      builder.material(partIndex, pos->second);
                  } else {
                      MaterialInstance* colorMaterial;
                      if (part.opacity < 1.0f) {
                          colorMaterial = mDefaultTransparentColorMaterial->createInstance();
                          colorMaterial->setParameter("baseColor", RgbaType::sRGB,
                                  sRGBColorA { part.baseColor, part.opacity });
                      } else {
                          colorMaterial = mDefaultColorMaterial->createInstance();
                          colorMaterial->setParameter("baseColor", RgbType::sRGB, part.baseColor);
                          colorMaterial->setParameter("reflectance", part.reflectance);
                      }
                      colorMaterial->setParameter("metallic", part.metallic);
                      colorMaterial->setParameter("roughness", part.roughness);
                      builder.material(partIndex, colorMaterial);
                      materials[part.material] = colorMaterial;
                  }
              }
              partIndex++;
          }
  
          const size_t meshIndex = &mesh - asset.meshes.data();
          Entity entity = mRenderables[startIndex + meshIndex];
          if (!mesh.parts.empty()) {
              builder.build(mEngine, entity);
          }
          auto pindex = asset.parents[meshIndex];
          TransformManager::Instance parent((pindex < 0) ?
                  tcm.getInstance(rootEntity) : tcm.getInstance(mRenderables[pindex]));
          tcm.create(entity, parent, mesh.transform);
      }
  }
  
  using Assimp::Importer;
  
  bool MeshAssimp::setFromFile(Asset& asset, std::map<std::string, MaterialInstance*>& outMaterials) {
      Importer importer;
      importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE,
              aiPrimitiveType_LINE | aiPrimitiveType_POINT);
      importer.SetPropertyBool(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION, true);
      importer.SetPropertyBool(AI_CONFIG_PP_PTV_KEEP_HIERARCHY, true);
  
      aiScene const* scene = importer.ReadFile(asset.file,
              // normals and tangents
              aiProcess_GenSmoothNormals |
              aiProcess_CalcTangentSpace |
              // UV Coordinates
              aiProcess_GenUVCoords |
              // topology optimization
              aiProcess_FindInstances |
              aiProcess_OptimizeMeshes |
              aiProcess_JoinIdenticalVertices |
              // misc optimization
              aiProcess_ImproveCacheLocality |
              aiProcess_SortByPType |
              // we only support triangles
              aiProcess_Triangulate);
  
      size_t index = importer.GetImporterIndex(asset.file.getExtension().c_str());
      const aiImporterDesc* importerDesc = importer.GetImporterInfo(index);
      bool isGLTF = importerDesc &&
              (!strncmp("glTF Importer",  importerDesc->mName, 13) ||
               !strncmp("glTF2 Importer", importerDesc->mName, 14));
  
      if (!scene) {
          std::cout << "No scene" << std::endl;
      }
  
      if (scene && !scene->mRootNode) {
          std::cout << "No root node" << std::endl;
      }
  
      // we could use those, but we want to keep the graph if any, for testing
      //      aiProcess_OptimizeGraph
      //      aiProcess_PreTransformVertices
  
      const std::function<void(aiNode const* node, size_t& totalVertexCount, size_t& totalIndexCount)>
              countVertices = [scene, &countVertices]
              (aiNode const* node, size_t& totalVertexCount, size_t& totalIndexCount) {
          for (size_t i = 0; i < node->mNumMeshes; i++) {
              aiMesh const *mesh = scene->mMeshes[node->mMeshes[i]];
              totalVertexCount += mesh->mNumVertices;
  
              const aiFace *faces = mesh->mFaces;
              const size_t numFaces = mesh->mNumFaces;
              totalIndexCount += numFaces * faces[0].mNumIndices;
          }
  
          for (size_t i = 0; i < node->mNumChildren; i++) {
              countVertices(node->mChildren[i], totalVertexCount, totalIndexCount);
          }
      };
  
      if (scene) {
          size_t deep = 0;
          size_t depth = 0;
          size_t matCount = 0;
  
          aiNode const* node = scene->mRootNode;
  
          size_t totalVertexCount = 0;
          size_t totalIndexCount = 0;
  
          countVertices(node, totalVertexCount, totalIndexCount);
  
          asset.positions.reserve(asset.positions.size() + totalVertexCount);
          asset.tangents.reserve(asset.tangents.size() + totalVertexCount);
          asset.texCoords0.reserve(asset.texCoords0.size() + totalVertexCount);
          asset.texCoords1.reserve(asset.texCoords1.size() + totalVertexCount);
          asset.indices.reserve(asset.indices.size() + totalIndexCount);
  
          float2 minUV0 = float2(std::numeric_limits<float>::max());
          float2 maxUV0 = float2(std::numeric_limits<float>::lowest());
          getMinMaxUV(scene, node, minUV0, maxUV0, 0);
          float2 minUV1 = float2(std::numeric_limits<float>::max());
          float2 maxUV1 = float2(std::numeric_limits<float>::lowest());
          getMinMaxUV(scene, node, minUV1, maxUV1, 1);
  
          asset.snormUV0 = minUV0.x >= -1.0f && minUV0.x <= 1.0f && maxUV0.x >= -1.0f && maxUV0.x <= 1.0f &&
                           minUV0.y >= -1.0f && minUV0.y <= 1.0f && maxUV0.y >= -1.0f && maxUV0.y <= 1.0f;
  
          asset.snormUV1 = minUV1.x >= -1.0f && minUV1.x <= 1.0f && maxUV1.x >= -1.0f && maxUV1.x <= 1.0f &&
                           minUV1.y >= -1.0f && minUV1.y <= 1.0f && maxUV1.y >= -1.0f && maxUV1.y <= 1.0f;
  
          if (asset.snormUV0) {
              if (asset.snormUV1) {
                  processNode<true, true>(asset, outMaterials,
                                          scene, isGLTF, deep, matCount, node, -1, depth);
              } else {
                  processNode<true, false>(asset, outMaterials,
                                          scene, isGLTF, deep, matCount, node, -1, depth);
              }
          } else {
              if (asset.snormUV1) {
                  processNode<false, true>(asset, outMaterials,
                                          scene, isGLTF, deep, matCount, node, -1, depth);
              } else {
                  processNode<false, false>(asset, outMaterials,
                                          scene, isGLTF, deep, matCount, node, -1, depth);
              }
          }
  
          // compute the aabb and find bounding box of entire model
          for (auto& mesh : asset.meshes) {
              mesh.aabb = RenderableManager::computeAABB(
                      asset.positions.data(),
                      asset.indices.data() + mesh.offset,
                      mesh.count);
  
              Box transformedAabb = computeTransformedAABB(
                      asset.positions.data(),
                      asset.indices.data() + mesh.offset,
                      mesh.count,
                      mesh.accTransform);
  
              float3 aabbMin = transformedAabb.getMin();
              float3 aabbMax = transformedAabb.getMax();
  
              if (!isinf(aabbMin.x) && !isinf(aabbMax.x)) {
                  if (minBound.x > maxBound.x) {
                      minBound.x = aabbMin.x;
                      maxBound.x = aabbMax.x;
                  } else {
                      minBound.x = fmin(minBound.x, aabbMin.x);
                      maxBound.x = fmax(maxBound.x, aabbMax.x);
                  }
              }
  
              if (!isinf(aabbMin.y) && !isinf(aabbMax.y)) {
                  if (minBound.y > maxBound.y) {
                      minBound.y = aabbMin.y;
                      maxBound.y = aabbMax.y;
                  } else {
                      minBound.y = fmin(minBound.y, aabbMin.y);
                      maxBound.y = fmax(maxBound.y, aabbMax.y);
                  }
              }
  
              if (!isinf(aabbMin.z) && !isinf(aabbMax.z)) {
                  if (minBound.z > maxBound.z) {
                      minBound.z = aabbMin.z;
                      maxBound.z = aabbMax.z;
                  } else {
                      minBound.z = fmin(minBound.z, aabbMin.z);
                      maxBound.z = fmax(maxBound.z, aabbMax.z);
                  }
              }
          }
  
          return true;
      }
      return false;
  }
  
  template<bool SNORMUV0, bool SNORMUV1>
  void MeshAssimp::processNode(Asset& asset,
          std::map<std::string,
          MaterialInstance *> &outMaterials,
          const aiScene *scene,
          bool isGLTF,
          size_t deep,
          size_t matCount,
          const aiNode *node,
          int parentIndex,
          size_t &depth) const {
      mat4f const& current = transpose(*reinterpret_cast<mat4f const*>(&node->mTransformation));
  
      size_t totalIndices = 0;
      asset.parents.push_back(parentIndex);
      asset.meshes.push_back(Mesh{});
      asset.meshes.back().offset = asset.indices.size();
      asset.meshes.back().transform = current;
  
      mat4f parentTransform = parentIndex >= 0 ? asset.meshes[parentIndex].accTransform : mat4f();
      asset.meshes.back().accTransform = parentTransform * current;
  
      for (size_t i = 0; i < node->mNumMeshes; i++) {
              aiMesh const* mesh = scene->mMeshes[node->mMeshes[i]];
  
              float3 const* positions  = reinterpret_cast<float3 const*>(mesh->mVertices);
              float3 const* tangents   = reinterpret_cast<float3 const*>(mesh->mTangents);
              float3 const* bitangents = reinterpret_cast<float3 const*>(mesh->mBitangents);
              float3 const* normals    = reinterpret_cast<float3 const*>(mesh->mNormals);
              float3 const* texCoords0 = reinterpret_cast<float3 const*>(mesh->mTextureCoords[0]);
              float3 const* texCoords1 = reinterpret_cast<float3 const*>(mesh->mTextureCoords[1]);
  
              const size_t numVertices = mesh->mNumVertices;
  
              if (numVertices > 0) {
                  const aiFace* faces = mesh->mFaces;
                  const size_t numFaces = mesh->mNumFaces;
  
                  if (numFaces > 0) {
                      size_t indicesOffset = asset.positions.size();
  
                      for (size_t j = 0; j < numVertices; j++) {
                          float3 normal = normals[j];
                          float3 tangent;
                          float3 bitangent;
  
                          // Assimp always returns 3D tex coords but we only support 2D tex coords.
                          float2 texCoord0 = texCoords0 ? texCoords0[j].xy : float2{0.0};
                          float2 texCoord1 = texCoords1 ? texCoords1[j].xy : float2{0.0};
                          // If the tangent and bitangent don't exist, make arbitrary ones. This only
                          // occurs when the mesh is missing texture coordinates, because assimp
                          // computes tangents for us. (search up for aiProcess_CalcTangentSpace)
                          if (!tangents) {
                              bitangent = normalize(cross(normal, float3{1.0, 0.0, 0.0}));
                              tangent = normalize(cross(normal, bitangent));
                          } else {
                              tangent = tangents[j];
                              bitangent = bitangents[j];
                          }
  
                          quatf q = filament::math::details::TMat33<float>::packTangentFrame({tangent, bitangent, normal});
                          asset.tangents.push_back(packSnorm16(q.xyzw));
                          asset.texCoords0.emplace_back(convertUV<SNORMUV0>(texCoord0));
                          asset.texCoords1.emplace_back(convertUV<SNORMUV1>(texCoord1));
  
                          asset.positions.emplace_back(positions[j], 1.0_h);
                      }
  
                      // Populate the index buffer. All faces are triangles at this point because we
                      // asked assimp to perform triangulation.
                      size_t indicesCount = numFaces * faces[0].mNumIndices;
                      size_t indexBufferOffset = asset.indices.size();
                      totalIndices += indicesCount;
  
                      for (size_t j = 0; j < numFaces; ++j) {
                          const aiFace& face = faces[j];
                          for (size_t k = 0; k < face.mNumIndices; ++k) {
                              asset.indices.push_back(uint32_t(face.mIndices[k] + indicesOffset));
                          }
                      }
  
                      uint32_t materialId = mesh->mMaterialIndex;
                      aiMaterial const* material = scene->mMaterials[materialId];
  
                      aiString name;
                      std::string materialName;
  
                      if (material->Get(AI_MATKEY_NAME, name) != AI_SUCCESS) {
                          if (isGLTF) {
                              while (outMaterials.find("_mat_" + std::to_string(matCount))
                                     != outMaterials.end()) {
                                  matCount++;
                              }
                              materialName = "_mat_" + std::to_string(matCount);
                          } else {
                              materialName = AI_DEFAULT_MATERIAL_NAME;
                          }
                      } else {
                          materialName = name.C_Str();
                      }
  
                      if (isGLTF && outMaterials.find(materialName) == outMaterials.end()) {
                          std::string dirName = asset.file.getParent();
                          processGLTFMaterial(scene, material, materialName, dirName, outMaterials);
                      }
  
                      aiColor3D color;
                      sRGBColor baseColor{1.0f};
                      if (material->Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) {
                          baseColor = *reinterpret_cast<sRGBColor*>(&color);
                      }
  
                      float opacity;
                      if (material->Get(AI_MATKEY_OPACITY, opacity) != AI_SUCCESS) {
                          opacity = 1.0f;
                      }
                      if (opacity <= 0.0f) opacity = 1.0f;
  
                      float shininess;
                      if (material->Get(AI_MATKEY_SHININESS, shininess) != AI_SUCCESS) {
                          shininess = 0.0f;
                      }
  
                      // convert shininess to roughness
                      float roughness = sqrt(2.0f / (shininess + 2.0f));
  
                      float metallic = 0.0f;
                      float reflectance = 0.5f;
                      if (material->Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS) {
                          // if there's a non-grey specular color, assume a metallic surface
                          if (color.r != color.g && color.r != color.b) {
                              metallic = 1.0f;
                              baseColor = *reinterpret_cast<sRGBColor*>(&color);
                          } else {
                              if (baseColor.r == 0.0f && baseColor.g == 0.0f && baseColor.b == 0.0f) {
                                  metallic = 1.0f;
                                  baseColor = *reinterpret_cast<sRGBColor*>(&color);
                              } else {
                                  // TODO: the conversion formula is correct
                                  // reflectance = sqrtf(color.r / 0.16f);
                              }
                          }
                      }
  
                      asset.meshes.back().parts.push_back({
                              indexBufferOffset, indicesCount, materialName,
                              baseColor, opacity, metallic, roughness, reflectance
                      });
                  }
              }
          }
  
      if (node->mNumMeshes > 0) {
          asset.meshes.back().count = totalIndices;
      }
  
      if (node->mNumChildren) {
          parentIndex = static_cast<int>(asset.meshes.size()) - 1;
          deep++;
          depth = std::max(deep, depth);
          for (size_t i = 0, c = node->mNumChildren; i < c; i++) {
              processNode<SNORMUV0, SNORMUV1>(asset, outMaterials, scene,
                                              isGLTF, deep, matCount, node->mChildren[i], parentIndex, depth);
          }
          deep--;
      }
  }
  
  void MeshAssimp::processGLTFMaterial(const aiScene* scene, const aiMaterial* material,
          const std::string& materialName, const std::string& dirName,
           std::map<std::string, MaterialInstance*>& outMaterials) const {
  
      aiString baseColorPath;
      aiString AOPath;
      aiString MRPath;
      aiString normalPath;
      aiString emissivePath;
      aiTextureMapMode mapMode[3];
      MaterialConfig matConfig;
  
      material->Get(AI_MATKEY_TWOSIDED, matConfig.doubleSided);
      material->Get(AI_MATKEY_GLTF_UNLIT, matConfig.unlit);
  
      aiString alphaMode;
      material->Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode);
      if (strcmp(alphaMode.C_Str(), "BLEND") == 0) {
          matConfig.alphaMode = AlphaMode::TRANSPARENT;
      } else if (strcmp(alphaMode.C_Str(), "MASK") == 0) {
          matConfig.alphaMode = AlphaMode::MASKED;
          float maskThreshold = 0.5;
          material->Get(AI_MATKEY_GLTF_ALPHACUTOFF, maskThreshold);
          matConfig.maskThreshold = maskThreshold;
      }
  
      material->Get(_AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE,
                    matConfig.baseColorUV);
      material->Get(_AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE,
                    matConfig.metallicRoughnessUV);
      material->Get(_AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, aiTextureType_LIGHTMAP, 0, matConfig.aoUV);
      material->Get(_AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, aiTextureType_NORMALS, 0, matConfig.normalUV);
      material->Get(_AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, aiTextureType_EMISSIVE, 0, matConfig.emissiveUV);
  
      uint64_t configHash = hashMaterialConfig(matConfig);
  
      if (mGltfMaterialCache.find(configHash) == mGltfMaterialCache.end()) {
          mGltfMaterialCache[configHash] = createMaterialFromConfig(mEngine, matConfig);
      }
  
      outMaterials[materialName] = mGltfMaterialCache[configHash]->createInstance();
  
      // TODO: is there a way to use the same material for multiple mask threshold values?
  //    if (matConfig.alphaMode == masked) {
  //        float maskThreshold = 0.5;
  //        material->Get(AI_MATKEY_GLTF_ALPHACUTOFF, maskThreshold);
  //        outMaterials[materialName]->setParameter("maskThreshold", maskThreshold);
  //    }
  
      // Load property values for gltf files
      aiColor4D baseColorFactor;
      aiColor3D emissiveFactor;
      float metallicFactor = 1.0;
      float roughnessFactor = 1.0;
  
      // TODO: is occlusion strength available on Assimp now?
  
      // Load texture images for gltf files
      TextureSampler sampler(
              TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR,
              TextureSampler::MagFilter::LINEAR,
              TextureSampler::WrapMode::REPEAT);
  
      if (material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, &baseColorPath,
              nullptr, nullptr, nullptr, nullptr, mapMode) == AI_SUCCESS) {
          unsigned int minType = 0;
          unsigned int magType = 0;
          material->Get("$tex.mappingfiltermin", AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, minType);
          material->Get("$tex.mappingfiltermag", AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, magType);
  
          setTextureFromPath(scene, &mEngine, mTextures, baseColorPath,
                  materialName, dirName, mapMode, "baseColorMap", outMaterials, minType, magType);
      } else {
          outMaterials[materialName]->setParameter("baseColorMap", mDefaultMap, sampler);
      }
  
      if (material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &MRPath,
              nullptr, nullptr, nullptr, nullptr, mapMode) == AI_SUCCESS) {
          unsigned int minType = 0;
          unsigned int magType = 0;
          material->Get("$tex.mappingfiltermin", AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, minType);
          material->Get("$tex.mappingfiltermag", AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, magType);
  
          setTextureFromPath(scene, &mEngine, mTextures, MRPath, materialName,
                  dirName, mapMode, "metallicRoughnessMap", outMaterials, minType, magType);
      } else {
          outMaterials[materialName]->setParameter("metallicRoughnessMap", mDefaultMap, sampler);
          outMaterials[materialName]->setParameter("metallicFactor", mDefaultMetallic);
          outMaterials[materialName]->setParameter("roughnessFactor", mDefaultRoughness);
      }
  
      if (material->GetTexture(aiTextureType_LIGHTMAP, 0, &AOPath, nullptr,
              nullptr, nullptr, nullptr, mapMode) == AI_SUCCESS) {
          unsigned int minType = 0;
          unsigned int magType = 0;
          material->Get("$tex.mappingfiltermin", aiTextureType_LIGHTMAP, 0, minType);
          material->Get("$tex.mappingfiltermag", aiTextureType_LIGHTMAP, 0, magType);
          setTextureFromPath(scene, &mEngine, mTextures, AOPath, materialName,
                  dirName, mapMode, "aoMap", outMaterials, minType, magType);
      } else {
          outMaterials[materialName]->setParameter("aoMap", mDefaultMap, sampler);
      }
  
      if (material->GetTexture(aiTextureType_NORMALS, 0, &normalPath, nullptr,
              nullptr, nullptr, nullptr, mapMode) == AI_SUCCESS) {
          unsigned int minType = 0;
          unsigned int magType = 0;
          material->Get("$tex.mappingfiltermin", aiTextureType_NORMALS, 0, minType);
          material->Get("$tex.mappingfiltermag", aiTextureType_NORMALS, 0, magType);
          setTextureFromPath(scene, &mEngine, mTextures, normalPath, materialName,
                  dirName, mapMode, "normalMap", outMaterials, minType, magType);
      } else {
          outMaterials[materialName]->setParameter("normalMap", mDefaultNormalMap, sampler);
      }
  
      if (material->GetTexture(aiTextureType_EMISSIVE, 0, &emissivePath, nullptr,
              nullptr, nullptr, nullptr, mapMode) == AI_SUCCESS) {
          unsigned int minType = 0;
          unsigned int magType = 0;
          material->Get("$tex.mappingfiltermin", aiTextureType_EMISSIVE, 0, minType);
          material->Get("$tex.mappingfiltermag", aiTextureType_EMISSIVE, 0, magType);
          setTextureFromPath(scene, &mEngine, mTextures, emissivePath, materialName,
                  dirName, mapMode, "emissiveMap", outMaterials, minType, magType);
      }  else {
          outMaterials[materialName]->setParameter("emissiveMap", mDefaultMap, sampler);
          outMaterials[materialName]->setParameter("emissiveFactor", mDefaultEmissive);
      }
  
      //If the gltf has texture factors, override the default factor values
      if (material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, metallicFactor) == AI_SUCCESS) {
          outMaterials[materialName]->setParameter("metallicFactor", metallicFactor);
      }
  
      if (material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, roughnessFactor) == AI_SUCCESS) {
          outMaterials[materialName]->setParameter("roughnessFactor", roughnessFactor);
      }
  
      if (material->Get(AI_MATKEY_COLOR_EMISSIVE, emissiveFactor) == AI_SUCCESS) {
          sRGBColor emissiveFactorCast = *reinterpret_cast<sRGBColor*>(&emissiveFactor);
          outMaterials[materialName]->setParameter("emissiveFactor", emissiveFactorCast);
      }
  
      if (material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, baseColorFactor) == AI_SUCCESS) {
          sRGBColorA baseColorFactorCast = *reinterpret_cast<sRGBColorA*>(&baseColorFactor);
          outMaterials[materialName]->setParameter("baseColorFactor", baseColorFactorCast);
      }
  
      aiBool isSpecularGlossiness = false;
      if (material->Get(AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS, isSpecularGlossiness) == AI_SUCCESS) {
          if (isSpecularGlossiness) {
              std::cout << "Warning: pbrSpecularGlossiness textures are not currently supported" << std::endl;
          }
      }
  }
  