#include #include #include #define RAPIDJSON_ASSERT(x) if (!(x)) throw ::std::runtime_error("Error parsing glTF: " BOOST_PP_STRINGIZE(x)); #define RAPIDJSON_NOEXCEPT_ASSERT(x) #include #include #include namespace psemek::gfx { namespace { std::string_view to_string(rapidjson::ParseErrorCode const & code) { using namespace rapidjson; switch (code) { case kParseErrorNone: return "no error"; case kParseErrorDocumentEmpty: return "document is empty"; case kParseErrorDocumentRootNotSingular: return "document root must not be followed by other values"; case kParseErrorValueInvalid: return "invalid value"; case kParseErrorObjectMissName: return "missing a name for object member"; case kParseErrorObjectMissColon: return "missing a colon after a name of object member"; case kParseErrorObjectMissCommaOrCurlyBracket: return "missing a comma or '}' after an object member"; case kParseErrorArrayMissCommaOrSquareBracket: return "missing a comma or ']' after an array element"; case kParseErrorStringUnicodeEscapeInvalidHex: return R"(Incorrect hex digit after \\u escape in string)"; case kParseErrorStringUnicodeSurrogateInvalid: return "surrogate pair in string is invalid"; case kParseErrorStringEscapeInvalid: return "invalid escape character in string"; case kParseErrorStringMissQuotationMark: return "missing a closing quotation mark in string"; case kParseErrorStringInvalidEncoding: return "invalid encoding in string"; case kParseErrorNumberTooBig: return "number too big to be stored in double"; case kParseErrorNumberMissFraction: return "missing fraction part in number"; case kParseErrorNumberMissExponent: return "missing exponent in number"; case kParseErrorTermination: return "parsing was terminated"; case kParseErrorUnspecificSyntaxError: return "unspecific syntax error"; default: return "(unknown error)"; } } std::size_t parse_accessor_type(std::string const & type) { if (type == "SCALAR") return 1; if (type == "VEC2") return 2; if (type == "VEC3") return 3; if (type == "VEC4") return 4; throw std::runtime_error(util::to_string("Unknown accessor component type: ", type)); } static std::unordered_set supported_extensions = { "KHR_lights_punctual", }; } gltf_asset parse_gltf(io::istream && stream) { auto description_str = io::read_full(std::move(stream)).string(); rapidjson::Document document; document.ParseInsitu(description_str.data()); if (document.HasParseError()) throw std::runtime_error(util::to_string("Error parsing glTF: ", to_string(document.GetParseError()), " at ", document.GetErrorOffset())); gltf_asset result; if (document.HasMember("extensionsRequired")) for (auto const & extension : document["extensionsRequired"].GetArray()) if (!supported_extensions.contains(extension.GetString())) throw std::runtime_error("glTF extension " + std::string(extension.GetString()) + " is not supported"); for (auto const & node : document["nodes"].GetArray()) { auto & target = result.nodes.emplace_back(); target.name = node["name"].GetString(); if (node.HasMember("mesh")) target.mesh = node["mesh"].GetUint64(); target.translation = {0.f, 0.f, 0.f}; if (node.HasMember("translation")) { auto const & translation = node["translation"].GetArray(); for (std::size_t i = 0; i < 3; ++i) target.translation[i] = translation[i].GetFloat(); } target.rotation = geom::quaternion::identity(); if (node.HasMember("rotation")) { auto const & rotation = node["rotation"].GetArray(); for (std::size_t i = 0; i < 4; ++i) target.rotation[i] = rotation[i].GetFloat(); } target.scale = {0.f, 0.f, 0.f}; if (node.HasMember("scale")) { auto const & scale = node["scale"].GetArray(); for (std::size_t i = 0; i < 3; ++i) target.scale[i] = scale[i].GetFloat(); } if (node.HasMember("children")) { for (auto const & child : node["children"].GetArray()) target.children.push_back(child.GetUint64()); } if (node.HasMember("extensions")) for (auto const & extension : node["extensions"].GetObject()) { if (extension.name == "KHR_lights_punctual") { target.light = extension.value["light"].GetUint64(); } } } for (auto const & mesh : document["meshes"].GetArray()) { auto & target = result.meshes.emplace_back(); for (auto const & primitive : mesh["primitives"].GetArray()) { auto & primitive_target = target.primitives.emplace_back(); primitive_target.indices = primitive["indices"].GetUint64(); if (primitive.HasMember("material")) primitive_target.material = primitive["material"].GetUint64(); auto const & attributes = primitive["attributes"]; if (attributes.HasMember("POSITION")) primitive_target.position = attributes["POSITION"].GetUint64(); if (attributes.HasMember("NORMAL")) primitive_target.normal = attributes["NORMAL"].GetUint64(); if (attributes.HasMember("TEXCOORD_0")) primitive_target.texcoord = attributes["TEXCOORD_0"].GetUint64(); } } for (auto const & material : document["materials"].GetArray()) { auto & target = result.materials.emplace_back(); target.name = material["name"].GetString(); auto const & pbr = material["pbrMetallicRoughness"]; if (pbr.HasMember("baseColorFactor")) { auto const & color = pbr["baseColorFactor"].GetArray(); gfx::color_4f & target_color = target.albedo.emplace(); for (std::size_t i = 0; i < 4; ++i) target_color[i] = color[i].GetFloat(); } if (pbr.HasMember("baseColorTexture")) { target.texture = document["textures"].GetArray()[pbr["baseColorTexture"]["index"].GetUint64()]["source"].GetInt64(); } if (pbr.HasMember("metallicRoughnessTexture")) { target.material_texture = document["textures"].GetArray()[pbr["metallicRoughnessTexture"]["index"].GetUint64()]["source"].GetInt64(); } if (material.HasMember("emissiveFactor")) { auto const & emission = material["emissiveFactor"].GetArray(); gfx::color_3f & target_emission = target.emission.emplace(); for (std::size_t i = 0; i < 3; ++i) target_emission[i] = emission[i].GetFloat(); } } for (auto const & image : document["images"].GetArray()) { auto & target = result.textures.emplace_back(); target.uri = image["uri"].GetString(); } for (auto const & accessor : document["accessors"].GetArray()) { auto & target = result.accessors.emplace_back(); target.buffer_view = accessor["bufferView"].GetUint64(); target.component_type = accessor["componentType"].GetUint64(); target.count = accessor["count"].GetUint64(); target.type = parse_accessor_type(accessor["type"].GetString()); if (accessor.HasMember("normalized")) target.normalized = accessor["normalized"].GetBool(); else target.normalized = false; } for (auto const & buffer_view : document["bufferViews"].GetArray()) { auto & target = result.buffer_views.emplace_back(); target.buffer = buffer_view["buffer"].GetUint64(); target.offset = buffer_view["byteOffset"].GetUint64(); target.length = buffer_view["byteLength"].GetUint64(); target.stride = 0; if (buffer_view.HasMember("byteStride")) target.stride = buffer_view["byteStride"].GetUint64(); } for (auto const & buffer : document["buffers"].GetArray()) { auto & target = result.buffers.emplace_back(); target.length = buffer["byteLength"].GetUint64(); target.uri = buffer["uri"].GetString(); } if (document.HasMember("extensions")) for (auto const & extension : document["extensions"].GetObject()) { if (extension.name == "KHR_lights_punctual") { for (auto const & light : extension.value["lights"].GetArray()) { auto & target = result.lights.emplace_back(); target.color = {1.f, 1.f, 1.f}; target.intensity = 1.f; target.range = std::numeric_limits::infinity(); target.cone_angle = {0.f, geom::pi / 4.f}; std::string type = light["type"].GetString(); if (type == "directional") target.type = gltf_asset::light::directional; else if (type == "point") target.type = gltf_asset::light::point; else if (type == "spot") target.type = gltf_asset::light::spot; else throw std::runtime_error("unknown light type: " + type); if (light.HasMember("color")) { auto const & color = light["color"].GetArray(); for (std::size_t i = 0; i < 3; ++i) target.color[i] = color[i].GetFloat(); } if (light.HasMember("intensity")) target.intensity = light["intensity"].GetFloat(); if (light.HasMember("range")) target.range = light["range"].GetFloat(); if (target.type == gltf_asset::light::spot) { auto const & spot = light["spot"].GetObject(); if (spot.HasMember("innerConeAngle")) target.cone_angle.min = spot["innerConeAngle"].GetFloat(); if (spot.HasMember("outerConeAngle")) target.cone_angle.max = spot["outerConeAngle"].GetFloat(); } } } } for (std::size_t i = 0; i < result.nodes.size(); ++i) result.node_index[result.nodes[i].name] = i; for (std::size_t i = 0; i < result.materials.size(); ++i) result.material_index[result.materials[i].name] = i; return result; } }