diff --git a/src/coders/vec3.cpp b/src/coders/vec3.cpp new file mode 100644 index 00000000..286261d6 --- /dev/null +++ b/src/coders/vec3.cpp @@ -0,0 +1,215 @@ +#include "vec3.hpp" + +#include + +#include "byte_utils.hpp" +#include "util/data_io.hpp" +#include "util/stringutil.hpp" +#include "graphics/core/Model.hpp" + +inline constexpr int VERSION = 1; + +inline constexpr int FLAG_ZLIB = 0x1; +inline constexpr int FLAG_16BIT_INDICES = 0x2; + +using namespace vec3; + +vec3::Model::~Model() = default; + +enum AttributeType { + POSITION = 0, + UV, + NORMAL, + COLOR +}; + +struct VertexAttribute { + AttributeType type; + int flags; + util::Buffer data; + + VertexAttribute() = default; + + VertexAttribute(VertexAttribute&&) = default; + + VertexAttribute& operator=(VertexAttribute&& o) { + type = o.type; + flags = o.flags; + data = std::move(o.data); + return *this; + } +}; + +static VertexAttribute load_attribute(ByteReader& reader) { + auto type = static_cast(reader.get()); + int flags = reader.get(); + assert(type >= POSITION && flags <= COLOR); + if (flags != 0) { + throw std::runtime_error("attribute compression is not supported yet"); + } + int size = reader.getInt32(); + + util::Buffer data(size / sizeof(float)); + reader.get(reinterpret_cast(data.data()), size); + if (dataio::is_big_endian()) { + for (int i = 0; i < data.size(); i++) { + data[i] = dataio::swap(data[i]); + } + } + return VertexAttribute {type, flags, std::move(data)}; +} + +static model::Mesh build_mesh( + const std::vector& attrs, + const util::Buffer& indices, + const std::string& texture +) { + const glm::vec3* coords = nullptr; + const glm::vec2* uvs = nullptr; + const glm::vec3* normals = nullptr; + + int coordsIndex, uvsIndex, normalsIndex; + + for (int i = 0; i < attrs.size(); i++) { + const auto& attr = attrs[i]; + switch (attr.type) { + case POSITION: + coords = reinterpret_cast(attr.data.data()); + coordsIndex = i; + break; + case UV: + uvs = reinterpret_cast(attr.data.data()); + uvsIndex = i; + break; + case NORMAL: + normals = reinterpret_cast(attr.data.data()); + normalsIndex = i; + break; + case COLOR: // unused + break; + } + } + std::vector vertices; + int attrsCount = attrs.size(); + int verticesCount = indices.size() / attrsCount; + for (int i = 0; i < verticesCount; i++) { + model::Vertex vertex {}; + if (coords) { + vertex.coord = coords[indices[i * attrsCount + coordsIndex]]; + } + if (uvs) { + vertex.uv = uvs[indices[i * attrsCount + uvsIndex]]; + } + if (normals) { + vertex.normal = normals[indices[i * attrsCount + normalsIndex]]; + } + } + return model::Mesh {texture, std::move(vertices)}; +} + +static model::Mesh load_mesh(ByteReader& reader) { + int triangleCount = reader.getInt32(); + int materialId = reader.getInt16(); + int flags = reader.getInt16(); + int attributeCount = reader.getInt16(); + if (flags == FLAG_ZLIB) { + throw std::runtime_error("compression is not supported yet"); + } + assert(flags == 0); + std::vector attributes; + for (int i = 0; i < attributeCount; i++) { + attributes.push_back(load_attribute(reader)); + } + + util::Buffer indices(triangleCount * 3 * attributeCount); + if ((flags & FLAG_16BIT_INDICES) == 0){ + util::Buffer smallIndices(indices.size()); + reader.get( + reinterpret_cast(smallIndices.data()), + indices.size() * sizeof(uint8_t) + ); + for (int i = 0; i < indices.size(); i++) { + indices[i] = smallIndices[i]; + } + } + if (dataio::is_big_endian()) { + for (int i = 0; i < indices.size(); i++) { + indices[i] = dataio::swap(indices[i]); + } + } + return build_mesh( + attributes, + indices, + // encode material index to UTF-8 because materials are not loaded yet + util::wstr2str_utf8(std::wstring({static_cast(materialId)})) + ); +} + +static Model load_model(ByteReader& reader) { + int nameLength = reader.getInt16(); + assert(nameLength >= 0); + float x = reader.getFloat32(); + float y = reader.getFloat32(); + float z = reader.getFloat32(); + int meshCount = reader.getInt32(); + assert(meshCount >= 0); + + std::vector meshes; + for (int i = 0; i < meshCount; i++) { + meshes.push_back(load_mesh(reader)); + } + util::Buffer chars(nameLength); + reader.get(chars.data(), nameLength); + std::string name(chars.data(), nameLength); + return Model {std::move(name), model::Model {std::move(meshes)}, {x, y, z}}; +} + +static Material load_material(ByteReader& reader) { + int flags = reader.getInt16(); + int nameLength = reader.getInt16(); + assert(nameLength >= 0); + util::Buffer chars(nameLength); + reader.get(chars.data(), nameLength); + std::string name(chars.data(), nameLength); + return Material {flags, std::move(name)}; +} + +File vec3::load( + const std::string_view file, const util::Buffer& src +) { + ByteReader reader(src.data(), src.size()); + + // Header + reader.checkMagic("\0\0VEC3\0\0", 8); + int version = reader.getInt16(); + int reserved = reader.getInt16(); + if (version > VERSION) { + throw std::runtime_error("unsupported VEC3 version"); + } + assert(reserved == 0); + + // Body + int modelCount = reader.getInt16(); + int materialCount = reader.getInt16(); + assert(modelCount >= 0); + assert(materialCount >= 0); + + std::unordered_map models; + for (int i = 0; i < modelCount; i++) { + Model model = load_model(reader); + models[model.name] = std::move(model); + } + std::vector materials; + for (int i = 0; i < materialCount; i++) { + materials.push_back(load_material(reader)); + } + + // Resolve textures + for (auto& [_, model] : models) { + for (auto& mesh : model.model.meshes) { + int materialId = util::str2wstr_utf8(mesh.texture).at(0); + mesh.texture = materials.at(materialId).name; + } + } + return File {std::move(models), std::move(materials)}; +} diff --git a/src/coders/vec3.hpp b/src/coders/vec3.hpp new file mode 100644 index 00000000..8a91250f --- /dev/null +++ b/src/coders/vec3.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "typedefs.hpp" +#include "util/Buffer.hpp" +#include "graphics/core/Model.hpp" + +/// See /doc/specs/vec3_model_spec.md +namespace vec3 { + struct Material { + int flags; + std::string name; + }; + + struct Model { + std::string name; + model::Model model; + glm::vec3 origin; + + Model& operator=(Model&&) = default; + + ~Model(); + }; + + struct File { + std::unordered_map models; + std::vector materials; + }; + + File load(const std::string_view file, const util::Buffer& src); +} diff --git a/test/coders/vec3.cpp b/test/coders/vec3.cpp new file mode 100644 index 00000000..e4643921 --- /dev/null +++ b/test/coders/vec3.cpp @@ -0,0 +1,12 @@ +#include + +#include "coders/vec3.hpp" +#include "files/files.hpp" + +TEST(VEC3, Decode) { + auto file = std::filesystem::u8path( + "../res/content/base/models/demo.vec3" + ); + auto bytes = files::read_bytes_buffer(file); + auto model = vec3::load(file.u8string(), bytes); +}