#include #include #include #include #include #include #include #include #include #if !defined(__ANDROID__) && !defined(_WIN32) #include #endif #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Weverything" #endif #include "../../json.hpp" using json = nlohmann::json; #ifdef __clang__ #pragma clang diagnostic pop #endif #ifdef _WIN32 #include "../../tiny_gltf.h" #else #include "tiny_gltf.h" #endif #include "mesh-util.hh" #ifdef __clang__ #pragma clang diagnostic ignored "-Wunused-function" #endif namespace { static std::string PrintMode(int mode) { if (mode == TINYGLTF_MODE_POINTS) { return "POINTS"; } else if (mode == TINYGLTF_MODE_LINE) { return "LINE"; } else if (mode == TINYGLTF_MODE_LINE_LOOP) { return "LINE_LOOP"; } else if (mode == TINYGLTF_MODE_TRIANGLES) { return "TRIANGLES"; } else if (mode == TINYGLTF_MODE_TRIANGLE_FAN) { return "TRIANGLE_FAN"; } else if (mode == TINYGLTF_MODE_TRIANGLE_STRIP) { return "TRIANGLE_STRIP"; } return "**UNKNOWN**"; } static std::string PrintTarget(int target) { if (target == 34962) { return "GL_ARRAY_BUFFER"; } else if (target == 34963) { return "GL_ELEMENT_ARRAY_BUFFER"; } else { return "**UNKNOWN**"; } } static std::string PrintType(int ty) { if (ty == TINYGLTF_TYPE_SCALAR) { return "SCALAR"; } else if (ty == TINYGLTF_TYPE_VECTOR) { return "VECTOR"; } else if (ty == TINYGLTF_TYPE_VEC2) { return "VEC2"; } else if (ty == TINYGLTF_TYPE_VEC3) { return "VEC3"; } else if (ty == TINYGLTF_TYPE_VEC4) { return "VEC4"; } else if (ty == TINYGLTF_TYPE_MATRIX) { return "MATRIX"; } else if (ty == TINYGLTF_TYPE_MAT2) { return "MAT2"; } else if (ty == TINYGLTF_TYPE_MAT3) { return "MAT3"; } else if (ty == TINYGLTF_TYPE_MAT4) { return "MAT4"; } return "**UNKNOWN**"; } static std::string PrintComponentType(int ty) { if (ty == TINYGLTF_COMPONENT_TYPE_BYTE) { return "BYTE"; } else if (ty == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { return "UNSIGNED_BYTE"; } else if (ty == TINYGLTF_COMPONENT_TYPE_SHORT) { return "SHORT"; } else if (ty == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { return "UNSIGNED_SHORT"; } else if (ty == TINYGLTF_COMPONENT_TYPE_INT) { return "INT"; } else if (ty == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { return "UNSIGNED_INT"; } else if (ty == TINYGLTF_COMPONENT_TYPE_FLOAT) { return "FLOAT"; } else if (ty == TINYGLTF_COMPONENT_TYPE_DOUBLE) { return "DOUBLE"; } return "**UNKNOWN**"; } #if 0 // TODO(syoyo): Support sparse accessor(sparse vertex attribute). // TODO(syoyo): Support more data type struct VertexAttrib { std::string name; // Value are converted to float type. std::vector data; // Corresponding info in binary data int data_type; // e.g. TINYGLTF_TYPE_VEC2 int component_type; // storage type. e.g. // TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT uint64_t buffer_offset{0}; size_t buffer_length{0}; }; struct MeshPrim { std::string name; int32_t id{-1}; int mode{TINYGLTF_MODE_TRIANGLES}; VertexAttrib position; // vec3 VertexAttrib normal; // vec3 VertexAttrib tangent; // vec4 VertexAttrib color; // vec3 or vec4 std::map texcoords; // vec2 std::map weights; // std::map joints; // store data as float type int indices_type{-1}; // storage type(componentType) of `indices`. std::vector indices; // vertex indices }; #endif #if 0 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 ""; } #endif static size_t ComponentTypeByteSize(int type) { switch (type) { case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: case TINYGLTF_COMPONENT_TYPE_BYTE: return sizeof(char); case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: case TINYGLTF_COMPONENT_TYPE_SHORT: return sizeof(short); case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: case TINYGLTF_COMPONENT_TYPE_INT: return sizeof(int); case TINYGLTF_COMPONENT_TYPE_FLOAT: return sizeof(float); case TINYGLTF_COMPONENT_TYPE_DOUBLE: return sizeof(double); default: return 0; } } std::vector LoadBin(const std::string &filename) { std::vector data; std::ifstream is(filename, std::ios::binary | std::ios::in | std::ios::ate); if (is.is_open()) { size_t size = size_t(is.tellg()); is.seekg(0, std::ios::beg); if (size < 4) { std::cerr << "File size is zero or too short: " << size << "\n"; return data; } data.resize(size); is.read(reinterpret_cast(data.data()), std::streamsize(size)); } return data; } // TODO(syoyo): Use C++17 like filesystem library bool FileExists(const std::string &abs_filename) { bool ret; #ifdef _WIN32 // TODO(syoyo): Support utf8 filepath FILE *fp = nullptr; errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb"); if (err != 0) { return false; } #else FILE *fp = fopen(abs_filename.c_str(), "rb"); #endif if (fp) { ret = true; fclose(fp); } else { ret = false; } return ret; } static std::string JoinPath(const std::string &path0, const std::string &path1) { if (path0.empty()) { return path1; } else { // check '/' char lastChar = *path0.rbegin(); if (lastChar != '/') { return path0 + std::string("/") + path1; } else { return path0 + path1; } } } std::string ExpandFilePath(const std::string &filepath) { #ifdef _WIN32 DWORD len = ExpandEnvironmentStringsA(filepath.c_str(), NULL, 0); char *str = new char[len]; ExpandEnvironmentStringsA(filepath.c_str(), str, len); std::string s(str); delete[] str; return s; #else #if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \ defined(__ANDROID__) || defined(__EMSCRIPTEN__) // no expansion std::string s = filepath; #else std::string s; wordexp_t p; if (filepath.empty()) { return ""; } // Quote the string to keep any spaces in filepath intact. std::string quoted_path = "\"" + filepath + "\""; // char** w; int ret = wordexp(quoted_path.c_str(), &p, 0); if (ret) { // err s = filepath; return s; } // Use first element only. if (p.we_wordv) { s = std::string(p.we_wordv[0]); wordfree(&p); } else { s = filepath; } #endif return s; #endif } static std::string FindFile(const std::vector &paths, const std::string &filepath) { for (size_t i = 0; i < paths.size(); i++) { std::string absPath = ExpandFilePath(JoinPath(paths[i], filepath)); if (FileExists(absPath)) { return absPath; } } return std::string(); } #if 0 static std::string GetBaseDir(const std::string &filepath) { if (filepath.find_last_of("/\\") != std::string::npos) return filepath.substr(0, filepath.find_last_of("/\\")); return ""; } #endif static int GetSlotId(const std::string &name) { if (name.rfind("TEXCOORD_", 0) == 0) { int id = 0; sscanf(name.c_str(), "TEXCOORD_%d", &id); return id; } else if (name.rfind("WEIGHTS_", 0) == 0) { int id = 0; sscanf(name.c_str(), "WEIGHTS_%d", &id); return id; } else if (name.rfind("JOINTS_", 0) == 0) { int id = 0; sscanf(name.c_str(), "JOINTS_%d", &id); return id; } return -1; } static bool IsAttributeSupported(const std::string &name) { constexpr int max_slots = 8; if (name.compare("POSITION") == 0) { return true; } if (name.compare("NORMAL") == 0) { return true; } if (name.compare("TANGENT") == 0) { return true; } for (int i = 0; i < max_slots; i++) { std::string n = "TEXCOORD_" + std::to_string(i); if (n.compare(name) == 0) { return true; } } for (int i = 0; i < max_slots; i++) { std::string n = "WEIGHTS_" + std::to_string(i); if (n.compare(name) == 0) { return true; } } for (int i = 0; i < max_slots; i++) { std::string n = "JOINTS_" + std::to_string(i); if (n.compare(name) == 0) { return true; } } return false; } static float Unpack(const unsigned char *ptr, int type) { if (type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { unsigned char data = *ptr; return float(data); } else if (type == TINYGLTF_COMPONENT_TYPE_BYTE) { char data = static_cast(*ptr); return float(data); } else if (type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { uint16_t data = *reinterpret_cast(ptr); return float(data); } else if (type == TINYGLTF_COMPONENT_TYPE_SHORT) { int16_t data = *reinterpret_cast(ptr); return float(data); } else if (type == TINYGLTF_COMPONENT_TYPE_FLOAT) { float data = *reinterpret_cast(ptr); return data; } else { std::cerr << "???: Unsupported type: " << PrintComponentType(type) << "\n"; return 0.0f; } } static uint32_t UnpackIndex(const unsigned char *ptr, int type) { if (type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { unsigned char data = *ptr; return uint32_t(data); } else if (type == TINYGLTF_COMPONENT_TYPE_BYTE) { char data = static_cast(*ptr); return uint32_t(data); } else if (type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { uint16_t data = *reinterpret_cast(ptr); return uint32_t(data); } else if (type == TINYGLTF_COMPONENT_TYPE_SHORT) { int16_t data = *reinterpret_cast(ptr); return uint32_t(data); } else if (type == TINYGLTF_COMPONENT_TYPE_INT) { // TODO(syoyo): Check overflow(2G+ index) int32_t data = *reinterpret_cast(ptr); return uint32_t(data); } else if (type == TINYGLTF_COMPONENT_TYPE_SHORT) { uint32_t data = *reinterpret_cast(ptr); return data; } else { std::cerr << "???: Unsupported type: " << PrintComponentType(type) << "\n"; return static_cast(-1); } } static bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh, example::MeshPrim *out) { for (size_t i = 0; i < mesh.primitives.size(); i++) { const tinygltf::Primitive &primitive = mesh.primitives[i]; if (primitive.indices < 0) { std::cerr << "Primitive indices must be provided\n"; return false; } // indices. { const tinygltf::Accessor &indexAccessor = model.accessors[size_t(primitive.indices)]; size_t num_elements = indexAccessor.count; std::cout << "index.elements = " << num_elements << "\n"; size_t byte_stride = ComponentTypeByteSize(indexAccessor.componentType); const tinygltf::BufferView &indexBufferView = model.bufferViews[size_t(indexAccessor.bufferView)]; // should be 34963(ELEMENT_ARRAY_BUFFER) std::cout << "index.target = " << PrintTarget(indexBufferView.target) << "\n"; if (indexBufferView.target != TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) { std::cerr << "indexBufferView.target must be ELEMENT_ARRAY_BUFFER\n"; return false; } const tinygltf::Buffer &indexBuffer = model.buffers[size_t(indexBufferView.buffer)]; std::vector indices; for (size_t k = 0; k < num_elements; k++) { // TODO(syoyo): out-of-bounds check. const unsigned char *ptr = indexBuffer.data.data() + indexBufferView.byteOffset + (k * byte_stride) + indexAccessor.byteOffset; uint32_t idx = UnpackIndex(ptr, indexAccessor.componentType); std::cout << "vertex_index[" << k << "] = " << idx << "\n"; indices.push_back(idx); } out->indices = indices; out->indices_type = indexAccessor.componentType; } // attributes { std::map::const_iterator it( primitive.attributes.begin()); std::map::const_iterator itEnd( primitive.attributes.end()); for (; it != itEnd; it++) { // it->first would be "POSITION", "NORMAL", "TEXCOORD_0", ... if (!IsAttributeSupported(it->first)) { std::cout << "Unsupported attribute: " << it->first << "\n"; continue; } if (size_t(it->second) >= model.accessors.size()) { std::cerr << "Invalid accessor id: " << it->second << "\n"; return false; } const tinygltf::Accessor &accessor = model.accessors[size_t(it->second)]; size_t elem_size = 1; if (accessor.type == TINYGLTF_TYPE_SCALAR) { elem_size = 1; } else if (accessor.type == TINYGLTF_TYPE_VEC2) { elem_size = 2; } else if (accessor.type == TINYGLTF_TYPE_VEC3) { elem_size = 3; } else if (accessor.type == TINYGLTF_TYPE_VEC4) { elem_size = 4; } else { std::cerr << "Invalid or unsupported accessor type: " << PrintType(accessor.type) << "\n"; return false; } std::cout << PrintComponentType(accessor.componentType) << "\n"; size_t byte_stride = ComponentTypeByteSize(accessor.componentType); std::cout << "attribute: " << it->first << "\n"; // if (gGLProgramState.attribs[it->first] >= 0) { // Compute byteStride from Accessor + BufferView combination. int byteStride = accessor.ByteStride(model.bufferViews[size_t(accessor.bufferView)]); assert(byteStride != -1); std::cout << "byteOffset: " << accessor.byteOffset << "\n"; const tinygltf::BufferView &bufferView = model.bufferViews[size_t(accessor.bufferView)]; const tinygltf::Buffer &buffer = model.buffers[size_t(bufferView.buffer)]; size_t num_elems = accessor.count * elem_size; example::VertexAttrib attrib; for (size_t k = 0; k < num_elems; k++) { // TODO(syoyo): out-of-bounds check. const unsigned char *ptr = buffer.data.data() + bufferView.byteOffset + (k * byte_stride) + accessor.byteOffset; float value = Unpack(ptr, accessor.componentType); std::cout << "[" << k << "] value = " << value << "\n"; attrib.data.push_back(value); } attrib.component_type = accessor.componentType; attrib.data_type = accessor.type; attrib.name = it->first; if (attrib.name.compare("POSITION") == 0) { out->position = attrib; } else if (attrib.name.compare("NORMAL") == 0) { out->normal = attrib; } else if (attrib.name.compare("TANGENT") == 0) { out->tangent = attrib; } else if (attrib.name.rfind("TEXCOORD_", 0) == 0) { int id = GetSlotId(attrib.name); std::cout << "texcoord[" << id << "]\n"; out->texcoords[id] = attrib; } else if (attrib.name.rfind("JOINTS_", 0) == 0) { int id = GetSlotId(attrib.name); std::cout << "joints[" << id << "]\n"; out->joints[id] = attrib; } else if (attrib.name.rfind("WEIGHTS_", 0) == 0) { int id = GetSlotId(attrib.name); std::cout << "weights[" << id << "]\n"; out->weights[id] = attrib; } else { std::cerr << "???: attrib.name = " << attrib.name << "\n"; return false; } } } const tinygltf::Accessor &indexAccessor = model.accessors[size_t(primitive.indices)]; (void)indexAccessor; PrintMode(primitive.mode); if (primitive.mode != TINYGLTF_MODE_TRIANGLES) { std::cerr << "Supported Primitive mode is TRIANGLES only at the moment\n"; return false; } out->mode = primitive.mode; out->name = mesh.name; } return true; } #if 0 static bool ExtractMesh(const std::string &asset_path, tinygltf::Model &model, std::vector *outs) { // Get .bin data { if (model.buffers.size() != 1) { std::cerr << "Single element of `buffers` is supported at the moment.\n"; return false; } const tinygltf::Buffer &buffer = model.buffers[0]; if (buffer.uri.empty()) { std::cerr << "buffer.uri must be a filepath.\n"; return false; } if (buffer.data.size() < 4) { std::cerr << "Invalid buffer.byteLength.\n"; return false; } std::vector search_paths; search_paths.push_back(asset_path); std::string abs_filepath = FindFile(search_paths, buffer.uri); std::vector bin = LoadBin(buffer.uri); if (bin.size() != buffer.data.size()) { std::cerr << "Byte size mismatch. Failed to load file: " << buffer.uri << "\n"; std::cerr << " .bin size = " << bin.size() << ", size in 'buffer.uri' = " << buffer.data.size() << "\n"; return false; } } for (const auto &mesh : model.meshes) { std::cout << "mesh.name: " << mesh.name << "\n"; example::MeshPrim output; bool ret = DumpMesh(model, mesh, &output); if (!ret) { return false; } outs->push_back(output); } return true; } #endif } // namespace int main(int argc, char **argv) { if (argc < 2) { std::cout << "mesh-dump input.gltf" << std::endl; } #if 0 tinygltf::Model model; tinygltf::TinyGLTF loader; std::string err; std::string warn; #ifdef _WIN32 std::string input_filename(argv[1] ? argv[1] : "../../../models/Cube/Cube.gltf"); #else std::string input_filename(argv[1] ? argv[1] : "../../models/Cube/Cube.gltf"); #endif std::string ext = GetFilePathExtension(input_filename); { bool ret = false; if (ext.compare("glb") == 0) { // assume binary glTF. ret = loader.LoadBinaryFromFile(&model, &err, &warn, input_filename.c_str()); } else { // assume ascii glTF. ret = loader.LoadASCIIFromFile(&model, &err, &warn, input_filename.c_str()); } if (!warn.empty()) { printf("Warn: %s\n", warn.c_str()); } if (!err.empty()) { printf("ERR: %s\n", err.c_str()); } if (!ret) { printf("Failed to load .glTF : %s\n", argv[1]); exit(-1); } } json j; { std::ifstream i(input_filename); i >> j; } std::cout << "j = " << j << "\n"; json j_patch = R"([ { "op": "add", "path": "/buffers/-", "value": { "name": "plane/data", "byteLength": 480, "uri": "plane1.bin" } } ])"_json; // a JSON value json j_original = R"({ "baz": ["one", "two", "three"], "foo": "bar" })"_json; //json j_patch = R"([ // { "op": "remove", "path": "/buffers" } //])"_json; std::cout << "patch = " << j_patch.dump(2) << "\n"; json j_ret = j.patch(j_patch); std::cout << "patched = " << j_ret.dump(2) << "\n"; std::string basedir = GetBaseDir(input_filename); std::vector meshes; bool ret = ExtractMesh(basedir, model, &meshes); size_t n = 0; for (const auto &mesh : meshes) { // Assume no duplicated name in .glTF data std::string filename; if (mesh.name.empty()) { filename = "untitled-" + std::to_string(n) + ".obj"; } else { filename = mesh.name + ".obj"; } bool flip_y = true; // flip texcoord Y? bool ok = example::SaveAsObjMesh(filename, mesh,); if (!ok) { return EXIT_FAILURE; } n++; } return ret ? EXIT_SUCCESS : EXIT_FAILURE; #else { std::string input_filename(argv[1]); // Require facevarying layout? // false = try to keep GL-like mesh data as much as possible. // true = reorder vertex data and re-assign vertex indices. bool facevarying = false; example::MeshPrim mesh; bool ok = example::LoadObjMesh(input_filename, facevarying, &mesh); if (!ok) { return EXIT_FAILURE; } PrintMeshPrim(mesh); std::string output_filename("output.gltf"); ok = example::SaveAsGLTFMesh(output_filename, mesh); if (!ok) { std::cerr << "Failed to save mesh as glTF\n"; return EXIT_FAILURE; } std::cout << "Write glTF: " << output_filename << "\n"; } return EXIT_SUCCESS; #endif }