Implicit surface rendering via ray tracing
 
 
 

1295 lines
44 KiB

/*
* Copyright (c) 2014-2021, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*
* SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "gltfscene.hpp"
#include "boundingbox.hpp"
#include "nvprint.hpp"
#include <iostream>
#include <numeric>
#include <limits>
#include <set>
#include <sstream>
#include <thread>
#include "parallel_work.hpp"
namespace nvh {
#define EXTENSION_ATTRIB_IRAY "NV_attributes_iray"
//--------------------------------------------------------------------------------------------------
// Collect the value of all materials
//
void GltfScene::importMaterials(const tinygltf::Model& tmodel)
{
m_materials.reserve(tmodel.materials.size());
for(auto& tmat : tmodel.materials)
{
GltfMaterial gmat;
gmat.tmaterial = &tmat; // Reference
gmat.alphaCutoff = static_cast<float>(tmat.alphaCutoff);
gmat.alphaMode = tmat.alphaMode == "MASK" ? 1 : (tmat.alphaMode == "BLEND" ? 2 : 0);
gmat.doubleSided = tmat.doubleSided ? 1 : 0;
gmat.emissiveFactor = tmat.emissiveFactor.size() == 3 ?
nvmath::vec3f(tmat.emissiveFactor[0], tmat.emissiveFactor[1], tmat.emissiveFactor[2]) :
nvmath::vec3f(0.f);
gmat.emissiveTexture = tmat.emissiveTexture.index;
gmat.normalTexture = tmat.normalTexture.index;
gmat.normalTextureScale = static_cast<float>(tmat.normalTexture.scale);
gmat.occlusionTexture = tmat.occlusionTexture.index;
gmat.occlusionTextureStrength = static_cast<float>(tmat.occlusionTexture.strength);
// PbrMetallicRoughness
auto& tpbr = tmat.pbrMetallicRoughness;
gmat.baseColorFactor =
nvmath::vec4f(tpbr.baseColorFactor[0], tpbr.baseColorFactor[1], tpbr.baseColorFactor[2], tpbr.baseColorFactor[3]);
gmat.baseColorTexture = tpbr.baseColorTexture.index;
gmat.metallicFactor = static_cast<float>(tpbr.metallicFactor);
gmat.metallicRoughnessTexture = tpbr.metallicRoughnessTexture.index;
gmat.roughnessFactor = static_cast<float>(tpbr.roughnessFactor);
// KHR_materials_pbrSpecularGlossiness
if(tmat.extensions.find(KHR_MATERIALS_PBRSPECULARGLOSSINESS_EXTENSION_NAME) != tmat.extensions.end())
{
gmat.shadingModel = 1;
const auto& ext = tmat.extensions.find(KHR_MATERIALS_PBRSPECULARGLOSSINESS_EXTENSION_NAME)->second;
getVec4(ext, "diffuseFactor", gmat.specularGlossiness.diffuseFactor);
getFloat(ext, "glossinessFactor", gmat.specularGlossiness.glossinessFactor);
getVec3(ext, "specularFactor", gmat.specularGlossiness.specularFactor);
getTexId(ext, "diffuseTexture", gmat.specularGlossiness.diffuseTexture);
getTexId(ext, "specularGlossinessTexture", gmat.specularGlossiness.specularGlossinessTexture);
}
// KHR_materials_specular
if(tmat.extensions.find(KHR_MATERIALS_SPECULAR_EXTENSION_NAME) != tmat.extensions.end())
{
// Since KHR_materials_specular affects the metallic-roughness model:
gmat.shadingModel = 0;
const auto& ext = tmat.extensions.find(KHR_MATERIALS_SPECULAR_EXTENSION_NAME)->second;
getFloat(ext, "specularFactor", gmat.specular.specularFactor);
getTexId(ext, "specularTexture", gmat.specular.specularTexture);
getVec3(ext, "specularColorFactor", gmat.specular.specularColorFactor);
getTexId(ext, "specularColorTexture", gmat.specular.specularColorTexture);
}
// KHR_texture_transform
if(tpbr.baseColorTexture.extensions.find(KHR_TEXTURE_TRANSFORM_EXTENSION_NAME) != tpbr.baseColorTexture.extensions.end())
{
const auto& ext = tpbr.baseColorTexture.extensions.find(KHR_TEXTURE_TRANSFORM_EXTENSION_NAME)->second;
auto& tt = gmat.textureTransform;
getVec2(ext, "offset", tt.offset);
getVec2(ext, "scale", tt.scale);
getFloat(ext, "rotation", tt.rotation);
getInt(ext, "texCoord", tt.texCoord);
// Computing the transformation
auto translation = nvmath::mat3f(1, 0, tt.offset.x, 0, 1, tt.offset.y, 0, 0, 1);
auto rotation = nvmath::mat3f(cos(tt.rotation), sin(tt.rotation), 0, -sin(tt.rotation), cos(tt.rotation), 0, 0, 0, 1);
auto scale = nvmath::mat3f(tt.scale.x, 0, 0, 0, tt.scale.y, 0, 0, 0, 1);
tt.uvTransform = scale * rotation * translation;
}
// KHR_materials_unlit
if(tmat.extensions.find(KHR_MATERIALS_UNLIT_EXTENSION_NAME) != tmat.extensions.end())
{
gmat.unlit.active = 1;
}
// KHR_materials_anisotropy
if(tmat.extensions.find(KHR_MATERIALS_ANISOTROPY_EXTENSION_NAME) != tmat.extensions.end())
{
const auto& ext = tmat.extensions.find(KHR_MATERIALS_ANISOTROPY_EXTENSION_NAME)->second;
getFloat(ext, "anisotropy", gmat.anisotropy.factor);
getVec3(ext, "anisotropyDirection", gmat.anisotropy.direction);
getTexId(ext, "anisotropyTexture", gmat.anisotropy.texture);
}
// KHR_materials_clearcoat
if(tmat.extensions.find(KHR_MATERIALS_CLEARCOAT_EXTENSION_NAME) != tmat.extensions.end())
{
const auto& ext = tmat.extensions.find(KHR_MATERIALS_CLEARCOAT_EXTENSION_NAME)->second;
getFloat(ext, "clearcoatFactor", gmat.clearcoat.factor);
getTexId(ext, "clearcoatTexture", gmat.clearcoat.texture);
getFloat(ext, "clearcoatRoughnessFactor", gmat.clearcoat.roughnessFactor);
getTexId(ext, "clearcoatRoughnessTexture", gmat.clearcoat.roughnessTexture);
getTexId(ext, "clearcoatNormalTexture", gmat.clearcoat.normalTexture);
}
// KHR_materials_sheen
if(tmat.extensions.find(KHR_MATERIALS_SHEEN_EXTENSION_NAME) != tmat.extensions.end())
{
const auto& ext = tmat.extensions.find(KHR_MATERIALS_SHEEN_EXTENSION_NAME)->second;
getVec3(ext, "sheenColorFactor", gmat.sheen.colorFactor);
getTexId(ext, "sheenColorTexture", gmat.sheen.colorTexture);
getFloat(ext, "sheenRoughnessFactor", gmat.sheen.roughnessFactor);
getTexId(ext, "sheenRoughnessTexture", gmat.sheen.roughnessTexture);
}
// KHR_materials_transmission
if(tmat.extensions.find(KHR_MATERIALS_TRANSMISSION_EXTENSION_NAME) != tmat.extensions.end())
{
const auto& ext = tmat.extensions.find(KHR_MATERIALS_TRANSMISSION_EXTENSION_NAME)->second;
getFloat(ext, "transmissionFactor", gmat.transmission.factor);
getTexId(ext, "transmissionTexture", gmat.transmission.texture);
}
// KHR_materials_ior
if(tmat.extensions.find(KHR_MATERIALS_IOR_EXTENSION_NAME) != tmat.extensions.end())
{
const auto& ext = tmat.extensions.find(KHR_MATERIALS_IOR_EXTENSION_NAME)->second;
getFloat(ext, "ior", gmat.ior.ior);
}
// KHR_materials_volume
if(tmat.extensions.find(KHR_MATERIALS_VOLUME_EXTENSION_NAME) != tmat.extensions.end())
{
const auto& ext = tmat.extensions.find(KHR_MATERIALS_VOLUME_EXTENSION_NAME)->second;
getFloat(ext, "thicknessFactor", gmat.volume.thicknessFactor);
getTexId(ext, "thicknessTexture", gmat.volume.thicknessTexture);
getFloat(ext, "attenuationDistance", gmat.volume.attenuationDistance);
getVec3(ext, "attenuationColor", gmat.volume.attenuationColor);
};
// KHR_materials_displacement
if(tmat.extensions.find(KHR_MATERIALS_DISPLACEMENT_NAME) != tmat.extensions.end())
{
const auto& ext = tmat.extensions.find(KHR_MATERIALS_DISPLACEMENT_NAME)->second;
getTexId(ext, "displacementGeometryTexture", gmat.displacement.displacementGeometryTexture);
getFloat(ext, "displacementGeometryFactor", gmat.displacement.displacementGeometryFactor);
getFloat(ext, "displacementGeometryOffset", gmat.displacement.displacementGeometryOffset);
}
m_materials.emplace_back(gmat);
}
// Make default
if(m_materials.empty())
{
GltfMaterial gmat;
gmat.metallicFactor = 0;
m_materials.emplace_back(gmat);
}
}
//--------------------------------------------------------------------------------------------------
// Linearize the scene graph to world space nodes.
//
void GltfScene::importDrawableNodes(const tinygltf::Model& tmodel, GltfAttributes requestedAttributes, GltfAttributes forceRequested /*= GltfAttributes::All*/)
{
checkRequiredExtensions(tmodel);
int defaultScene = tmodel.defaultScene > -1 ? tmodel.defaultScene : 0;
const auto& tscene = tmodel.scenes[defaultScene];
// Finding only the mesh that are used in the scene
std::set<uint32_t> usedMeshes;
for(auto nodeIdx : tscene.nodes)
{
findUsedMeshes(tmodel, usedMeshes, nodeIdx);
}
// Find the number of vertex(attributes) and index
//uint32_t nbVert{0};
uint32_t nbIndex{0};
uint32_t primCnt{0}; // " " " "
for(const auto& m : usedMeshes)
{
auto& tmesh = tmodel.meshes[m];
std::vector<uint32_t> vprim;
for(const auto& primitive : tmesh.primitives)
{
if(primitive.mode != 4) // Triangle
continue;
const auto& posAccessor = tmodel.accessors[primitive.attributes.find("POSITION")->second];
//nbVert += static_cast<uint32_t>(posAccessor.count);
if(primitive.indices > -1)
{
const auto& indexAccessor = tmodel.accessors[primitive.indices];
nbIndex += static_cast<uint32_t>(indexAccessor.count);
}
else
{
nbIndex += static_cast<uint32_t>(posAccessor.count);
}
vprim.emplace_back(primCnt++);
}
m_meshToPrimMeshes[m] = std::move(vprim); // mesh-id = { prim0, prim1, ... }
}
// Reserving memory
m_indices.reserve(nbIndex);
// Convert all mesh/primitives+ to a single primitive per mesh
for(const auto& m : usedMeshes)
{
auto& tmesh = tmodel.meshes[m];
for(const auto& tprimitive : tmesh.primitives)
{
processMesh(tmodel, tprimitive, requestedAttributes, forceRequested, tmesh.name);
m_primMeshes.back().tmesh = &tmesh;
m_primMeshes.back().tprim = &tprimitive;
}
}
// Fixing tangents, if any were null
uint32_t num_threads = std::min((uint32_t)m_tangents.size(), std::thread::hardware_concurrency());
nvh::parallel_batches(
m_tangents.size(),
[&](uint64_t i) {
auto& t = m_tangents[i];
if(nvmath::nv_sq_norm(nvmath::vec3f(t)) < 0.01F || std::abs(t.w) < 0.5F)
{
const auto& n = m_normals[i];
const float sgn = n.z > 0.0F ? 1.0F : -1.0F;
const float a = -1.0F / (sgn + n.z);
const float b = n.x * n.y * a;
t = nvmath::vec4f(1.0f + sgn * n.x * n.x * a, sgn * b, -sgn * n.x, sgn);
}
},
num_threads);
// Transforming the scene hierarchy to a flat list
for(auto nodeIdx : tscene.nodes)
{
processNode(tmodel, nodeIdx, nvmath::mat4f(1));
}
computeSceneDimensions();
computeCamera();
m_meshToPrimMeshes.clear();
primitiveIndices32u.clear();
primitiveIndices16u.clear();
primitiveIndices8u.clear();
}
//--------------------------------------------------------------------------------------------------
//
//
void GltfScene::processNode(const tinygltf::Model& tmodel, int& nodeIdx, const nvmath::mat4f& parentMatrix)
{
const auto& tnode = tmodel.nodes[nodeIdx];
nvmath::mat4f matrix = getLocalMatrix(tnode);
nvmath::mat4f worldMatrix = parentMatrix * matrix;
if(tnode.mesh > -1)
{
const auto& meshes = m_meshToPrimMeshes[tnode.mesh]; // A mesh could have many primitives
for(const auto& mesh : meshes)
{
GltfNode node;
node.primMesh = mesh;
node.worldMatrix = worldMatrix;
node.tnode = &tnode;
m_nodes.emplace_back(node);
}
}
else if(tnode.camera > -1)
{
GltfCamera camera;
camera.worldMatrix = worldMatrix;
camera.cam = tmodel.cameras[tmodel.nodes[nodeIdx].camera];
// If the node has the Iray extension, extract the camera information.
if(hasExtension(tnode.extensions, EXTENSION_ATTRIB_IRAY))
{
auto& iray_ext = tnode.extensions.at(EXTENSION_ATTRIB_IRAY);
auto& attributes = iray_ext.Get("attributes");
for(size_t idx = 0; idx < attributes.ArrayLen(); idx++)
{
auto& attrib = attributes.Get((int)idx);
std::string attName = attrib.Get("name").Get<std::string>();
auto& attValue = attrib.Get("value");
if(attValue.IsArray())
{
auto vec = getVector<float>(attValue);
if(attName == "iview:position")
camera.eye = {vec[0], vec[1], vec[2]};
else if(attName == "iview:interest")
camera.center = {vec[0], vec[1], vec[2]};
else if(attName == "iview:up")
camera.up = {vec[0], vec[1], vec[2]};
}
}
}
m_cameras.emplace_back(camera);
}
else if(tnode.extensions.find(KHR_LIGHTS_PUNCTUAL_EXTENSION_NAME) != tnode.extensions.end())
{
GltfLight light;
const auto& ext = tnode.extensions.find(KHR_LIGHTS_PUNCTUAL_EXTENSION_NAME)->second;
auto lightIdx = ext.Get("light").GetNumberAsInt();
light.light = tmodel.lights[lightIdx];
light.worldMatrix = worldMatrix;
m_lights.emplace_back(light);
}
// Recursion for all children
for(auto child : tnode.children)
{
processNode(tmodel, child, worldMatrix);
}
}
//--------------------------------------------------------------------------------------------------
// Extracting the values to a linear buffer
//
void GltfScene::processMesh(const tinygltf::Model& tmodel,
const tinygltf::Primitive& tmesh,
GltfAttributes requestedAttributes,
GltfAttributes forceRequested,
const std::string& name)
{
// Only triangles are supported
// 0:point, 1:lines, 2:line_loop, 3:line_strip, 4:triangles, 5:triangle_strip, 6:triangle_fan
if(tmesh.mode != 4)
return;
GltfPrimMesh resultMesh;
resultMesh.name = name;
resultMesh.materialIndex = std::max(0, tmesh.material);
resultMesh.vertexOffset = static_cast<uint32_t>(m_positions.size());
resultMesh.firstIndex = static_cast<uint32_t>(m_indices.size());
// Create a key made of the attributes, to see if the primitive was already
// processed. If it is, we will re-use the cache, but allow the material and
// indices to be different.
std::stringstream o;
for(auto& a : tmesh.attributes)
{
o << a.first << a.second;
}
std::string key = o.str();
bool primMeshCached = false;
// Found a cache - will not need to append vertex
auto it = m_cachePrimMesh.find(key);
if(it != m_cachePrimMesh.end())
{
primMeshCached = true;
GltfPrimMesh cacheMesh = it->second;
resultMesh.vertexCount = cacheMesh.vertexCount;
resultMesh.vertexOffset = cacheMesh.vertexOffset;
}
// INDICES
if(tmesh.indices > -1)
{
const tinygltf::Accessor& indexAccessor = tmodel.accessors[tmesh.indices];
resultMesh.indexCount = static_cast<uint32_t>(indexAccessor.count);
switch(indexAccessor.componentType)
{
case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: {
primitiveIndices32u.resize(indexAccessor.count);
copyAccessorData(primitiveIndices32u, 0, tmodel, indexAccessor, 0, indexAccessor.count);
m_indices.insert(m_indices.end(), primitiveIndices32u.begin(), primitiveIndices32u.end());
break;
}
case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: {
primitiveIndices16u.resize(indexAccessor.count);
copyAccessorData(primitiveIndices16u, 0, tmodel, indexAccessor, 0, indexAccessor.count);
m_indices.insert(m_indices.end(), primitiveIndices16u.begin(), primitiveIndices16u.end());
break;
}
case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: {
primitiveIndices8u.resize(indexAccessor.count);
copyAccessorData(primitiveIndices8u, 0, tmodel, indexAccessor, 0, indexAccessor.count);
m_indices.insert(m_indices.end(), primitiveIndices8u.begin(), primitiveIndices8u.end());
break;
}
default:
LOGE("Index component type %i not supported!\n", indexAccessor.componentType);
return;
}
}
else
{
// Primitive without indices, creating them
const auto& accessor = tmodel.accessors[tmesh.attributes.find("POSITION")->second];
for(auto i = 0; i < accessor.count; i++)
m_indices.push_back(i);
resultMesh.indexCount = static_cast<uint32_t>(accessor.count);
}
if(primMeshCached == false) // Need to add this primitive
{
// POSITION
{
const bool hadPosition = getAttribute<nvmath::vec3f>(tmodel, tmesh, m_positions, "POSITION");
if(!hadPosition)
{
LOGE("This glTF file is invalid: it had a primitive with no POSITION attribute.\n");
return;
}
// Keeping the size of this primitive (Spec says this is required information)
const auto& accessor = tmodel.accessors[tmesh.attributes.find("POSITION")->second];
resultMesh.vertexCount = static_cast<uint32_t>(accessor.count);
if(!accessor.minValues.empty())
{
resultMesh.posMin = nvmath::vec3f(accessor.minValues[0], accessor.minValues[1], accessor.minValues[2]);
}
else
{
resultMesh.posMin = nvmath::vec3f(std::numeric_limits<float>::max());
for(const auto& p : m_positions)
{
for(int i = 0; i < 3; i++)
{
if(p[i] < resultMesh.posMin[i])
resultMesh.posMin[i] = p[i];
}
}
}
if(!accessor.maxValues.empty())
{
resultMesh.posMax = nvmath::vec3f(accessor.maxValues[0], accessor.maxValues[1], accessor.maxValues[2]);
}
else
{
resultMesh.posMax = nvmath::vec3f(-std::numeric_limits<float>::max());
for(const auto& p : m_positions)
for(int i = 0; i < 3; i++)
{
if(p[i] > resultMesh.posMax[i])
resultMesh.posMax[i] = p[i];
}
}
}
// NORMAL
if(hasFlag(requestedAttributes, GltfAttributes::Normal))
{
bool normalCreated = getAttribute<nvmath::vec3f>(tmodel, tmesh, m_normals, "NORMAL");
if(!normalCreated && hasFlag(forceRequested, GltfAttributes::Normal))
createNormals(resultMesh);
}
// TEXCOORD_0
if(hasFlag(requestedAttributes, GltfAttributes::Texcoord_0))
{
bool texcoordCreated = getAttribute<nvmath::vec2f>(tmodel, tmesh, m_texcoords0, "TEXCOORD_0");
if(!texcoordCreated)
texcoordCreated = getAttribute<nvmath::vec2f>(tmodel, tmesh, m_texcoords0, "TEXCOORD");
if(!texcoordCreated && hasFlag(forceRequested, GltfAttributes::Texcoord_0))
createTexcoords(resultMesh);
}
// TANGENT
if(hasFlag(requestedAttributes, GltfAttributes::Tangent))
{
bool tangentCreated = getAttribute<nvmath::vec4f>(tmodel, tmesh, m_tangents, "TANGENT");
if(!tangentCreated && hasFlag(forceRequested, GltfAttributes::Tangent))
createTangents(resultMesh);
}
// COLOR_0
if(hasFlag(requestedAttributes, GltfAttributes::Color_0))
{
bool colorCreated = getAttribute<nvmath::vec4f>(tmodel, tmesh, m_colors0, "COLOR_0");
if(!colorCreated && hasFlag(forceRequested, GltfAttributes::Color_0))
createColors(resultMesh);
}
}
// Keep result in cache
m_cachePrimMesh[key] = resultMesh;
// Append prim mesh to the list of all primitive meshes
m_primMeshes.emplace_back(resultMesh);
} // namespace nvh
void GltfScene::createNormals(GltfPrimMesh& resultMesh)
{
// Need to compute the normals
std::vector<nvmath::vec3f> geonormal(resultMesh.vertexCount);
for(size_t i = 0; i < resultMesh.indexCount; i += 3)
{
uint32_t ind0 = m_indices[resultMesh.firstIndex + i + 0];
uint32_t ind1 = m_indices[resultMesh.firstIndex + i + 1];
uint32_t ind2 = m_indices[resultMesh.firstIndex + i + 2];
const auto& pos0 = m_positions[ind0 + resultMesh.vertexOffset];
const auto& pos1 = m_positions[ind1 + resultMesh.vertexOffset];
const auto& pos2 = m_positions[ind2 + resultMesh.vertexOffset];
const auto v1 = nvmath::normalize(pos1 - pos0); // Many normalize, but when objects are really small the
const auto v2 = nvmath::normalize(pos2 - pos0); // cross will go below nv_eps and the normal will be (0,0,0)
const auto n = nvmath::cross(v1, v2);
geonormal[ind0] += n;
geonormal[ind1] += n;
geonormal[ind2] += n;
}
for(auto& n : geonormal)
n = nvmath::normalize(n);
m_normals.insert(m_normals.end(), geonormal.begin(), geonormal.end());
}
void GltfScene::createTexcoords(GltfPrimMesh& resultMesh)
{
// Set them all to zero
// m_texcoords0.insert(m_texcoords0.end(), resultMesh.vertexCount, nvmath::vec2f(0, 0));
// Cube map projection
for(uint32_t i = 0; i < resultMesh.vertexCount; i++)
{
const auto& pos = m_positions[resultMesh.vertexOffset + i];
float absX = fabs(pos.x);
float absY = fabs(pos.y);
float absZ = fabs(pos.z);
int isXPositive = pos.x > 0 ? 1 : 0;
int isYPositive = pos.y > 0 ? 1 : 0;
int isZPositive = pos.z > 0 ? 1 : 0;
float maxAxis{}, uc{}, vc{}; // Zero-initialize in case pos = {NaN, NaN, NaN}
// POSITIVE X
if(isXPositive && absX >= absY && absX >= absZ)
{
// u (0 to 1) goes from +z to -z
// v (0 to 1) goes from -y to +y
maxAxis = absX;
uc = -pos.z;
vc = pos.y;
}
// NEGATIVE X
if(!isXPositive && absX >= absY && absX >= absZ)
{
// u (0 to 1) goes from -z to +z
// v (0 to 1) goes from -y to +y
maxAxis = absX;
uc = pos.z;
vc = pos.y;
}
// POSITIVE Y
if(isYPositive && absY >= absX && absY >= absZ)
{
// u (0 to 1) goes from -x to +x
// v (0 to 1) goes from +z to -z
maxAxis = absY;
uc = pos.x;
vc = -pos.z;
}
// NEGATIVE Y
if(!isYPositive && absY >= absX && absY >= absZ)
{
// u (0 to 1) goes from -x to +x
// v (0 to 1) goes from -z to +z
maxAxis = absY;
uc = pos.x;
vc = pos.z;
}
// POSITIVE Z
if(isZPositive && absZ >= absX && absZ >= absY)
{
// u (0 to 1) goes from -x to +x
// v (0 to 1) goes from -y to +y
maxAxis = absZ;
uc = pos.x;
vc = pos.y;
}
// NEGATIVE Z
if(!isZPositive && absZ >= absX && absZ >= absY)
{
// u (0 to 1) goes from +x to -x
// v (0 to 1) goes from -y to +y
maxAxis = absZ;
uc = -pos.x;
vc = pos.y;
}
// Convert range from -1 to 1 to 0 to 1
float u = 0.5f * (uc / maxAxis + 1.0f);
float v = 0.5f * (vc / maxAxis + 1.0f);
m_texcoords0.emplace_back(u, v);
}
}
void GltfScene::createTangents(GltfPrimMesh& resultMesh)
{
// #TODO - Should calculate tangents using default MikkTSpace algorithms
// See: https://github.com/mmikk/MikkTSpace
std::vector<nvmath::vec3f> tangent(resultMesh.vertexCount);
std::vector<nvmath::vec3f> bitangent(resultMesh.vertexCount);
// Current implementation
// http://foundationsofgameenginedev.com/FGED2-sample.pdf
for(size_t i = 0; i < resultMesh.indexCount; i += 3)
{
// local index
uint32_t i0 = m_indices[resultMesh.firstIndex + i + 0];
uint32_t i1 = m_indices[resultMesh.firstIndex + i + 1];
uint32_t i2 = m_indices[resultMesh.firstIndex + i + 2];
assert(i0 < resultMesh.vertexCount);
assert(i1 < resultMesh.vertexCount);
assert(i2 < resultMesh.vertexCount);
// global index
uint32_t gi0 = i0 + resultMesh.vertexOffset;
uint32_t gi1 = i1 + resultMesh.vertexOffset;
uint32_t gi2 = i2 + resultMesh.vertexOffset;
const auto& p0 = m_positions[gi0];
const auto& p1 = m_positions[gi1];
const auto& p2 = m_positions[gi2];
const auto& uv0 = m_texcoords0[gi0];
const auto& uv1 = m_texcoords0[gi1];
const auto& uv2 = m_texcoords0[gi2];
nvmath::vec3f e1 = p1 - p0;
nvmath::vec3f e2 = p2 - p0;
nvmath::vec2f duvE1 = uv1 - uv0;
nvmath::vec2f duvE2 = uv2 - uv0;
float r = 1.0F;
float a = duvE1.x * duvE2.y - duvE2.x * duvE1.y;
if(fabs(a) > 0) // Catch degenerated UV
{
r = 1.0f / a;
}
nvmath::vec3f t = (e1 * duvE2.y - e2 * duvE1.y) * r;
nvmath::vec3f b = (e2 * duvE1.x - e1 * duvE2.x) * r;
tangent[i0] += t;
tangent[i1] += t;
tangent[i2] += t;
bitangent[i0] += b;
bitangent[i1] += b;
bitangent[i2] += b;
}
for(uint32_t a = 0; a < resultMesh.vertexCount; a++)
{
const auto& t = tangent[a];
const auto& b = bitangent[a];
const auto& n = m_normals[resultMesh.vertexOffset + a];
// Gram-Schmidt orthogonalize
nvmath::vec3f otangent = nvmath::normalize(t - (nvmath::dot(n, t) * n));
// In case the tangent is invalid
if(otangent == nvmath::vec3f(0, 0, 0))
{
if(abs(n.x) > abs(n.y))
otangent = nvmath::vec3f(n.z, 0, -n.x) / sqrt(n.x * n.x + n.z * n.z);
else
otangent = nvmath::vec3f(0, -n.z, n.y) / sqrt(n.y * n.y + n.z * n.z);
}
// Calculate handedness
float handedness = (nvmath::dot(nvmath::cross(n, t), b) < 0.0F) ? 1.0F : -1.0F;
m_tangents.emplace_back(otangent.x, otangent.y, otangent.z, handedness);
}
}
void GltfScene::createColors(GltfPrimMesh& resultMesh)
{
// Set them all to one
m_colors0.insert(m_colors0.end(), resultMesh.vertexCount, nvmath::vec4f(1, 1, 1, 1));
}
//--------------------------------------------------------------------------------------------------
// Return the matrix of the node
//
nvmath::mat4f getLocalMatrix(const tinygltf::Node& tnode)
{
nvmath::mat4f mtranslation{1};
nvmath::mat4f mscale{1};
nvmath::mat4f mrot{1};
nvmath::mat4f matrix{1};
nvmath::quatf mrotation;
if(!tnode.translation.empty())
mtranslation.as_translation(nvmath::vec3f(tnode.translation[0], tnode.translation[1], tnode.translation[2]));
if(!tnode.scale.empty())
mscale.as_scale(nvmath::vec3f(tnode.scale[0], tnode.scale[1], tnode.scale[2]));
if(!tnode.rotation.empty())
{
mrotation[0] = static_cast<float>(tnode.rotation[0]);
mrotation[1] = static_cast<float>(tnode.rotation[1]);
mrotation[2] = static_cast<float>(tnode.rotation[2]);
mrotation[3] = static_cast<float>(tnode.rotation[3]);
mrotation.to_matrix(mrot);
}
if(!tnode.matrix.empty())
{
for(int i = 0; i < 16; ++i)
matrix.mat_array[i] = static_cast<float>(tnode.matrix[i]);
}
return mtranslation * mrot * mscale * matrix;
}
void GltfScene::destroy()
{
m_materials.clear();
m_nodes.clear();
m_primMeshes.clear();
m_cameras.clear();
m_lights.clear();
m_positions.clear();
m_indices.clear();
m_normals.clear();
m_tangents.clear();
m_texcoords0.clear();
m_texcoords1.clear();
m_colors0.clear();
m_cameras.clear();
//m_joints0.clear();
//m_weights0.clear();
m_dimensions = {};
m_meshToPrimMeshes.clear();
primitiveIndices32u.clear();
primitiveIndices16u.clear();
primitiveIndices8u.clear();
m_cachePrimMesh.clear();
}
//--------------------------------------------------------------------------------------------------
// Get the dimension of the scene
//
void GltfScene::computeSceneDimensions()
{
Bbox scnBbox;
for(const auto& node : m_nodes)
{
const auto& mesh = m_primMeshes[node.primMesh];
Bbox bbox(mesh.posMin, mesh.posMax);
bbox = bbox.transform(node.worldMatrix);
scnBbox.insert(bbox);
}
if(scnBbox.isEmpty() || !scnBbox.isVolume())
{
LOGE("glTF: Scene bounding box invalid, Setting to: [-1,-1,-1], [1,1,1]");
scnBbox.insert({-1.0f, -1.0f, -1.0f});
scnBbox.insert({1.0f, 1.0f, 1.0f});
}
m_dimensions.min = scnBbox.min();
m_dimensions.max = scnBbox.max();
m_dimensions.size = scnBbox.extents();
m_dimensions.center = scnBbox.center();
m_dimensions.radius = scnBbox.radius();
}
static uint32_t recursiveTriangleCount(const tinygltf::Model& model, int nodeIdx, const std::vector<uint32_t>& meshTriangle)
{
auto& node = model.nodes[nodeIdx];
uint32_t nbTriangles{0};
for(const auto child : node.children)
{
nbTriangles += recursiveTriangleCount(model, child, meshTriangle);
}
if(node.mesh >= 0)
nbTriangles += meshTriangle[node.mesh];
return nbTriangles;
}
//--------------------------------------------------------------------------------------------------
// Retrieving information about the scene
//
GltfStats GltfScene::getStatistics(const tinygltf::Model& tinyModel)
{
GltfStats stats;
stats.nbCameras = static_cast<uint32_t>(tinyModel.cameras.size());
stats.nbImages = static_cast<uint32_t>(tinyModel.images.size());
stats.nbTextures = static_cast<uint32_t>(tinyModel.textures.size());
stats.nbMaterials = static_cast<uint32_t>(tinyModel.materials.size());
stats.nbSamplers = static_cast<uint32_t>(tinyModel.samplers.size());
stats.nbNodes = static_cast<uint32_t>(tinyModel.nodes.size());
stats.nbMeshes = static_cast<uint32_t>(tinyModel.meshes.size());
stats.nbLights = static_cast<uint32_t>(tinyModel.lights.size());
// Computing the memory usage for images
for(const auto& image : tinyModel.images)
{
stats.imageMem += image.width * image.height * image.component * image.bits / 8;
}
// Computing the number of triangles
std::vector<uint32_t> meshTriangle(tinyModel.meshes.size());
uint32_t meshIdx{0};
for(const auto& mesh : tinyModel.meshes)
{
for(const auto& primitive : mesh.primitives)
{
if(primitive.indices > -1)
{
const tinygltf::Accessor& indexAccessor = tinyModel.accessors[primitive.indices];
meshTriangle[meshIdx] += static_cast<uint32_t>(indexAccessor.count) / 3;
}
else
{
const auto& posAccessor = tinyModel.accessors[primitive.attributes.find("POSITION")->second];
meshTriangle[meshIdx] += static_cast<uint32_t>(posAccessor.count) / 3;
}
}
meshIdx++;
}
stats.nbUniqueTriangles = std::accumulate(meshTriangle.begin(), meshTriangle.end(), 0, std::plus<>());
for(auto& node : tinyModel.scenes[0].nodes)
{
stats.nbTriangles += recursiveTriangleCount(tinyModel, node, meshTriangle);
}
return stats;
}
//--------------------------------------------------------------------------------------------------
// Going through all cameras and find the position and center of interest.
// - The eye or position of the camera is found in the translation part of the matrix
// - The center of interest is arbitrary set in front of the camera to a distance equivalent
// to the eye and the center of the scene. If the camera is pointing toward the middle
// of the scene, the camera center will be equal to the scene center.
// - The up vector is always Y up for now.
//
void GltfScene::computeCamera()
{
for(auto& camera : m_cameras)
{
if(camera.eye == camera.center) // Applying the rule only for uninitialized camera.
{
camera.worldMatrix.get_translation(camera.eye);
float distance = nvmath::length(m_dimensions.center - camera.eye);
auto rotMat = camera.worldMatrix.get_rot_mat3();
camera.center = {0, 0, -distance};
camera.center = camera.eye + (rotMat * camera.center);
camera.up = {0, 1, 0};
}
}
}
void GltfScene::checkRequiredExtensions(const tinygltf::Model& tmodel)
{
std::set<std::string> supportedExtensions{
KHR_LIGHTS_PUNCTUAL_EXTENSION_NAME,
KHR_TEXTURE_TRANSFORM_EXTENSION_NAME,
KHR_MATERIALS_PBRSPECULARGLOSSINESS_EXTENSION_NAME,
KHR_MATERIALS_SPECULAR_EXTENSION_NAME,
KHR_MATERIALS_UNLIT_EXTENSION_NAME,
KHR_MATERIALS_ANISOTROPY_EXTENSION_NAME,
KHR_MATERIALS_IOR_EXTENSION_NAME,
KHR_MATERIALS_VOLUME_EXTENSION_NAME,
KHR_MATERIALS_TRANSMISSION_EXTENSION_NAME,
KHR_TEXTURE_BASISU_NAME,
};
for(auto& e : tmodel.extensionsRequired)
{
if(supportedExtensions.find(e) == supportedExtensions.end())
{
LOGE(
"\n---------------------------------------\n"
"The extension %s is REQUIRED and not supported \n",
e.c_str());
}
}
}
void GltfScene::findUsedMeshes(const tinygltf::Model& tmodel, std::set<uint32_t>& usedMeshes, int nodeIdx)
{
const auto& node = tmodel.nodes[nodeIdx];
if(node.mesh >= 0)
usedMeshes.insert(node.mesh);
for(const auto& c : node.children)
findUsedMeshes(tmodel, usedMeshes, c);
}
//--------------------------------------------------------------------------------------------------
//
//
void GltfScene::exportDrawableNodes(tinygltf::Model& tmodel, GltfAttributes requestedAttributes)
{
// We are rewriting buffer 0, but we will keep the information accessing the other buffers.
// Information will be added at the end of the function.
// Copy the buffer view and its position, such that we have a reference from the accessors
std::unordered_map<uint32_t, tinygltf::BufferView> tempBufferView;
for(uint32_t i = 0; i < (uint32_t)tmodel.bufferViews.size(); i++)
{
if(tmodel.bufferViews[i].buffer > 0)
tempBufferView[i] = tmodel.bufferViews[i];
}
// Copy all accessors, referencing to one of the temp BufferView
std::vector<tinygltf::Accessor> tempAccessors;
for(auto& accessor : tmodel.accessors)
{
if(tempBufferView.find(accessor.bufferView) != tempBufferView.end())
{
tempAccessors.push_back(accessor);
}
}
// Clear what will the rewritten
tmodel.bufferViews.clear();
tmodel.accessors.clear();
tmodel.scenes[0].nodes.clear();
// Default assets
tmodel.asset.copyright = "NVIDIA Corporation";
tmodel.asset.generator = "glTF exporter";
tmodel.asset.version = "2.0"; // glTF version 2.0
bool hasExt = std::find(tmodel.extensionsUsed.begin(), tmodel.extensionsUsed.end(), EXTENSION_ATTRIB_IRAY)
!= tmodel.extensionsUsed.end();
if(!hasExt)
tmodel.extensionsUsed.emplace_back(EXTENSION_ATTRIB_IRAY);
// Reset all information in the buffer 0
tmodel.buffers[0] = {};
auto& tBuffer = tmodel.buffers[0];
tBuffer.data.reserve(1000000000); // Reserving 1 GB to make the allocations faster (avoid re-allocations)
struct OffsetLen
{
uint32_t offset{0};
uint32_t len{0};
};
OffsetLen olIdx, olPos, olNrm, olTan, olCol, olTex;
olIdx.len = appendData(tBuffer, m_indices);
olPos.offset = olIdx.offset + olIdx.len;
olPos.len = appendData(tBuffer, m_positions);
olNrm.offset = olPos.offset + olPos.len;
if(hasFlag(requestedAttributes, GltfAttributes::Normal) && !m_normals.empty())
{
olNrm.len = appendData(tBuffer, m_normals);
}
olTex.offset = olNrm.offset + olNrm.len;
if(hasFlag(requestedAttributes, GltfAttributes::Texcoord_0) && !m_texcoords0.empty())
{
olTex.len = appendData(tBuffer, m_texcoords0);
}
olTan.offset = olTex.offset + olTex.len;
if(hasFlag(requestedAttributes, GltfAttributes::Tangent) && !m_tangents.empty())
{
olTan.len = appendData(tBuffer, m_tangents);
}
olCol.offset = olTan.offset + olTan.len;
if(hasFlag(requestedAttributes, GltfAttributes::Color_0) && !m_colors0.empty())
{
olCol.len = appendData(tBuffer, m_colors0);
}
nvmath::matrix4<double> identityMat;
identityMat.identity();
// Nodes
std::vector<tinygltf::Node> tnodes;
uint32_t nodeID = 0;
for(auto& n : m_nodes)
{
auto* node = &tnodes.emplace_back();
node->name = "";
if(n.tnode)
{
node->name = n.tnode->name;
}
// Matrix -- Adding it only if not identity
{
bool isIdentity{true};
std::vector<double> matrix(16);
for(int i = 0; i < 16; i++)
{
isIdentity &= (identityMat.get_value()[i] == n.worldMatrix.get_value()[i]);
matrix[i] = n.worldMatrix.get_value()[i];
}
if(!isIdentity)
node->matrix = matrix;
}
node->mesh = n.primMesh;
// Adding node to the scene
tmodel.scenes[0].nodes.push_back(nodeID++);
}
// Camera
if(!m_cameras.empty())
{
tmodel.cameras.clear();
auto& camera = tmodel.cameras.emplace_back();
// Copy the tiny camera
camera = m_cameras[0].cam;
// Add camera node
auto& node = tnodes.emplace_back();
node.name = "Camera";
node.camera = 0;
node.matrix.resize(16);
for(int i = 0; i < 16; i++)
node.matrix[i] = m_cameras[0].worldMatrix.get_value()[i];
// Add extension with pos, interest and up
auto fct = [](const std::string& name, nvmath::vec3f val) {
tinygltf::Value::Array tarr;
tarr.emplace_back(val.x);
tarr.emplace_back(val.y);
tarr.emplace_back(val.z);
tinygltf::Value::Object oattrib;
oattrib["name"] = tinygltf::Value(name);
oattrib["type"] = tinygltf::Value(std::string("Float32<3>"));
oattrib["value"] = tinygltf::Value(tarr);
return oattrib;
};
tinygltf::Value::Array vattrib;
vattrib.emplace_back(fct("iview:position", m_cameras[0].eye));
vattrib.emplace_back(fct("iview:interest", m_cameras[0].center));
vattrib.emplace_back(fct("iview:up", m_cameras[0].up));
tinygltf::Value::Object oattrib;
oattrib["attributes"] = tinygltf::Value(vattrib);
node.extensions[EXTENSION_ATTRIB_IRAY] = tinygltf::Value(oattrib);
// Add camera to scene
tmodel.scenes[0].nodes.push_back((int)tnodes.size() - 1);
}
tmodel.nodes = tnodes;
// Mesh/Primitive
std::vector<tinygltf::Mesh> tmeshes;
for(const auto& pm : m_primMeshes)
{
Bbox bb;
{
for(size_t v_ctx = 0; v_ctx < pm.vertexCount; v_ctx++)
{
size_t idx = pm.vertexOffset + v_ctx;
bb.insert(m_positions[idx]);
}
}
// Adding a new mesh
tmeshes.emplace_back();
auto& tMesh = tmeshes.back();
tMesh.name = pm.name;
if(pm.tmesh)
{
tMesh.name = pm.tmesh->name;
tMesh.extensions = pm.tmesh->extensions;
tMesh.extensions_json_string = pm.tmesh->extensions_json_string;
}
// primitive
tMesh.primitives.emplace_back();
auto& tPrim = tMesh.primitives.back();
tPrim.mode = TINYGLTF_MODE_TRIANGLES;
if(pm.tprim)
{
tPrim.extensions = pm.tprim->extensions;
tPrim.extensions_json_string = pm.tprim->extensions_json_string;
}
// Material reference
tPrim.material = pm.materialIndex;
// Attributes
auto& attributes = tPrim.attributes;
{
// Buffer View (INDICES)
tmodel.bufferViews.emplace_back();
auto& tBufferView = tmodel.bufferViews.back();
tBufferView.buffer = 0;
tBufferView.byteOffset = olIdx.offset + pm.firstIndex * sizeof(uint32_t);
tBufferView.byteStride = 0; // "bufferView.byteStride must not be defined for indices accessor." ;
tBufferView.byteLength = sizeof(uint32_t) * pm.indexCount;
// Accessor (INDICES)
tmodel.accessors.emplace_back();
auto& tAccessor = tmodel.accessors.back();
tAccessor.bufferView = static_cast<int>(tmodel.bufferViews.size() - 1);
tAccessor.byteOffset = 0;
tAccessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT;
tAccessor.count = pm.indexCount;
tAccessor.type = TINYGLTF_TYPE_SCALAR;
// The accessor for the indices
tPrim.indices = static_cast<int>(tmodel.accessors.size() - 1);
}
{
// Buffer View (POSITION)
tmodel.bufferViews.emplace_back();
auto& tBufferView = tmodel.bufferViews.back();
tBufferView.buffer = 0;
tBufferView.byteOffset = olPos.offset + pm.vertexOffset * sizeof(nvmath::vec3f);
tBufferView.byteStride = 3 * sizeof(float);
tBufferView.byteLength = pm.vertexCount * tBufferView.byteStride;
// Accessor (POSITION)
tmodel.accessors.emplace_back();
auto& tAccessor = tmodel.accessors.back();
tAccessor.bufferView = static_cast<int>(tmodel.bufferViews.size() - 1);
tAccessor.byteOffset = 0;
tAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
tAccessor.count = pm.vertexCount;
tAccessor.type = TINYGLTF_TYPE_VEC3;
tAccessor.minValues = {bb.min()[0], bb.min()[1], bb.min()[2]};
tAccessor.maxValues = {bb.max()[0], bb.max()[1], bb.max()[2]};
assert(tAccessor.count > 0);
attributes["POSITION"] = static_cast<int>(tmodel.accessors.size() - 1);
}
if(hasFlag(requestedAttributes, GltfAttributes::Normal) && !m_normals.empty())
{
// Buffer View (NORMAL)
tmodel.bufferViews.emplace_back();
auto& tBufferView = tmodel.bufferViews.back();
tBufferView.buffer = 0;
tBufferView.byteOffset = olNrm.offset + pm.vertexOffset * sizeof(nvmath::vec3f);
tBufferView.byteStride = 3 * sizeof(float);
tBufferView.byteLength = pm.vertexCount * tBufferView.byteStride;
// Accessor (NORMAL)
tmodel.accessors.emplace_back();
auto& tAccessor = tmodel.accessors.back();
tAccessor.bufferView = static_cast<int>(tmodel.bufferViews.size() - 1);
tAccessor.byteOffset = 0;
tAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
tAccessor.count = pm.vertexCount;
tAccessor.type = TINYGLTF_TYPE_VEC3;
attributes["NORMAL"] = static_cast<int>(tmodel.accessors.size() - 1);
}
if(hasFlag(requestedAttributes, GltfAttributes::Texcoord_0) && !m_texcoords0.empty())
{
// Buffer View (TEXCOORD_0)
tmodel.bufferViews.emplace_back();
auto& tBufferView = tmodel.bufferViews.back();
tBufferView.buffer = 0;
tBufferView.byteOffset = olTex.offset + pm.vertexOffset * sizeof(nvmath::vec2f);
tBufferView.byteStride = 2 * sizeof(float);
tBufferView.byteLength = pm.vertexCount * tBufferView.byteStride;
// Accessor (TEXCOORD_0)
tmodel.accessors.emplace_back();
auto& tAccessor = tmodel.accessors.back();
tAccessor.bufferView = static_cast<int>(tmodel.bufferViews.size() - 1);
tAccessor.byteOffset = 0;
tAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
tAccessor.count = pm.vertexCount;
tAccessor.type = TINYGLTF_TYPE_VEC2;
attributes["TEXCOORD_0"] = static_cast<int>(tmodel.accessors.size() - 1);
}
if(hasFlag(requestedAttributes, GltfAttributes::Tangent) && !m_tangents.empty())
{
// Buffer View (TANGENT)
tmodel.bufferViews.emplace_back();
auto& tBufferView = tmodel.bufferViews.back();
tBufferView.buffer = 0;
tBufferView.byteOffset = olTan.offset + pm.vertexOffset * sizeof(nvmath::vec4f);
tBufferView.byteStride = 4 * sizeof(float);
tBufferView.byteLength = pm.vertexCount * tBufferView.byteStride;
// Accessor (TANGENT)
tmodel.accessors.emplace_back();
auto& tAccessor = tmodel.accessors.back();
tAccessor.bufferView = static_cast<int>(tmodel.bufferViews.size() - 1);
tAccessor.byteOffset = 0;
tAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
tAccessor.count = pm.vertexCount;
tAccessor.type = TINYGLTF_TYPE_VEC4;
attributes["TANGENT"] = static_cast<int>(tmodel.accessors.size() - 1);
}
if(hasFlag(requestedAttributes, GltfAttributes::Color_0) && !m_colors0.empty())
{
// Buffer View (COLOR_O)
tmodel.bufferViews.emplace_back();
auto& tBufferView = tmodel.bufferViews.back();
tBufferView.buffer = 0;
tBufferView.byteOffset = olCol.offset + pm.vertexOffset * sizeof(nvmath::vec4f);
tBufferView.byteStride = 4 * sizeof(float);
tBufferView.byteLength = pm.vertexCount * tBufferView.byteStride;
// Accessor (COLOR_O)
tmodel.accessors.emplace_back();
auto& tAccessor = tmodel.accessors.back();
tAccessor.bufferView = static_cast<int>(tmodel.bufferViews.size() - 1);
tAccessor.byteOffset = 0;
tAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
tAccessor.count = pm.vertexCount;
tAccessor.type = TINYGLTF_TYPE_VEC4;
attributes["COLOR_O"] = static_cast<int>(tmodel.accessors.size() - 1);
}
}
tmodel.meshes = tmeshes;
// Add back accessors for extra buffers (see collection at start of function)
std::unordered_map<uint32_t, uint32_t> correspondance;
for(auto& t : tempBufferView)
{
correspondance[t.first] = (uint32_t)tmodel.bufferViews.size(); // remember position of buffer view
tmodel.bufferViews.push_back(t.second);
}
for(auto& t : tempAccessors)
{
t.bufferView = correspondance[t.bufferView]; // Find from previous buffer view, the new index
tmodel.accessors.push_back(t);
}
}
} // namespace nvh