From 8ec9af7f2efe08690b8502c6e0003187fd0a0c30 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 8 Mar 2020 22:13:39 +0900 Subject: [PATCH] Initial mesh-dump example. --- examples/mesh-dump/Makefile | 4 + examples/mesh-dump/README.md | 13 + examples/mesh-dump/mesh-dump.cc | 575 ++++++++++++++++++++++++++++++++ 3 files changed, 592 insertions(+) create mode 100644 examples/mesh-dump/Makefile create mode 100644 examples/mesh-dump/README.md create mode 100644 examples/mesh-dump/mesh-dump.cc diff --git a/examples/mesh-dump/Makefile b/examples/mesh-dump/Makefile new file mode 100644 index 0000000..8c29968 --- /dev/null +++ b/examples/mesh-dump/Makefile @@ -0,0 +1,4 @@ +EXTRA_CXXFLAGS := -fsanitize=address -fno-omit-frame-pointer -Wall -Werror -Weverything -Wno-c++11-long-long -Wno-c++98-compat + +all: + clang++ -std=c++11 -I../../ $(EXTRA_CXXFLAGS) -o mesh-dump mesh-dump.cc diff --git a/examples/mesh-dump/README.md b/examples/mesh-dump/README.md new file mode 100644 index 0000000..7d2975c --- /dev/null +++ b/examples/mesh-dump/README.md @@ -0,0 +1,13 @@ +# Mesh dump experiment + +Sometimes we want to tweak mesh attributes(e.g. vertex position, uv coord, etc). +glTF itself does not allow ASCII representation of such data. + +This example show how to + +- Export mesh data from .bin to .csv +- Import mesh data to .bin(update corresponding buffer data) from .csv + +## Requirement + +Assume Buffer is stored as external file(`.bin`) diff --git a/examples/mesh-dump/mesh-dump.cc b/examples/mesh-dump/mesh-dump.cc new file mode 100644 index 0000000..c2fa233 --- /dev/null +++ b/examples/mesh-dump/mesh-dump.cc @@ -0,0 +1,575 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define TINYGLTF_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 +#include "../../tiny_gltf.h" +#else +#include "tiny_gltf.h" +#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**"; +} + +#if 0 +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**"; + } +} +#endif + +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**"; +} + +// 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 +}; + +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 ""; +} + +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(); +} + +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 ""; +} + +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 bool DumpMesh(const tinygltf::Model &model, const tinygltf::Mesh &mesh, + MeshPrim *out) { + for (size_t i = 0; i < mesh.primitives.size(); i++) { + const tinygltf::Primitive &primitive = mesh.primitives[i]; + + if (primitive.indices < 0) return false; + + { + 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; + + 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; + } + + return true; +} + +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"; + return false; + } + } + + for (const auto &mesh : model.meshes) { + std::cout << "mesh.name: " << mesh.name << "\n"; + + MeshPrim output; + bool ret = DumpMesh(model, mesh, &output); + if (!ret) { + return false; + } + + outs->push_back(output); + } + + return true; +} + +} // namespace + +int main(int argc, char **argv) { + if (argc < 2) { + std::cout << "mesh-dump input.gltf" << std::endl; + } + + 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); + } + } + + std::string basedir = GetBaseDir(input_filename); + std::vector meshes; + bool ret = ExtractMesh(basedir, model, &meshes); + + return ret ? EXIT_SUCCESS : EXIT_FAILURE; +}