#include "gltf-loader.h" #include #include // c++11 #define TINYGLTF_IMPLEMENTATION #include namespace example { static std::string GetFilePathExtension(const std::string &FileName) { if (FileName.find_last_of(".") != std::string::npos) return FileName.substr(FileName.find_last_of(".") + 1); return ""; } /// /// Loads glTF 2.0 mesh /// bool LoadGLTF(const std::string &filename, float scale, std::vector > *meshes, std::vector *materials, std::vector *textures) { // TODO(syoyo): Texture // TODO(syoyo): Material tinygltf::Model model; tinygltf::TinyGLTF loader; std::string err; const std::string ext = GetFilePathExtension(filename); bool ret = false; if (ext.compare("glb") == 0) { // assume binary glTF. ret = loader.LoadBinaryFromFile(&model, &err, filename.c_str()); } else { // assume ascii glTF. ret = loader.LoadASCIIFromFile(&model, &err, filename.c_str()); } if (!err.empty()) { std::cerr << "glTF parse error: " << err << std::endl; } if (!ret) { std::cerr << "Failed to load glTF: " << filename << std::endl; return false; } std::cout << "loaded glTF file has:\n" << model.accessors.size() << " accessors\n" << model.animations.size() << " animations\n" << model.buffers.size() << " buffers\n" << model.bufferViews.size() << " bufferViews\n" << model.materials.size() << " materials\n" << model.meshes.size() << " meshes\n" << model.nodes.size() << " nodes\n" << model.textures.size() << " textures\n" << model.images.size() << " images\n" << model.skins.size() << " skins\n" << model.samplers.size() << " samplers\n" << model.cameras.size() << " cameras\n" << model.scenes.size() << " scenes\n" << model.lights.size() << " lights\n"; // Iterate through all the meshes in the glTF file for (const auto &gltfMesh : model.meshes) { std::cout << "Current mesh has " << gltfMesh.primitives.size() << " primitives:\n"; // Create a mesh object Mesh loadedMesh(sizeof(float) * 3); // To store the min and max of the buffer (as 3D vector of floats) v3f pMin = {}, pMax = {}; // Store the name of the glTF mesh (if defined) loadedMesh.name = gltfMesh.name; // For each primitive for (const auto &meshPrimitive : gltfMesh.primitives) { // Boolean used to check if we have converted the vertex buffer format bool convertedToTriangleList = false; // This permit to get a type agnostic way of reading the index buffer std::unique_ptr indicesArrayPtr = nullptr; { const auto &indicesAccessor = model.accessors[meshPrimitive.indices]; const auto &bufferView = model.bufferViews[indicesAccessor.bufferView]; const auto &buffer = model.buffers[bufferView.buffer]; const auto dataAddress = buffer.data.data() + bufferView.byteOffset + indicesAccessor.byteOffset; const auto byteStride = indicesAccessor.ByteStride(bufferView); const auto count = indicesAccessor.count; // Allocate the index array in the pointer-to-base declared in the // parent scope switch (indicesAccessor.componentType) { case TINYGLTF_COMPONENT_TYPE_BYTE: indicesArrayPtr = std::unique_ptr >(new intArray( arrayAdapter(dataAddress, count, byteStride))); break; case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: indicesArrayPtr = std::unique_ptr >( new intArray(arrayAdapter( dataAddress, count, byteStride))); break; case TINYGLTF_COMPONENT_TYPE_SHORT: indicesArrayPtr = std::unique_ptr >(new intArray( arrayAdapter(dataAddress, count, byteStride))); break; case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: indicesArrayPtr = std::unique_ptr >( new intArray(arrayAdapter( dataAddress, count, byteStride))); break; case TINYGLTF_COMPONENT_TYPE_INT: indicesArrayPtr = std::unique_ptr >(new intArray( arrayAdapter(dataAddress, count, byteStride))); break; case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: indicesArrayPtr = std::unique_ptr >( new intArray(arrayAdapter( dataAddress, count, byteStride))); break; default: break; } } const auto &indices = *indicesArrayPtr; if (indicesArrayPtr) { std::cout << "indices: "; for (size_t i(0); i < indicesArrayPtr->size(); ++i) { std::cout << indices[i] << " "; loadedMesh.faces.push_back(indices[i]); } std::cout << '\n'; } switch (meshPrimitive.mode) { // We re-arrange the indices so that it describe a simple list of // triangles case TINYGLTF_MODE_TRIANGLE_FAN: if (!convertedToTriangleList) { std::cout << "TRIANGLE_FAN\n"; // This only has to be done once per primitive convertedToTriangleList = true; // We steal the guts of the vector auto triangleFan = std::move(loadedMesh.faces); loadedMesh.faces.clear(); // Push back the indices that describe just one triangle one by one for (size_t i{2}; i < triangleFan.size(); ++i) { loadedMesh.faces.push_back(triangleFan[0]); loadedMesh.faces.push_back(triangleFan[i - 1]); loadedMesh.faces.push_back(triangleFan[i]); } } case TINYGLTF_MODE_TRIANGLE_STRIP: if (!convertedToTriangleList) { std::cout << "TRIANGLE_STRIP\n"; // This only has to be done once per primitive convertedToTriangleList = true; auto triangleStrip = std::move(loadedMesh.faces); loadedMesh.faces.clear(); for (size_t i{2}; i < triangleStrip.size(); ++i) { loadedMesh.faces.push_back(triangleStrip[i - 2]); loadedMesh.faces.push_back(triangleStrip[i - 1]); loadedMesh.faces.push_back(triangleStrip[i]); } } case TINYGLTF_MODE_TRIANGLES: // this is the simpliest case to handle { std::cout << "TRIANGLES\n"; for (const auto &attribute : meshPrimitive.attributes) { const auto attribAccessor = model.accessors[attribute.second]; const auto &bufferView = model.bufferViews[attribAccessor.bufferView]; const auto &buffer = model.buffers[bufferView.buffer]; const auto dataPtr = buffer.data.data() + bufferView.byteOffset + attribAccessor.byteOffset; const auto byte_stride = attribAccessor.ByteStride(bufferView); const auto count = attribAccessor.count; std::cout << "current attribute has count " << count << " and stride " << byte_stride << " bytes\n"; std::cout << "attribute string is : " << attribute.first << '\n'; if (attribute.first == "POSITION") { std::cout << "found position attribute\n"; // get the position min/max for computing the boundingbox pMin.x = attribAccessor.minValues[0]; pMin.y = attribAccessor.minValues[1]; pMin.z = attribAccessor.minValues[2]; pMax.x = attribAccessor.maxValues[0]; pMax.y = attribAccessor.maxValues[1]; pMax.z = attribAccessor.maxValues[2]; switch (attribAccessor.type) { case TINYGLTF_TYPE_VEC3: { switch (attribAccessor.componentType) { case TINYGLTF_COMPONENT_TYPE_FLOAT: std::cout << "Type is FLOAT\n"; // 3D vector of float v3fArray positions( arrayAdapter(dataPtr, count, byte_stride)); std::cout << "positions's size : " << positions.size() << '\n'; for (size_t i{0}; i < positions.size(); ++i) { const auto v = positions[i]; std::cout << "positions[" << i << "]: (" << v.x << ", " << v.y << ", " << v.z << ")\n"; loadedMesh.vertices.push_back(v.x * scale); loadedMesh.vertices.push_back(v.y * scale); loadedMesh.vertices.push_back(v.z * scale); } } break; case TINYGLTF_COMPONENT_TYPE_DOUBLE: { std::cout << "Type is DOUBLE\n"; switch (attribAccessor.type) { case TINYGLTF_TYPE_VEC3: { v3dArray positions( arrayAdapter(dataPtr, count, byte_stride)); for (size_t i{0}; i < positions.size(); ++i) { const auto v = positions[i]; std::cout << "positions[" << i << "]: (" << v.x << ", " << v.y << ", " << v.z << ")\n"; loadedMesh.vertices.push_back(v.x * scale); loadedMesh.vertices.push_back(v.y * scale); loadedMesh.vertices.push_back(v.z * scale); } } break; default: // TODO Handle error break; } break; default: break; } } break; } } if (attribute.first == "NORMAL") { std::cout << "found normal attribute\n"; switch (attribAccessor.type) { case TINYGLTF_TYPE_VEC3: { std::cout << "Normal is VEC3\n"; switch (attribAccessor.componentType) { case TINYGLTF_COMPONENT_TYPE_FLOAT: { std::cout << "Normal is FLOAT\n"; v3fArray normals( arrayAdapter(dataPtr, count, byte_stride)); // IMPORTANT: We need to reorder normals (and texture // coordinates into "facevarying" order) for each face // For each triangle : for (size_t i{0}; i < indices.size() / 3; ++i) { // get the i'th triange's indexes auto f0 = indices[3 * i + 0]; auto f1 = indices[3 * i + 1]; auto f2 = indices[3 * i + 2]; // get the 3 normal vectors for that face v3f n0, n1, n2; n0 = normals[f0]; n1 = normals[f1]; n2 = normals[f2]; // Put them in the array in the correct order loadedMesh.facevarying_normals.push_back(n0.x); loadedMesh.facevarying_normals.push_back(n0.y); loadedMesh.facevarying_normals.push_back(n0.z); loadedMesh.facevarying_normals.push_back(n1.x); loadedMesh.facevarying_normals.push_back(n1.y); loadedMesh.facevarying_normals.push_back(n2.z); loadedMesh.facevarying_normals.push_back(n2.x); loadedMesh.facevarying_normals.push_back(n2.y); loadedMesh.facevarying_normals.push_back(n2.z); } } break; case TINYGLTF_COMPONENT_TYPE_DOUBLE: { std::cout << "Normal is DOUBLE\n"; v3dArray normals( arrayAdapter(dataPtr, count, byte_stride)); // IMPORTANT: We need to reorder normals (and texture // coordinates into "facevarying" order) for each face // For each triangle : for (size_t i{0}; i < indices.size() / 3; ++i) { // get the i'th triange's indexes auto f0 = indices[3 * i + 0]; auto f1 = indices[3 * i + 1]; auto f2 = indices[3 * i + 2]; // get the 3 normal vectors for that face v3d n0, n1, n2; n0 = normals[f0]; n1 = normals[f1]; n2 = normals[f2]; // Put them in the array in the correct order loadedMesh.facevarying_normals.push_back(n0.x); loadedMesh.facevarying_normals.push_back(n0.y); loadedMesh.facevarying_normals.push_back(n0.z); loadedMesh.facevarying_normals.push_back(n1.x); loadedMesh.facevarying_normals.push_back(n1.y); loadedMesh.facevarying_normals.push_back(n2.z); loadedMesh.facevarying_normals.push_back(n2.x); loadedMesh.facevarying_normals.push_back(n2.y); loadedMesh.facevarying_normals.push_back(n2.z); } } break; default: std::cerr << "Unhandeled componant type for normal\n"; } } break; default: std::cerr << "Unhandeled vector type for normal\n"; } // Face varying comment on the normals is also true for the UVs if (attribute.first == "TEXCOORD_0") { std::cout << "Found texture coordinates\n"; switch (attribAccessor.type) { case TINYGLTF_TYPE_VEC2: { std::cout << "TEXTCOORD is VEC2\n"; switch (attribAccessor.componentType) { case TINYGLTF_COMPONENT_TYPE_FLOAT: { std::cout << "TEXTCOORD is FLOAT\n"; v2fArray uvs( arrayAdapter(dataPtr, count, byte_stride)); for (size_t i{0}; i < indices.size() / 3; ++i) { // get the i'th triange's indexes auto f0 = indices[3 * i + 0]; auto f1 = indices[3 * i + 1]; auto f2 = indices[3 * i + 2]; // get the texture coordinates for each triangle's // vertices v2f uv0, uv1, uv2; uv0 = uvs[f0]; uv1 = uvs[f1]; uv2 = uvs[f2]; // push them in order into the mesh data loadedMesh.facevarying_uvs.push_back(uv0.x); loadedMesh.facevarying_uvs.push_back(uv0.y); loadedMesh.facevarying_uvs.push_back(uv1.x); loadedMesh.facevarying_uvs.push_back(uv1.y); loadedMesh.facevarying_uvs.push_back(uv2.x); loadedMesh.facevarying_uvs.push_back(uv2.y); } } break; case TINYGLTF_COMPONENT_TYPE_DOUBLE: { std::cout << "TEXTCOORD is DOUBLE\n"; v2dArray uvs( arrayAdapter(dataPtr, count, byte_stride)); for (size_t i{0}; i < indices.size() / 3; ++i) { // get the i'th triange's indexes auto f0 = indices[3 * i + 0]; auto f1 = indices[3 * i + 1]; auto f2 = indices[3 * i + 2]; v2d uv0, uv1, uv2; uv0 = uvs[f0]; uv1 = uvs[f1]; uv2 = uvs[f2]; loadedMesh.facevarying_uvs.push_back(uv0.x); loadedMesh.facevarying_uvs.push_back(uv0.y); loadedMesh.facevarying_uvs.push_back(uv1.x); loadedMesh.facevarying_uvs.push_back(uv1.y); loadedMesh.facevarying_uvs.push_back(uv2.x); loadedMesh.facevarying_uvs.push_back(uv2.y); } } break; default: std::cerr << "unrecognized vector type for UV"; } } break; default: std::cerr << "unreconized componant type for UV"; } } } } break; default: std::cerr << "primitive mode not implemented"; break; // These aren't triangles: case TINYGLTF_MODE_POINTS: case TINYGLTF_MODE_LINE: case TINYGLTF_MODE_LINE_LOOP: std::cerr << "primitive is not triangle based, ignoring"; } } // bbox : v3f bCenter; bCenter.x = 0.5f * (pMax.x - pMin.x) + pMin.x; bCenter.y = 0.5f * (pMax.y - pMin.y) + pMin.y; bCenter.z = 0.5f * (pMax.z - pMin.z) + pMin.z; for (size_t v = 0; v < loadedMesh.vertices.size() / 3; v++) { loadedMesh.vertices[3 * v + 0] -= bCenter.x; loadedMesh.vertices[3 * v + 1] -= bCenter.y; loadedMesh.vertices[3 * v + 2] -= bCenter.z; } loadedMesh.pivot_xform[0][0] = 1.0f; loadedMesh.pivot_xform[0][1] = 0.0f; loadedMesh.pivot_xform[0][2] = 0.0f; loadedMesh.pivot_xform[0][3] = 0.0f; loadedMesh.pivot_xform[1][0] = 0.0f; loadedMesh.pivot_xform[1][1] = 1.0f; loadedMesh.pivot_xform[1][2] = 0.0f; loadedMesh.pivot_xform[1][3] = 0.0f; loadedMesh.pivot_xform[2][0] = 0.0f; loadedMesh.pivot_xform[2][1] = 0.0f; loadedMesh.pivot_xform[2][2] = 1.0f; loadedMesh.pivot_xform[2][3] = 0.0f; loadedMesh.pivot_xform[3][0] = bCenter.x; loadedMesh.pivot_xform[3][1] = bCenter.y; loadedMesh.pivot_xform[3][2] = bCenter.z; loadedMesh.pivot_xform[3][3] = 1.0f; // TODO handle materials for (size_t i{0}; i < loadedMesh.faces.size(); ++i) loadedMesh.material_ids.push_back(materials->at(0).id); meshes->push_back(loadedMesh); ret = true; } } // Iterate through all texture declaration in glTF file for (const auto &gltfTexture : model.textures) { std::cout << "Found texture!"; Texture loadedTexture; const auto &image = model.images[gltfTexture.source]; loadedTexture.components = image.component; loadedTexture.width = image.width; loadedTexture.height = image.height; const auto size = image.component * image.width * image.height * sizeof(unsigned char); loadedTexture.image = new unsigned char[size]; memcpy(loadedTexture.image, image.image.data(), size); textures->push_back(loadedTexture); } return ret; } } // namespace example