diff --git a/tiny_gltf_loader.h b/tiny_gltf_loader.h index 3d3e141..430a17c 100644 --- a/tiny_gltf_loader.h +++ b/tiny_gltf_loader.h @@ -66,6 +66,36 @@ namespace tinygltf { #define TINYGLTF_COMPONENT_TYPE_FLOAT (5126) #define TINYGLTF_COMPONENT_TYPE_DOUBLE (5127) +// Redeclarations of the above for technique.parameters. +#define TINYGLTF_PARAMETER_TYPE_BYTE (5120) +#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE (5121) +#define TINYGLTF_PARAMETER_TYPE_SHORT (5122) +#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT (5123) +#define TINYGLTF_PARAMETER_TYPE_INT (5124) +#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT (5125) +#define TINYGLTF_PARAMETER_TYPE_FLOAT (5126) + +#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC2 (35664) +#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC3 (35665) +#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC4 (35666) + +#define TINYGLTF_PARAMETER_TYPE_INT_VEC2 (35667) +#define TINYGLTF_PARAMETER_TYPE_INT_VEC3 (35668) +#define TINYGLTF_PARAMETER_TYPE_INT_VEC4 (35669) + +#define TINYGLTF_PARAMETER_TYPE_BOOL (35670) +#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC2 (35671) +#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC3 (35672) +#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC4 (35673) + +#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT2 (35674) +#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT3 (35675) +#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT4 (35676) + +#define TINYGLTF_PARAMETER_TYPE_SAMPLER_2D (35678) + +// End parameter types + #define TINYGLTF_TYPE_VEC2 (2) #define TINYGLTF_TYPE_VEC3 (3) #define TINYGLTF_TYPE_VEC4 (4) @@ -88,6 +118,9 @@ namespace tinygltf { #define TINYGLTF_TARGET_ARRAY_BUFFER (34962) #define TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER (34963) +#define TINYGLTF_SHADER_TYPE_VERTEX_SHADER (35633) +#define TINYGLTF_SHADER_TYPE_FRAGMENT_SHADER (35632) + typedef struct { std::string string_value; std::vector number_array; @@ -199,6 +232,36 @@ typedef struct { std::vector data; } Buffer; +typedef struct { + std::string name; + int type; + std::vector source; +} Shader; + +typedef struct { + std::string name; + std::string vertexShader; + std::string fragmentShader; + std::vector attributes; +} Program; + +typedef struct +{ + int count; + std::string node; + std::string semantic; + int type; + Parameter value; +} TechniqueParameter; + +typedef struct { + std::string name; + std::string program; + std::map parameters; + std::map attributes; + std::map uniforms; +} Technique; + typedef struct { std::string generator; std::string version; @@ -221,6 +284,9 @@ class Scene { std::map nodes; std::map textures; std::map images; + std::map shaders; + std::map programs; + std::map techniques; std::map > scenes; // list of nodes std::string defaultScene; @@ -547,7 +613,7 @@ static bool LoadExternalFile(std::vector *out, std::string *err, std::string filepath = FindFile(paths, filename); if (filepath.empty()) { if (err) { - (*err) += "File not found : " + filename; + (*err) += "File not found : " + filename + "\n"; } return false; } @@ -555,7 +621,7 @@ static bool LoadExternalFile(std::vector *out, std::string *err, std::ifstream f(filepath.c_str(), std::ifstream::binary); if (!f) { if (err) { - (*err) += "File open error : " + filepath; + (*err) += "File open error : " + filepath + "\n"; } return false; } @@ -650,6 +716,11 @@ static bool IsDataURI(const std::string &in) { return true; } + header = "data:text/plain;base64,"; + if (in.find(header) == 0) { + return true; + } + return false; } @@ -676,6 +747,13 @@ static bool DecodeDataURI(std::vector *out, } } + if (data.empty()) { + header = "data:text/plain;base64,"; + if (in.find(header) == 0) { + data = base64_decode(in.substr(header.size())); + } + } + if (data.empty()) { return false; } @@ -860,6 +938,54 @@ static bool ParseStringArrayProperty(std::vector *ret, 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); + if (it == o.end()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing.\n"; + } + } + return false; + } + + // Make sure we are dealing with an object / dictionary. + if (!it->second.is()) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an object.\n"; + } + } + return false; + } + + ret->clear(); + const picojson::object &dict = it->second.get(); + + picojson::object::const_iterator dictIt(dict.begin()); + picojson::object::const_iterator dictItEnd(dict.end()); + + for(; dictIt != dictItEnd; ++dictIt) { + // Check that the value is a string. + if(!dictIt->second.is()) { + if(required) { + if(err) { + (*err) += "'" + property + "' value is not a string.\n"; + } + } + return false; + } + + // Insert into the list. + (*ret)[dictIt->first] = dictIt->second.get(); + } + return true; +} + static bool ParseKHRBinaryExtension(const picojson::object &o, std::string *err, std::string *buffer_view, std::string *mime_type, int *image_width, @@ -1308,26 +1434,9 @@ static bool ParsePrimitive(Primitive *primitive, std::string *err, primitive->indices = ""; ParseStringProperty(&primitive->indices, err, o, "indices", false); - primitive->attributes.clear(); - picojson::object::const_iterator attribsObject = o.find("attributes"); - if ((attribsObject != o.end()) && - (attribsObject->second).is()) { - const picojson::object &attribs = - (attribsObject->second).get(); - picojson::object::const_iterator it(attribs.begin()); - picojson::object::const_iterator itEnd(attribs.end()); - for (; it != itEnd; it++) { - const std::string &name = it->first; - if (!(it->second).is()) { - if (err) { - (*err) += "attribute expects string value.\n"; - } - return false; - } - const std::string &value = (it->second).get(); - - primitive->attributes[name] = value; - } + if(!ParseStringMapProperty(&primitive->attributes, err, o, + "attributes", true)) { + return false; } return true; @@ -1383,6 +1492,36 @@ static bool ParseNode(Node *node, std::string *err, const picojson::object &o) { return true; } +static bool ParseParameterProperty(Parameter *param, std::string *err, + const picojson::object &o, + const std::string &prop, bool required) +{ + double num_val; + + // A parameter value can either be a string or an array of either a boolean or + // a number. Booleans of any kind aren't supported here. Granted, it + // complicates the Parameter structure and breaks it semantically in the sense + // that the client probably works off the assumption that if the string is + // empty the vector is used, etc. Would a tagged union work? + if (ParseStringProperty(¶m->string_value, err, o, prop, false)) { + // Found string property. + return true; + } else if (ParseNumberArrayProperty(¶m->number_array, err,o,prop,false)) { + // Found a number array. + return true; + } else if (ParseNumberProperty(&num_val, err, o, prop, false)) { + param->number_array.push_back(num_val); + return true; + } else { + if(required) { + if(err) { + (*err) += "parameter must be a string or number / number array.\n"; + } + } + return false; + } +} + static bool ParseMaterial(Material *material, std::string *err, const picojson::object &o) { ParseStringProperty(&material->name, err, o, "name", false); @@ -1390,28 +1529,145 @@ static bool ParseMaterial(Material *material, std::string *err, material->values.clear(); picojson::object::const_iterator valuesIt = o.find("values"); + if ((valuesIt != o.end()) && (valuesIt->second).is()) { + const picojson::object &values_object = (valuesIt->second).get(); + picojson::object::const_iterator it(values_object.begin()); picojson::object::const_iterator itEnd(values_object.end()); for (; it != itEnd; it++) { - // Assume number values. Parameter param; - if (ParseStringProperty(¶m.string_value, err, values_object, - it->first, false)) { - // Found string property. - } else if (!ParseNumberArrayProperty(¶m.number_array, err, - values_object, it->first, false)) { - // Fallback to numer property. - double value; - if (ParseNumberProperty(&value, err, values_object, it->first, false)) { - param.number_array.push_back(value); - } + if(ParseParameterProperty(¶m, err, values_object, it->first, false)) { + material->values[it->first] = param; } + } + } - material->values[it->first] = param; + return true; +} + +static bool ParseShader(Shader *shader, std::string *err, + const picojson::object &o, const std::string &basedir) { + std::string uri; + if (!ParseStringProperty(&uri, err, o, "uri", true)) { + return false; + } + + // Load shader source from data uri + // TODO: Support ascii or utf-8 data uris. + if (IsDataURI(uri)) { + if (!DecodeDataURI(&shader->source, uri, 0, false)) { + if (err) { + (*err) += "Failed to decode 'uri'.\n"; + } + return false; + } + } else { + // Assume external file + if (!LoadExternalFile(&shader->source, err, uri, basedir, 0, false)) { + if (err) { + (*err) += "Failed to load external 'uri'.\n"; + } + return false; + } + if (shader->source.empty()) { + if (err) { + (*err) += "File is empty.\n"; + } + return false; + } + } + + double type; + if (!ParseNumberProperty(&type, err, o, "type", true)) { + return false; + } + + shader->type = static_cast(type); + + 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); + + 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& param_obj = it->second.get(); + if(ParseTechniqueParameter(¶m, err, param_obj)) { + // Add if successful + technique->parameters[it->first] = param; + } } } @@ -1689,6 +1945,59 @@ bool TinyGLTFLoader::LoadFromString(Scene *scene, std::string *err, } } + // 10. Parse Shader + if (v.contains("shaders") && v.get("shaders").is()) { + const picojson::object &root = v.get("shaders").get(); + + 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)) { + return false; + } + + scene->shaders[it->first] = shader; + } + } + + // 11. 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; + } + } + + // 12. 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; + } + } + return true; }