text: /*
   * Copyright (C) 2019 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.
   */
  
  #include <gltfio/AssetLoader.h>
  #include <gltfio/MaterialProvider.h>
  
  #include "FFilamentAsset.h"
  #include "GltfEnums.h"
  
  #include <filament/Box.h>
  #include <filament/Engine.h>
  #include <filament/IndexBuffer.h>
  #include <filament/LightManager.h>
  #include <filament/Material.h>
  #include <filament/RenderableManager.h>
  #include <filament/Scene.h>
  #include <filament/TextureSampler.h>
  #include <filament/TransformManager.h>
  #include <filament/VertexBuffer.h>
  
  #include <math/mat4.h>
  #include <math/vec3.h>
  #include <math/vec4.h>
  
  #include <utils/EntityManager.h>
  #include <utils/Log.h>
  #include <utils/NameComponentManager.h>
  
  #include <tsl/robin_map.h>
  
  #include <vector>
  
  #define CGLTF_IMPLEMENTATION
  #include <cgltf.h>
  
  #include "math.h"
  #include "upcast.h"
  
  using namespace filament;
  using namespace filament::math;
  using namespace utils;
  
  namespace gltfio {
  namespace details {
  
  static const auto FREE_CALLBACK = [](void* mem, size_t, void*) { free(mem); };
  
  // MeshCache
  // ---------
  // If a given glTF mesh is referenced by multiple glTF nodes, then it generates a separate Filament
  // renderable for each of those nodes. All renderables generated by a given mesh share a common set
  // of VertexBuffer and IndexBuffer objects. To achieve the sharing behavior, the loader maintains a
  // small cache. The cache keys are glTF mesh definitions and the cache entries are lists of
  // primitives, where a "primitive" is a reference to a Filament VertexBuffer and IndexBuffer.
  struct Primitive {
      VertexBuffer* vertices = nullptr;
      IndexBuffer* indices = nullptr;
      Aabb aabb; // object-space bounding box
  };
  using MeshCache = tsl::robin_map<const cgltf_mesh*, std::vector<Primitive>>;
  
  // MatInstanceCache
  // ----------------
  // Each glTF material definition corresponds to a single filament::MaterialInstance, which are
  // cached here in the loader. The filament::Material objects that are used to create instances are
  // cached in MaterialProvider. If a given glTF material is referenced by multiple glTF meshes, then
  // their corresponding filament primitives will share the same Filament MaterialInstance and UvMap.
  // The UvMap is a mapping from each texcoord slot in glTF to one of Filament's 2 texcoord sets.
  struct MaterialEntry {
      MaterialInstance* instance;
      UvMap uvmap;
  };
  using MatInstanceCache = tsl::robin_map<intptr_t, MaterialEntry>;
  
  // Sometimes a glTF bufferview includes unused data at the end (e.g. in skinning.gltf) so we need to
  // compute the correct size of the vertex buffer. Filament automatically infers the size of
  // driver-level vertex buffers from the attribute data (stride, count, offset) and clients are
  // expected to avoid uploading data blobs that exceed this size. Since this information doesn't
  // exist in the glTF we need to compute it manually. This is a bit of a cheat, cgltf_calc_size is
  // private but its implementation file is available in this cpp file.
  uint32_t computeBindingSize(const cgltf_accessor* accessor) {
      cgltf_size element_size = cgltf_calc_size(accessor->type, accessor->component_type);
      return uint32_t(accessor->stride * (accessor->count - 1) + element_size);
  }
  
  uint32_t computeBindingOffset(const cgltf_accessor* accessor) {
      return uint32_t(accessor->offset + accessor->buffer_view->offset);
  }
  
  static const char* getNodeName(const cgltf_node* node, const char* defaultNodeName) {
      if (node->name) return node->name;
      if (node->mesh && node->mesh->name) return node->mesh->name;
      if (node->light && node->light->name) return node->light->name;
      return defaultNodeName;
  }
  
  struct FAssetLoader : public AssetLoader {
      FAssetLoader(const AssetConfiguration& config) :
              mEntityManager(config.entities ? *config.entities : EntityManager::get()),
              mRenderableManager(config.engine->getRenderableManager()),
              mNameManager(config.names),
              mTransformManager(config.engine->getTransformManager()),
              mMaterials(config.materials),
              mEngine(config.engine),
              mDefaultNodeName(config.defaultNodeName) {}
  
      FFilamentAsset* createAssetFromJson(const uint8_t* bytes, uint32_t nbytes);
      FilamentAsset* createAssetFromBinary(const uint8_t* bytes, uint32_t nbytes);
  
      ~FAssetLoader() {
          delete mMaterials;
      }
  
      void destroyAsset(const FFilamentAsset* asset) {
          delete asset;
      }
  
      size_t getMaterialsCount() const noexcept {
          return mMaterials->getMaterialsCount();
      }
  
      NameComponentManager* getNames() const noexcept {
          return mNameManager;
      }
  
      const Material* const* getMaterials() const noexcept {
          return mMaterials->getMaterials();
      }
  
      void createAsset(const cgltf_data* srcAsset);
      void createEntity(const cgltf_node* node, Entity parent);
      void createRenderable(const cgltf_node* node, Entity entity, const char* name);
      bool createPrimitive(const cgltf_primitive* inPrim, Primitive* outPrim, const UvMap& uvmap,
              const char* name);
      void createLight(const cgltf_node* node, Entity entity);
      MaterialInstance* createMaterialInstance(const cgltf_material* inputMat, UvMap* uvmap,
              bool vertexColor);
      void addTextureBinding(MaterialInstance* materialInstance, const char* parameterName,
              const cgltf_texture* srcTexture, bool srgb);
      bool primitiveHasVertexColor(const cgltf_primitive* inPrim) const;
  
      static LightManager::Type getLightType(const cgltf_light_type type);
  
      EntityManager& mEntityManager;
      RenderableManager& mRenderableManager;
      NameComponentManager* mNameManager;
      TransformManager& mTransformManager;
      MaterialProvider* mMaterials;
      Engine* mEngine;
  
      // The loader owns a few transient mappings used only for the current asset being loaded.
      FFilamentAsset* mResult;
      MatInstanceCache mMatInstanceCache;
      MeshCache mMeshCache;
      const char* mDefaultNodeName;
      bool mError = false;
      bool mDiagnosticsEnabled = false;
  };
  
  FILAMENT_UPCAST(AssetLoader)
  
  } // namespace details
  
  using namespace details;
  
  FFilamentAsset* FAssetLoader::createAssetFromJson(const uint8_t* bytes, uint32_t nbytes) {
      cgltf_options options { cgltf_file_type_invalid };
      cgltf_data* sourceAsset;
      cgltf_result result = cgltf_parse(&options, bytes, nbytes, &sourceAsset);
      if (result != cgltf_result_success) {
          slog.e << "Unable to parse JSON file." << io::endl;
          return nullptr;
      }
      createAsset(sourceAsset);
      return mResult;
  }
  
  FilamentAsset* FAssetLoader::createAssetFromBinary(const uint8_t* bytes, uint32_t nbytes) {
  
      // The cgltf library handles GLB efficiently by pointing all buffer views into the source data.
      // However, we wish our API to be simple and safe, allowing clients to free up their source blob
      // immediately, without worrying about when all the data has finished uploading asynchronously
      // to the GPU. To achieve this we create a copy of the source blob and stash it inside the
      // asset, asking cgltf to parse the copy. This allows us to free it at the correct time (i.e.
      // after all GPU uploads have completed). Although it incurs a copy, the added safety of this
      // API seems worthwhile.
      std::vector<uint8_t> glbdata(bytes, bytes + nbytes);
  
      cgltf_options options { cgltf_file_type_glb };
      cgltf_data* sourceAsset;
      cgltf_result result = cgltf_parse(&options, glbdata.data(), nbytes, &sourceAsset);
      if (result != cgltf_result_success) {
          slog.e << "Unable to parse glb file." << io::endl;
          return nullptr;
      }
      createAsset(sourceAsset);
      if (mResult) {
          glbdata.swap(mResult->mGlbData);
      }
      return mResult;
  }
  
  void FAssetLoader::createAsset(const cgltf_data* srcAsset) {
      #if !GLTFIO_DRACO_SUPPORTED
      for (cgltf_size i = 0; i < srcAsset->extensions_required_count; i++) {
          if (!strcmp(srcAsset->extensions_required[i], "KHR_draco_mesh_compression")) {
              slog.e << "KHR_draco_mesh_compression is not supported." << io::endl;
              mResult = nullptr;
              return;
          }
      }
      #endif
  
      mResult = new FFilamentAsset(mEngine, mNameManager);
      mResult->mSourceAsset = srcAsset;
      mResult->acquireSourceAsset();
  
      // If there is no default scene specified, then the default is the first one.
      // It is not an error for a glTF file to have zero scenes.
      const cgltf_scene* scene = srcAsset->scene ? srcAsset->scene : srcAsset->scenes;
      if (!scene) {
          return;
      }
  
      // Create a single root node with an identity transform as a convenience to the client.
      mResult->mRoot = mEntityManager.create();
      mTransformManager.create(mResult->mRoot);
  
      // One scene may have multiple root nodes. Recurse down and create an entity for each node.
      cgltf_node** nodes = scene->nodes;
      for (cgltf_size i = 0, len = scene->nodes_count; i < len; ++i) {
          const cgltf_node* root = nodes[i];
          createEntity(root, mResult->mRoot);
      }
  
      if (mError) {
          delete mResult;
          mResult = nullptr;
      }
  
      // Find every unique resource URI and store a pointer to any of the cgltf-owned cstrings
      // that match the URI. These strings get freed during releaseSourceData().
      tsl::robin_map<std::string, const char*> resourceUris;
      auto addResourceUri = [&resourceUris](const char* uri) {
          if (uri) {
              resourceUris[uri] = uri;
          }
      };
      for (cgltf_size i = 0, len = srcAsset->buffers_count; i < len; ++i) {
          addResourceUri(srcAsset->buffers[i].uri);
      }
      for (cgltf_size i = 0, len = srcAsset->images_count; i < len; ++i) {
          addResourceUri(srcAsset->images[i].uri);
      }
      mResult->mResourceUris.reserve(resourceUris.size());
      for (auto pair : resourceUris) {
          mResult->mResourceUris.push_back(pair.second);
      }
  
      // We're done with the import, so free up transient bookkeeping resources.
      mMatInstanceCache.clear();
      mMeshCache.clear();
      mError = false;
  }
  
  void FAssetLoader::createEntity(const cgltf_node* node, Entity parent) {
      Entity entity = mEntityManager.create();
  
      // Always create a transform component to reflect the original hierarchy.
      mat4f localTransform;
      if (node->has_matrix) {
          memcpy(&localTransform[0][0], &node->matrix[0], 16 * sizeof(float));
      } else {
          quatf* rotation = (quatf*) &node->rotation[0];
          float3* scale = (float3*) &node->scale[0];
          float3* translation = (float3*) &node->translation[0];
          localTransform = composeMatrix(*translation, *rotation, *scale);
      }
  
      auto parentTransform = mTransformManager.getInstance(parent);
      mTransformManager.create(entity, parentTransform, localTransform);
  
      // Update the asset's entity list and private node mapping.
      mResult->mEntities.push_back(entity);
      mResult->mNodeMap[node] = entity;
  
      const char* name = getNodeName(node, mDefaultNodeName);
  
      if (mNameManager && name) {
          mNameManager->addComponent(entity);
          mNameManager->setName(mNameManager->getInstance(entity), name);
      }
  
      // If no name is provided in the glTF or AssetConfiguration, use "node" for error messages.
      name = name ? name : "node";
  
      // If the node has a mesh, then create a renderable component.
      if (node->mesh) {
          createRenderable(node, entity, name);
      }
  
      if (node->light) {
          createLight(node, entity);
      }
  
      for (cgltf_size i = 0, len = node->children_count; i < len; ++i) {
          createEntity(node->children[i], entity);
      }
  }
  
  void FAssetLoader::createRenderable(const cgltf_node* node, Entity entity, const char* name) {
      const cgltf_mesh* mesh = node->mesh;
  
      // Compute the transform relative to the root.
      auto thisTransform = mTransformManager.getInstance(entity);
      mat4f worldTransform = mTransformManager.getWorldTransform(thisTransform);
  
      cgltf_size nprims = mesh->primitives_count;
      RenderableManager::Builder builder(nprims);
  
      // If the mesh is already loaded, obtain the list of Filament VertexBuffer / IndexBuffer objects
      // that were already generated (one for each primitive), otherwise allocate a new list of
      // pointers for the primitives.
      auto iter = mMeshCache.find(mesh);
      if (iter == mMeshCache.end()) {
          mMeshCache[mesh].resize(nprims);
      }
      Primitive* outputPrim = mMeshCache[mesh].data();
      const cgltf_primitive* inputPrim = &mesh->primitives[0];
  
      Aabb aabb;
  
      cgltf_size numMorphTargets = 0;
  
      // For each prim, create a Filament VertexBuffer, IndexBuffer, and MaterialInstance.
      for (cgltf_size index = 0; index < nprims; ++index, ++outputPrim, ++inputPrim) {
          RenderableManager::PrimitiveType primType;
          if (!getPrimitiveType(inputPrim->type, &primType)) {
              slog.e << "Unsupported primitive type in " << name << io::endl;
          }
  
          if (inputPrim->targets_count > 0) {
              if (numMorphTargets > 0 && inputPrim->targets_count != numMorphTargets) {
                  slog.e << "Sister primitives must all have the same number of morph targets."
                          << io::endl;
              }
              numMorphTargets = inputPrim->targets_count;
          }
  
          // Create a material instance for this primitive or fetch one from the cache.
          UvMap uvmap {};
          bool hasVertexColor = primitiveHasVertexColor(inputPrim);
          MaterialInstance* mi = createMaterialInstance(inputPrim->material, &uvmap, hasVertexColor);
          if (!mi) {
              mError = true;
              continue;
          }
  
          mResult->mDependencyGraph.addEdge(entity, mi);
          builder.material(index, mi);
  
          // Create a Filament VertexBuffer and IndexBuffer for this prim if we haven't already.
          if (!outputPrim->vertices && !createPrimitive(inputPrim, outputPrim, uvmap, name)) {
              mError = true;
              continue;
          }
  
          // Expand the object-space bounding box.
          aabb.min = min(outputPrim->aabb.min, aabb.min);
          aabb.max = max(outputPrim->aabb.max, aabb.max);
  
          // We are not using the optional offset, minIndex, maxIndex, and count arguments when
          // calling geometry() on the builder. It appears that the glTF spec does not have
          // facilities for these parameters, which is not a huge loss since some of the buffer
          // view and accessor features already have this functionality.
          builder.geometry(index, primType, outputPrim->vertices, outputPrim->indices);
      }
  
      if (numMorphTargets > 0) {
          builder.morphing(true);
      }
  
      const Aabb transformed = aabb.transform(worldTransform);
  
      // Expand the world-space bounding box.
      mResult->mBoundingBox.min = min(mResult->mBoundingBox.min, transformed.min);
      mResult->mBoundingBox.max = max(mResult->mBoundingBox.max, transformed.max);
  
      if (node->skin) {
         builder.skinning(node->skin->joints_count);
      }
  
      // Per the spec, glTF models must have valid mix / max annotations for position attributes.
      // However in practice these can be missing and we should be as robust as other glTF viewers.
      // If desired, clients can enable the "recomputeBoundingBoxes" feature in ResourceLoader.
      Box box = Box().set(aabb.min, aabb.max);
      if (box.isEmpty()) {
          slog.w << "Missing bounding box in " << name << io::endl;
          box = Box().set(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max());
      }
  
      builder
          .boundingBox(box)
          .culling(true)
          .castShadows(true)
          .receiveShadows(true)
          .build(*mEngine, entity);
  
      // According to the spec, the mesh may or may not specify default weights, regardless of whether
      // it actually has morph targets. If it has morphing enabled then the default weights are 0. If
      // node weights are provided, they override the ones specified on the mesh.
      if (numMorphTargets > 0) {
          RenderableManager::Instance renderable = mRenderableManager.getInstance(entity);
          float4 weights(0, 0, 0, 0);
          for (cgltf_size i = 0; i < std::min(MAX_MORPH_TARGETS, mesh->weights_count); ++i) {
              weights[i] = mesh->weights[i];
          }
          for (cgltf_size i = 0; i < std::min(MAX_MORPH_TARGETS, node->weights_count); ++i) {
              weights[i] = node->weights[i];
          }
          mRenderableManager.setMorphWeights(renderable, weights);
      }
  }
  
  bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* outPrim,
          const UvMap& uvmap, const char* name) {
      // Create a little lambda that appends to the asset's vertex buffer slots.
      auto slots = &mResult->mBufferSlots;
      auto addBufferSlot = [slots](BufferSlot entry) {
          slots->push_back(entry);
      };
  
      // In glTF, each primitive may or may not have an index buffer.
      IndexBuffer* indices;
      const cgltf_accessor* accessor = inPrim->indices;
      if (accessor) {
          IndexBuffer::IndexType indexType;
          if (!getIndexType(accessor->component_type, &indexType)) {
              utils::slog.e << "Unrecognized index type in " << name << utils::io::endl;
              return false;
          }
  
          indices = IndexBuffer::Builder()
              .indexCount(accessor->count)
              .bufferType(indexType)
              .build(*mEngine);
  
          BufferSlot slot = { accessor };
          slot.indexBuffer = indices;
          addBufferSlot(slot);
      } else {
          // If a primitive does not have an index buffer, generate a trivial one now.
          const uint32_t vertexCount = inPrim->attributes[0].data->count;
  
          indices = IndexBuffer::Builder()
              .indexCount(vertexCount)
              .bufferType(IndexBuffer::IndexType::UINT)
              .build(*mEngine);
  
          const size_t indexDataSize = vertexCount * sizeof(uint32_t);
          uint32_t* indexData = (uint32_t*) malloc(indexDataSize);
          for (size_t i = 0; i < vertexCount; ++i) {
              indexData[i] = i;
          }
          IndexBuffer::BufferDescriptor bd(indexData, indexDataSize, FREE_CALLBACK);
          indices->setBuffer(*mEngine, std::move(bd));
      }
      mResult->mIndexBuffers.push_back(indices);
  
      VertexBuffer::Builder vbb;
  
      bool hasUv0 = false, hasUv1 = false, hasVertexColor = false, hasNormals = false;
      uint32_t vertexCount = 0;
  
      const size_t firstSlot = mResult->mBufferSlots.size();
      int slot = 0;
  
      for (cgltf_size aindex = 0; aindex < inPrim->attributes_count; aindex++) {
          const cgltf_attribute& attribute = inPrim->attributes[aindex];
          const int index = attribute.index;
          const cgltf_attribute_type atype = attribute.type;
          const cgltf_accessor* accessor = attribute.data;
  
          // The glTF tangent data is ignored here, but honored in ResourceLoader.
          if (atype == cgltf_attribute_type_tangent) {
              continue;
          }
  
          // At a minimum, surface orientation requires normals to be present in the source data.
          // Here we re-purpose the normals slot to point to the quats that get computed later.
          if (atype == cgltf_attribute_type_normal) {
              vbb.attribute(VertexAttribute::TANGENTS, slot, VertexBuffer::AttributeType::SHORT4);
              vbb.normalized(VertexAttribute::TANGENTS);
              hasNormals = true;
              addBufferSlot({&mResult->mGenerateTangents, atype, slot++});
              continue;
          }
  
          if (atype == cgltf_attribute_type_color) {
              hasVertexColor = true;
          }
  
          // Translate the cgltf attribute enum into a Filament enum and ignore all uv sets
          // that do not have entries in the mapping table.
          VertexAttribute semantic;
          if (!getVertexAttrType(atype, &semantic)) {
              utils::slog.e << "Unrecognized vertex semantic in " << name << utils::io::endl;
              return false;
          }
          if (atype == cgltf_attribute_type_texcoord) {
              if (index >= UvMapSize) {
                  utils::slog.e << "Too many texture coordinate sets in " << name << utils::io::endl;
                  continue;
              }
              UvSet uvset = uvmap[index];
              switch (uvset) {
                  case UV0:
                      semantic = VertexAttribute::UV0;
                      hasUv0 = true;
                      break;
                  case UV1:
                      semantic = VertexAttribute::UV1;
                      hasUv1 = true;
                      break;
                  case UNUSED:
                      // It is perfectly acceptable to drop unused texture coordinate sets. In fact
                      // this can occur quite frequently, e.g. if the material has attached textures.
                      continue;
              }
          }
  
          vertexCount = accessor->count;
  
          // The positions accessor is required to have min/max properties, use them to expand
          // the bounding box for this primitive.
          if (atype == cgltf_attribute_type_position) {
              const float* minp = &accessor->min[0];
              const float* maxp = &accessor->max[0];
              outPrim->aabb.min = min(outPrim->aabb.min, float3(minp[0], minp[1], minp[2]));
              outPrim->aabb.max = max(outPrim->aabb.max, float3(maxp[0], maxp[1], maxp[2]));
          }
  
          VertexBuffer::AttributeType fatype;
          if (!getElementType(accessor->type, accessor->component_type, &fatype)) {
              slog.e << "Unsupported accessor type in " << name << io::endl;
              return false;
          }
  
          // The cgltf library provides a stride value for all accessors, even though they do not
          // exist in the glTF file. It is computed from the type and the stride of the buffer view.
          // As a convenience, cgltf also replaces zero (default) stride with the actual stride.
          vbb.attribute(semantic, slot, fatype, 0, accessor->stride);
          vbb.normalized(semantic, accessor->normalized);
          addBufferSlot({accessor, atype, slot++});
      }
  
      // If the model is lit but does not have normals, we'll need to generate flat normals.
      if (inPrim->material && !inPrim->material->unlit && !hasNormals) {
          vbb.attribute(VertexAttribute::TANGENTS, slot, VertexBuffer::AttributeType::SHORT4);
          vbb.normalized(VertexAttribute::TANGENTS);
          cgltf_attribute_type atype = cgltf_attribute_type_normal;
          addBufferSlot({&mResult->mGenerateNormals, atype, slot++});
      }
  
      cgltf_size targetsCount = inPrim->targets_count;
      if (targetsCount > MAX_MORPH_TARGETS) {
          slog.w << "Too many morph targets in " << name << io::endl;
          targetsCount = MAX_MORPH_TARGETS;
      }
  
      constexpr int baseTangentsAttr = (int) VertexAttribute::MORPH_TANGENTS_0;
      constexpr int basePositionAttr = (int) VertexAttribute::MORPH_POSITION_0;
  
      for (cgltf_size targetIndex = 0; targetIndex < targetsCount; targetIndex++) {
          const cgltf_morph_target& morphTarget = inPrim->targets[targetIndex];
          for (cgltf_size aindex = 0; aindex < morphTarget.attributes_count; aindex++) {
              const cgltf_attribute& attribute = morphTarget.attributes[aindex];
              const cgltf_accessor* accessor = attribute.data;
              const cgltf_attribute_type atype = attribute.type;
              const int morphId = targetIndex + 1;
  
              // The glTF tangent data is ignored here, but honored in ResourceLoader.
              if (atype == cgltf_attribute_type_tangent) {
                  continue;
              }
  
              if (atype == cgltf_attribute_type_normal) {
                  VertexAttribute attr = (VertexAttribute) (baseTangentsAttr + targetIndex);
                  vbb.attribute(attr, slot, VertexBuffer::AttributeType::SHORT4);
                  vbb.normalized(attr);
                  addBufferSlot({&mResult->mGenerateTangents, atype, slot++, morphId});
                  continue;
              }
  
              if (atype != cgltf_attribute_type_position) {
                  utils::slog.e << "Only positions, normals, and tangents can be morphed."
                          << utils::io::endl;
                  return false;
              }
  
              const float* minp = &accessor->min[0];
              const float* maxp = &accessor->max[0];
              outPrim->aabb.min = min(outPrim->aabb.min, float3(minp[0], minp[1], minp[2]));
              outPrim->aabb.max = max(outPrim->aabb.max, float3(maxp[0], maxp[1], maxp[2]));
  
              VertexBuffer::AttributeType fatype;
              if (!getElementType(accessor->type, accessor->component_type, &fatype)) {
                  slog.e << "Unsupported accessor type in " << name << io::endl;
                  return false;
              }
  
              VertexAttribute attr = (VertexAttribute) (basePositionAttr + targetIndex);
              vbb.attribute(attr, slot, fatype, 0, accessor->stride);
              vbb.normalized(attr, accessor->normalized);
              addBufferSlot({accessor, atype, slot++, morphId});
          }
      }
  
      vbb.vertexCount(vertexCount);
  
      // If an ubershader is used, then we provide a single dummy buffer for all unfulfilled vertex
      // requirements. The color data should be a sequence of normalized UBYTE4, so dummy UVs are
      // USHORT2 to make the sizes match.
      bool needsDummyData = false;
      if (mMaterials->getSource() == LOAD_UBERSHADERS) {
          if (!hasUv0) {
              needsDummyData = true;
              vbb.attribute(VertexAttribute::UV0, slot, VertexBuffer::AttributeType::USHORT2);
          }
          if (!hasUv1) {
              needsDummyData = true;
              vbb.attribute(VertexAttribute::UV1, slot, VertexBuffer::AttributeType::USHORT2);
          }
          if (!hasVertexColor) {
              needsDummyData = true;
              vbb.attribute(VertexAttribute::COLOR, slot, VertexBuffer::AttributeType::UBYTE4);
              vbb.normalized(VertexAttribute::COLOR);
          }
      } else {
          int numUvSets = getNumUvSets(uvmap);
          if (!hasUv0 && numUvSets > 0) {
              needsDummyData = true;
              vbb.attribute(VertexAttribute::UV0, slot, VertexBuffer::AttributeType::USHORT2);
              slog.w << "Missing UV0 data in " << name << io::endl;
          }
          if (!hasUv1 && numUvSets > 1) {
              needsDummyData = true;
              vbb.attribute(VertexAttribute::UV1, slot, VertexBuffer::AttributeType::USHORT2);
              slog.w << "Missing UV1 data in " << name << io::endl;
          }
      }
  
      vbb.bufferCount(needsDummyData ? slot + 1 : slot);
  
      VertexBuffer* vertices = vbb.build(*mEngine);
  
      outPrim->indices = indices;
      outPrim->vertices = vertices;
      mResult->mPrimitives.push_back({inPrim, vertices});
      mResult->mVertexBuffers.push_back(vertices);
  
      for (size_t i = firstSlot; i < mResult->mBufferSlots.size(); ++i) {
          mResult->mBufferSlots[i].vertexBuffer = vertices;
      }
  
      if (needsDummyData) {
          uint32_t size = sizeof(ubyte4) * vertexCount;
          uint32_t* dummyData = (uint32_t*) malloc(size);
          memset(dummyData, 0xff, size);
          VertexBuffer::BufferDescriptor bd(dummyData, size, FREE_CALLBACK);
          vertices->setBufferAt(*mEngine, slot, std::move(bd));
      }
  
      return true;
  }
  
  void FAssetLoader::createLight(const cgltf_node* node, Entity entity) {
      const cgltf_light* light = node->light;
  
      LightManager::Type type = getLightType(light->type);
      LightManager::Builder builder(type);
  
      builder.direction({0.0f, 0.0f, -1.0f});
      builder.color({light->color[0], light->color[1], light->color[2]});
  
      switch (type) {
          case LightManager::Type::SUN:
          case LightManager::Type::DIRECTIONAL:
              builder.intensity(light->intensity);
              break;
          case LightManager::Type::POINT:
              builder.intensityCandela(light->intensity);
              break;
          case LightManager::Type::FOCUSED_SPOT:
          case LightManager::Type::SPOT:
              // glTF specifies half angles, so does Filament
              builder.spotLightCone(
                      light->spot_inner_cone_angle,
                      light->spot_outer_cone_angle);
              builder.intensityCandela(light->intensity);
              break;
      }
  
      if (light->range == 0.0f) {
          // Use 10.0f units as a resonable default falloff value.
          builder.falloff(10.0f);
      } else {
          builder.falloff(light->range);
      }
  
      builder.build(*mEngine, entity);
      mResult->mLightEntities.push_back(entity);
  }
  
  MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_material* inputMat,
          UvMap* uvmap, bool vertexColor) {
      intptr_t key = ((intptr_t) inputMat) ^ (vertexColor ? 1 : 0);
      auto iter = mMatInstanceCache.find(key);
      if (iter != mMatInstanceCache.end()) {
          *uvmap = iter->second.uvmap;
          return iter->second.instance;
      }
  
      // The default glTF material.
      static const cgltf_material kDefaultMat = {
          .name = (char*) "Default GLTF material",
          .has_pbr_metallic_roughness = true,
          .has_pbr_specular_glossiness = false,
          .has_clearcoat = false,
          .pbr_metallic_roughness = {
  	        .base_color_factor = {1.0, 1.0, 1.0, 1.0},
  	        .metallic_factor = 1.0,
  	        .roughness_factor = 1.0,
          },
      };
      inputMat = inputMat ? inputMat : &kDefaultMat;
  
      auto mrConfig = inputMat->pbr_metallic_roughness;
      auto sgConfig = inputMat->pbr_specular_glossiness;
      auto ccConfig = inputMat->clearcoat;
  
      bool hasTextureTransforms =
          sgConfig.diffuse_texture.has_transform ||
          sgConfig.specular_glossiness_texture.has_transform ||
          mrConfig.base_color_texture.has_transform ||
          mrConfig.metallic_roughness_texture.has_transform ||
          inputMat->normal_texture.has_transform ||
          inputMat->occlusion_texture.has_transform ||
          inputMat->emissive_texture.has_transform ||
          ccConfig.clearcoat_texture.has_transform ||
          ccConfig.clearcoat_roughness_texture.has_transform ||
          ccConfig.clearcoat_normal_texture.has_transform;
  
      cgltf_texture_view baseColorTexture = mrConfig.base_color_texture;
      cgltf_texture_view metallicRoughnessTexture = mrConfig.metallic_roughness_texture;
  
      MaterialKey matkey {
          .doubleSided = (bool) inputMat->double_sided,
          .unlit = (bool) inputMat->unlit,
          .hasVertexColors = vertexColor,
          .hasBaseColorTexture = !!baseColorTexture.texture,
          .hasNormalTexture = !!inputMat->normal_texture.texture,
          .hasOcclusionTexture = !!inputMat->occlusion_texture.texture,
          .hasEmissiveTexture = !!inputMat->emissive_texture.texture,
          .useSpecularGlossiness = false,
          .alphaMode = AlphaMode::OPAQUE,
          .enableDiagnostics = mDiagnosticsEnabled,
          .hasMetallicRoughnessTexture = !!metallicRoughnessTexture.texture,
          .metallicRoughnessUV = (uint8_t) metallicRoughnessTexture.texcoord,
          .baseColorUV = (uint8_t) baseColorTexture.texcoord,
          .hasClearCoatTexture = !!ccConfig.clearcoat_texture.texture,
          .clearCoatUV = (uint8_t) ccConfig.clearcoat_texture.texcoord,
          .hasClearCoatRoughnessTexture = !!ccConfig.clearcoat_roughness_texture.texture,
          .clearCoatRoughnessUV = (uint8_t) ccConfig.clearcoat_roughness_texture.texcoord,
          .hasClearCoatNormalTexture = !!ccConfig.clearcoat_normal_texture.texture,
          .clearCoatNormalUV = (uint8_t) ccConfig.clearcoat_normal_texture.texcoord,
          .hasClearCoat = (bool) inputMat->has_clearcoat,
          .hasTextureTransforms = hasTextureTransforms,
          .emissiveUV = (uint8_t) inputMat->emissive_texture.texcoord,
          .aoUV = (uint8_t) inputMat->occlusion_texture.texcoord,
          .normalUV = (uint8_t) inputMat->normal_texture.texcoord,
      };
  
      if (inputMat->has_pbr_specular_glossiness) {
          matkey.useSpecularGlossiness = true;
          if (sgConfig.diffuse_texture.texture) {
              baseColorTexture = sgConfig.diffuse_texture;
              matkey.hasBaseColorTexture = true;
              matkey.baseColorUV = (uint8_t) baseColorTexture.texcoord;
          }
          if (sgConfig.specular_glossiness_texture.texture) {
              metallicRoughnessTexture = sgConfig.specular_glossiness_texture;
              matkey.hasSpecularGlossinessTexture = true;
              matkey.specularGlossinessUV = (uint8_t) metallicRoughnessTexture.texcoord;
          }
      }
  
      switch (inputMat->alpha_mode) {
          case cgltf_alpha_mode_opaque:
              matkey.alphaMode = AlphaMode::OPAQUE;
              break;
          case cgltf_alpha_mode_mask:
              matkey.alphaMode = AlphaMode::MASK;
              break;
          case cgltf_alpha_mode_blend:
              matkey.alphaMode = AlphaMode::BLEND;
              break;
      }
  
      // This not only creates a material instance, it modifies the material key according to our
      // rendering constraints. For example, Filament only supports 2 sets of texture coordinates.
      MaterialInstance* mi = mMaterials->createMaterialInstance(&matkey, uvmap, inputMat->name);
      if (!mi) {
          slog.e << "No material with the specified requirements exists." << io::endl;
          return nullptr;
      }
  
      mResult->mMaterialInstances.push_back(mi);
  
      if (inputMat->alpha_mode == cgltf_alpha_mode_mask) {
          mi->setMaskThreshold(inputMat->alpha_cutoff);
      }
  
      const float* e = inputMat->emissive_factor;
      mi->setParameter("emissiveFactor", float3(e[0], e[1], e[2]));
  
      const float* c = mrConfig.base_color_factor;
      mi->setParameter("baseColorFactor", float4(c[0], c[1], c[2], c[3]));
      mi->setParameter("metallicFactor", mrConfig.metallic_factor);
      mi->setParameter("roughnessFactor", mrConfig.roughness_factor);
  
      if (matkey.useSpecularGlossiness) {
          const float* df = sgConfig.diffuse_factor;
          const float* sf = sgConfig.specular_factor;
          mi->setParameter("baseColorFactor", float4(df[0], df[1], df[2], df[3]));
          mi->setParameter("specularFactor", float3(sf[0], sf[1], sf[2]));
          mi->setParameter("glossinessFactor", sgConfig.glossiness_factor);
      }
  
      if (matkey.hasBaseColorTexture) {
          addTextureBinding(mi, "baseColorMap", baseColorTexture.texture, true);
          if (matkey.hasTextureTransforms) {
              const cgltf_texture_transform& uvt = baseColorTexture.transform;
              auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
              mi->setParameter("baseColorUvMatrix", uvmat);
          }
      }
  
      if (matkey.hasMetallicRoughnessTexture) {
          // The "metallicRoughnessMap" is actually a specular-glossiness map when the extension is
          // enabled. Note that KHR_materials_pbrSpecularGlossiness specifies that diffuseTexture and
          // specularGlossinessTexture are both sRGB, whereas the core glTF spec stipulates that
          // metallicRoughness is not sRGB.
          bool srgb = inputMat->has_pbr_specular_glossiness;
          addTextureBinding(mi, "metallicRoughnessMap", metallicRoughnessTexture.texture, srgb);
          if (matkey.hasTextureTransforms) {
              const cgltf_texture_transform& uvt = metallicRoughnessTexture.transform;
              auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
              mi->setParameter("metallicRoughnessUvMatrix", uvmat);
          }
      }
  
      if (matkey.hasNormalTexture) {
          addTextureBinding(mi, "normalMap", inputMat->normal_texture.texture, false);
          if (matkey.hasTextureTransforms) {
              const cgltf_texture_transform& uvt = inputMat->normal_texture.transform;
              auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
              mi->setParameter("normalUvMatrix", uvmat);
          }
          mi->setParameter("normalScale", inputMat->normal_texture.scale);
      } else {
          mi->setParameter("normalScale", 1.0f);
      }
  
      if (matkey.hasOcclusionTexture) {
          addTextureBinding(mi, "occlusionMap", inputMat->occlusion_texture.texture, false);
          if (matkey.hasTextureTransforms) {
              const cgltf_texture_transform& uvt = inputMat->occlusion_texture.transform;
              auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
              mi->setParameter("occlusionUvMatrix", uvmat);
          }
          mi->setParameter("aoStrength", inputMat->occlusion_texture.scale);
      } else {
          mi->setParameter("aoStrength", 1.0f);
      }
  
      if (matkey.hasEmissiveTexture) {
          addTextureBinding(mi, "emissiveMap", inputMat->emissive_texture.texture, true);
          if (matkey.hasTextureTransforms) {
              const cgltf_texture_transform& uvt = inputMat->emissive_texture.transform;
              auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
              mi->setParameter("emissiveUvMatrix", uvmat);
          }
      }
  
      if (matkey.hasClearCoat) {
          mi->setParameter("clearCoatFactor", ccConfig.clearcoat_factor);
          mi->setParameter("clearCoatRoughnessFactor", ccConfig.clearcoat_roughness_factor);
  
          if (matkey.hasClearCoatTexture) {
              addTextureBinding(mi, "clearCoatMap", ccConfig.clearcoat_texture.texture, false);
              if (matkey.hasTextureTransforms) {
                  const cgltf_texture_transform& uvt = ccConfig.clearcoat_texture.transform;
                  auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
                  mi->setParameter("clearCoatUvMatrix", uvmat);
              }
          }
          if (matkey.hasClearCoatRoughnessTexture) {
              addTextureBinding(mi, "clearCoatRoughnessMap", ccConfig.clearcoat_roughness_texture.texture, false);
              if (matkey.hasTextureTransforms) {
                  const cgltf_texture_transform& uvt = ccConfig.clearcoat_roughness_texture.transform;
                  auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
                  mi->setParameter("clearCoatRoughnessUvMatrix", uvmat);
              }
          }
          if (matkey.hasClearCoatNormalTexture) {
              addTextureBinding(mi, "clearCoatNormalMap", ccConfig.clearcoat_normal_texture.texture, false);
              if (matkey.hasTextureTransforms) {
                  const cgltf_texture_transform& uvt = ccConfig.clearcoat_normal_texture.transform;
                  auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
                  mi->setParameter("clearCoatNormalUvMatrix", uvmat);
              }
              mi->setParameter("clearCoatNormalScale", ccConfig.clearcoat_normal_texture.scale);
          }
      }
  
      mMatInstanceCache[key] = {mi, *uvmap};
      return mi;
  }
  
  void FAssetLoader::addTextureBinding(MaterialInstance* materialInstance, const char* parameterName,
          const cgltf_texture* srcTexture, bool srgb) {
      if (!srcTexture->image) {
          slog.w << "Texture is missing image (" << srcTexture->name << ")." << io::endl;
          return;
      }
      TextureSampler dstSampler;
      auto srcSampler = srcTexture->sampler;
      if (srcSampler) {
          dstSampler.setWrapModeS(getWrapMode(srcSampler->wrap_s));
          dstSampler.setWrapModeT(getWrapMode(srcSampler->wrap_t));
          dstSampler.setMagFilter(getMagFilter(srcSampler->mag_filter));
          dstSampler.setMinFilter(getMinFilter(srcSampler->min_filter));
      } else {
          // These defaults are stipulated by the spec:
          dstSampler.setWrapModeS(TextureSampler::WrapMode::REPEAT);
          dstSampler.setWrapModeT(TextureSampler::WrapMode::REPEAT);
  
          // These defaults are up the implementation but since we generate mipmaps unconditionally,
          // we might as well use them. In practice the conformance models look awful without
          // using mipmapping by default.
          dstSampler.setMagFilter(TextureSampler::MagFilter::LINEAR);
          dstSampler.setMinFilter(TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR);
      }
  
      mResult->mTextureSlots.push_back({
          .texture = srcTexture,
          .materialInstance = materialInstance,
          .materialParameter = parameterName,
          .sampler = dstSampler,
          .srgb = srgb
      });
      mResult->mDependencyGraph.addEdge(materialInstance, parameterName);
  }
  
  bool FAssetLoader::primitiveHasVertexColor(const cgltf_primitive* inPrim) const {
      for (int slot = 0; slot < inPrim->attributes_count; slot++) {
          const cgltf_attribute& inputAttribute = inPrim->attributes[slot];
          if (inputAttribute.type == cgltf_attribute_type_color) {
              return true;
          }
      }
      return false;
  }
  
  LightManager::Type FAssetLoader::getLightType(const cgltf_light_type light) {
      switch (light) {
          case cgltf_light_type_invalid:
          case cgltf_light_type_directional:
              return LightManager::Type::DIRECTIONAL;
          case cgltf_light_type_point:
              return LightManager::Type::POINT;
          case cgltf_light_type_spot:
              return LightManager::Type::FOCUSED_SPOT;
      }
  }
  
  AssetLoader* AssetLoader::create(const AssetConfiguration& config) {
      return new FAssetLoader(config);
  }
  
  void AssetLoader::destroy(AssetLoader** loader) {
      delete *loader;
      *loader = nullptr;
  }
  
  FilamentAsset* AssetLoader::createAssetFromJson(uint8_t const* bytes, uint32_t nbytes) {
      return upcast(this)->createAssetFromJson(bytes, nbytes);
  }
  
  FilamentAsset* AssetLoader::createAssetFromBinary(uint8_t const* bytes, uint32_t nbytes) {
      return upcast(this)->createAssetFromBinary(bytes, nbytes);
  }
  
  FilamentAsset* AssetLoader::createAssetFromHandle(const void* handle) {
      const cgltf_data* sourceAsset = (const cgltf_data*) handle;
      upcast(this)->createAsset(sourceAsset);
      upcast(this)->mResult->mSharedSourceAsset = true;
      return upcast(this)->mResult;
  }
  
  void AssetLoader::enableDiagnostics(bool enable) {
      upcast(this)->mDiagnosticsEnabled = enable;
  }
  
  void AssetLoader::destroyAsset(const FilamentAsset* asset) {
      upcast(this)->destroyAsset(upcast(asset));
  }
  
  size_t AssetLoader::getMaterialsCount() const noexcept {
      return upcast(this)->getMaterialsCount();
  }
  
  NameComponentManager* AssetLoader::getNames() const noexcept {
      return upcast(this)->getNames();
  }
  
  const Material* const* AssetLoader::getMaterials() const noexcept {
      return upcast(this)->getMaterials();
  }
  
  } // namespace gltfio
  