Add gltf asset parser & mesh generator
This commit is contained in:
parent
e19b515404
commit
13590ff9e5
4 changed files with 324 additions and 0 deletions
40
libs/gfx/include/psemek/gfx/gltf_mesh.hpp
Normal file
40
libs/gfx/include/psemek/gfx/gltf_mesh.hpp
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/gfx/buffer.hpp>
|
||||||
|
#include <psemek/gfx/array.hpp>
|
||||||
|
#include <psemek/gfx/gltf_parser.hpp>
|
||||||
|
#include <psemek/util/span.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace psemek::gfx
|
||||||
|
{
|
||||||
|
|
||||||
|
struct gltf_mesh
|
||||||
|
{
|
||||||
|
struct mesh
|
||||||
|
{
|
||||||
|
struct primitive
|
||||||
|
{
|
||||||
|
gfx::array vao;
|
||||||
|
std::optional<std::size_t> material;
|
||||||
|
std::size_t index_count;
|
||||||
|
std::size_t index_offset;
|
||||||
|
GLenum index_type;
|
||||||
|
|
||||||
|
void draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<primitive> primitives;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<gfx::buffer> buffers;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, mesh> meshes;
|
||||||
|
|
||||||
|
gltf_mesh() = default;
|
||||||
|
gltf_mesh(gltf_asset const & asset, std::vector<util::span<char const>> const & buffers);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
72
libs/gfx/include/psemek/gfx/gltf_parser.hpp
Normal file
72
libs/gfx/include/psemek/gfx/gltf_parser.hpp
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/io/stream.hpp>
|
||||||
|
#include <psemek/gfx/color.hpp>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace psemek::gfx
|
||||||
|
{
|
||||||
|
|
||||||
|
struct gltf_asset
|
||||||
|
{
|
||||||
|
struct node
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::size_t mesh;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mesh
|
||||||
|
{
|
||||||
|
struct primitive
|
||||||
|
{
|
||||||
|
std::optional<std::size_t> position;
|
||||||
|
std::optional<std::size_t> normal;
|
||||||
|
std::optional<std::size_t> texcoord;
|
||||||
|
std::size_t indices;
|
||||||
|
std::optional<std::size_t> material;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<primitive> 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<node> nodes;
|
||||||
|
std::vector<mesh> meshes;
|
||||||
|
std::vector<material> materials;
|
||||||
|
std::vector<accessor> accessors;
|
||||||
|
std::vector<buffer_view> buffer_views;
|
||||||
|
std::vector<buffer> buffers;
|
||||||
|
};
|
||||||
|
|
||||||
|
gltf_asset parse_gltf(io::istream && stream);
|
||||||
|
|
||||||
|
}
|
||||||
67
libs/gfx/source/gltf_mesh.cpp
Normal file
67
libs/gfx/source/gltf_mesh.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
#include <psemek/gfx/gltf_mesh.hpp>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace psemek::gfx
|
||||||
|
{
|
||||||
|
|
||||||
|
void gltf_mesh::mesh::primitive::draw()
|
||||||
|
{
|
||||||
|
vao.bind();
|
||||||
|
gl::DrawElements(gl::TRIANGLES, index_count, index_type, reinterpret_cast<void const *>(index_offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
gltf_mesh::gltf_mesh(gltf_asset const & asset, std::vector<util::span<char const>> 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<GLuint, std::optional<std::size_t>> 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<void const *>(view.offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
145
libs/gfx/source/gltf_parser.cpp
Normal file
145
libs/gfx/source/gltf_parser.cpp
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
#include <psemek/gfx/gltf_parser.hpp>
|
||||||
|
#include <psemek/util/to_string.hpp>
|
||||||
|
|
||||||
|
#include <boost/preprocessor/stringize.hpp>
|
||||||
|
|
||||||
|
#define RAPIDJSON_ASSERT(x) if (!(x)) throw ::std::runtime_error("Error parsing glTF: " BOOST_PP_STRINGIZE(x));
|
||||||
|
#define RAPIDJSON_NOEXCEPT_ASSERT(x)
|
||||||
|
|
||||||
|
#include <rapidjson/document.h>
|
||||||
|
#include <rapidjson/istreamwrapper.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue