diff --git a/.travis.yml b/.travis.yml index dfea1fc..04f3384 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,4 +43,4 @@ script: - export CXX="${CXX}-${COMPILER_VERSION}" - ${CC} -v - ${CXX} ${EXTRA_CXXFLAGS} -Wall -g -o loader_example loader_example.cc - - ./loader_example box.gltf + - ./loader_example ./models/Cube/Cube.gltf diff --git a/tiny_gltf_loader.h b/tiny_gltf_loader.h index 6f17932..84140cb 100644 --- a/tiny_gltf_loader.h +++ b/tiny_gltf_loader.h @@ -1,5 +1,5 @@ // -// Header-only tiny glTF 2.0 loader. +// Header-only tiny glTF 2.0 loader and serializer. // // // The MIT License (MIT) @@ -240,6 +240,10 @@ class Value { return keys; } + size_t Size() const { + return (IsArray() ? ArrayLen() : Keys().size()); + } + protected: int type_; @@ -318,7 +322,11 @@ struct Skin { int skeleton; // The index of the node used as a skeleton root std::vector joints; // Indices of skeleton nodes - Skin() { inverseBindMatrices = -1; } + Skin() + { + inverseBindMatrices = -1; + skeleton = -1; + } }; struct Sampler { @@ -526,6 +534,7 @@ class Model { int defaultScene; std::vector extensionsUsed; + std::vector extensionsRequired; Asset asset; @@ -579,6 +588,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); @@ -1293,70 +1304,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, - "KHR_binary_glTF")) { - 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, bool required) { @@ -1446,34 +1393,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; } @@ -1519,9 +1458,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); @@ -1539,9 +1476,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()) { @@ -1555,15 +1501,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)) { @@ -1584,18 +1529,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 { @@ -1772,6 +1709,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; @@ -2104,7 +2060,7 @@ static bool ParseSkin(Skin *skin, std::string *err, const picojson::object &o) { return false; } - double skeleton; + double skeleton = -1.0; ParseNumberProperty(&skeleton, err, o, "skeleton", false, "Skin"); skin->skeleton = static_cast(skeleton); @@ -2185,6 +2141,7 @@ bool TinyGLTFLoader::LoadFromString(Model *model, std::string *err, model->meshes.clear(); model->nodes.clear(); model->extensionsUsed.clear(); + model->extensionsRequired.clear(); model->defaultScene = -1; // 0. Parse Asset @@ -2203,6 +2160,13 @@ bool TinyGLTFLoader::LoadFromString(Model *model, std::string *err, 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()) { @@ -2545,7 +2509,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. @@ -2590,6 +2554,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(int(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(reinterpret_cast(&data[0]), ssize_t(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", int(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 k = 0; k < gltfPrimitive.targets.size(); ++k) + { + picojson::object targetAttributes; + std::map targetData = gltfPrimitive.targets[k]; + 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 pos = binFilePath.rfind('.', binFilePath.length()); + + if (pos != std::string::npos) + { + binFilePath = binFilePath.substr(0, pos) + 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