#include "vec3.hpp" #include #include "byte_utils.hpp" #include "util/data_io.hpp" #include "util/stringutil.hpp" #include "graphics/commons/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]]; } else if (coords) { // Flat normal calculation int idx = (i / 3) * 3; auto a = coords[indices[idx * attrsCount + coordsIndex]]; auto b = coords[indices[(idx + 1) * attrsCount + coordsIndex]]; auto c = coords[indices[(idx + 2) * attrsCount + coordsIndex]]; vertex.normal = glm::normalize(glm::cross(b - a, c - a)); } vertices.push_back(std::move(vertex)); } return model::Mesh {texture, std::move(vertices)}; } static model::Mesh load_mesh( ByteReader& reader, const std::vector& materials ) { 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"); } 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]; } } else { reader.get( reinterpret_cast(indices.data()), indices.size() * sizeof(uint16_t) ); } if (dataio::is_big_endian()) { for (int i = 0; i < indices.size(); i++) { indices[i] = dataio::swap(indices[i]); } } return build_mesh( attributes, indices, materials.at(materialId).name ); } static Model load_model( ByteReader& reader, const std::vector& materials ) { 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, materials)); } util::Buffer chars(nameLength); reader.get(chars.data(), nameLength); std::string name(chars.data(), nameLength); glm::vec3 offset {x, y, z}; for (auto& mesh : meshes) { for (auto& vertex : mesh.vertices) { vertex.coord -= offset; } } 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 materialCount = reader.getInt16(); int modelCount = reader.getInt16(); assert(materialCount >= 0); assert(modelCount >= 0); std::vector materials; for (int i = 0; i < materialCount; i++) { materials.push_back(load_material(reader)); } std::unordered_map models; for (int i = 0; i < modelCount; i++) { Model model = load_model(reader, materials); models[model.name] = std::move(model); } return File {std::move(models), std::move(materials)}; }