| // |
| // Tiny glTF loader. |
| // |
| // Copyright (c) 2015, Syoyo Fujita. |
| // All rights reserved. |
| // (Licensed under 2-clause BSD liecense) |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, |
| // this |
| // list of conditions and the following disclaimer. |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND |
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE |
| // FOR |
| // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES |
| // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| // |
| // |
| // Version: |
| // - v0.9.1 Support loading glTF asset from memory |
| // - v0.9.0 Initial |
| // |
| // Tiny glTF loader is using following libraries: |
| // |
| // - picojson: C++ JSON library. |
| // - base64: base64 decode/encode library. |
| // - stb_image: Image loading library. |
| // |
| #ifndef TINY_GLTF_LOADER_H |
| #define TINY_GLTF_LOADER_H |
| |
| #include <string> |
| #include <vector> |
| #include <map> |
| |
| namespace tinygltf { |
| |
| #define TINYGLTF_MODE_POINTS (0) |
| #define TINYGLTF_MODE_LINE (1) |
| #define TINYGLTF_MODE_LINE_LOOP (2) |
| #define TINYGLTF_MODE_TRIANGLES (4) |
| #define TINYGLTF_MODE_TRIANGLE_STRIP (5) |
| #define TINYGLTF_MODE_TRIANGLE_FAN (6) |
| |
| #define TINYGLTF_COMPONENT_TYPE_BYTE (5120) |
| #define TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE (5121) |
| #define TINYGLTF_COMPONENT_TYPE_SHORT (5122) |
| #define TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT (5123) |
| #define TINYGLTF_COMPONENT_TYPE_INT (5124) |
| #define TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT (5125) |
| #define TINYGLTF_COMPONENT_TYPE_FLOAT (5126) |
| #define TINYGLTF_COMPONENT_TYPE_DOUBLE (5127) |
| |
| #define TINYGLTF_TYPE_VEC2 (2) |
| #define TINYGLTF_TYPE_VEC3 (3) |
| #define TINYGLTF_TYPE_VEC4 (4) |
| #define TINYGLTF_TYPE_MAT2 (32 + 2) |
| #define TINYGLTF_TYPE_MAT3 (32 + 3) |
| #define TINYGLTF_TYPE_MAT4 (32 + 4) |
| #define TINYGLTF_TYPE_SCALAR (64 + 1) |
| #define TINYGLTF_TYPE_VECTOR (64 + 4) |
| #define TINYGLTF_TYPE_MATRIX (64 + 16) |
| |
| #define TINYGLTF_IMAGE_FORMAT_JPEG (0) |
| #define TINYGLTF_IMAGE_FORMAT_PNG (1) |
| #define TINYGLTF_IMAGE_FORMAT_BMP (2) |
| #define TINYGLTF_IMAGE_FORMAT_GIF (3) |
| |
| #define TINYGLTF_TARGET_ARRAY_BUFFER (34962) |
| #define TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER (34963) |
| |
| typedef std::map<std::string, std::vector<double> > FloatParameterMap; |
| |
| // LDR 8bit image |
| typedef struct { |
| std::string name; |
| int width; |
| int height; |
| int component; |
| std::vector<unsigned char> image; |
| } Image; |
| |
| typedef struct { |
| std::string name; |
| std::string technique; |
| FloatParameterMap values; |
| } Material; |
| |
| typedef struct { |
| std::string name; |
| std::string buffer; // Required |
| size_t byteOffset; // Required |
| size_t byteLength; // default: 0 |
| int target; |
| } BufferView; |
| |
| typedef struct { |
| std::string bufferView; |
| std::string name; |
| size_t byteOffset; |
| size_t byteStride; |
| int componentType; // One of TINYGLTF_COMPONENT_TYPE_*** |
| size_t count; |
| int type; // One of TINYGLTF_TYPE_*** |
| std::vector<double> minValues; // Optional |
| std::vector<double> maxValues; // Optional |
| } Accessor; |
| |
| class Camera { |
| public: |
| Camera() {} |
| ~Camera() {} |
| |
| std::string name; |
| bool isOrthographic; // false = perspective. |
| |
| // Some common properties. |
| float aspectRatio; |
| float yFov; |
| float zFar; |
| float zNear; |
| }; |
| |
| typedef struct { |
| std::map<std::string, std::string> attributes; // A dictionary object of |
| // strings, where each string |
| // is the ID of the accessor |
| // containing an attribute. |
| std::string material; // The ID of the material to apply to this primitive |
| // when rendering. |
| std::string indices; // The ID of the accessor that contains the indices. |
| int mode; // one of TINYGLTF_MODE_*** |
| } Primitive; |
| |
| typedef struct { |
| std::string name; |
| std::vector<Primitive> primitives; |
| } Mesh; |
| |
| class Node { |
| public: |
| Node() {} |
| ~Node() {} |
| |
| std::string camera; // camera object referenced by this node. |
| |
| std::string name; |
| std::vector<std::string> children; |
| std::vector<double> rotation; // length must be 0 or 4 |
| std::vector<double> scale; // length must be 0 or 3 |
| std::vector<double> translation; // length must be 0 or 3 |
| std::vector<double> matrix; // length must be 0 or 16 |
| std::vector<std::string> meshes; |
| }; |
| |
| typedef struct { |
| std::string name; |
| std::vector<unsigned char> data; |
| } Buffer; |
| |
| typedef struct { |
| std::string generator; |
| std::string version; |
| std::string profile_api; |
| std::string profile_version; |
| bool premultipliedAlpha; |
| } Asset; |
| |
| class Scene { |
| public: |
| Scene() {} |
| ~Scene() {} |
| |
| std::map<std::string, Accessor> accessors; |
| std::map<std::string, Buffer> buffers; |
| std::map<std::string, BufferView> bufferViews; |
| std::map<std::string, Material> materials; |
| std::map<std::string, Mesh> meshes; |
| std::map<std::string, Node> nodes; |
| std::map<std::string, Image> images; |
| std::map<std::string, std::vector<std::string> > scenes; // list of nodes |
| |
| std::string defaultScene; |
| |
| Asset asset; |
| }; |
| |
| class TinyGLTFLoader { |
| public: |
| TinyGLTFLoader(){}; |
| ~TinyGLTFLoader(){}; |
| |
| /// Loads glTF asset from a file. |
| /// Returns false and set error string to `err` if there's an error. |
| bool LoadFromFile(Scene &scene, std::string &err, |
| const std::string &filename); |
| |
| /// 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); |
| |
| }; |
| |
| } // namespace tinygltf |
| |
| #ifdef TINYGLTF_LOADER_IMPLEMENTATION |
| #include <sstream> |
| #include <fstream> |
| #include <cassert> |
| |
| #include "picojson.h" |
| #include "stb_image.h" |
| |
| #ifdef _WIN32 |
| #include <Windows.h> |
| #else |
| #include <wordexp.h> |
| #endif |
| |
| using namespace tinygltf; |
| |
| namespace { |
| |
| bool FileExists(const std::string &abs_filename) { |
| bool ret; |
| FILE *fp = fopen(abs_filename.c_str(), "rb"); |
| if (fp) { |
| ret = true; |
| fclose(fp); |
| } else { |
| ret = false; |
| } |
| |
| return ret; |
| } |
| |
| 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 TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR |
| // no expansion |
| std::string s = filepath; |
| #else |
| std::string s; |
| wordexp_t p; |
| |
| if (filepath.empty()) { |
| return ""; |
| } |
| |
| // char** w; |
| int ret = wordexp(filepath.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 |
| } |
| |
| 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 FindFile(const std::vector<std::string> &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(); |
| } |
| |
| // std::string GetFilePathExtension(const std::string& FileName) |
| //{ |
| // if(FileName.find_last_of(".") != std::string::npos) |
| // return FileName.substr(FileName.find_last_of(".")+1); |
| // return ""; |
| //} |
| |
| std::string GetBaseDir(const std::string &filepath) { |
| if (filepath.find_last_of("/\\") != std::string::npos) |
| return filepath.substr(0, filepath.find_last_of("/\\")); |
| return ""; |
| } |
| |
| // std::string base64_encode(unsigned char const* , unsigned int len); |
| std::string base64_decode(std::string const &s); |
| |
| /* |
| base64.cpp and base64.h |
| |
| Copyright (C) 2004-2008 René Nyffenegger |
| |
| This source code is provided 'as-is', without any express or implied |
| warranty. In no event will the author be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this source code must not be misrepresented; you must not |
| claim that you wrote the original source code. If you use this source code |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original source code. |
| |
| 3. This notice may not be removed or altered from any source distribution. |
| |
| René Nyffenegger rene.nyffenegger@adp-gmbh.ch |
| |
| */ |
| |
| //#include "base64.h" |
| //#include <iostream> |
| |
| static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| "abcdefghijklmnopqrstuvwxyz" |
| "0123456789+/"; |
| |
| static inline bool is_base64(unsigned char c) { |
| return (isalnum(c) || (c == '+') || (c == '/')); |
| } |
| |
| #if 0 |
| std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { |
| std::string ret; |
| int i = 0; |
| int j = 0; |
| unsigned char char_array_3[3]; |
| unsigned char char_array_4[4]; |
| |
| while (in_len--) { |
| char_array_3[i++] = *(bytes_to_encode++); |
| if (i == 3) { |
| char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; |
| char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); |
| char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); |
| char_array_4[3] = char_array_3[2] & 0x3f; |
| |
| for(i = 0; (i <4) ; i++) |
| ret += base64_chars[char_array_4[i]]; |
| i = 0; |
| } |
| } |
| |
| if (i) |
| { |
| for(j = i; j < 3; j++) |
| char_array_3[j] = '\0'; |
| |
| char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; |
| char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); |
| char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); |
| char_array_4[3] = char_array_3[2] & 0x3f; |
| |
| for (j = 0; (j < i + 1); j++) |
| ret += base64_chars[char_array_4[j]]; |
| |
| while((i++ < 3)) |
| ret += '='; |
| |
| } |
| |
| return ret; |
| |
| } |
| #endif |
| |
| std::string base64_decode(std::string const &encoded_string) { |
| int in_len = encoded_string.size(); |
| int i = 0; |
| int j = 0; |
| int in_ = 0; |
| unsigned char char_array_4[4], char_array_3[3]; |
| std::string ret; |
| |
| while (in_len-- && (encoded_string[in_] != '=') && |
| is_base64(encoded_string[in_])) { |
| char_array_4[i++] = encoded_string[in_]; |
| in_++; |
| if (i == 4) { |
| for (i = 0; i < 4; i++) |
| char_array_4[i] = base64_chars.find(char_array_4[i]); |
| |
| char_array_3[0] = |
| (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); |
| char_array_3[1] = |
| ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); |
| char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; |
| |
| for (i = 0; (i < 3); i++) |
| ret += char_array_3[i]; |
| i = 0; |
| } |
| } |
| |
| if (i) { |
| for (j = i; j < 4; j++) |
| char_array_4[j] = 0; |
| |
| for (j = 0; j < 4; j++) |
| char_array_4[j] = 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] = |
| ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); |
| char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; |
| |
| for (j = 0; (j < i - 1); j++) |
| ret += char_array_3[j]; |
| } |
| |
| return ret; |
| } |
| |
| bool LoadExternalFile(std::vector<unsigned char> &out, std::string &err, |
| const std::string &filename, const std::string &basedir, |
| size_t reqBytes, bool checkSize) { |
| out.clear(); |
| |
| std::vector<std::string> paths; |
| paths.push_back(basedir); |
| paths.push_back("."); |
| |
| std::string filepath = FindFile(paths, filename); |
| if (filepath.empty()) { |
| err += "File not found : " + filename; |
| return false; |
| } |
| |
| std::ifstream f(filepath.c_str(), std::ifstream::binary); |
| if (!f) { |
| err += "File open error : " + filepath; |
| return false; |
| } |
| |
| f.seekg(0, f.end); |
| size_t sz = f.tellg(); |
| std::vector<unsigned char> buf(sz); |
| |
| f.seekg(0, f.beg); |
| f.read(reinterpret_cast<char *>(&buf.at(0)), sz); |
| f.close(); |
| |
| if (checkSize) { |
| if (reqBytes == sz) { |
| out.swap(buf); |
| return true; |
| } else { |
| std::stringstream ss; |
| ss << "File size mismatch : " << filepath << ", requestedBytes " |
| << reqBytes << ", but got " << sz << std::endl; |
| err += ss.str(); |
| return false; |
| } |
| } |
| |
| out.swap(buf); |
| return true; |
| } |
| |
| bool IsDataURI(const std::string &in) { |
| std::string header = "data:application/octet-stream;base64,"; |
| if (in.find(header) == 0) { |
| return true; |
| } |
| |
| header = "data:image/png;base64,"; |
| if (in.find(header) == 0) { |
| return true; |
| } |
| |
| header = "data:image/jpeg;base64,"; |
| if (in.find(header) == 0) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool DecodeDataURI(std::vector<unsigned char> &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) { |
| data = base64_decode(in.substr(header.size())); // cut mime string. |
| } |
| |
| if (data.empty()) { |
| header = "data:image/jpeg;base64,"; |
| if (in.find(header) == 0) { |
| data = base64_decode(in.substr(header.size())); // cut mime string. |
| } |
| } |
| |
| if (data.empty()) { |
| header = "data:image/png;base64,"; |
| if (in.find(header) == 0) { |
| data = base64_decode(in.substr(header.size())); // cut mime string. |
| } |
| } |
| |
| if (data.empty()) { |
| return false; |
| } |
| |
| if (checkSize) { |
| if (data.size() != reqBytes) { |
| return false; |
| } |
| out.resize(reqBytes); |
| } else { |
| out.resize(data.size()); |
| } |
| std::copy(data.begin(), data.end(), out.begin()); |
| return true; |
| |
| return false; |
| } |
| |
| bool ParseBooleanProperty(bool &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) { |
| err += "'" + property + "' property is missing.\n"; |
| } |
| return false; |
| } |
| |
| if (!it->second.is<bool>()) { |
| if (required) { |
| err += "'" + property + "' property is not a bool type.\n"; |
| } |
| return false; |
| } |
| |
| ret = it->second.get<bool>(); |
| |
| return true; |
| } |
| |
| bool ParseNumberProperty(double &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) { |
| err += "'" + property + "' property is missing.\n"; |
| } |
| return false; |
| } |
| |
| if (!it->second.is<double>()) { |
| if (required) { |
| err += "'" + property + "' property is not a number type.\n"; |
| } |
| return false; |
| } |
| |
| ret = it->second.get<double>(); |
| |
| return true; |
| } |
| |
| bool ParseNumberArrayProperty(std::vector<double> &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) { |
| err += "'" + property + "' property is missing.\n"; |
| } |
| return false; |
| } |
| |
| if (!it->second.is<picojson::array>()) { |
| if (required) { |
| err += "'" + property + "' property is not an array.\n"; |
| } |
| return false; |
| } |
| |
| ret.clear(); |
| const picojson::array &arr = it->second.get<picojson::array>(); |
| for (size_t i = 0; i < arr.size(); i++) { |
| if (!arr[i].is<double>()) { |
| if (required) { |
| err += "'" + property + "' property is not a number.\n"; |
| } |
| return false; |
| } |
| ret.push_back(arr[i].get<double>()); |
| } |
| |
| return true; |
| } |
| |
| bool ParseStringProperty(std::string &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) { |
| err += "'" + property + "' property is missing.\n"; |
| } |
| return false; |
| } |
| |
| if (!it->second.is<std::string>()) { |
| if (required) { |
| err += "'" + property + "' property is not a string type.\n"; |
| } |
| return false; |
| } |
| |
| ret = it->second.get<std::string>(); |
| |
| return true; |
| } |
| |
| bool ParseStringArrayProperty(std::vector<std::string> &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) { |
| err += "'" + property + "' property is missing.\n"; |
| } |
| return false; |
| } |
| |
| if (!it->second.is<picojson::array>()) { |
| if (required) { |
| err += "'" + property + "' property is not an array.\n"; |
| } |
| return false; |
| } |
| |
| ret.clear(); |
| const picojson::array &arr = it->second.get<picojson::array>(); |
| for (size_t i = 0; i < arr.size(); i++) { |
| if (!arr[i].is<std::string>()) { |
| if (required) { |
| err += "'" + property + "' property is not a string.\n"; |
| } |
| return false; |
| } |
| ret.push_back(arr[i].get<std::string>()); |
| } |
| |
| return true; |
| } |
| |
| 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); |
| |
| ParseStringProperty(asset.version, err, o, "version", false); |
| |
| picojson::object::const_iterator profile = o.find("profile"); |
| if (profile != o.end()) { |
| const picojson::value &v = profile->second; |
| if (v.contains("api") & v.get("api").is<std::string>()) { |
| asset.profile_api = v.get("api").get<std::string>(); |
| } |
| if (v.contains("version") & v.get("version").is<std::string>()) { |
| asset.profile_version = v.get("version").get<std::string>(); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ParseImage(Image &image, std::string &err, const picojson::object &o, |
| const std::string &basedir) { |
| |
| std::string uri; |
| if (!ParseStringProperty(uri, err, o, "uri", true)) { |
| return false; |
| } |
| |
| ParseStringProperty(image.name, err, o, "name", false); |
| |
| std::vector<unsigned char> img; |
| if (IsDataURI(uri)) { |
| if (!DecodeDataURI(img, uri, 0, false)) { |
| err += "Failed to decode 'uri'.\n"; |
| return false; |
| } |
| } else { |
| // Assume external file |
| if (!LoadExternalFile(img, err, uri, basedir, 0, false)) { |
| err += "Failed to load external 'uri'.\n"; |
| return false; |
| } |
| if (img.empty()) { |
| err += "File is empty.\n"; |
| return false; |
| } |
| } |
| |
| int w, h, comp; |
| unsigned char *data = |
| stbi_load_from_memory(&img.at(0), img.size(), &w, &h, &comp, 0); |
| if (!data) { |
| err += "Unknown image format.\n"; |
| return false; |
| } |
| |
| if (w < 1 || h < 1) { |
| err += "Unknown image format.\n"; |
| return false; |
| } |
| |
| image.width = w; |
| image.height = h; |
| image.component = comp; |
| image.image.resize(w * h * comp); |
| std::copy(data, data + w * h * comp, image.image.begin()); |
| |
| return true; |
| } |
| |
| bool ParseBuffer(Buffer &buffer, std::string &err, const picojson::object &o, |
| const std::string &basedir) { |
| double byteLength; |
| if (!ParseNumberProperty(byteLength, err, o, "byteLength", true)) { |
| return false; |
| } |
| |
| std::string uri; |
| if (!ParseStringProperty(uri, err, o, "uri", true)) { |
| return false; |
| } |
| |
| picojson::object::const_iterator type = o.find("type"); |
| if (type != o.end()) { |
| if (type->second.is<std::string>()) { |
| const std::string &ty = (type->second).get<std::string>(); |
| if (ty.compare("arraybuffer") == 0) { |
| // buffer.type = "arraybuffer"; |
| } |
| } |
| } |
| |
| size_t bytes = static_cast<size_t>(byteLength); |
| if (IsDataURI(uri)) { |
| if (!DecodeDataURI(buffer.data, uri, bytes, true)) { |
| err += "Failed to decode 'uri'.\n"; |
| return false; |
| } |
| } else { |
| // Assume external .bin file. |
| if (!LoadExternalFile(buffer.data, err, uri, basedir, bytes, true)) { |
| return false; |
| } |
| } |
| |
| ParseStringProperty(buffer.name, err, o, "name", false); |
| |
| return true; |
| } |
| |
| bool ParseBufferView(BufferView &bufferView, std::string &err, |
| const picojson::object &o) { |
| std::string buffer; |
| if (!ParseStringProperty(buffer, err, o, "buffer", true)) { |
| return false; |
| } |
| |
| double byteOffset; |
| if (!ParseNumberProperty(byteOffset, err, o, "byteOffset", true)) { |
| return false; |
| } |
| |
| double byteLength = 0.0; |
| ParseNumberProperty(byteLength, err, o, "byteLength", false); |
| |
| double target = 0.0; |
| ParseNumberProperty(target, err, o, "target", false); |
| int targetValue = static_cast<int>(target); |
| if ((targetValue == TINYGLTF_TARGET_ARRAY_BUFFER) || |
| (targetValue == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)) { |
| // OK |
| } else { |
| targetValue = 0; |
| } |
| bufferView.target = targetValue; |
| |
| ParseStringProperty(bufferView.name, err, o, "name", false); |
| |
| bufferView.buffer = buffer; |
| bufferView.byteOffset = static_cast<size_t>(byteOffset); |
| bufferView.byteLength = static_cast<size_t>(byteLength); |
| |
| return true; |
| } |
| |
| bool ParseAccessor(Accessor &accessor, std::string &err, |
| const picojson::object &o) { |
| std::string bufferView; |
| if (!ParseStringProperty(bufferView, err, o, "bufferView", true)) { |
| return false; |
| } |
| |
| double byteOffset; |
| if (!ParseNumberProperty(byteOffset, err, o, "byteOffset", true)) { |
| return false; |
| } |
| |
| double componentType; |
| if (!ParseNumberProperty(componentType, err, o, "componentType", true)) { |
| return false; |
| } |
| |
| double count = 0.0; |
| if (!ParseNumberProperty(count, err, o, "count", true)) { |
| return false; |
| } |
| |
| std::string type; |
| if (!ParseStringProperty(type, err, o, "type", true)) { |
| return false; |
| } |
| |
| if (type.compare("SCALAR") == 0) { |
| accessor.type = TINYGLTF_TYPE_SCALAR; |
| } else if (type.compare("VEC2") == 0) { |
| accessor.type = TINYGLTF_TYPE_VEC2; |
| } else if (type.compare("VEC3") == 0) { |
| accessor.type = TINYGLTF_TYPE_VEC3; |
| } else if (type.compare("VEC4") == 0) { |
| accessor.type = TINYGLTF_TYPE_VEC4; |
| } else if (type.compare("MAT2") == 0) { |
| accessor.type = TINYGLTF_TYPE_MAT2; |
| } else if (type.compare("MAT3") == 0) { |
| accessor.type = TINYGLTF_TYPE_MAT3; |
| } else if (type.compare("MAT4") == 0) { |
| accessor.type = TINYGLTF_TYPE_MAT4; |
| } else { |
| std::stringstream ss; |
| ss << "Unsupported `type` for accessor object. Got \"" << type << "\"\n"; |
| err += ss.str(); |
| return false; |
| } |
| |
| double byteStride = 0.0; |
| ParseNumberProperty(byteStride, err, o, "byteStride", false); |
| |
| ParseStringProperty(accessor.name, err, o, "name", false); |
| |
| accessor.minValues.clear(); |
| accessor.maxValues.clear(); |
| ParseNumberArrayProperty(accessor.minValues, err, o, "min", false); |
| ParseNumberArrayProperty(accessor.maxValues, err, o, "max", false); |
| |
| accessor.count = static_cast<size_t>(count); |
| accessor.bufferView = bufferView; |
| accessor.byteOffset = static_cast<size_t>(byteOffset); |
| accessor.byteStride = static_cast<size_t>(byteStride); |
| |
| { |
| int comp = static_cast<size_t>(componentType); |
| if (comp >= TINYGLTF_COMPONENT_TYPE_BYTE && |
| comp <= TINYGLTF_COMPONENT_TYPE_DOUBLE) { |
| // OK |
| accessor.componentType = comp; |
| } else { |
| std::stringstream ss; |
| ss << "Invalid `componentType` in accessor. Got " << comp << "\n"; |
| err += ss.str(); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ParsePrimitive(Primitive &primitive, std::string &err, |
| const picojson::object &o) { |
| if (!ParseStringProperty(primitive.material, err, o, "material", true)) { |
| return false; |
| } |
| |
| double mode = static_cast<double>(TINYGLTF_MODE_TRIANGLES); |
| ParseNumberProperty(mode, err, o, "mode", false); |
| |
| int primMode = static_cast<int>(mode); |
| if (primMode != TINYGLTF_MODE_TRIANGLES) { |
| err += "Currently TinyGLTFLoader doesn not support primitive mode other \n" |
| "than TRIANGLES.\n"; |
| return false; |
| } |
| primitive.mode = primMode; |
| |
| primitive.indices = ""; |
| ParseStringProperty(primitive.indices, err, o, "indices", false); |
| |
| primitive.attributes.clear(); |
| picojson::object::const_iterator attribsObject = o.find("attributes"); |
| if ((attribsObject != o.end()) && |
| (attribsObject->second).is<picojson::object>()) { |
| const picojson::object &attribs = |
| (attribsObject->second).get<picojson::object>(); |
| picojson::object::const_iterator it(attribs.begin()); |
| picojson::object::const_iterator itEnd(attribs.end()); |
| for (; it != itEnd; it++) { |
| const std::string &name = it->first; |
| if (!(it->second).is<std::string>()) { |
| err += "attribute expects string value.\n"; |
| return false; |
| } |
| const std::string &value = (it->second).get<std::string>(); |
| |
| primitive.attributes[name] = value; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ParseMesh(Mesh &mesh, std::string &err, const picojson::object &o) { |
| ParseStringProperty(mesh.name, err, o, "name", false); |
| |
| mesh.primitives.clear(); |
| picojson::object::const_iterator primObject = o.find("primitives"); |
| if ((primObject != o.end()) && (primObject->second).is<picojson::array>()) { |
| const picojson::array &primArray = |
| (primObject->second).get<picojson::array>(); |
| for (size_t i = 0; i < primArray.size(); i++) { |
| Primitive primitive; |
| ParsePrimitive(primitive, err, primArray[i].get<picojson::object>()); |
| mesh.primitives.push_back(primitive); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ParseNode(Node &node, std::string &err, const picojson::object &o) { |
| ParseStringProperty(node.name, err, o, "name", false); |
| |
| ParseNumberArrayProperty(node.rotation, err, o, "rotation", false); |
| ParseNumberArrayProperty(node.scale, err, o, "scale", false); |
| ParseNumberArrayProperty(node.translation, err, o, "translation", false); |
| ParseNumberArrayProperty(node.matrix, err, o, "matrix", false); |
| ParseStringArrayProperty(node.meshes, err, o, "meshes", false); |
| |
| node.children.clear(); |
| picojson::object::const_iterator childrenObject = o.find("children"); |
| if ((childrenObject != o.end()) && |
| (childrenObject->second).is<picojson::array>()) { |
| const picojson::array &childrenArray = |
| (childrenObject->second).get<picojson::array>(); |
| for (size_t i = 0; i < childrenArray.size(); i++) { |
| Node node; |
| if (!childrenArray[i].is<std::string>()) { |
| err += "Invalid `children` array.\n"; |
| return false; |
| } |
| const std::string &childrenNode = childrenArray[i].get<std::string>(); |
| node.children.push_back(childrenNode); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ParseMaterial(Material &material, std::string &err, |
| const picojson::object &o) { |
| ParseStringProperty(material.name, err, o, "name", false); |
| ParseStringProperty(material.technique, err, o, "technique", false); |
| |
| material.values.clear(); |
| picojson::object::const_iterator valuesIt = o.find("values"); |
| if ((valuesIt != o.end()) && (valuesIt->second).is<picojson::object>()) { |
| |
| const picojson::object &valuesObject = |
| (valuesIt->second).get<picojson::object>(); |
| picojson::object::const_iterator it(valuesObject.begin()); |
| picojson::object::const_iterator itEnd(valuesObject.end()); |
| |
| for (; it != itEnd; it++) { |
| // Assume number values. |
| std::vector<double> values; |
| if (!ParseNumberArrayProperty(values, err, valuesObject, it->first, |
| false)) { |
| // Fallback to numer property. |
| double value; |
| if (ParseNumberProperty(value, err, valuesObject, it->first, false)) { |
| values.push_back(value); |
| } |
| } |
| |
| material.values[it->first] = values; |
| } |
| } |
| |
| return true; |
| } |
| } |
| |
| bool TinyGLTFLoader::LoadFromString(Scene &scene, std::string &err, |
| const char *str, unsigned int length, const std::string& baseDir) { |
| picojson::value v; |
| std::string perr = picojson::parse(v, str, str + length); |
| |
| if (!perr.empty()) { |
| err = perr; |
| return false; |
| } |
| |
| if (v.contains("scene") && v.get("scene").is<std::string>()) { |
| // OK |
| } else { |
| err += "\"scene\" object not found in .gltf\n"; |
| return false; |
| } |
| |
| if (v.contains("scenes") && v.get("scenes").is<picojson::object>()) { |
| // OK |
| } else { |
| err += "\"scenes\" object not found in .gltf\n"; |
| return false; |
| } |
| |
| if (v.contains("nodes") && v.get("nodes").is<picojson::object>()) { |
| // OK |
| } else { |
| err += "\"nodes\" object not found in .gltf\n"; |
| return false; |
| } |
| |
| if (v.contains("accessors") && v.get("accessors").is<picojson::object>()) { |
| // OK |
| } else { |
| err += "\"accessors\" object not found in .gltf\n"; |
| return false; |
| } |
| |
| if (v.contains("buffers") && v.get("buffers").is<picojson::object>()) { |
| // OK |
| } else { |
| err += "\"buffers\" object not found in .gltf\n"; |
| return false; |
| } |
| |
| if (v.contains("bufferViews") && |
| v.get("bufferViews").is<picojson::object>()) { |
| // OK |
| } else { |
| err += "\"bufferViews\" object not found in .gltf\n"; |
| return false; |
| } |
| |
| scene.buffers.clear(); |
| scene.bufferViews.clear(); |
| scene.accessors.clear(); |
| scene.meshes.clear(); |
| scene.nodes.clear(); |
| scene.defaultScene = ""; |
| |
| // 0. Parse Asset |
| if (v.contains("asset") && v.get("asset").is<picojson::object>()) { |
| const picojson::object &root = v.get("asset").get<picojson::object>(); |
| |
| ParseAsset(scene.asset, err, root); |
| } |
| |
| // 1. Parse Buffer |
| if (v.contains("buffers") && v.get("buffers").is<picojson::object>()) { |
| const picojson::object &root = v.get("buffers").get<picojson::object>(); |
| |
| picojson::object::const_iterator it(root.begin()); |
| picojson::object::const_iterator itEnd(root.end()); |
| for (; it != itEnd; it++) { |
| |
| Buffer buffer; |
| if (!ParseBuffer(buffer, err, (it->second).get<picojson::object>(), |
| baseDir)) { |
| return false; |
| } |
| |
| scene.buffers[it->first] = buffer; |
| } |
| } |
| |
| // 2. Parse BufferView |
| if (v.contains("bufferViews") && |
| v.get("bufferViews").is<picojson::object>()) { |
| |
| const picojson::object &root = v.get("bufferViews").get<picojson::object>(); |
| |
| picojson::object::const_iterator it(root.begin()); |
| picojson::object::const_iterator itEnd(root.end()); |
| for (; it != itEnd; it++) { |
| |
| BufferView bufferView; |
| if (!ParseBufferView(bufferView, err, |
| (it->second).get<picojson::object>())) { |
| return false; |
| } |
| |
| scene.bufferViews[it->first] = bufferView; |
| } |
| } |
| |
| // 3. Parse Accessor |
| if (v.contains("accessors") && v.get("accessors").is<picojson::object>()) { |
| |
| const picojson::object &root = v.get("accessors").get<picojson::object>(); |
| |
| picojson::object::const_iterator it(root.begin()); |
| picojson::object::const_iterator itEnd(root.end()); |
| for (; it != itEnd; it++) { |
| |
| Accessor accessor; |
| if (!ParseAccessor(accessor, err, (it->second).get<picojson::object>())) { |
| return false; |
| } |
| |
| scene.accessors[it->first] = accessor; |
| } |
| } |
| |
| // 4. Parse Mesh |
| if (v.contains("meshes") && v.get("meshes").is<picojson::object>()) { |
| |
| const picojson::object &root = v.get("meshes").get<picojson::object>(); |
| |
| picojson::object::const_iterator it(root.begin()); |
| picojson::object::const_iterator itEnd(root.end()); |
| for (; it != itEnd; it++) { |
| |
| Mesh mesh; |
| if (!ParseMesh(mesh, err, (it->second).get<picojson::object>())) { |
| return false; |
| } |
| |
| scene.meshes[it->first] = mesh; |
| } |
| } |
| |
| // 5. Parse Node |
| if (v.contains("nodes") && v.get("nodes").is<picojson::object>()) { |
| |
| const picojson::object &root = v.get("nodes").get<picojson::object>(); |
| |
| picojson::object::const_iterator it(root.begin()); |
| picojson::object::const_iterator itEnd(root.end()); |
| for (; it != itEnd; it++) { |
| |
| Node node; |
| if (!ParseNode(node, err, (it->second).get<picojson::object>())) { |
| return false; |
| } |
| |
| scene.nodes[it->first] = node; |
| } |
| } |
| |
| // 6. Parse scenes. |
| if (v.contains("scenes") && v.get("scenes").is<picojson::object>()) { |
| |
| const picojson::object &root = v.get("scenes").get<picojson::object>(); |
| |
| picojson::object::const_iterator it(root.begin()); |
| picojson::object::const_iterator itEnd(root.end()); |
| for (; it != itEnd; it++) { |
| |
| const picojson::object &o = (it->second).get<picojson::object>(); |
| std::vector<std::string> nodes; |
| if (!ParseStringArrayProperty(nodes, err, o, "nodes", false)) { |
| return false; |
| } |
| |
| scene.scenes[it->first] = nodes; |
| } |
| } |
| |
| // 7. Parse default scenes. |
| if (v.contains("scene") && v.get("scene").is<std::string>()) { |
| |
| const std::string defaultScene = v.get("scene").get<std::string>(); |
| |
| scene.defaultScene = defaultScene; |
| } |
| |
| // 8. Parse Material |
| if (v.contains("materials") && v.get("materials").is<picojson::object>()) { |
| |
| const picojson::object &root = v.get("materials").get<picojson::object>(); |
| |
| picojson::object::const_iterator it(root.begin()); |
| picojson::object::const_iterator itEnd(root.end()); |
| for (; it != itEnd; it++) { |
| |
| Material material; |
| if (!ParseMaterial(material, err, (it->second).get<picojson::object>())) { |
| return false; |
| } |
| |
| scene.materials[it->first] = material; |
| } |
| } |
| |
| // 9. Parse Image |
| if (v.contains("images") && v.get("images").is<picojson::object>()) { |
| |
| const picojson::object &root = v.get("images").get<picojson::object>(); |
| |
| picojson::object::const_iterator it(root.begin()); |
| picojson::object::const_iterator itEnd(root.end()); |
| for (; it != itEnd; it++) { |
| |
| Image image; |
| if (!ParseImage(image, err, (it->second).get<picojson::object>(), |
| baseDir)) { |
| return false; |
| } |
| |
| scene.images[it->first] = image; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool TinyGLTFLoader::LoadFromFile(Scene &scene, std::string &err, |
| const std::string &filename) { |
| std::stringstream ss; |
| |
| std::ifstream f(filename.c_str()); |
| if (!f) { |
| ss << "Failed to open file: " << filename << std::endl; |
| err = ss.str(); |
| return false; |
| } |
| |
| f.seekg(0, f.end); |
| size_t sz = f.tellg(); |
| std::vector<char> buf(sz); |
| |
| f.seekg(0, f.beg); |
| f.read(&buf.at(0), sz); |
| f.close(); |
| |
| std::string basedir = GetBaseDir(filename); |
| |
| bool ret = LoadFromString(scene, err, &buf.at(0), buf.size(), basedir); |
| |
| return ret; |
| } |
| |
| #endif // TINYGLTF_LOADER_IMPLEMENTATION |
| |
| #endif // TINY_GLTF_LOADER_H |