diff --git a/.travis.yml b/.travis.yml index 5ff634e..47ef58f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,5 +42,5 @@ script: - export CC="${CC}-${COMPILER_VERSION}" - export CXX="${CXX}-${COMPILER_VERSION}" - ${CC} -v - - ${CXX} ${EXTRA_CXXFLAGS} -Wall -Werror -g -o loader_test test.cc - - ./loader_test box.gltf + - ${CXX} ${EXTRA_CXXFLAGS} -Wall -Werror -g -o loader_example loader_example.cc + - ./loader_example box.gltf diff --git a/Makefile b/Makefile index 858e807..0ceb93c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ all: - clang++ -fsanitize=address -Wall -Werror -Weverything -Wno-c++11-long-long -g -O0 -o example example.cc + clang++ -fsanitize=address -Wall -Werror -Weverything -Wno-c++11-long-long -g -O0 -o loader_example loader_example.cc lint: ./cpplint.py tiny_gltf_loader.h diff --git a/README.md b/README.md index e170c08..ed54f67 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,11 @@ ## Features -* Portable C++. C++-98 with STL dependency only. +* Portable C++. C++-03 with STL dependency only. * Moderate parsing time and memory consumption. * glTF specification v1.0.0 + * [x] ASCII glTF + * [x] Binary glTF(https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_binary_glTF) * Buffers * [x] Parse BASE64 encoded embedded buffer fata(DataURI). * [x] Load `.bin` file. @@ -34,8 +36,7 @@ * [ ] Parse `animation`, `program`, `sampler`, `shader`, `technique` * [ ] Compression/decompression(Open3DGC, etc) * [ ] Support `extensions` and `extras` property -* [ ] HDR image -* [ ] Binary glTF. +* [ ] HDR image? ## License @@ -64,7 +65,8 @@ Scene scene; TinyGLTFLoader loader; std::string err; -bool ret = loader.LoadFromFile(scene, err, argv[1]); +bool ret = loader.LoadASCIIFromFile(scene, err, argv[1]); +//bool ret = loader.LoadBinaryFromFile(scene, err, argv[1]); // for binary glTF(.glb) if (!err.empty()) { printf("Err: %s\n", err.c_str()); } diff --git a/examples/glview/glview.cc b/examples/glview/glview.cc index 8c3cdfc..3002815 100644 --- a/examples/glview/glview.cc +++ b/examples/glview/glview.cc @@ -67,6 +67,12 @@ void CheckErrors(std::string desc) { } } +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 ""; +} + bool LoadShader(GLenum shaderType, // GL_VERTEX_SHADER or GL_FRAGMENT_SHADER(or // maybe GL_COMPUTE_SHADER) GLuint &shader, const char *shaderSourceFilename) { @@ -264,7 +270,7 @@ static void SetupGLState(tinygltf::Scene &scene, GLuint progId) { tinygltf::Material &mat = scene.materials[primitive.material]; printf("material.name = %s\n", mat.name.c_str()); if (mat.values.find("diffuse") != mat.values.end()) { - std::string diffuseTexName = mat.values["diffuse"].stringValue; + std::string diffuseTexName = mat.values["diffuse"].string_value; if (scene.textures.find(diffuseTexName) != scene.textures.end()) { tinygltf::Texture &tex = scene.textures[diffuseTexName]; if (scene.images.find(tex.source) != scene.images.end()) { @@ -435,8 +441,18 @@ int main(int argc, char **argv) { tinygltf::Scene scene; tinygltf::TinyGLTFLoader loader; std::string err; + std::string input_filename(argv[1]); + std::string ext = GetFilePathExtension(input_filename); + + bool ret = false; + if (ext.compare("glb") == 0) { + // assume binary glTF. + ret = loader.LoadBinaryFromFile(&scene, &err, input_filename.c_str()); + } else { + // assume ascii glTF. + ret = loader.LoadASCIIFromFile(&scene, &err, input_filename.c_str()); + } - bool ret = loader.LoadFromFile(&scene, &err, argv[1]); if (!err.empty()) { printf("ERR: %s\n", err.c_str()); } @@ -452,7 +468,10 @@ int main(int argc, char **argv) { return -1; } - window = glfwCreateWindow(width, height, "Simple glTF geometry viewer", NULL, + char title[1024]; + sprintf(title, "Simple glTF viewer: %s", input_filename.c_str()); + + window = glfwCreateWindow(width, height, title, NULL, NULL); if (window == NULL) { std::cerr << "Failed to open GLFW window. " << std::endl; diff --git a/example.cc b/loader_example.cc similarity index 90% rename from example.cc rename to loader_example.cc index 44e154b..15a2860 100644 --- a/example.cc +++ b/loader_example.cc @@ -6,7 +6,13 @@ #include #include -std::string PrintMode(int mode) { +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 std::string PrintMode(int mode) { if (mode == TINYGLTF_MODE_POINTS) { return "POINTS"; } else if (mode == TINYGLTF_MODE_LINE) { @@ -23,7 +29,7 @@ std::string PrintMode(int mode) { return "**UNKNOWN**"; } -std::string PrintType(int ty) { +static std::string PrintType(int ty) { if (ty == TINYGLTF_TYPE_SCALAR) { return "SCALAR"; } else if (ty == TINYGLTF_TYPE_VECTOR) { @@ -46,7 +52,7 @@ std::string PrintType(int ty) { return "**UNKNOWN**"; } -std::string PrintComponentType(int ty) { +static std::string PrintComponentType(int ty) { if (ty == TINYGLTF_COMPONENT_TYPE_BYTE) { return "BYTE"; } else if (ty == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { @@ -68,7 +74,7 @@ std::string PrintComponentType(int ty) { return "**UNKNOWN**"; } -std::string PrintFloatArray(const std::vector &arr) { +static std::string PrintFloatArray(const std::vector &arr) { if (arr.size() == 0) { return ""; } @@ -83,7 +89,7 @@ std::string PrintFloatArray(const std::vector &arr) { return ss.str(); } -std::string PrintStringArray(const std::vector &arr) { +static std::string PrintStringArray(const std::vector &arr) { if (arr.size() == 0) { return ""; } @@ -98,7 +104,7 @@ std::string PrintStringArray(const std::vector &arr) { return ss.str(); } -std::string Indent(int indent) { +static std::string Indent(int indent) { std::string s; for (int i = 0; i < indent; i++) { s += " "; @@ -107,7 +113,7 @@ std::string Indent(int indent) { return s; } -void DumpNode(const tinygltf::Node &node, int indent) { +static void DumpNode(const tinygltf::Node &node, int indent) { std::cout << Indent(indent) << "name : " << node.name << std::endl; std::cout << Indent(indent) << "camera : " << node.camera << std::endl; if (!node.rotation.empty()) { @@ -137,7 +143,7 @@ void DumpNode(const tinygltf::Node &node, int indent) { << "children : " << PrintStringArray(node.children) << std::endl; } -void DumpPrimitive(const tinygltf::Primitive &primitive, int indent) { +static void DumpPrimitive(const tinygltf::Primitive &primitive, int indent) { std::cout << Indent(indent) << "material : " << primitive.material << std::endl; std::cout << Indent(indent) << "mode : " << PrintMode(primitive.mode) @@ -155,7 +161,7 @@ void DumpPrimitive(const tinygltf::Primitive &primitive, int indent) { } } -void Dump(const tinygltf::Scene &scene) { +static void Dump(const tinygltf::Scene &scene) { std::cout << "=== Dump glTF ===" << std::endl; std::cout << "asset.generator : " << scene.asset.generator << std::endl; @@ -289,12 +295,12 @@ void Dump(const tinygltf::Scene &scene) { tinygltf::ParameterMap::const_iterator p(it->second.values.begin()); tinygltf::ParameterMap::const_iterator pEnd(it->second.values.end()); for (; p != pEnd; p++) { - if (!p->second.numberArray.empty()) { + if (!p->second.number_array.empty()) { std::cout << Indent(3) << p->first - << PrintFloatArray(p->second.numberArray) << std::endl; + << PrintFloatArray(p->second.number_array) << std::endl; } - if (!p->second.stringValue.empty()) { - std::cout << Indent(3) << p->first << " : " << p->second.stringValue + if (!p->second.string_value.empty()) { + std::cout << Indent(3) << p->first << " : " << p->second.string_value << std::endl; } } @@ -359,8 +365,17 @@ int main(int argc, char **argv) { tinygltf::Scene scene; tinygltf::TinyGLTFLoader loader; std::string err; + std::string input_filename(argv[1]); + std::string ext = GetFilePathExtension(input_filename); - bool ret = loader.LoadFromFile(&scene, &err, argv[1]); + bool ret = false; + if (ext.compare("glb") == 0) { + // assume binary glTF. + ret = loader.LoadBinaryFromFile(&scene, &err, input_filename.c_str()); + } else { + // assume ascii glTF. + ret = loader.LoadASCIIFromFile(&scene, &err, input_filename.c_str()); + } if (!err.empty()) { printf("Err: %s\n", err.c_str()); diff --git a/premake4.lua b/premake4.lua index d6178d7..0d58fe7 100644 --- a/premake4.lua +++ b/premake4.lua @@ -1,5 +1,5 @@ sources = { - "test.cc", + "loader_example.cc", } -- premake4.lua @@ -21,9 +21,9 @@ solution "TinyGLTFLoaderSolution" configuration "Debug" defines { "DEBUG" } -- -DDEBUG flags { "Symbols" } - targetname "test_tinygltfloader_debug" + targetname "loader_example_tinygltfloader_debug" configuration "Release" -- defines { "NDEBUG" } -- -NDEBUG flags { "Symbols", "Optimize" } - targetname "test_tinygltfloader" + targetname "loader_example_tinygltfloader" diff --git a/test_runner.py b/test_runner.py index 26d27e8..7472808 100644 --- a/test_runner.py +++ b/test_runner.py @@ -22,7 +22,7 @@ success = [] def run(filename): print("Testing: " + filename) - cmd = ["./loader_test", filename] + cmd = ["./example", filename] try: p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = p.communicate() @@ -45,7 +45,7 @@ def test(): if os.path.isdir(p): for k in kinds: targetDir = os.path.join(p, k) - g = glob.glob(targetDir + "/*.gltf") + g = glob.glob(targetDir + "/*.gltf") + glob.glob(targetDir + "/*.glb") for gltf in g: run(gltf) diff --git a/tiny_gltf_loader.h b/tiny_gltf_loader.h index 001c379..90babeb 100644 --- a/tiny_gltf_loader.h +++ b/tiny_gltf_loader.h @@ -102,6 +102,9 @@ typedef struct { int component; int pad0; std::vector image; + + std::string bufferView; // KHR_binary_glTF extenstion. + std::string mimeType; // KHR_binary_glTF extenstion. } Image; typedef struct { @@ -137,7 +140,7 @@ typedef struct { int componentType; // One of TINYGLTF_COMPONENT_TYPE_*** int pad0; size_t count; - int type; // One of TINYGLTF_TYPE_*** + int type; // One of TINYGLTF_TYPE_*** int pad1; std::vector minValues; // Optional std::vector maxValues; // Optional @@ -235,34 +238,36 @@ class TinyGLTFLoader { /// Loads glTF ASCII asset from a file. /// Returns false and set error string to `err` if there's an error. bool LoadASCIIFromFile(Scene *scene, std::string *err, - const std::string &filename); + const std::string &filename); /// Loads glTF ASCII asset from string(memory). /// `length` = strlen(str); /// Returns false and set error string to `err` if there's an error. bool LoadASCIIFromString(Scene *scene, std::string *err, const char *str, - const unsigned int length, const std::string &baseDir); + const unsigned int length, + const std::string &base_dir); /// Loads glTF binary asset from a file. /// Returns false and set error string to `err` if there's an error. bool LoadBinaryFromFile(Scene *scene, std::string *err, - const std::string &filename); + const std::string &filename); /// Loads glTF binary asset from memory. /// `length` = strlen(str); /// Returns false and set error string to `err` if there's an error. - bool LoadBinaryFromMemory(Scene *scene, std::string *err, const unsigned char *bytes, - const unsigned int length); - -private: + bool LoadBinaryFromMemory(Scene *scene, std::string *err, + const unsigned char *bytes, + const unsigned int length, + const std::string &base_dir = ""); + private: /// Loads glTF asset from string(memory). /// `length` = strlen(str); /// Returns false and set error string to `err` if there's an error. bool LoadFromString(Scene *scene, std::string *err, const char *str, - const unsigned int length, const std::string &baseDir); + const unsigned int length, const std::string &base_dir); - const unsigned char* bin_data_; + const unsigned char *bin_data_; size_t bin_size_; bool is_binary_; char pad[7]; @@ -275,7 +280,7 @@ private: #include #include #include - + // Disable some warnings for external files. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wfloat-equal" @@ -308,21 +313,6 @@ private: namespace tinygltf { -#if 0 -static void swap2(unsigned short *val) { -#ifdef TINYGLTF_LITTLE_ENDIAN - (void)val; -#else - unsigned short tmp = *val; - unsigned char *dst = reinterpret_cast(val); - unsigned char *src = reinterpret_cast(&tmp); - - dst[0] = src[1]; - dst[1] = src[0]; -#endif -} -#endif - static void swap4(unsigned int *val) { #ifdef TINYGLTF_LITTLE_ENDIAN (void)val; @@ -338,34 +328,13 @@ static void swap4(unsigned int *val) { #endif } -#if 0 -static void swap8(unsigned long long *val) { -#ifdef TINYGLTF_LITTLE_ENDIAN - (void)val; -#else - unsigned long long tmp = (*val); - unsigned char *dst = reinterpret_cast(val); - unsigned char *src = reinterpret_cast(&tmp); - - dst[0] = src[7]; - dst[1] = src[6]; - dst[2] = src[5]; - dst[3] = src[4]; - dst[4] = src[3]; - dst[5] = src[2]; - dst[6] = src[1]; - dst[7] = src[0]; -#endif -} -#endif - static bool FileExists(const std::string &abs_filename) { bool ret; #ifdef _WIN32 FILE *fp; errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb"); if (err != 0) { - return false; + return false; } #else FILE *fp = fopen(abs_filename.c_str(), "rb"); @@ -426,7 +395,8 @@ static std::string ExpandFilePath(const std::string &filepath) { #endif } -static std::string JoinPath(const std::string &path0, const std::string &path1) { +static std::string JoinPath(const std::string &path0, + const std::string &path1) { if (path0.empty()) { return path1; } else { @@ -441,7 +411,7 @@ static std::string JoinPath(const std::string &path0, const std::string &path1) } static std::string FindFile(const std::vector &paths, - const std::string &filepath) { + const std::string &filepath) { for (size_t i = 0; i < paths.size(); i++) { std::string absPath = ExpandFilePath(JoinPath(paths[i], filepath)); if (FileExists(absPath)) { @@ -523,7 +493,8 @@ std::string base64_decode(std::string const &encoded_string) { in_++; if (i == 4) { for (i = 0; i < 4; i++) - char_array_4[i] = static_cast(base64_chars.find(char_array_4[i])); + char_array_4[i] = + static_cast(base64_chars.find(char_array_4[i])); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); @@ -540,7 +511,8 @@ std::string base64_decode(std::string const &encoded_string) { for (j = i; j < 4; j++) char_array_4[j] = 0; for (j = 0; j < 4; j++) - char_array_4[j] = static_cast(base64_chars.find(char_array_4[j])); + char_array_4[j] = + static_cast(base64_chars.find(char_array_4[j])); char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); char_array_3[1] = @@ -555,8 +527,9 @@ std::string base64_decode(std::string const &encoded_string) { #pragma clang diagnostic pop static bool LoadExternalFile(std::vector *out, std::string *err, - const std::string &filename, const std::string &basedir, - size_t reqBytes, bool checkSize) { + const std::string &filename, + const std::string &basedir, size_t reqBytes, + bool checkSize) { out->clear(); std::vector paths; @@ -584,7 +557,8 @@ static bool LoadExternalFile(std::vector *out, std::string *err, std::vector buf(sz); f.seekg(0, f.beg); - f.read(reinterpret_cast(&buf.at(0)), static_cast(sz)); + f.read(reinterpret_cast(&buf.at(0)), + static_cast(sz)); f.close(); if (checkSize) { @@ -606,6 +580,52 @@ static bool LoadExternalFile(std::vector *out, std::string *err, return true; } +static bool LoadImageData(Image *image, std::string *err, int req_width, + int req_height, const unsigned char *bytes, + int size) { + int w, h, comp; + unsigned char *data = stbi_load_from_memory(bytes, size, &w, &h, &comp, 0); + if (!data) { + if (err) { + (*err) += "Unknown image format.\n"; + } + return false; + } + + if (w < 1 || h < 1) { + if (err) { + (*err) += "Unknown image format.\n"; + } + return false; + } + + if (req_width > 0) { + if (req_width != w) { + if (err) { + (*err) += "Image width mismatch.\n"; + } + return false; + } + } + + if (req_height > 0) { + if (req_height != h) { + if (err) { + (*err) += "Image height mismatch.\n"; + } + return false; + } + } + + image->width = w; + image->height = h; + image->component = comp; + image->image.resize(static_cast(w * h * comp)); + std::copy(data, data + w * h * comp, image->image.begin()); + + return true; +} + static bool IsDataURI(const std::string &in) { std::string header = "data:application/octet-stream;base64,"; if (in.find(header) == 0) { @@ -625,8 +645,9 @@ static bool IsDataURI(const std::string &in) { return false; } -static bool DecodeDataURI(std::vector *out, const std::string &in, - size_t reqBytes, bool checkSize) { +static bool DecodeDataURI(std::vector *out, + const std::string &in, size_t reqBytes, + bool checkSize) { std::string header = "data:application/octet-stream;base64,"; std::string data; if (in.find(header) == 0) { @@ -664,8 +685,8 @@ static bool DecodeDataURI(std::vector *out, const std::string &in } static bool ParseBooleanProperty(bool *ret, std::string *err, - const picojson::object &o, - const std::string &property, bool required) { + const picojson::object &o, + const std::string &property, bool required) { picojson::object::const_iterator it = o.find(property); if (it == o.end()) { if (required) { @@ -693,8 +714,8 @@ static bool ParseBooleanProperty(bool *ret, std::string *err, } static bool ParseNumberProperty(double *ret, std::string *err, - const picojson::object &o, const std::string &property, - bool required) { + const picojson::object &o, + const std::string &property, bool required) { picojson::object::const_iterator it = o.find(property); if (it == o.end()) { if (required) { @@ -722,8 +743,9 @@ static bool ParseNumberProperty(double *ret, std::string *err, } static bool ParseNumberArrayProperty(std::vector *ret, std::string *err, - const picojson::object &o, - const std::string &property, bool required) { + const picojson::object &o, + const std::string &property, + bool required) { picojson::object::const_iterator it = o.find(property); if (it == o.end()) { if (required) { @@ -761,8 +783,8 @@ static bool ParseNumberArrayProperty(std::vector *ret, std::string *err, } static bool ParseStringProperty(std::string *ret, std::string *err, - const picojson::object &o, const std::string &property, - bool required) { + const picojson::object &o, + const std::string &property, bool required) { picojson::object::const_iterator it = o.find(property); if (it == o.end()) { if (required) { @@ -789,9 +811,11 @@ static bool ParseStringProperty(std::string *ret, std::string *err, return true; } -static bool ParseStringArrayProperty(std::vector *ret, std::string *err, - const picojson::object &o, - const std::string &property, bool required) { +static bool ParseStringArrayProperty(std::vector *ret, + std::string *err, + const picojson::object &o, + const std::string &property, + bool required) { picojson::object::const_iterator it = o.find(property); if (it == o.end()) { if (required) { @@ -828,7 +852,72 @@ static bool ParseStringArrayProperty(std::vector *ret, std::string return true; } -static bool ParseAsset(Asset *asset, std::string *err, const picojson::object &o) { +static bool ParseKHRBinaryExtension(const picojson::object &o, std::string *err, + std::string *buffer_view, + std::string *mime_type, int *image_width, + int *image_height) { + picojson::object j = o; + + if (j.find("extensions") == j.end()) { + if (err) { + (*err) += "`extensions' property is missing.\n"; + } + return false; + } + + if (!(j["extensions"].is())) { + if (err) { + (*err) += "Invalid `extensions' property.\n"; + } + return false; + } + + picojson::object ext = j["extensions"].get(); + + if (ext.find("KHR_binary_glTF") == ext.end()) { + if (err) { + (*err) += + "`KHR_binary_glTF' property is missing in extension property.\n"; + } + return false; + } + + if (!(ext["KHR_binary_glTF"].is())) { + if (err) { + (*err) += "Invalid `KHR_binary_glTF' property.\n"; + } + return false; + } + + picojson::object k = ext["KHR_binary_glTF"].get(); + + if (!ParseStringProperty(buffer_view, err, k, "bufferView", true)) { + return false; + } + + if (mime_type) { + ParseStringProperty(mime_type, err, k, "mimeType", false); + } + + if (image_width) { + double width = 0.0; + if (ParseNumberProperty(&width, err, k, "width", false)) { + (*image_width) = static_cast(width); + } + } + + if (image_height) { + double height = 0.0; + if (ParseNumberProperty(&height, err, k, "height", false)) { + (*image_height) = static_cast(height); + } + } + + return true; +} + +static bool ParseAsset(Asset *asset, std::string *err, + const picojson::object &o) { ParseStringProperty(&asset->generator, err, o, "generator", false); ParseBooleanProperty(&asset->premultipliedAlpha, err, o, "premultipliedAlpha", false); @@ -849,8 +938,10 @@ static bool ParseAsset(Asset *asset, std::string *err, const picojson::object &o return true; } -static bool ParseImage(Image *image, std::string *err, const picojson::object &o, - const std::string &basedir) { +static bool ParseImage(Image *image, std::string *err, + const picojson::object &o, const std::string &basedir, + bool is_binary, const unsigned char *bin_data, + size_t bin_size) { std::string uri; if (!ParseStringProperty(&uri, err, o, "uri", true)) { return false; @@ -859,57 +950,90 @@ static bool ParseImage(Image *image, std::string *err, const picojson::object &o ParseStringProperty(&image->name, err, o, "name", false); std::vector img; - if (IsDataURI(uri)) { - if (!DecodeDataURI(&img, uri, 0, false)) { - if (err) { - (*err) += "Failed to decode 'uri'.\n"; + + if (is_binary) { + // Still binary glTF accepts external dataURI. First try external resources. + bool loaded = false; + if (IsDataURI(uri)) { + loaded = DecodeDataURI(&img, uri, 0, false); + } else { + // Assume external .bin file. + loaded = LoadExternalFile(&img, err, uri, basedir, 0, false); + } + + if (!loaded) { + // load data from (embedded) binary data + + if ((bin_size == 0) || (bin_data == NULL)) { + if (err) { + (*err) += "Invalid binary data.\n"; + } + return false; } - return false; + + // There should be "extensions" property. + // "extensions":{"KHR_binary_glTF":{"bufferView": "id", ... + + std::string buffer_view; + std::string mime_type; + int image_width; + int image_height; + bool ret = ParseKHRBinaryExtension(o, err, &buffer_view, &mime_type, + &image_width, &image_height); + if (!ret) { + return false; + } + + if (uri.compare("data:,") == 0) { + // ok + } else { + if (err) { + (*err) += "Invalid URI for binary data.\n"; + } + return false; + } + + // Just only save some information here. Loading actual image data from + // bufferView is done in other place. + image->bufferView = buffer_view; + image->mimeType = mime_type; + image->width = image_width; + image->height = image_height; + + return true; } } else { - // Assume external file - if (!LoadExternalFile(&img, err, uri, basedir, 0, false)) { - if (err) { - (*err) += "Failed to load external 'uri'.\n"; + if (IsDataURI(uri)) { + if (!DecodeDataURI(&img, uri, 0, false)) { + if (err) { + (*err) += "Failed to decode 'uri'.\n"; + } + return false; } - return false; - } - if (img.empty()) { - if (err) { - (*err) += "File is empty.\n"; + } else { + // Assume external file + if (!LoadExternalFile(&img, err, uri, basedir, 0, false)) { + if (err) { + (*err) += "Failed to load external 'uri'.\n"; + } + return false; + } + if (img.empty()) { + if (err) { + (*err) += "File is empty.\n"; + } + return false; } - return false; } } - int w, h, comp; - unsigned char *data = - stbi_load_from_memory(&img.at(0), static_cast(img.size()), &w, &h, &comp, 0); - if (!data) { - if (err) { - (*err) += "Unknown image format.\n"; - } - return false; - } - - if (w < 1 || h < 1) { - if (err) { - (*err) += "Unknown image format.\n"; - } - return false; - } - - image->width = w; - image->height = h; - image->component = comp; - image->image.resize(static_cast(w * h * comp)); - std::copy(data, data + w * h * comp, image->image.begin()); - - return true; + return LoadImageData(image, err, 0, 0, &img.at(0), + static_cast(img.size())); } -static bool ParseTexture(Texture *texture, std::string *err, const picojson::object &o, - const std::string &basedir) { +static bool ParseTexture(Texture *texture, std::string *err, + const picojson::object &o, + const std::string &basedir) { (void)basedir; if (!ParseStringProperty(&texture->sampler, err, o, "sampler", true)) { @@ -942,8 +1066,11 @@ static bool ParseTexture(Texture *texture, std::string *err, const picojson::obj return true; } -static bool ParseBuffer(Buffer *buffer, std::string *err, const picojson::object &o, - const std::string &basedir, bool is_binary = false, const unsigned char* bin_data = NULL, size_t bin_size = 0) { +static bool ParseBuffer(Buffer *buffer, std::string *err, + const picojson::object &o, const std::string &basedir, + bool is_binary = false, + const unsigned char *bin_data = NULL, + size_t bin_size = 0) { double byteLength; if (!ParseNumberProperty(&byteLength, err, o, "byteLength", true)) { return false; @@ -966,35 +1093,49 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const picojson::object size_t bytes = static_cast(byteLength); if (is_binary) { - if ((bin_size == 0) || (bin_data == NULL)) { - if (err) { - (*err) += "Invalid binary data.\n"; - } - return false; - } - - if (byteLength > bin_size) { - if (err) { - std::stringstream ss; - ss << "Invalid `byteLength'. Must be equal or less than binary size: `byteLength' = " << byteLength << ", binary size = " << bin_size << std::endl; - (*err) += ss.str(); - } - return false; - } - - if (uri.compare("data:,") == 0) { - - // @todo { check uri } - buffer->data.resize(static_cast(byteLength)); - memcpy(&(buffer->data.at(0)), bin_data, static_cast(byteLength)); - + // Still binary glTF accepts external dataURI. First try external resources. + bool loaded = false; + if (IsDataURI(uri)) { + loaded = DecodeDataURI(&buffer->data, uri, bytes, true); } else { - if (err) { - (*err) += "Invalid URI for binary data.\n"; - } - return false; + // Assume external .bin file. + loaded = LoadExternalFile(&buffer->data, err, uri, basedir, bytes, true); } + if (!loaded) { + // load data from (embedded) binary data + + if ((bin_size == 0) || (bin_data == NULL)) { + if (err) { + (*err) += "Invalid binary data.\n"; + } + return false; + } + + if (byteLength > bin_size) { + if (err) { + std::stringstream ss; + ss << "Invalid `byteLength'. Must be equal or less than binary size: " + "`byteLength' = " << byteLength + << ", binary size = " << bin_size << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (uri.compare("data:,") == 0) { + // @todo { check uri } + buffer->data.resize(static_cast(byteLength)); + memcpy(&(buffer->data.at(0)), bin_data, + static_cast(byteLength)); + + } else { + if (err) { + (*err) += "Invalid URI for binary data.\n"; + } + return false; + } + } } else { if (IsDataURI(uri)) { @@ -1018,7 +1159,7 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const picojson::object } static bool ParseBufferView(BufferView *bufferView, std::string *err, - const picojson::object &o) { + const picojson::object &o) { std::string buffer; if (!ParseStringProperty(&buffer, err, o, "buffer", true)) { return false; @@ -1053,7 +1194,7 @@ static bool ParseBufferView(BufferView *bufferView, std::string *err, } static bool ParseAccessor(Accessor *accessor, std::string *err, - const picojson::object &o) { + const picojson::object &o) { std::string bufferView; if (!ParseStringProperty(&bufferView, err, o, "bufferView", true)) { return false; @@ -1137,7 +1278,7 @@ static bool ParseAccessor(Accessor *accessor, std::string *err, } static bool ParsePrimitive(Primitive *primitive, std::string *err, - const picojson::object &o) { + const picojson::object &o) { if (!ParseStringProperty(&primitive->material, err, o, "material", true)) { return false; } @@ -1233,7 +1374,7 @@ static bool ParseNode(Node *node, std::string *err, const picojson::object &o) { } static bool ParseMaterial(Material *material, std::string *err, - const picojson::object &o) { + const picojson::object &o) { ParseStringProperty(&material->name, err, o, "name", false); ParseStringProperty(&material->technique, err, o, "technique", false); @@ -1248,8 +1389,8 @@ static bool ParseMaterial(Material *material, std::string *err, for (; it != itEnd; it++) { // Assume number values. Parameter param; - if (ParseStringProperty(¶m.string_value, err, values_object, it->first, - false)) { + if (ParseStringProperty(¶m.string_value, err, values_object, + it->first, false)) { // Found string property. } else if (!ParseNumberArrayProperty(¶m.number_array, err, values_object, it->first, false)) { @@ -1269,7 +1410,7 @@ static bool ParseMaterial(Material *material, std::string *err, bool TinyGLTFLoader::LoadFromString(Scene *scene, std::string *err, const char *str, unsigned int length, - const std::string &baseDir) { + const std::string &base_dir) { picojson::value v; std::string perr = picojson::parse(v, str, str + length); @@ -1358,7 +1499,7 @@ bool TinyGLTFLoader::LoadFromString(Scene *scene, std::string *err, for (; it != itEnd; it++) { Buffer buffer; if (!ParseBuffer(&buffer, err, (it->second).get(), - baseDir, is_binary_, bin_data_, bin_size_)) { + base_dir, is_binary_, bin_data_, bin_size_)) { return false; } @@ -1483,10 +1624,34 @@ bool TinyGLTFLoader::LoadFromString(Scene *scene, std::string *err, for (; it != itEnd; it++) { Image image; if (!ParseImage(&image, err, (it->second).get(), - baseDir)) { + base_dir, is_binary_, bin_data_, bin_size_)) { return false; } + if (!image.bufferView.empty()) { + // Load image from the buffer view. + if (scene->bufferViews.find(image.bufferView) == + scene->bufferViews.end()) { + if (err) { + std::stringstream ss; + ss << "bufferView \"" << image.bufferView + << "\" not found in the scene." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const BufferView &bufferView = scene->bufferViews[image.bufferView]; + const Buffer &buffer = scene->buffers[bufferView.buffer]; + + bool ret = LoadImageData(&image, err, image.width, image.height, + &buffer.data[bufferView.byteOffset], + static_cast(bufferView.byteLength)); + if (!ret) { + return false; + } + } + scene->images[it->first] = image; } } @@ -1500,7 +1665,7 @@ bool TinyGLTFLoader::LoadFromString(Scene *scene, std::string *err, for (; it != itEnd; it++) { Texture texture; if (!ParseTexture(&texture, err, (it->second).get(), - baseDir)) { + base_dir)) { return false; } @@ -1512,17 +1677,17 @@ bool TinyGLTFLoader::LoadFromString(Scene *scene, std::string *err, } bool TinyGLTFLoader::LoadASCIIFromString(Scene *scene, std::string *err, - const char *str, unsigned int length, - const std::string &baseDir) { + const char *str, unsigned int length, + const std::string &base_dir) { is_binary_ = false; bin_data_ = NULL; bin_size_ = 0; - return LoadFromString(scene, err, str, length, baseDir); + return LoadFromString(scene, err, str, length, base_dir); } bool TinyGLTFLoader::LoadASCIIFromFile(Scene *scene, std::string *err, - const std::string &filename) { + const std::string &filename) { std::stringstream ss; std::ifstream f(filename.c_str()); @@ -1544,14 +1709,16 @@ bool TinyGLTFLoader::LoadASCIIFromFile(Scene *scene, std::string *err, std::string basedir = GetBaseDir(filename); - bool ret = LoadASCIIFromString(scene, err, &buf.at(0), static_cast(buf.size()), basedir); + bool ret = LoadASCIIFromString( + scene, err, &buf.at(0), static_cast(buf.size()), basedir); return ret; } bool TinyGLTFLoader::LoadBinaryFromMemory(Scene *scene, std::string *err, - const unsigned char* bytes, unsigned int size) { - + const unsigned char *bytes, + unsigned int size, + const std::string &base_dir) { if (size < 20) { if (err) { (*err) = "Too short data size for glTF Binary."; @@ -1559,7 +1726,8 @@ bool TinyGLTFLoader::LoadBinaryFromMemory(Scene *scene, std::string *err, return false; } - if (bytes[0] == 'g' && bytes[1] == 'l' && bytes[2] == 'T' && bytes[3] == 'F') { + if (bytes[0] == 'g' && bytes[1] == 'l' && bytes[2] == 'T' && + bytes[3] == 'F') { // ok } else { if (err) { @@ -1568,20 +1736,23 @@ bool TinyGLTFLoader::LoadBinaryFromMemory(Scene *scene, std::string *err, return false; } - unsigned int version; // 4 bytes - unsigned int length; // 4 bytes - unsigned int scene_length; // 4 bytes - unsigned int scene_format; // 4 bytes; + unsigned int version; // 4 bytes + unsigned int length; // 4 bytes + unsigned int scene_length; // 4 bytes + unsigned int scene_format; // 4 bytes; // @todo { Endian swap for big endian machine. } - memcpy(&version, bytes + 4, 4); swap4(&version); - memcpy(&length, bytes + 8, 4); swap4(&length); - memcpy(&scene_length, bytes + 12, 4); swap4(&scene_length); - memcpy(&scene_format, bytes + 16, 4); swap4(&scene_format); + memcpy(&version, bytes + 4, 4); + swap4(&version); + memcpy(&length, bytes + 8, 4); + swap4(&length); + memcpy(&scene_length, bytes + 12, 4); + swap4(&scene_length); + memcpy(&scene_format, bytes + 16, 4); + swap4(&scene_format); - if ((20 + scene_length >= size) || - (scene_length < 1) || - (scene_format != 0)) { // 0 = JSON format. + if ((20 + scene_length >= size) || (scene_length < 1) || + (scene_format != 0)) { // 0 = JSON format. if (err) { (*err) = "Invalid glTF binary."; } @@ -1589,13 +1760,17 @@ bool TinyGLTFLoader::LoadBinaryFromMemory(Scene *scene, std::string *err, } // Extract JSON string. - std::string jsonString(reinterpret_cast(&bytes[20]), scene_length); + std::string jsonString(reinterpret_cast(&bytes[20]), + scene_length); is_binary_ = true; bin_data_ = bytes + 20 + scene_length; - bin_size_ = length - (20 + scene_length); // extract header + JSON scene data. + bin_size_ = + length - (20 + scene_length); // extract header + JSON scene data. - bool ret = LoadFromString(scene, err, reinterpret_cast(&bytes[20]), scene_length, ""); + bool ret = + LoadFromString(scene, err, reinterpret_cast(&bytes[20]), + scene_length, base_dir); if (!ret) { return ret; } @@ -1604,7 +1779,7 @@ bool TinyGLTFLoader::LoadBinaryFromMemory(Scene *scene, std::string *err, } bool TinyGLTFLoader::LoadBinaryFromFile(Scene *scene, std::string *err, - const std::string &filename) { + const std::string &filename) { std::stringstream ss; std::ifstream f(filename.c_str()); @@ -1624,7 +1799,11 @@ bool TinyGLTFLoader::LoadBinaryFromFile(Scene *scene, std::string *err, f.read(&buf.at(0), static_cast(sz)); f.close(); - bool ret = LoadBinaryFromMemory(scene, err, reinterpret_cast(&buf.at(0)), static_cast(buf.size())); + std::string basedir = GetBaseDir(filename); + + bool ret = LoadBinaryFromMemory( + scene, err, reinterpret_cast(&buf.at(0)), + static_cast(buf.size()), basedir); return ret; }