From 0aab1575d5b5f8e4302959d50f97c6387a74564a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Chatelain?= Date: Tue, 30 May 2017 15:03:48 +0000 Subject: [PATCH 1/7] Update buffer parsing (2.0) --- tiny_gltf_loader.h | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tiny_gltf_loader.h b/tiny_gltf_loader.h index a371660..1798b9c 100644 --- a/tiny_gltf_loader.h +++ b/tiny_gltf_loader.h @@ -1558,9 +1558,18 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, return false; } + // In glTF 2.0, uri is not mandatory anymore std::string uri; ParseStringProperty(&uri, err, o, "uri", false, "Buffer"); + // having an empty uri for a non embedded image should not be valid + if(!is_binary && uri.empty()) + { + if (err) { + (*err) += "'uri' is missing from non binary glTF file buffer.\n"; + } + } + picojson::object::const_iterator type = o.find("type"); if (type != o.end()) { if (type->second.is()) { @@ -1574,15 +1583,14 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, size_t bytes = static_cast(byteLength); if (is_binary) { // Still binary glTF accepts external dataURI. First try external resources. - bool loaded = false; - if (IsDataURI(uri)) { - loaded = DecodeDataURI(&buffer->data, uri, bytes, true); - } else { - // Assume external .bin file. - loaded = LoadExternalFile(&buffer->data, err, uri, basedir, bytes, true); - } - if (!loaded) { + if(!uri.empty()) + { + // External .bin file. + LoadExternalFile(&buffer->data, err, uri, basedir, bytes, true); + } + else + { // load data from (embedded) binary data if ((bin_size == 0) || (bin_data == NULL)) { @@ -1603,18 +1611,10 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, 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 in `Buffer'.\n"; - } - return false; - } + // Read buffer data + buffer->data.resize(static_cast(byteLength)); + memcpy(&(buffer->data.at(0)), bin_data, + static_cast(byteLength)); } } else { @@ -2571,7 +2571,7 @@ bool TinyGLTFLoader::LoadBinaryFromMemory(Model *model, std::string *err, model_length); is_binary_ = true; - bin_data_ = bytes + 20 + model_length; + bin_data_ = bytes + 20 + model_length + 8; // 4 bytes (buffer_length) + 4 bytes(buffer_format) bin_size_ = length - (20 + model_length); // extract header + JSON scene data. From 6d3bfd7e1ba2c6a493033aef3557c785e9b20eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Chatelain?= Date: Tue, 30 May 2017 15:04:32 +0000 Subject: [PATCH 2/7] Fix image parsing (2.0) --- tiny_gltf_loader.h | 94 ++++++---------------------------------------- 1 file changed, 11 insertions(+), 83 deletions(-) diff --git a/tiny_gltf_loader.h b/tiny_gltf_loader.h index 1798b9c..90e02f3 100644 --- a/tiny_gltf_loader.h +++ b/tiny_gltf_loader.h @@ -1311,70 +1311,6 @@ static bool ParseStringIntProperty(std::map *ret, return true; } -static bool ParseKHRBinaryExtension(const picojson::object &o, std::string *err, - double *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 (!ParseNumberProperty(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 ParseJSONProperty(std::map *ret, std::string *err, const picojson::object &o, const std::string &property, @@ -1465,34 +1401,26 @@ static bool ParseImage(Image *image, std::string *err, return false; } - // There should be "extensions" property. - // "extensions":{"KHR_binary_glTF":{"bufferView": "id", ... - double buffer_view = -1.0; - 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) { + if (!ParseNumberProperty(&buffer_view, err, o, "bufferView", true)) { return false; } - if (uri.compare("data:,") == 0) { - // ok - } else { - if (err) { - (*err) += "Invalid URI for binary data.\n"; - } - return false; - } + std::string mime_type; + ParseStringProperty(&mime_type, err, o, "mimeType", false); + + double width = 0.0; + ParseNumberProperty(&width, err, o, "width", false); + + double height = 0.0; + ParseNumberProperty(&height, err, o, "height", false); // Just only save some information here. Loading actual image data from // bufferView is done in other place. image->bufferView = static_cast(buffer_view); image->mimeType = mime_type; - image->width = image_width; - image->height = image_height; + image->width = static_cast(width); + image->height = static_cast(height); return true; } From 08c9d256294c5010f334dc5ffce2379445bc423c Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Wed, 31 May 2017 00:16:45 +0900 Subject: [PATCH 3/7] Revert "Feature/update tinygltf 20" --- tiny_gltf_loader.h | 1244 +++++++++++++++++++++++--------------------- 1 file changed, 658 insertions(+), 586 deletions(-) diff --git a/tiny_gltf_loader.h b/tiny_gltf_loader.h index 467c3e0..3e33d4c 100644 --- a/tiny_gltf_loader.h +++ b/tiny_gltf_loader.h @@ -283,147 +283,102 @@ TINYGLTF_VALUE_GET(Value::Object, object_value_) #undef TINYGLTF_VALUE_GET typedef struct { - bool bool_value; std::string string_value; std::vector number_array; - std::map json_double_value; } Parameter; typedef std::map ParameterMap; -struct AnimationChannel { - int sampler; // required - int target_node; // required (index of the node to target) - std::string target_path; // required in ["translation", "rotation", "scale", "weights"] +typedef struct { + std::string sampler; + std::string target_id; + std::string target_path; Value extras; +} AnimationChannel; - AnimationChannel() - { - sampler = -1; - target_node = -1; - } -}; - -struct AnimationSampler { - int input; // required - std::string interpolation; // in ["LINEAR", "STEP", "CATMULLROMSPLINE", "CUBICSPLINE"], default "LINEAR" - int output; // required - - AnimationSampler() - { - input = -1; - output = -1; - } -}; +typedef struct { + std::string input; + std::string interpolation; + std::string output; + Value extras; +} AnimationSampler; typedef struct { std::string name; std::vector channels; - std::vector samplers; + std::map samplers; + ParameterMap parameters; Value extras; } Animation; -struct Skin { +typedef struct { std::string name; - int inverseBindMatrices; // required here but not in the spec - int skeleton; // The index of the node used as a skeleton root - std::vector joints; // Indices of skeleton nodes - - Skin() - { - inverseBindMatrices = -1; - } -}; - -struct Sampler { - std::string name; - int minFilter; // ["NEAREST", "LINEAR", "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_NEAREST", "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_LINEAR"] - int magFilter; // ["NEAREST", "LINEAR"] - int wrapS; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", "REPEAT"], default "REPEAT" - int wrapT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", "REPEAT"], default "REPEAT" + int minFilter; + int magFilter; + int wrapS; + int wrapT; int wrapR; // TinyGLTF extension int pad0; Value extras; +} Sampler; - Sampler() - { - wrapS = TINYGLTF_TEXTURE_WRAP_RPEAT; - wrapT = TINYGLTF_TEXTURE_WRAP_RPEAT; - } -}; - -struct Image{ +typedef struct { std::string name; int width; int height; int component; int pad0; std::vector image; - int bufferView; // (required if no uri) - std::string mimeType; // (required if no uri) ["image/jpeg", "image/png"] - std::string uri; // (reqiored if no mimeType) + + std::string bufferView; // KHR_binary_glTF extenstion. + std::string mimeType; // KHR_binary_glTF extenstion. + Value extras; +} Image; - Image() - { - bufferView = -1; - } -}; - -struct Texture { - int sampler; - int source; // Required (not specified in the spec ?) - Value extras; - - Texture() - { - sampler = -1; - source =-1; - } -}; - -// Each extension should be stored in a ParameterMap. -// members not in the values could be included in the ParameterMap -// to keep a single material model -struct Material { +typedef struct { + int format; + int internalFormat; + std::string sampler; // Required + std::string source; // Required + int target; + int type; std::string name; - - ParameterMap values; // PBR metal/roughness workflow - ParameterMap additionalValues; // normal/occlusion/emissive values - ParameterMap extCommonValues; // KHR_common_material extension - ParameterMap extPBRValues; Value extras; -}; +} Texture; -struct BufferView{ +typedef struct { std::string name; - int buffer; // Required - size_t byteOffset; // minimum 0, default 0 - size_t byteLength; // required, minimum 1 - size_t byteStride; // minimum 4, maximum 252 (multiple of 4) - int target; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] + std::string technique; + ParameterMap values; + + Value extras; +} Material; + +typedef struct { + std::string name; + std::string buffer; // Required + size_t byteOffset; // Required + size_t byteLength; // default: 0 + int target; int pad0; Value extras; -}; +} BufferView; -struct Accessor { - int bufferView; // optional in spec but required here since sparse accessor are not supported +typedef struct { + std::string bufferView; std::string name; size_t byteOffset; size_t byteStride; - int componentType; // (required) One of TINYGLTF_COMPONENT_TYPE_*** - size_t count; // required - int type; // (required) One of TINYGLTF_TYPE_*** .. + int componentType; // One of TINYGLTF_COMPONENT_TYPE_*** + int pad0; + size_t count; + int type; // One of TINYGLTF_TYPE_*** + int pad1; + std::vector minValues; // Optional + std::vector maxValues; // Optional Value extras; - - std::vector minValues; // required - std::vector maxValues; // required - - Accessor() - { - bufferView = -1; - } -}; +} Accessor; class Camera { public: @@ -433,73 +388,47 @@ class Camera { std::string name; bool isOrthographic; // false = perspective. - // Orthographic properties - float xMag; // required - float yMag; // required - float zFar; // required - float zNear; //required - - // Perspective properties + // Some common properties. float aspectRatio; - float yfov; // required - float zfar; - float znear; // required - - ParameterMap extensions; - Value extras; + float yFov; + float zFar; + float zNear; }; -struct Primitive { - std::map attributes; // (required) A dictionary object of - // integer, where each integer - // is the index of the accessor - // containing an attribute. - int material; // The index of the material to apply to this primitive - // when rendering. - int indices; // The index of the accessor that contains the indices. - int mode; // one of TINYGLTF_MODE_*** - std::vector > targets; // array of morph targets, - //where each target is a dict with attribues in ["POSITION, "NORMAL", "TANGENT"] pointing - // to their corresponding accessors - Value extras; +typedef struct { + std::map 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_*** + int pad0; - Primitive() - { - material = -1; - indices = -1; - } -}; + Value extras; // "extra" property +} Primitive; typedef struct { std::string name; std::vector primitives; - std::vector weights; // weights to be applied to the Morph Targets - std::vector >targets; - ParameterMap extensions; Value extras; } Mesh; class Node { public: - Node() - { - mesh = -1, - skin = -1; - } - + Node() {} ~Node() {} - int camera; // the index of the camera referenced by this node + std::string camera; // camera object referenced by this node. std::string name; - int skin; - int mesh; - std::vector children; + std::vector children; std::vector rotation; // length must be 0 or 4 std::vector scale; // length must be 0 or 3 std::vector translation; // length must be 0 or 3 std::vector matrix; // length must be 0 or 16 - std::vector weights; // The weights of the instantiated Morph Target + std::vector meshes; Value extras; }; @@ -507,47 +436,78 @@ class Node { typedef struct { std::string name; std::vector data; - std::string uri; // considered as required here but not in the spec (need to clarify) Value extras; } Buffer; typedef struct { - std::string version; // required + std::string name; + int type; + int pad0; + std::vector source; + + Value extras; +} Shader; + +typedef struct { + std::string name; + std::string vertexShader; + std::string fragmentShader; + std::vector attributes; + + Value extras; +} Program; + +typedef struct { + int count; + int pad0; + std::string node; + std::string semantic; + int type; + int pad1; + Parameter value; +} TechniqueParameter; + +typedef struct { + std::string name; + std::string program; + std::map parameters; + std::map attributes; + std::map uniforms; + + Value extras; +} Technique; + +typedef struct { std::string generator; - std::string minVersion; - std::string copyright; - ParameterMap extensions; + std::string version; + std::string profile_api; + std::string profile_version; + bool premultipliedAlpha; + char pad[7]; Value extras; } Asset; -struct Scene { - std::string name; - std::vector nodes; - - ParameterMap extensions; - ParameterMap extras; -}; - -class Model { +class Scene { public: - Model() {} - ~Model() {} + Scene() {} + ~Scene() {} - std::vector accessors; - std::vector animations; - std::vector buffers; - std::vector bufferViews; - std::vector materials; - std::vector meshes; - std::vector nodes; - std::vector textures; - std::vector images; - std::vector skins; - std::vector samplers; - std::vector scenes; + std::map accessors; + std::map animations; + std::map buffers; + std::map bufferViews; + std::map materials; + std::map meshes; + std::map nodes; + std::map textures; + std::map images; + std::map shaders; + std::map programs; + std::map techniques; + std::map samplers; + std::map > scenes; // list of nodes - int defaultScene; - std::vector extensionsUsed; + std::string defaultScene; Asset asset; @@ -574,28 +534,28 @@ class TinyGLTFLoader { /// Loads glTF ASCII asset from a file. /// Returns false and set error string to `err` if there's an error. - bool LoadASCIIFromFile(Model *model, std::string *err, + bool LoadASCIIFromFile(Scene *scene, std::string *err, const std::string &filename, unsigned int check_sections = REQUIRE_ALL); /// 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(Model *model, std::string *err, const char *str, + bool LoadASCIIFromString(Scene *scene, std::string *err, const char *str, const unsigned int length, const std::string &base_dir, unsigned int check_sections = REQUIRE_ALL); /// Loads glTF binary asset from a file. /// Returns false and set error string to `err` if there's an error. - bool LoadBinaryFromFile(Model *model, std::string *err, + bool LoadBinaryFromFile(Scene *scene, std::string *err, const std::string &filename, unsigned int check_sections = REQUIRE_ALL); /// Loads glTF binary asset from memory. /// `length` = strlen(str); /// Returns false and set error string to `err` if there's an error. - bool LoadBinaryFromMemory(Model *model, std::string *err, + bool LoadBinaryFromMemory(Scene *scene, std::string *err, const unsigned char *bytes, const unsigned int length, const std::string &base_dir = "", @@ -605,7 +565,7 @@ class TinyGLTFLoader { /// Loads glTF asset from string(memory). /// `length` = strlen(str); /// Returns false and set error string to `err` if there's an error. - bool LoadFromString(Model *model, std::string *err, const char *str, + bool LoadFromString(Scene *scene, std::string *err, const char *str, const unsigned int length, const std::string &base_dir, unsigned int check_sections); @@ -946,16 +906,12 @@ static bool LoadImageData(Image *image, std::string *err, int req_width, int req_height, const unsigned char *bytes, int size) { int w, h, comp; - // if image cannot be decoded, ignore parsing and keep it by its path - // don't break in this case - //FIXME we should only enter this function if the image is embedded. If image->uri references - // an image file, it should be left as it is. Image loading should not be mandatory (to support other formats) unsigned char *data = stbi_load_from_memory(bytes, size, &w, &h, &comp, 0); if (!data) { if (err) { (*err) += "Unknown image format.\n"; } - return true; + return false; } if (w < 1 || h < 1) { @@ -963,7 +919,7 @@ static bool LoadImageData(Image *image, std::string *err, int req_width, if (err) { (*err) += "Unknown image format.\n"; } - return true; + return false; } if (req_width > 0) { @@ -1244,7 +1200,48 @@ static bool ParseStringProperty( return true; } -static bool ParseStringIntProperty(std::map *ret, +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) { + if (err) { + (*err) += "'" + property + "' property is missing.\n"; + } + } + return false; + } + + if (!it->second.is()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an array.\n"; + } + } + return false; + } + + ret->clear(); + const picojson::array &arr = it->second.get(); + for (size_t i = 0; i < arr.size(); i++) { + if (!arr[i].is()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a string.\n"; + } + } + return false; + } + ret->push_back(arr[i].get()); + } + + return true; +} + +static bool ParseStringMapProperty(std::map *ret, std::string *err, const picojson::object &o, const std::string &property, bool required) { picojson::object::const_iterator it = o.find(property); @@ -1274,23 +1271,24 @@ static bool ParseStringIntProperty(std::map *ret, picojson::object::const_iterator dictItEnd(dict.end()); for (; dictIt != dictItEnd; ++dictIt) { - if (!dictIt->second.is()) { + // Check that the value is a string. + if (!dictIt->second.is()) { if (required) { if (err) { - (*err) += "'" + property + "' value is not an int.\n"; + (*err) += "'" + property + "' value is not a string.\n"; } } return false; } // Insert into the list. - (*ret)[dictIt->first] = static_cast(dictIt->second.get()); + (*ret)[dictIt->first] = dictIt->second.get(); } return true; } static bool ParseKHRBinaryExtension(const picojson::object &o, std::string *err, - double *buffer_view, + std::string *buffer_view, std::string *mime_type, int *image_width, int *image_height) { picojson::object j = o; @@ -1328,7 +1326,7 @@ static bool ParseKHRBinaryExtension(const picojson::object &o, std::string *err, picojson::object k = ext["KHR_binary_glTF"].get(); - if (!ParseNumberProperty(buffer_view, err, k, "bufferView", true)) { + if (!ParseStringProperty(buffer_view, err, k, "bufferView", true)) { return false; } @@ -1353,51 +1351,24 @@ static bool ParseKHRBinaryExtension(const picojson::object &o, std::string *err, return true; } -static bool ParseJSONProperty(std::map *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) { - if(err) { - (*err) += "'" + property + "' property is missing. \n'"; - } - } - return false; - } - - if(!it->second.is()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a JSON object.\n"; - } - } - return false; - } - - ret->clear(); - const picojson::object &obj = it->second.get(); - picojson::object::const_iterator it2(obj.begin()); - picojson::object::const_iterator itEnd(obj.end()); - for (; it2 != itEnd; it2++) { - if(it2->second.is()) - ret->insert(std::pair(it2->first, it2->second.get())); - } - - return true; -} - static bool ParseAsset(Asset *asset, std::string *err, const picojson::object &o) { - ParseStringProperty(&asset->version, err, o, "version", true); ParseStringProperty(&asset->generator, err, o, "generator", false); - ParseStringProperty(&asset->minVersion, err, o, "minVersion", false); + ParseBooleanProperty(&asset->premultipliedAlpha, err, o, "premultipliedAlpha", + false); - // Unity exporter version is added as extra here - ParseExtrasProperty(&(asset->extras), o); + 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()) { + asset->profile_api = v.get("api").get(); + } + if (v.contains("version") & v.get("version").is()) { + asset->profile_version = v.get("version").get(); + } + } return true; } @@ -1406,16 +1377,8 @@ 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) { - // A glTF image must either reference a bufferView or an image uri - double bufferView = -1; - bool isEmbedded = ParseNumberProperty(&bufferView, err, o, "bufferView", true); - isEmbedded = isEmbedded && static_cast(bufferView) != -1; - std::string uri; - if (!ParseStringProperty(&uri, err, o, "uri", true) && !isEmbedded) { - if (err) { - (*err) += "Invalid image data (required data is missing).\n"; - } + if (!ParseStringProperty(&uri, err, o, "uri", true)) { return false; } @@ -1446,7 +1409,7 @@ static bool ParseImage(Image *image, std::string *err, // There should be "extensions" property. // "extensions":{"KHR_binary_glTF":{"bufferView": "id", ... - double buffer_view = -1.0; + std::string buffer_view; std::string mime_type; int image_width; int image_height; @@ -1467,7 +1430,7 @@ static bool ParseImage(Image *image, std::string *err, // Just only save some information here. Loading actual image data from // bufferView is done in other place. - image->bufferView = static_cast(buffer_view); + image->bufferView = buffer_view; image->mimeType = mime_type; image->width = image_width; image->height = image_height; @@ -1484,16 +1447,11 @@ static bool ParseImage(Image *image, std::string *err, } } else { // Assume external file - - // Keep texture path (for textures that cannot be decoded) - image->uri = uri; - if (!LoadExternalFile(&img, err, uri, basedir, 0, false)) { if (err) { (*err) += "Failed to load external 'uri'. for image parameter\n"; } - // If the image cannot be loaded, keep uri as image->uri. - return true; + return false; } if (img.empty()) { if (err) { @@ -1512,16 +1470,33 @@ static bool ParseTexture(Texture *texture, std::string *err, const picojson::object &o, const std::string &basedir) { (void)basedir; - double sampler = -1.0; - double source = -1.0; - ParseNumberProperty(&sampler, err, o, "sampler", false); - if (!ParseNumberProperty(&source, err, o, "source", true)) { + if (!ParseStringProperty(&texture->sampler, err, o, "sampler", true)) { return false; } - texture->sampler = static_cast(sampler); - texture->source = static_cast(source); + if (!ParseStringProperty(&texture->source, err, o, "source", true)) { + return false; + } + + ParseStringProperty(&texture->name, err, o, "name", false); + + double format = TINYGLTF_TEXTURE_FORMAT_RGBA; + ParseNumberProperty(&format, err, o, "format", false); + + double internalFormat = TINYGLTF_TEXTURE_FORMAT_RGBA; + ParseNumberProperty(&internalFormat, err, o, "internalFormat", false); + + double target = TINYGLTF_TEXTURE_TARGET_TEXTURE2D; + ParseNumberProperty(&target, err, o, "target", false); + + double type = TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE; + ParseNumberProperty(&type, err, o, "type", false); + + texture->format = static_cast(format); + texture->internalFormat = static_cast(internalFormat); + texture->target = static_cast(target); + texture->type = static_cast(type); return true; } @@ -1620,8 +1595,8 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, static bool ParseBufferView(BufferView *bufferView, std::string *err, const picojson::object &o) { - double buffer = -1.0; - if (!ParseNumberProperty(&buffer, err, o, "buffer", true)) { + std::string buffer; + if (!ParseStringProperty(&buffer, err, o, "buffer", true)) { return false; } @@ -1630,13 +1605,8 @@ static bool ParseBufferView(BufferView *bufferView, std::string *err, return false; } - double byteLength = 1.0; - if(!ParseNumberProperty(&byteLength, err, o, "byteLength", true)) { - return false; - } - - double byteStride = 4.0; - ParseNumberProperty(&byteLength, err, o, "byteStride", false); + double byteLength = 0.0; + ParseNumberProperty(&byteLength, err, o, "byteLength", false); double target = 0.0; ParseNumberProperty(&target, err, o, "target", false); @@ -1651,27 +1621,26 @@ static bool ParseBufferView(BufferView *bufferView, std::string *err, ParseStringProperty(&bufferView->name, err, o, "name", false); - bufferView->buffer = static_cast(buffer); + bufferView->buffer = buffer; bufferView->byteOffset = static_cast(byteOffset); bufferView->byteLength = static_cast(byteLength); - bufferView->byteStride = static_cast(byteStride); return true; } static bool ParseAccessor(Accessor *accessor, std::string *err, const picojson::object &o) { - double bufferView = -1.0; - if (!ParseNumberProperty(&bufferView, err, o, "bufferView", true)) { + std::string bufferView; + if (!ParseStringProperty(&bufferView, err, o, "bufferView", true)) { return false; } - double byteOffset = 0.0; + double byteOffset; if (!ParseNumberProperty(&byteOffset, err, o, "byteOffset", true)) { return false; } - double componentType = 0.0; + double componentType; if (!ParseNumberProperty(&componentType, err, o, "componentType", true)) { return false; } @@ -1716,16 +1685,11 @@ static bool ParseAccessor(Accessor *accessor, std::string *err, accessor->minValues.clear(); accessor->maxValues.clear(); - if(!ParseNumberArrayProperty(&accessor->minValues, err, o, "min", true)) { - return false; - } - - if(!ParseNumberArrayProperty(&accessor->maxValues, err, o, "max", true)) { - return false; - } + ParseNumberArrayProperty(&accessor->minValues, err, o, "min", false); + ParseNumberArrayProperty(&accessor->maxValues, err, o, "max", false); accessor->count = static_cast(count); - accessor->bufferView = static_cast(bufferView); + accessor->bufferView = bufferView; accessor->byteOffset = static_cast(byteOffset); accessor->byteStride = static_cast(byteStride); @@ -1752,23 +1716,21 @@ static bool ParseAccessor(Accessor *accessor, std::string *err, static bool ParsePrimitive(Primitive *primitive, std::string *err, const picojson::object &o) { - double material = -1.0; - ParseNumberProperty(&material, err, o, "material", false); - primitive->material = static_cast(material); + if (!ParseStringProperty(&primitive->material, err, o, "material", true, + "mesh.primitive")) { + return false; + } double mode = static_cast(TINYGLTF_MODE_TRIANGLES); ParseNumberProperty(&mode, err, o, "mode", false); int primMode = static_cast(mode); - primitive->mode = primMode; // Why only triangled were supported ? + primitive->mode = primMode; - double indices = -1.0; - ParseNumberProperty(&indices, err, o, "indices", false); - primitive->indices = static_cast(indices); - if (!ParseStringIntProperty(&primitive->attributes, err, o, "attributes", - true)) { - return false; - } + primitive->indices = ""; + ParseStringProperty(&primitive->indices, err, o, "indices", false); + + ParseStringMapProperty(&primitive->attributes, err, o, "attributes", false); ParseExtrasProperty(&(primitive->extras), o); @@ -1793,28 +1755,6 @@ static bool ParseMesh(Mesh *mesh, std::string *err, const picojson::object &o) { } } - // Look for morph targets - picojson::object::const_iterator targetsObject = o.find("targets"); - if ((targetsObject != o.end()) && (targetsObject->second).is()) { - const picojson::array &targetArray = - (targetsObject->second).get(); - for (size_t i = 0; i < targetArray.size(); i++) { - std::map targetAttribues; - - const picojson::object &dict = targetArray[i].get(); - picojson::object::const_iterator dictIt(dict.begin()); - picojson::object::const_iterator dictItEnd(dict.end()); - - for (; dictIt != dictItEnd; ++dictIt) { - targetAttribues[dictIt->first] = static_cast(dictIt->second.get()); - } - mesh->targets.push_back(targetAttribues); - } - } - - // Should probably check if has targets and if dimensions fit - ParseNumberArrayProperty(&mesh->weights, err, o, "weights", false); - ParseExtrasProperty(&(mesh->extras), o); return true; @@ -1823,25 +1763,11 @@ static bool ParseMesh(Mesh *mesh, std::string *err, const picojson::object &o) { static bool ParseNode(Node *node, std::string *err, const picojson::object &o) { ParseStringProperty(&node->name, err, o, "name", false); - double skin = -1.0; - ParseNumberProperty(&skin, err, o, "skin", false); - node->skin = static_cast(skin); - - // Matrix and T/R/S are exclusive - if(!ParseNumberArrayProperty(&node->matrix, err, o, "matrix", false)) { - - ParseNumberArrayProperty(&node->rotation, err, o, "rotation", false); - ParseNumberArrayProperty(&node->scale, err, o, "scale", false); - ParseNumberArrayProperty(&node->translation, err, o, "translation", false); - } - - double camera = -1.0; - ParseNumberProperty(&camera, err, o, "camera", false); - node->camera = static_cast(camera); - - double mesh = -1.0; - ParseNumberProperty(&mesh, err, o, "mesh", false); - node->mesh = mesh; + 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"); @@ -1850,13 +1776,13 @@ static bool ParseNode(Node *node, std::string *err, const picojson::object &o) { const picojson::array &childrenArray = (childrenObject->second).get(); for (size_t i = 0; i < childrenArray.size(); i++) { - if (!childrenArray[i].is()) { + if (!childrenArray[i].is()) { if (err) { (*err) += "Invalid `children` array.\n"; } return false; } - const int &childrenNode = static_cast(childrenArray[i].get()); + const std::string &childrenNode = childrenArray[i].get(); node->children.push_back(childrenNode); } } @@ -1886,10 +1812,6 @@ static bool ParseParameterProperty(Parameter *param, std::string *err, } else if (ParseNumberProperty(&num_val, err, o, prop, false)) { param->number_array.push_back(num_val); return true; - } else if(ParseJSONProperty(¶m->json_double_value, err, o, prop, false)) { - return true; - } else if(ParseBooleanProperty(¶m->bool_value, err, o, prop, false)) { - return true; } else { if (required) { if (err) { @@ -1901,78 +1823,214 @@ static bool ParseParameterProperty(Parameter *param, std::string *err, } 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); material->values.clear(); - material->extPBRValues.clear(); - material->additionalValues.clear(); + picojson::object::const_iterator valuesIt = o.find("values"); - picojson::object::const_iterator it(o.begin()); - picojson::object::const_iterator itEnd(o.end()); + if ((valuesIt != o.end()) && (valuesIt->second).is()) { + const picojson::object &values_object = + (valuesIt->second).get(); - for (; it != itEnd; it++) { - if(it->first == "pbrMetallicRoughness") - { - if ((it->second).is()) { - const picojson::object &values_object = - (it->second).get(); + picojson::object::const_iterator it(values_object.begin()); + picojson::object::const_iterator itEnd(values_object.end()); - picojson::object::const_iterator itVal(values_object.begin()); - picojson::object::const_iterator itEnd(values_object.end()); - - for (; itVal != itEnd; itVal++) { - Parameter param; - if (ParseParameterProperty(¶m, err, values_object, itVal->first, - false)) { - material->values[itVal->first] = param; - } - } - } - } - else if(it->first == "extensions") - { - if ((it->second).is()) { - const picojson::object &extension = (it->second).get(); - - picojson::object::const_iterator extIt = extension.begin(); - if(!extIt->second.is()) - continue; - - const picojson::object &values_object = - (extIt->second).get(); - - picojson::object::const_iterator itVal(values_object.begin()); - picojson::object::const_iterator itEnd(values_object.end()); - - for (; itVal != itEnd; itVal++) { - Parameter param; - if (ParseParameterProperty(¶m, err, values_object, itVal->first, - false)) { - material->extPBRValues[itVal->first] = param; - } - } - } - } - else - { - Parameter param; - if (ParseParameterProperty(¶m, err, o, it->first, - false)) { - material->additionalValues[it->first] = param; - } + for (; it != itEnd; it++) { + Parameter param; + if (ParseParameterProperty(¶m, err, values_object, it->first, + false)) { + material->values[it->first] = param; } } + } ParseExtrasProperty(&(material->extras), o); return true; } +static bool ParseShader(Shader *shader, 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) { + std::string uri; + if (!ParseStringProperty(&uri, err, o, "uri", true)) { + return false; + } + + if (is_binary) { + // Still binary glTF accepts external dataURI. First try external resources. + bool loaded = false; + if (IsDataURI(uri)) { + loaded = DecodeDataURI(&shader->source, uri, 0, false); + } else { + // Assume external .bin file. + loaded = LoadExternalFile(&shader->source, 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; + } + + // 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; + } + } + } else { + // Load shader source from data uri + // TODO(syoyo): Support ascii or utf-8 data uris. + if (IsDataURI(uri)) { + if (!DecodeDataURI(&shader->source, uri, 0, false)) { + if (err) { + (*err) += "Failed to decode 'uri' for shader parameter.\n"; + } + return false; + } + } else { + // Assume external file + if (!LoadExternalFile(&shader->source, err, uri, basedir, 0, false)) { + if (err) { + (*err) += "Failed to load external 'uri' for shader parameter.\n"; + } + return false; + } + if (shader->source.empty()) { + if (err) { + (*err) += "shader is empty.\n"; // This may be OK? + } + return false; + } + } + } + + double type; + if (!ParseNumberProperty(&type, err, o, "type", true)) { + return false; + } + + shader->type = static_cast(type); + + ParseExtrasProperty(&(shader->extras), o); + + return true; +} + +static bool ParseProgram(Program *program, std::string *err, + const picojson::object &o) { + ParseStringProperty(&program->name, err, o, "name", false); + + if (!ParseStringProperty(&program->vertexShader, err, o, "vertexShader", + true)) { + return false; + } + if (!ParseStringProperty(&program->fragmentShader, err, o, "fragmentShader", + true)) { + return false; + } + + // I suppose the list of attributes isn't needed, but a technique doesn't + // really make sense without it. + ParseStringArrayProperty(&program->attributes, err, o, "attributes", false); + + ParseExtrasProperty(&(program->extras), o); + + return true; +} + +static bool ParseTechniqueParameter(TechniqueParameter *param, std::string *err, + const picojson::object &o) { + double count = 1; + ParseNumberProperty(&count, err, o, "count", false); + + double type; + if (!ParseNumberProperty(&type, err, o, "type", true)) { + return false; + } + + ParseStringProperty(¶m->node, err, o, "node", false); + ParseStringProperty(¶m->semantic, err, o, "semantic", false); + + ParseParameterProperty(¶m->value, err, o, "value", false); + + param->count = static_cast(count); + param->type = static_cast(type); + + return true; +} + +static bool ParseTechnique(Technique *technique, std::string *err, + const picojson::object &o) { + ParseStringProperty(&technique->name, err, o, "name", false); + + if (!ParseStringProperty(&technique->program, err, o, "program", true)) { + return false; + } + + ParseStringMapProperty(&technique->attributes, err, o, "attributes", false); + ParseStringMapProperty(&technique->uniforms, err, o, "uniforms", false); + + technique->parameters.clear(); + picojson::object::const_iterator paramsIt = o.find("parameters"); + + // Verify parameters is an object + if ((paramsIt != o.end()) && (paramsIt->second).is()) { + // For each parameter in params_object. + const picojson::object ¶ms_object = + (paramsIt->second).get(); + + picojson::object::const_iterator it(params_object.begin()); + picojson::object::const_iterator itEnd(params_object.end()); + + for (; it != itEnd; it++) { + TechniqueParameter param; + + // Skip non-objects + if (!it->second.is()) continue; + + // Parse the technique parameter + const picojson::object ¶m_obj = it->second.get(); + if (ParseTechniqueParameter(¶m, err, param_obj)) { + // Add if successful + technique->parameters[it->first] = param; + } + } + } + + ParseExtrasProperty(&(technique->extras), o); + + return true; +} + static bool ParseAnimationChannel(AnimationChannel *channel, std::string *err, const picojson::object &o) { - double samplerIndex = -1.0; - double targetIndex = -1.0; - if (!ParseNumberProperty(&samplerIndex, err, o, "sampler", true)) { + if (!ParseStringProperty(&channel->sampler, err, o, "sampler", true)) { if (err) { (*err) += "`sampler` field is missing in animation channels\n"; } @@ -1984,7 +2042,7 @@ static bool ParseAnimationChannel(AnimationChannel *channel, std::string *err, const picojson::object &target_object = (targetIt->second).get(); - if (!ParseNumberProperty(&targetIndex, err, target_object, "node", + if (!ParseStringProperty(&channel->target_id, err, target_object, "id", true)) { if (err) { (*err) += "`id` field is missing in animation.channels.target\n"; @@ -2001,9 +2059,6 @@ static bool ParseAnimationChannel(AnimationChannel *channel, std::string *err, } } - channel->sampler = static_cast(samplerIndex); - channel->target_node = static_cast(targetIndex); - ParseExtrasProperty(&(channel->extras), o); return true; @@ -2029,20 +2084,21 @@ static bool ParseAnimation(Animation *animation, std::string *err, { picojson::object::const_iterator samplerIt = o.find("samplers"); - if ((samplerIt != o.end()) && (samplerIt->second).is()) { - const picojson::array &sampler_array = - (samplerIt->second).get(); + if ((samplerIt != o.end()) && (samplerIt->second).is()) { + const picojson::object &sampler_object = + (samplerIt->second).get(); - picojson::array::const_iterator it = sampler_array.begin(); - picojson::array::const_iterator itEnd = sampler_array.end(); + picojson::object::const_iterator it = sampler_object.begin(); + picojson::object::const_iterator itEnd = sampler_object.end(); for (; it != itEnd; it++) { - const picojson::object &s = it->get(); + // Skip non-objects + if (!it->second.is()) continue; + + const picojson::object &s = it->second.get(); AnimationSampler sampler; - double inputIndex = -1.0; - double outputIndex = -1.0; - if (!ParseNumberProperty(&inputIndex, err, s, "input", true)) { + if (!ParseStringProperty(&sampler.input, err, s, "input", true)) { if (err) { (*err) += "`input` field is missing in animation.sampler\n"; } @@ -2055,19 +2111,35 @@ static bool ParseAnimation(Animation *animation, std::string *err, } return false; } - if (!ParseNumberProperty(&outputIndex, err, s, "output", true)) { + if (!ParseStringProperty(&sampler.output, err, s, "output", true)) { if (err) { (*err) += "`output` field is missing in animation.sampler\n"; } return false; } - sampler.input = static_cast(inputIndex); - sampler.output = static_cast(outputIndex); - animation->samplers.push_back(sampler); + + animation->samplers[it->first] = sampler; } } } + picojson::object::const_iterator parametersIt = o.find("parameters"); + if ((parametersIt != o.end()) && + (parametersIt->second).is()) { + const picojson::object ¶meters_object = + (parametersIt->second).get(); + + picojson::object::const_iterator it(parameters_object.begin()); + picojson::object::const_iterator itEnd(parameters_object.end()); + + for (; it != itEnd; it++) { + Parameter param; + if (ParseParameterProperty(¶m, err, parameters_object, it->first, + false)) { + animation->parameters[it->first] = param; + } + } + } ParseStringProperty(&animation->name, err, o, "name", false); ParseExtrasProperty(&(animation->extras), o); @@ -2099,30 +2171,7 @@ static bool ParseSampler(Sampler *sampler, std::string *err, return true; } -static bool ParseSkin(Skin *skin, std::string *err, - const picojson::object &o) { - - ParseStringProperty(&skin->name, err, o, "name", false); - - std::vector joints; - if (!ParseNumberArrayProperty(&joints, err, o, "joints", false)) { - return false; - } - - double skeleton; - ParseNumberProperty(&skeleton, err, o, "skeleton", false); - skin->skeleton = static_cast(skeleton); - - skin->joints = std::vector(joints.begin(), joints.end()); - - double invBind = -1.0; - ParseNumberProperty(&invBind, err, o, "inverseBindMatrices", true); - skin->inverseBindMatrices = static_cast(invBind); - - return true; -} - -bool TinyGLTFLoader::LoadFromString(Model *model, std::string *err, +bool TinyGLTFLoader::LoadFromString(Scene *scene, std::string *err, const char *str, unsigned int length, const std::string &base_dir, unsigned int check_sections) { @@ -2136,10 +2185,16 @@ bool TinyGLTFLoader::LoadFromString(Model *model, std::string *err, return false; } - // scene is not mandatory. - //FIXME Maybe a better way to handle it than removing the code + if (v.contains("scene") && v.get("scene").is()) { + // OK + } else if (check_sections & REQUIRE_SCENE) { + if (err) { + (*err) += "\"scene\" object not found in .gltf\n"; + } + return false; + } -if (v.contains("scenes") && v.get("scenes").is()) { + if (v.contains("scenes") && v.get("scenes").is()) { // OK } else if (check_sections & REQUIRE_SCENES) { if (err) { @@ -2148,7 +2203,7 @@ if (v.contains("scenes") && v.get("scenes").is()) { return false; } - if (v.contains("nodes") && v.get("nodes").is()) { + if (v.contains("nodes") && v.get("nodes").is()) { // OK } else if (check_sections & REQUIRE_NODES) { if (err) { @@ -2157,7 +2212,7 @@ if (v.contains("scenes") && v.get("scenes").is()) { return false; } - if (v.contains("accessors") && v.get("accessors").is()) { + if (v.contains("accessors") && v.get("accessors").is()) { // OK } else if (check_sections & REQUIRE_ACCESSORS) { if (err) { @@ -2166,7 +2221,7 @@ if (v.contains("scenes") && v.get("scenes").is()) { return false; } - if (v.contains("buffers") && v.get("buffers").is()) { + if (v.contains("buffers") && v.get("buffers").is()) { // OK } else if (check_sections & REQUIRE_BUFFERS) { if (err) { @@ -2176,7 +2231,7 @@ if (v.contains("scenes") && v.get("scenes").is()) { } if (v.contains("bufferViews") && - v.get("bufferViews").is()) { + v.get("bufferViews").is()) { // OK } else if (check_sections & REQUIRE_BUFFER_VIEWS) { if (err) { @@ -2185,184 +2240,168 @@ if (v.contains("scenes") && v.get("scenes").is()) { return false; } - model->buffers.clear(); - model->bufferViews.clear(); - model->accessors.clear(); - model->meshes.clear(); - model->nodes.clear(); - model->extensionsUsed.clear(); - model->defaultScene = -1; + 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()) { const picojson::object &root = v.get("asset").get(); - ParseAsset(&model->asset, err, root); - } - - // 0. Parse extensionUsed - if (v.contains("extensionsUsed") && v.get("extensionsUsed").is()) { - const picojson::array &root = v.get("extensionsUsed").get(); - for(unsigned int i=0; i< root.size(); ++i) - { - model->extensionsUsed.push_back(root[i].get()); - } + ParseAsset(&scene->asset, err, root); } // 1. Parse Buffer - if (v.contains("buffers") && v.get("buffers").is()) { - const picojson::array &root = v.get("buffers").get(); + if (v.contains("buffers") && v.get("buffers").is()) { + const picojson::object &root = v.get("buffers").get(); - picojson::array::const_iterator it(root.begin()); - picojson::array::const_iterator itEnd(root.end()); + 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->get(), + if (!ParseBuffer(&buffer, err, (it->second).get(), base_dir, is_binary_, bin_data_, bin_size_)) { return false; } - model->buffers.push_back(buffer); + scene->buffers[it->first] = buffer; } } // 2. Parse BufferView if (v.contains("bufferViews") && - v.get("bufferViews").is()) { - const picojson::array &root = v.get("bufferViews").get(); + v.get("bufferViews").is()) { + const picojson::object &root = v.get("bufferViews").get(); - picojson::array::const_iterator it(root.begin()); - picojson::array::const_iterator itEnd(root.end()); + 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->get())) { + (it->second).get())) { return false; } - model->bufferViews.push_back(bufferView); + scene->bufferViews[it->first] = bufferView; } } // 3. Parse Accessor - if (v.contains("accessors") && v.get("accessors").is()) { - const picojson::array &root = v.get("accessors").get(); + if (v.contains("accessors") && v.get("accessors").is()) { + const picojson::object &root = v.get("accessors").get(); - picojson::array::const_iterator it(root.begin()); - picojson::array::const_iterator itEnd(root.end()); + 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->get())) { + (it->second).get())) { return false; } - model->accessors.push_back(accessor); + scene->accessors[it->first] = accessor; } } // 4. Parse Mesh - if (v.contains("meshes") && v.get("meshes").is()) { - const picojson::array &root = v.get("meshes").get(); + if (v.contains("meshes") && v.get("meshes").is()) { + const picojson::object &root = v.get("meshes").get(); - picojson::array::const_iterator it(root.begin()); - picojson::array::const_iterator itEnd(root.end()); + 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->get())) { + if (!ParseMesh(&mesh, err, (it->second).get())) { return false; } - model->meshes.push_back(mesh); + scene->meshes[it->first] = mesh; } } // 5. Parse Node - if (v.contains("nodes") && v.get("nodes").is()) { - const picojson::array &root = v.get("nodes").get(); + if (v.contains("nodes") && v.get("nodes").is()) { + const picojson::object &root = v.get("nodes").get(); - picojson::array::const_iterator it(root.begin()); - picojson::array::const_iterator itEnd(root.end()); + 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->get())) { + if (!ParseNode(&node, err, (it->second).get())) { return false; } - model->nodes.push_back(node); + scene->nodes[it->first] = node; } } // 6. Parse scenes. - if (v.contains("scenes") && v.get("scenes").is()) { - const picojson::array &root = v.get("scenes").get(); + if (v.contains("scenes") && v.get("scenes").is()) { + const picojson::object &root = v.get("scenes").get(); - picojson::array::const_iterator it(root.begin()); - picojson::array::const_iterator itEnd(root.end()); + picojson::object::const_iterator it(root.begin()); + picojson::object::const_iterator itEnd(root.end()); for (; it != itEnd; it++) { - if (!(it->is())) { + if (!((it->second).is())) { if (err) { (*err) += "`scenes' does not contain an object."; } return false; } - const picojson::object &o = it->get(); - std::vector nodes; - if (!ParseNumberArrayProperty(&nodes, err, o, "nodes", false)) { + const picojson::object &o = (it->second).get(); + std::vector nodes; + if (!ParseStringArrayProperty(&nodes, err, o, "nodes", false)) { return false; } - Scene scene; - ParseStringProperty(&scene.name, err, o, "name", false); - std::vector nodesIds(nodes.begin(), nodes.end()); - scene.nodes = nodesIds; - - model->scenes.push_back(scene); + scene->scenes[it->first] = nodes; } } // 7. Parse default scenes. - if (v.contains("scene") && v.get("scene").is()) { - const int defaultScene = v.get("scene").get(); + if (v.contains("scene") && v.get("scene").is()) { + const std::string defaultScene = v.get("scene").get(); - model->defaultScene = static_cast(defaultScene); + scene->defaultScene = defaultScene; } // 8. Parse Material - if (v.contains("materials") && v.get("materials").is()) { - const picojson::array &root = v.get("materials").get(); - picojson::array::const_iterator it(root.begin()); - picojson::array::const_iterator itEnd(root.end()); + if (v.contains("materials") && v.get("materials").is()) { + const picojson::object &root = v.get("materials").get(); + + picojson::object::const_iterator it(root.begin()); + picojson::object::const_iterator itEnd(root.end()); for (; it != itEnd; it++) { - picojson::object jsonMaterial = it->get(); - Material material; - ParseStringProperty(&material.name, err, jsonMaterial, "name", false); - - if (!ParseMaterial(&material, err, jsonMaterial)) { + if (!ParseMaterial(&material, err, + (it->second).get())) { return false; } - model->materials.push_back(material); + scene->materials[it->first] = material; } } // 9. Parse Image - if (v.contains("images") && v.get("images").is()) { - const picojson::array &root = v.get("images").get(); + if (v.contains("images") && v.get("images").is()) { + const picojson::object &root = v.get("images").get(); - picojson::array::const_iterator it(root.begin()); - picojson::array::const_iterator itEnd(root.end()); + 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->get(), + if (!ParseImage(&image, err, (it->second).get(), base_dir, is_binary_, bin_data_, bin_size_)) { return false; } - if (image.bufferView != -1) { + if (!image.bufferView.empty()) { // Load image from the buffer view. - if ((size_t)image.bufferView >= model->bufferViews.size()) { + if (scene->bufferViews.find(image.bufferView) == + scene->bufferViews.end()) { if (err) { std::stringstream ss; ss << "bufferView \"" << image.bufferView @@ -2372,8 +2411,8 @@ if (v.contains("scenes") && v.get("scenes").is()) { return false; } - const BufferView &bufferView = model->bufferViews[image.bufferView]; - const Buffer &buffer = model->buffers[bufferView.buffer]; + 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], @@ -2383,80 +2422,113 @@ if (v.contains("scenes") && v.get("scenes").is()) { } } - model->images.push_back(image); + scene->images[it->first] = image; } } // 10. Parse Texture - if (v.contains("textures") && v.get("textures").is()) { - const picojson::array &root = v.get("textures").get(); + if (v.contains("textures") && v.get("textures").is()) { + const picojson::object &root = v.get("textures").get(); - picojson::array::const_iterator it(root.begin()); - picojson::array::const_iterator itEnd(root.end()); + picojson::object::const_iterator it(root.begin()); + picojson::object::const_iterator itEnd(root.end()); for (; it != itEnd; it++) { Texture texture; - if (!ParseTexture(&texture, err, it->get(), + if (!ParseTexture(&texture, err, (it->second).get(), base_dir)) { return false; } - model->textures.push_back(texture); + scene->textures[it->first] = texture; } } - // 11. Parse Animation - if (v.contains("animations") && v.get("animations").is()) { - const picojson::array &root = v.get("animations").get(); + // 11. Parse Shader + if (v.contains("shaders") && v.get("shaders").is()) { + const picojson::object &root = v.get("shaders").get(); - picojson::array::const_iterator it(root.begin()); - picojson::array::const_iterator itEnd(root.end()); + picojson::object::const_iterator it(root.begin()); + picojson::object::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + Shader shader; + if (!ParseShader(&shader, err, (it->second).get(), + base_dir, is_binary_, bin_data_, bin_size_)) { + return false; + } + + scene->shaders[it->first] = shader; + } + } + + // 12. Parse Program + if (v.contains("programs") && v.get("programs").is()) { + const picojson::object &root = v.get("programs").get(); + + picojson::object::const_iterator it(root.begin()); + picojson::object::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + Program program; + if (!ParseProgram(&program, err, (it->second).get())) { + return false; + } + + scene->programs[it->first] = program; + } + } + + // 13. Parse Technique + if (v.contains("techniques") && v.get("techniques").is()) { + const picojson::object &root = v.get("techniques").get(); + + picojson::object::const_iterator it(root.begin()); + picojson::object::const_iterator itEnd(root.end()); + for (; it != itEnd; ++it) { + Technique technique; + if (!ParseTechnique(&technique, err, + (it->second).get())) { + return false; + } + + scene->techniques[it->first] = technique; + } + } + + // 14. Parse Animation + if (v.contains("animations") && v.get("animations").is()) { + const picojson::object &root = v.get("animations").get(); + + picojson::object::const_iterator it(root.begin()); + picojson::object::const_iterator itEnd(root.end()); for (; it != itEnd; ++it) { Animation animation; if (!ParseAnimation(&animation, err, - it->get())) { + (it->second).get())) { return false; } - model->animations.push_back(animation); + scene->animations[it->first] = animation; } } - // 12. Parse Skin - if (v.contains("skins") && v.get("skins").is()) { - const picojson::array &root = v.get("skins").get(); + // 15. Parse Sampler + if (v.contains("samplers") && v.get("samplers").is()) { + const picojson::object &root = v.get("samplers").get(); - picojson::array::const_iterator it(root.begin()); - picojson::array::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - Skin skin; - if (!ParseSkin(&skin, err, - it->get())) { - return false; - } - - model->skins.push_back(skin); - } - } - - // 13. Parse Sampler - if (v.contains("samplers") && v.get("samplers").is()) { - const picojson::array &root = v.get("samplers").get(); - - picojson::array::const_iterator it(root.begin()); - picojson::array::const_iterator itEnd(root.end()); + picojson::object::const_iterator it(root.begin()); + picojson::object::const_iterator itEnd(root.end()); for (; it != itEnd; ++it) { Sampler sampler; - if (!ParseSampler(&sampler, err, it->get())) { + if (!ParseSampler(&sampler, err, (it->second).get())) { return false; } - model->samplers.push_back(sampler); + scene->samplers[it->first] = sampler; } } return true; } -bool TinyGLTFLoader::LoadASCIIFromString(Model *model, std::string *err, +bool TinyGLTFLoader::LoadASCIIFromString(Scene *scene, std::string *err, const char *str, unsigned int length, const std::string &base_dir, unsigned int check_sections) { @@ -2464,10 +2536,10 @@ bool TinyGLTFLoader::LoadASCIIFromString(Model *model, std::string *err, bin_data_ = NULL; bin_size_ = 0; - return LoadFromString(model, err, str, length, base_dir, check_sections); + return LoadFromString(scene, err, str, length, base_dir, check_sections); } -bool TinyGLTFLoader::LoadASCIIFromFile(Model *model, std::string *err, +bool TinyGLTFLoader::LoadASCIIFromFile(Scene *scene, std::string *err, const std::string &filename, unsigned int check_sections) { std::stringstream ss; @@ -2498,14 +2570,14 @@ bool TinyGLTFLoader::LoadASCIIFromFile(Model *model, std::string *err, std::string basedir = GetBaseDir(filename); - bool ret = LoadASCIIFromString(model, err, &buf.at(0), + bool ret = LoadASCIIFromString(scene, err, &buf.at(0), static_cast(buf.size()), basedir, check_sections); return ret; } -bool TinyGLTFLoader::LoadBinaryFromMemory(Model *model, std::string *err, +bool TinyGLTFLoader::LoadBinaryFromMemory(Scene *scene, std::string *err, const unsigned char *bytes, unsigned int size, const std::string &base_dir, @@ -2529,21 +2601,21 @@ bool TinyGLTFLoader::LoadBinaryFromMemory(Model *model, std::string *err, unsigned int version; // 4 bytes unsigned int length; // 4 bytes - unsigned int model_length; // 4 bytes - unsigned int model_format; // 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(&model_length, bytes + 12, 4); - swap4(&model_length); - memcpy(&model_format, bytes + 16, 4); - swap4(&model_format); + memcpy(&scene_length, bytes + 12, 4); + swap4(&scene_length); + memcpy(&scene_format, bytes + 16, 4); + swap4(&scene_format); - if ((20 + model_length >= size) || (model_length < 1) || - (model_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."; } @@ -2552,16 +2624,16 @@ bool TinyGLTFLoader::LoadBinaryFromMemory(Model *model, std::string *err, // Extract JSON string. std::string jsonString(reinterpret_cast(&bytes[20]), - model_length); + scene_length); is_binary_ = true; - bin_data_ = bytes + 20 + model_length; + bin_data_ = bytes + 20 + scene_length; bin_size_ = - length - (20 + model_length); // extract header + JSON scene data. + length - (20 + scene_length); // extract header + JSON scene data. bool ret = - LoadFromString(model, err, reinterpret_cast(&bytes[20]), - model_length, base_dir, check_sections); + LoadFromString(scene, err, reinterpret_cast(&bytes[20]), + scene_length, base_dir, check_sections); if (!ret) { return ret; } @@ -2569,7 +2641,7 @@ bool TinyGLTFLoader::LoadBinaryFromMemory(Model *model, std::string *err, return true; } -bool TinyGLTFLoader::LoadBinaryFromFile(Model *model, std::string *err, +bool TinyGLTFLoader::LoadBinaryFromFile(Scene *scene, std::string *err, const std::string &filename, unsigned int check_sections) { std::stringstream ss; @@ -2594,7 +2666,7 @@ bool TinyGLTFLoader::LoadBinaryFromFile(Model *model, std::string *err, std::string basedir = GetBaseDir(filename); bool ret = LoadBinaryFromMemory( - model, err, reinterpret_cast(&buf.at(0)), + scene, err, reinterpret_cast(&buf.at(0)), static_cast(buf.size()), basedir, check_sections); return ret; From 2682c6fb4b1b089afc902ee0af74a95727dababd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Chatelain?= Date: Wed, 14 Jun 2017 09:46:57 +0000 Subject: [PATCH 4/7] Fixes morph targets parsing --- tiny_gltf_loader.h | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/tiny_gltf_loader.h b/tiny_gltf_loader.h index 90e02f3..651e9d7 100644 --- a/tiny_gltf_loader.h +++ b/tiny_gltf_loader.h @@ -403,7 +403,7 @@ struct BufferView{ BufferView() : byteOffset(0) , byteStride(4) - {} + {} }; @@ -474,8 +474,7 @@ struct Primitive { typedef struct { std::string name; std::vector primitives; - std::vector weights; // weights to be applied to the Morph Targets - std::vector >targets; + std::vector weights; // weights to be applied to the Morph Targets ParameterMap extensions; Value extras; } Mesh; @@ -500,7 +499,6 @@ class Node { std::vector scale; // length must be 0 or 3 std::vector translation; // length must be 0 or 3 std::vector matrix; // length must be 0 or 16 - std::vector weights; // The weights of the instantiated Morph Target Value extras; }; @@ -1714,6 +1712,25 @@ static bool ParsePrimitive(Primitive *primitive, std::string *err, return false; } + // Look for morph targets + picojson::object::const_iterator targetsObject = o.find("targets"); + if ((targetsObject != o.end()) && (targetsObject->second).is()) { + const picojson::array &targetArray = + (targetsObject->second).get(); + for (size_t i = 0; i < targetArray.size(); i++) { + std::map targetAttribues; + + const picojson::object &dict = targetArray[i].get(); + picojson::object::const_iterator dictIt(dict.begin()); + picojson::object::const_iterator dictItEnd(dict.end()); + + for (; dictIt != dictItEnd; ++dictIt) { + targetAttribues[dictIt->first] = static_cast(dictIt->second.get()); + } + primitive->targets.push_back(targetAttribues); + } + } + ParseExtrasProperty(&(primitive->extras), o); return true; @@ -1737,25 +1754,6 @@ static bool ParseMesh(Mesh *mesh, std::string *err, const picojson::object &o) { } } - // Look for morph targets - picojson::object::const_iterator targetsObject = o.find("targets"); - if ((targetsObject != o.end()) && (targetsObject->second).is()) { - const picojson::array &targetArray = - (targetsObject->second).get(); - for (size_t i = 0; i < targetArray.size(); i++) { - std::map targetAttribues; - - const picojson::object &dict = targetArray[i].get(); - picojson::object::const_iterator dictIt(dict.begin()); - picojson::object::const_iterator dictItEnd(dict.end()); - - for (; dictIt != dictItEnd; ++dictIt) { - targetAttribues[dictIt->first] = static_cast(dictIt->second.get()); - } - mesh->targets.push_back(targetAttribues); - } - } - // Should probably check if has targets and if dimensions fit ParseNumberArrayProperty(&mesh->weights, err, o, "weights", false); From 8d5e0a75e898a7573c26b9cc871d729dfc191771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Chatelain?= Date: Mon, 19 Jun 2017 13:52:08 +0000 Subject: [PATCH 5/7] Fixes typos and useless data --- tiny_gltf_loader.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tiny_gltf_loader.h b/tiny_gltf_loader.h index 651e9d7..1424791 100644 --- a/tiny_gltf_loader.h +++ b/tiny_gltf_loader.h @@ -327,6 +327,7 @@ struct Skin { Skin() { inverseBindMatrices = -1; + skeleton = -1; } }; @@ -356,7 +357,7 @@ struct Image{ std::vector image; int bufferView; // (required if no uri) std::string mimeType; // (required if no uri) ["image/jpeg", "image/png"] - std::string uri; // (reqiored if no mimeType) + std::string uri; // (required if no mimeType) Value extras; Image() @@ -367,7 +368,7 @@ struct Image{ struct Texture { int sampler; - int source; // Required (not specified in the spec ?) + int source; Value extras; Texture() @@ -408,7 +409,7 @@ struct BufferView{ }; struct Accessor { - int bufferView; // optional in spec but required here since sparse accessor are not supported + int bufferView; // optional in spec but required here since sparse accessor are not yet supported std::string name; size_t byteOffset; size_t byteStride; @@ -474,7 +475,7 @@ struct Primitive { typedef struct { std::string name; std::vector primitives; - std::vector weights; // weights to be applied to the Morph Targets + std::vector weights; // weights to be applied to the morph targets ParameterMap extensions; Value extras; } Mesh; @@ -1464,9 +1465,7 @@ static bool ParseTexture(Texture *texture, std::string *err, double source = -1.0; ParseNumberProperty(&sampler, err, o, "sampler", false); - if (!ParseNumberProperty(&source, err, o, "source", true)) { - return false; - } + ParseNumberProperty(&source, err, o, "source", false); texture->sampler = static_cast(sampler); texture->source = static_cast(source); @@ -2051,7 +2050,7 @@ static bool ParseSkin(Skin *skin, std::string *err, return false; } - double skeleton; + double skeleton = -1.0; ParseNumberProperty(&skeleton, err, o, "skeleton", false, "Skin"); skin->skeleton = static_cast(skeleton); From 756ee6ba4d7945a084d292013861fea227e1e896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Chatelain?= Date: Mon, 19 Jun 2017 13:52:49 +0000 Subject: [PATCH 6/7] Adds extensionsRequired object --- tiny_gltf_loader.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tiny_gltf_loader.h b/tiny_gltf_loader.h index 1424791..19fc492 100644 --- a/tiny_gltf_loader.h +++ b/tiny_gltf_loader.h @@ -548,6 +548,7 @@ class Model { int defaultScene; std::vector extensionsUsed; + std::vector extensionsRequired; Asset asset; @@ -2132,6 +2133,7 @@ if (v.contains("scenes") && v.get("scenes").is()) { model->meshes.clear(); model->nodes.clear(); model->extensionsUsed.clear(); + model->extensionsRequired.clear(); model->defaultScene = -1; // 0. Parse Asset @@ -2141,7 +2143,7 @@ if (v.contains("scenes") && v.get("scenes").is()) { ParseAsset(&model->asset, err, root); } - // 0. Parse extensionUsed + // 0. Parse extensionsUsed ans extensionsRequired if (v.contains("extensionsUsed") && v.get("extensionsUsed").is()) { const picojson::array &root = v.get("extensionsUsed").get(); for(unsigned int i=0; i< root.size(); ++i) @@ -2149,6 +2151,13 @@ if (v.contains("scenes") && v.get("scenes").is()) { model->extensionsUsed.push_back(root[i].get()); } } + if (v.contains("extensionsRequired") && v.get("extensionsRequired").is()) { + const picojson::array &root = v.get("extensionsRequired").get(); + for(unsigned int i=0; i< root.size(); ++i) + { + model->extensionsRequired.push_back(root[i].get()); + } + } // 1. Parse Buffer if (v.contains("buffers") && v.get("buffers").is()) { From cc8ba991792e30a6296cf3bd4b786687610a549d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Chatelain?= Date: Mon, 19 Jun 2017 13:53:13 +0000 Subject: [PATCH 7/7] Adds serialization functions --- tiny_gltf_loader.h | 585 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 585 insertions(+) diff --git a/tiny_gltf_loader.h b/tiny_gltf_loader.h index 19fc492..2f5a4c9 100644 --- a/tiny_gltf_loader.h +++ b/tiny_gltf_loader.h @@ -239,6 +239,10 @@ class Value { return keys; } + size_t Size() const { + return (IsArray() ? ArrayLen() : Keys().size()); + } + protected: int type_; @@ -602,6 +606,8 @@ class TinyGLTFLoader { const std::string &base_dir = "", unsigned int check_sections = REQUIRE_ALL); + bool WriteGltfSceneToFile(Model *model, const std::string &filename/*, bool embedImages, bool embedBuffers, bool writeBinary*/); + private: /// Loads glTF asset from string(memory). /// `length` = strlen(str); @@ -2550,6 +2556,585 @@ bool TinyGLTFLoader::LoadBinaryFromFile(Model *model, std::string *err, return ret; } +/////////////////////// +// GLTF Serialization +/////////////////////// + +typedef std::pair json_object_pair; + +template +static void SerializeNumberProperty(const std::string &key, T number, picojson::object &obj) +{ + obj.insert(json_object_pair(key, picojson::value(static_cast(number)))); +} + +template +static void SerializeNumberArrayProperty(const std::string &key, const std::vector &value, picojson::object &obj) +{ + picojson::object o; + picojson::array vals; + + for(unsigned int i = 0; i < value.size() ; ++i) + { + vals.push_back(picojson::value(static_cast(value[i]))); + } + + obj.insert(json_object_pair(key, picojson::value(vals))); + +} + +static void SerializeStringProperty(const std::string &key, const std::string &value, picojson::object &obj) +{ + picojson::value strVal(value); + obj.insert(json_object_pair(key, strVal)); +} + +static void SerializeStringArrayProperty(const std::string &key, const std::vector &value, picojson::object &obj) +{ + picojson::object o; + picojson::array vals; + + for(unsigned int i = 0; i < value.size() ; ++i) + { + vals.push_back(picojson::value(value[i])); + } + + obj.insert(json_object_pair(key, picojson::value(vals))); +} + +static void SerializeValue(const std::string &key, const Value &value, picojson::object &obj) +{ + if(value.IsArray()) + { + picojson::array jsonValue; + for(unsigned int i = 0; i < value.ArrayLen(); ++i) + { + Value elementValue = value.Get(i); + if(elementValue.IsString()) + jsonValue.push_back(picojson::value(elementValue.Get())); + } + obj.insert(json_object_pair(key, picojson::value(jsonValue))); + } + else + { + picojson::object jsonValue; + std::vector valueKeys; + for(unsigned int i = 0; i < valueKeys.size(); ++i) + { + Value elementValue = value.Get(valueKeys[i]); + if(elementValue.IsInt()) + jsonValue.insert(json_object_pair(valueKeys[i], picojson::value(static_cast(elementValue.Get())))); + } + + obj.insert(json_object_pair(key, picojson::value(jsonValue))); + } + +} + +static void SerializeGltfBufferData(const std::vector &data, const std::string &binFilePath) +{ + std::ofstream output(binFilePath.c_str(), std::ofstream::binary); + output.write((const char*)&data[0], data.size()); + output.close(); +} + +static void SerializeParameterMap(ParameterMap ¶m, picojson::object &o) +{ + for(ParameterMap::iterator paramIt = param.begin(); paramIt != param.end(); ++paramIt) + { + if(paramIt->second.number_array.size()) + { + SerializeNumberArrayProperty(paramIt->first, paramIt->second.number_array, o); + } + else if(paramIt->second.json_double_value.size()) + { + picojson::object json_double_value; + + for(std::map::iterator it=paramIt->second.json_double_value.begin(); it != paramIt->second.json_double_value.end(); ++it) + { + json_double_value.insert(json_object_pair(it->first, picojson::value(it->second))); + } + + o.insert(json_object_pair(paramIt->first, picojson::value(json_double_value))); + } + else if(!paramIt->second.string_value.empty()) + { + SerializeStringProperty(paramIt->first, paramIt->second.string_value, o); + } + else + { + o.insert(json_object_pair(paramIt->first, picojson::value(paramIt->second.bool_value))); + } + } +} + +static void SerializeGltfAccessor(Accessor &accessor, picojson::object &o) +{ + SerializeNumberProperty("bufferView", accessor.bufferView, o); + + if(accessor.byteOffset != 0.0 ) + SerializeNumberProperty("byteOffset", accessor.byteOffset, o); + + SerializeNumberProperty("componentType", accessor.componentType, o); + SerializeNumberProperty("count", accessor.count, o); + SerializeNumberArrayProperty("min", accessor.minValues, o); + SerializeNumberArrayProperty("max", accessor.maxValues, o); + std::string type; + switch(accessor.type) + { + case TINYGLTF_TYPE_SCALAR: + type = "SCALAR"; + break; + case TINYGLTF_TYPE_VEC2: + type = "VEC2"; + break; + case TINYGLTF_TYPE_VEC3: + type = "VEC3"; + break; + case TINYGLTF_TYPE_VEC4: + type = "VEC4"; + break; + case TINYGLTF_TYPE_MAT2: + type = "MAT2"; + break; + case TINYGLTF_TYPE_MAT3: + type = "MAT3"; + break; + case TINYGLTF_TYPE_MAT4: + type = "MAT4"; + break; + } + + SerializeStringProperty("type", type, o); +} + +static void SerializeGltfAnimationChannel(AnimationChannel &channel, picojson::object &o) +{ + SerializeNumberProperty("sampler", channel.sampler, o); + picojson::object target; + SerializeNumberProperty("node", channel.target_node, target); + SerializeStringProperty("path", channel.target_path, target); + + o.insert(json_object_pair("target", picojson::value(target))); +} + +static void SerializeGltfAnimationSampler(AnimationSampler &sampler, picojson::object &o) +{ + SerializeNumberProperty("input", sampler.input, o); + SerializeNumberProperty("output", sampler.output, o); + SerializeStringProperty("interpolation", sampler.interpolation, o); +} + +static void SerializeGltfAnimation(Animation &animation, picojson::object &o) +{ + SerializeStringProperty("name", animation.name, o); + picojson::array channels; + for(unsigned int i = 0; i < animation.channels.size(); ++i) + { + picojson::object channel; + AnimationChannel gltfChannel = animation.channels[i]; + SerializeGltfAnimationChannel(gltfChannel, channel); + channels.push_back(picojson::value(channel)); + } + o.insert(json_object_pair("channels", picojson::value(channels))); + + picojson::array samplers; + for(unsigned int i = 0; i < animation.samplers.size(); ++i) + { + picojson::object sampler; + AnimationSampler gltfSampler = animation.samplers[i]; + SerializeGltfAnimationSampler(gltfSampler, sampler); + samplers.push_back(picojson::value(sampler)); + } + + o.insert(json_object_pair("samplers", picojson::value(samplers))); +} + +static void SerializeGltfAsset(Asset &asset, picojson::object &o) +{ + if(!asset.generator.empty()) + { + SerializeStringProperty("generator", asset.generator, o); + } + + if(!asset.version.empty()) + { + SerializeStringProperty("version", asset.version, o); + } + + if(asset.extras.Keys().size()) + { + SerializeValue("extras", asset.extras, o); + } +} + +static void SerializeGltfBuffer(Buffer &buffer, picojson::object &o, const std::string &binFilePath) +{ + SerializeGltfBufferData(buffer.data, binFilePath); + SerializeNumberProperty("byteLength", buffer.data.size(), o); + SerializeStringProperty("uri", binFilePath, o); + + if(buffer.name.size()) + SerializeStringProperty("name", buffer.name, o); +} + +static void SerializeGltfBufferView(BufferView &bufferView, picojson::object &o) +{ + SerializeNumberProperty("buffer", bufferView.buffer, o); + SerializeNumberProperty("byteLength", bufferView.byteLength, o); + SerializeNumberProperty("byteStride", bufferView.byteStride, o); + SerializeNumberProperty("byteOffset", bufferView.byteOffset, o); + SerializeNumberProperty("target", bufferView.target, o); + + if(bufferView.name.size()) + { + SerializeStringProperty("name", bufferView.name, o); + } +} + +// Only external textures are serialized for now +static void SerializeGltfImage(Image &image, picojson::object &o) +{ + SerializeStringProperty("uri", image.uri, o); + + if(image.name.size()) + { + SerializeStringProperty("name", image.name, o); + } +} + +static void SerializeGltfMaterial(Material &material, picojson::object &o) +{ + if(material.extPBRValues.size()) + { + // Serialize PBR specular/glossiness material + picojson::object values; + SerializeParameterMap(material.extPBRValues, values); + + picojson::object extension; + o.insert(json_object_pair("extensions", picojson::value(extension))); + } + + if(material.values.size()) + { + picojson::object pbrMetallicRoughness; + SerializeParameterMap(material.values, pbrMetallicRoughness); + o.insert(json_object_pair("pbrMetallicRoughness", picojson::value(pbrMetallicRoughness))); + } + + picojson::object additionalValues; + SerializeParameterMap(material.additionalValues, o); + + if(material.name.size()) + { + SerializeStringProperty("name", material.name, o); + } +} + +static void SerializeGltfMesh(Mesh &mesh, picojson::object &o) +{ + picojson::array primitives; + for(unsigned int i=0; i < mesh.primitives.size(); ++i) + { + picojson::object primitive; + picojson::object attributes; + Primitive gltfPrimitive = mesh.primitives[i]; + for(std::map::iterator attrIt = gltfPrimitive.attributes.begin(); attrIt != gltfPrimitive.attributes.end(); ++attrIt) + { + SerializeNumberProperty(attrIt->first, attrIt->second, attributes); + } + + primitive.insert(json_object_pair("attributes", picojson::value(attributes))); + SerializeNumberProperty("indices", gltfPrimitive.indices, primitive); + SerializeNumberProperty("material", gltfPrimitive.material, primitive); + SerializeNumberProperty("mode", gltfPrimitive.mode, primitive); + + // Morph targets + if(gltfPrimitive.targets.size()) + { + picojson::array targets; + for(unsigned int i = 0; i < gltfPrimitive.targets.size(); ++i) + { + picojson::object targetAttributes; + std::map targetData = gltfPrimitive.targets[i]; + for(std::map::iterator attrIt = targetData.begin(); attrIt != targetData.end(); ++attrIt) + { + SerializeNumberProperty(attrIt->first, attrIt->second, targetAttributes); + } + + targets.push_back(picojson::value(targetAttributes)); + } + primitive.insert(json_object_pair("targets", picojson::value(targets))); + } + + primitives.push_back(picojson::value(primitive)); + } + + o.insert(json_object_pair("primitives", picojson::value(primitives))); + if(mesh.weights.size()) + { + SerializeNumberArrayProperty("weights", mesh.weights, o); + } + + if(mesh.name.size()) + { + SerializeStringProperty("name", mesh.name, o); + } +} + +static void SerializeGltfNode(Node &node, picojson::object &o) +{ + if(node.translation.size() > 0) + { + SerializeNumberArrayProperty("translation", node.translation, o); + } + if(node.rotation.size() > 0) + { + SerializeNumberArrayProperty("rotation", node.rotation, o); + } + if(node.scale.size() > 0) + { + SerializeNumberArrayProperty("scale", node.scale, o); + } + if(node.matrix.size() > 0) + { + SerializeNumberArrayProperty("matrix", node.matrix, o); + } + if(node.mesh != -1) + { + SerializeNumberProperty("mesh", node.mesh, o); + } + + if(node.skin != -1) + { + SerializeNumberProperty("skin", node.skin, o); + } + + SerializeStringProperty("name", node.name, o); + SerializeNumberArrayProperty("children", node.children, o); +} + +static void SerializeGltfSampler(Sampler &sampler, picojson::object &o) +{ + SerializeNumberProperty("magFilter", sampler.magFilter, o); + SerializeNumberProperty("minFilter", sampler.minFilter, o); + SerializeNumberProperty("wrapS", sampler.wrapS, o); + SerializeNumberProperty("wrapT", sampler.wrapT, o); +} + +static void SerializeGltfScene(Scene &scene, picojson::object &o) +{ + SerializeNumberArrayProperty("nodes", scene.nodes, o); + + if(scene.name.size()) + { + SerializeStringProperty("name", scene.name, o); + } +} + +static void SerializeGltfSkin(Skin &skin, picojson::object &o) +{ + if(skin.inverseBindMatrices != -1) + SerializeNumberProperty("inverseBindMatrices", skin.inverseBindMatrices, o); + + SerializeNumberArrayProperty("joints", skin.joints, o); + SerializeNumberProperty("skeleton", skin.skeleton, o); + if(skin.name.size()) + { + SerializeStringProperty("name", skin.name, o); + } +} + +static void SerializeGltfTexture(Texture &texture, picojson::object &o) +{ + SerializeNumberProperty("sampler", texture.sampler, o); + SerializeNumberProperty("source", texture.source, o); + + if(texture.extras.Size()) + { + picojson::object extras; + SerializeValue("extras", texture.extras, o); + o.insert(json_object_pair("extras", picojson::value(extras))); + } +} + +static void WriteGltfFile(const std::string& output, const std::string& content) +{ + std::ofstream gltfFile(output.c_str()); + gltfFile << content << std::endl; +} + +bool TinyGLTFLoader::WriteGltfSceneToFile(Model *model, const std::string &filename/*, bool embedImages, bool embedBuffers, bool writeBinary*/) +{ + picojson::object output; + + //ACCESSORS + picojson::array accessors; + for(unsigned int i=0; i < model->accessors.size(); ++i) + { + picojson::object accessor; + SerializeGltfAccessor(model->accessors[i], accessor); + accessors.push_back(picojson::value(accessor)); + } + output.insert(json_object_pair("accessors", picojson::value(accessors))); + + //ANIMATIONS + if(model->animations.size()) + { + picojson::array animations; + for(unsigned int i=0; i < model->animations.size(); ++i) + { + if(model->animations[i].channels.size()) + { + picojson::object animation; + SerializeGltfAnimation(model->animations[i], animation); + animations.push_back(picojson::value(animation)); + } + } + output.insert(json_object_pair("animations", picojson::value(animations))); + } + + //ASSET + picojson::object asset; + SerializeGltfAsset(model->asset, asset); + output.insert(json_object_pair("asset", picojson::value(asset))); + + std::string binFilePath = filename; + std::string ext = ".bin"; + std::string::size_type i = binFilePath.rfind('.', binFilePath.length()); + + if (i != std::string::npos) + { + binFilePath = binFilePath.substr(0, i) + ext; + } + else + { + binFilePath = "./" + binFilePath + ".bin"; + } + + //BUFFERS (We expect only one buffer here) + picojson::array buffers; + for(unsigned int i=0; i < model->buffers.size(); ++i) + { + picojson::object buffer; + SerializeGltfBuffer(model->buffers[i], buffer, binFilePath); + buffers.push_back(picojson::value(buffer)); + } + output.insert(json_object_pair("buffers", picojson::value(buffers))); + + //BUFFERVIEWS + picojson::array bufferViews; + for(unsigned int i=0; i < model->bufferViews.size(); ++i) + { + picojson::object bufferView; + SerializeGltfBufferView(model->bufferViews[i], bufferView); + bufferViews.push_back(picojson::value(bufferView)); + } + output.insert(json_object_pair("bufferViews", picojson::value(bufferViews))); + + //Extensions used + if(model->extensionsUsed.size()) + { + SerializeStringArrayProperty("extensionsUsed", model->extensionsUsed, output); + } + + //Extensions required + if(model->extensionsRequired.size()) + { + SerializeStringArrayProperty("extensionsRequired", model->extensionsRequired, output); + } + + //IMAGES + picojson::array images; + for(unsigned int i=0; i < model->images.size(); ++i) + { + picojson::object image; + SerializeGltfImage(model->images[i], image); + images.push_back(picojson::value(image)); + } + output.insert(json_object_pair("images", picojson::value(images))); + + //MATERIALS + picojson::array materials; + for(unsigned int i=0; i < model->materials.size(); ++i) + { + picojson::object material; + SerializeGltfMaterial(model->materials[i], material); + materials.push_back(picojson::value(material)); + } + output.insert(json_object_pair("materials", picojson::value(materials))); + + //MESHES + picojson::array meshes; + for(unsigned int i=0; i < model->meshes.size(); ++i) + { + picojson::object mesh; + SerializeGltfMesh(model->meshes[i], mesh); + meshes.push_back(picojson::value(mesh)); + } + output.insert(json_object_pair("meshes", picojson::value(meshes))); + + //NODES + picojson::array nodes; + for(unsigned int i=0; i < model->nodes.size(); ++i) + { + picojson::object node; + SerializeGltfNode(model->nodes[i], node); + nodes.push_back(picojson::value(node)); + } + output.insert(json_object_pair("nodes", picojson::value(nodes))); + + //SCENE + SerializeNumberProperty("scene", model->defaultScene, output); + + //SCENES + picojson::array scenes; + for(unsigned int i=0; i < model->scenes.size(); ++i) + { + picojson::object currentScene; + SerializeGltfScene(model->scenes[i], currentScene); + scenes.push_back(picojson::value(currentScene)); + } + output.insert(json_object_pair("scenes", picojson::value(scenes))); + + //SKINS + if(model->skins.size()) + { + picojson::array skins; + for(unsigned int i=0; i < model->skins.size(); ++i) + { + picojson::object skin; + SerializeGltfSkin(model->skins[i], skin); + skins.push_back(picojson::value(picojson::value(skin))); + } + output.insert(json_object_pair("skins", picojson::value(skins))); + } + + //TEXTURES + picojson::array textures; + for(unsigned int i=0; i < model->textures.size(); ++i) + { + picojson::object texture; + SerializeGltfTexture(model->textures[i], texture); + textures.push_back(picojson::value(texture)); + } + output.insert(json_object_pair("textures", picojson::value(textures))); + + //SAMPLERS + picojson::array samplers; + for(unsigned int i=0; i < model->samplers.size(); ++i) + { + picojson::object sampler; + SerializeGltfSampler(model->samplers[i], sampler); + samplers.push_back(picojson::value(sampler)); + } + output.insert(json_object_pair("samplers", picojson::value(samplers))); + + WriteGltfFile(filename, picojson::value(output).serialize()); + return true; +} + + } // namespace tinygltf #endif // TINYGLTF_LOADER_IMPLEMENTATION