From 13590ff9e598b0fc807f7e2b3aad5e9a26846941 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Sat, 7 Jan 2023 02:26:26 +0300 Subject: [PATCH] Add gltf asset parser & mesh generator --- libs/gfx/include/psemek/gfx/gltf_mesh.hpp | 40 ++++++ libs/gfx/include/psemek/gfx/gltf_parser.hpp | 72 ++++++++++ libs/gfx/source/gltf_mesh.cpp | 67 +++++++++ libs/gfx/source/gltf_parser.cpp | 145 ++++++++++++++++++++ 4 files changed, 324 insertions(+) create mode 100644 libs/gfx/include/psemek/gfx/gltf_mesh.hpp create mode 100644 libs/gfx/include/psemek/gfx/gltf_parser.hpp create mode 100644 libs/gfx/source/gltf_mesh.cpp create mode 100644 libs/gfx/source/gltf_parser.cpp diff --git a/libs/gfx/include/psemek/gfx/gltf_mesh.hpp b/libs/gfx/include/psemek/gfx/gltf_mesh.hpp new file mode 100644 index 00000000..ef3ae6cb --- /dev/null +++ b/libs/gfx/include/psemek/gfx/gltf_mesh.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace psemek::gfx +{ + + struct gltf_mesh + { + struct mesh + { + struct primitive + { + gfx::array vao; + std::optional material; + std::size_t index_count; + std::size_t index_offset; + GLenum index_type; + + void draw(); + }; + + std::vector primitives; + }; + + std::vector buffers; + + std::unordered_map meshes; + + gltf_mesh() = default; + gltf_mesh(gltf_asset const & asset, std::vector> const & buffers); + }; + +} diff --git a/libs/gfx/include/psemek/gfx/gltf_parser.hpp b/libs/gfx/include/psemek/gfx/gltf_parser.hpp new file mode 100644 index 00000000..49244ba2 --- /dev/null +++ b/libs/gfx/include/psemek/gfx/gltf_parser.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace psemek::gfx +{ + + struct gltf_asset + { + struct node + { + std::string name; + std::size_t mesh; + }; + + struct mesh + { + struct primitive + { + std::optional position; + std::optional normal; + std::optional texcoord; + std::size_t indices; + std::optional material; + }; + + std::vector primitives; + }; + + struct material + { + color_4f albedo; + }; + + struct accessor + { + std::size_t buffer_view; + std::size_t component_type; + std::size_t count; + std::size_t type; + bool normalized; + }; + + struct buffer_view + { + std::size_t buffer; + std::size_t offset; + std::size_t length; + }; + + struct buffer + { + std::size_t length; + std::string uri; + }; + + std::vector nodes; + std::vector meshes; + std::vector materials; + std::vector accessors; + std::vector buffer_views; + std::vector buffers; + }; + + gltf_asset parse_gltf(io::istream && stream); + +} diff --git a/libs/gfx/source/gltf_mesh.cpp b/libs/gfx/source/gltf_mesh.cpp new file mode 100644 index 00000000..ce525b3e --- /dev/null +++ b/libs/gfx/source/gltf_mesh.cpp @@ -0,0 +1,67 @@ +#include + +#include + +namespace psemek::gfx +{ + + void gltf_mesh::mesh::primitive::draw() + { + vao.bind(); + gl::DrawElements(gl::TRIANGLES, index_count, index_type, reinterpret_cast(index_offset)); + } + + gltf_mesh::gltf_mesh(gltf_asset const & asset, std::vector> const & buffers) + { + if (buffers.size() != asset.buffers.size()) + throw std::runtime_error("wrong number of glTF buffers"); + + for (auto buffer : buffers) + this->buffers.emplace_back().load(buffer.data(), buffer.size(), gl::STATIC_DRAW); + + for (auto const & node : asset.nodes) + { + auto & target_mesh = meshes[node.name]; + + auto const & mesh = asset.meshes[node.mesh]; + + for (auto const & primitive : mesh.primitives) + { + auto & target_primitive = target_mesh.primitives.emplace_back(); + target_primitive.material = primitive.material; + + target_primitive.vao.bind(); + + { + auto const & indices_accessor = asset.accessors[primitive.indices]; + auto const & indices_view = asset.buffer_views[indices_accessor.buffer_view]; + + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, this->buffers[indices_view.buffer].id()); + target_primitive.index_offset = indices_view.offset; + target_primitive.index_type = indices_accessor.component_type; + target_primitive.index_count = indices_accessor.count; + } + + std::pair> attributes[3] = + { + {0, primitive.position}, + {1, primitive.normal}, + {2, primitive.texcoord}, + }; + + for (auto const & attribute : attributes) + { + if (!attribute.second) continue; + + auto const & accessor = asset.accessors[*attribute.second]; + auto const & view = asset.buffer_views[accessor.buffer_view]; + + gl::BindBuffer(gl::ARRAY_BUFFER, this->buffers[view.buffer].id()); + gl::EnableVertexAttribArray(attribute.first); + gl::VertexAttribPointer(attribute.first, accessor.type, accessor.component_type, accessor.normalized, 0, reinterpret_cast(view.offset)); + } + } + } + } + +} diff --git a/libs/gfx/source/gltf_parser.cpp b/libs/gfx/source/gltf_parser.cpp new file mode 100644 index 00000000..93a7d657 --- /dev/null +++ b/libs/gfx/source/gltf_parser.cpp @@ -0,0 +1,145 @@ +#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 + +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)); + } + + } + + gltf_asset parse_gltf(io::istream && stream) + { + auto description_str = io::read_full(std::move(stream)); + description_str.push_back(0); + + 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; + + for (auto const & node : document["nodes"].GetArray()) + { + auto & target = result.nodes.emplace_back(); + target.mesh = node["mesh"].GetUint64(); + target.name = node["name"].GetString(); + } + + 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(); + + auto const & color = material["pbrMetallicRoughness"]["baseColorFactor"].GetArray(); + for (std::size_t i = 0; i < 4; ++i) + target.albedo[i] = color[i].GetFloat(); + } + + 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(); + } + + for (auto const & buffer : document["buffers"].GetArray()) + { + auto & target = result.buffers.emplace_back(); + + target.length = buffer["byteLength"].GetUint64(); + target.uri = buffer["uri"].GetString(); + } + + return result; + } + +}