#include "mesh.hpp" #include #include #include #include #include std::map> assets::Mesh::s_texture_cache; std::map> assets::Mesh::s_skeleton_cache; static int GetVertexAttrFlags(int mesh_flags) { int attrs = gfx::VA_POSITION | gfx::VA_NORMAL | gfx::VA_UV; if (mesh_flags & assets::MF_HAS_LIGHTMAP_UV) { attrs |= gfx::VA_LIGHTMAP_UV; } if (mesh_flags & assets::MF_IS_SKELETAL) { attrs |= gfx::VA_BONE_INDICES | gfx::VA_BONE_WEIGHTS; } return attrs; } template static void BufferPut(std::vector& buffer, const T& val) { const char* data = reinterpret_cast(&val); buffer.insert(buffer.end(), data, data + sizeof(T)); } assets::Mesh::Mesh(std::span verts, std::span tris, std::span materials, int flags, std::shared_ptr skeleton) : va_(GetVertexAttrFlags(flags), gfx::VF_CREATE_EBO), skeleton_(std::move(skeleton)) { verts_.assign(verts.begin(), verts.end()); tris_.assign(tris.begin(), tris.end()); materials_.assign(materials.begin(), materials.end()); std::vector buffer; for (const MeshVertex& vert : verts) { BufferPut(buffer, vert.pos); BufferPut(buffer, vert.normal); BufferPut(buffer, vert.uv); if (flags & MF_HAS_LIGHTMAP_UV) { BufferPut(buffer, vert.lightmap_uv); } if (flags & MF_IS_SKELETAL) { for (int i = 0; i < 4; ++i) { BufferPut(buffer, static_cast(vert.bones[i].bone_index)); } for (int i = 0; i < 4; ++i) { BufferPut(buffer, vert.bones[i].weight); } } } va_.SetVBOData(buffer.data(), buffer.size()); va_.SetIndices(reinterpret_cast(tris.data()), tris.size() * 3); } std::shared_ptr assets::Mesh::LoadFromFile(const std::string& filename, bool load_collision) { std::shared_ptr collision_mesh; if (load_collision) collision_mesh = std::make_shared(); std::ifstream file(filename, std::ios::binary); if (!file.is_open()) { throw std::runtime_error("Failed to open mesh file: " + filename); } int flags = 0; std::shared_ptr skeleton; std::vector verts; std::vector tris; std::vector materials; std::vector bone_skel_ids; std::string line; while (std::getline(file, line)) { if (line.empty() || line[0] == '#') // Skip empty lines and comments continue; std::istringstream iss(line); std::string command; iss >> command; if (command == "v") // Vertex { MeshVertex& vertex = verts.emplace_back(); iss >> vertex.pos.x >> vertex.pos.y >> vertex.pos.z; iss >> vertex.normal.x >> vertex.normal.y >> vertex.normal.z; iss >> vertex.uv.x >> vertex.uv.y; if (flags & MF_HAS_LIGHTMAP_UV) { iss >> vertex.lightmap_uv.x >> vertex.lightmap_uv.y; } if (flags & MF_IS_SKELETAL) { int num_bones; iss >> num_bones; if (num_bones > 4) num_bones = 4; for (int i = 0; i < num_bones; ++i) { int bone_idx; float weight; iss >> bone_idx >> weight; if (bone_idx < 0 || bone_idx >= static_cast(bone_skel_ids.size())) { throw std::runtime_error("Bone index out of bounds in mesh file: " + filename); } vertex.bones[i].bone_index = bone_skel_ids[bone_idx]; vertex.bones[i].weight = weight; } // Fill remaining influences with zeroes for (int i = num_bones; i < 4; ++i) { vertex.bones[i].bone_index = -1; vertex.bones[i].weight = 0.0f; } } } else if (command == "m") // Material switch { if (materials.size() > 0) { MeshMaterial& last_material = materials.back(); last_material.num_tris = tris.size() - last_material.first_tri; } MeshMaterial& material = materials.emplace_back(); iss >> material.name; material.first_tri = tris.size(); } else if (command == "f") // Face { MeshTriangle& tri = tris.emplace_back(); glm::vec3 tri_verts[3]; for (size_t i = 0; i < 3; ++i) { uint32_t vert_index; iss >> vert_index; if (vert_index >= verts.size()) { throw std::runtime_error("Vertex index out of bounds in mesh file: " + filename); } tri.vert[i] = vert_index; tri_verts[i] = verts[vert_index].pos; } if (load_collision) { // Add triangle to collision mesh collision_mesh->AddTriangle(tri_verts[0], tri_verts[1], tri_verts[2]); } } else if (command == "d") { if (!skeleton) { throw std::runtime_error("Bone definition found in non-skeletal mesh file: " + filename); } std::string bone_name; iss >> bone_name; int idx = skeleton->GetBoneIndex(bone_name); if (idx < 0) { throw std::runtime_error("Bone referenced in mesh not found in provided skeleton: " + bone_name); } bone_skel_ids.push_back(idx); } else if (command == "luv") { flags |= MF_HAS_LIGHTMAP_UV; if (verts.size() > 0) { throw std::runtime_error("Lightmap UV flag must be specified before vertex definitions in mesh file: " + filename); } } else if (command == "skeleton") { if (verts.size() > 0) { throw std::runtime_error("Skeletal flag must be specified before vertex definitions in mesh file: " + filename); } flags |= MF_IS_SKELETAL; std::string skel_filename; iss >> skel_filename; // load skeleton skeleton = LoadSkeleton("data/" + skel_filename + ".skel"); } else { throw std::runtime_error("Unknown command in mesh file: " + command); } } if (materials.size() > 0) { MeshMaterial& last_material = materials.back(); last_material.num_tris = tris.size() - last_material.first_tri; } file.close(); if (verts.empty() || tris.empty()) { throw std::runtime_error("Mesh file is empty or malformed: " + filename); } // Load textures for materials for (MeshMaterial& material : materials) { if (!material.name.empty()) { try { material.texture = LoadTexture("data/" + material.name + ".png"); } catch (const std::exception& e) { throw std::runtime_error("Failed to load texture for material '" + material.name + "': " + e.what()); } } } // Create the mesh object std::shared_ptr mesh = std::make_shared(verts, tris, materials, flags, skeleton); if (load_collision) { collision_mesh->Build(); mesh->SetCollisionMesh(std::move(collision_mesh)); } return mesh; } std::shared_ptr assets::Mesh::LoadTexture(const std::string& filename) { auto it = s_texture_cache.find(filename); if (it != s_texture_cache.end()) { if (auto texture = it->second.lock()) { return texture; // Return cached texture } } auto texture = gfx::Texture::LoadFromFile(filename); s_texture_cache[filename] = texture; // Cache the texture return texture; } std::shared_ptr assets::Mesh::LoadSkeleton(const std::string& filename) { auto it = s_skeleton_cache.find(filename); if (it != s_skeleton_cache.end()) { if (auto skel = it->second.lock()) { return skel; // Return cached skeleton } } auto skel = Skeleton::LoadFromFile(filename); s_skeleton_cache[filename] = skel; // Cache the skeleton return skel; }