#include #include #include #include #include #include #include "IO/parser.h" #include "renderer/mesh.h" #include "renderer/wavefront.h" #define DEFAULT_MATERIAL_NAME "default" // ObjElement toElement(const std::string &s) { // if (s == "#") return ObjElement::OHASH; // if (s == "mtllib") return ObjElement::MTLLIB; // if (s == "usemtl") return ObjElement::USEMTL; // if (s == "o") return ObjElement::O; // if (s == "v") return ObjElement::V; // if (s == "vn") return ObjElement::VN; // if (s == "vt") return ObjElement::VT; // if (s == "f") return ObjElement::F; // return ObjElement::OUNKNOWN; // } inline ObjElement toElement(const char* s) { switch (s[0]) { case '#': return ObjElement::OHASH; case 'm': if (strcmp(s, "mtllib") == 0) return ObjElement::MTLLIB; break; case 'u': if (strcmp(s, "usemtl") == 0) return ObjElement::USEMTL; break; case 'o': if (s[1] == '\0') return ObjElement::O; break; case 'v': if (s[1] == '\0') return ObjElement::V; if (s[1] == 'n' && s[2] == '\0') return ObjElement::VN; if (s[1] == 't' && s[2] == '\0') return ObjElement::VT; break; case 'f': if (s[1] == '\0') return ObjElement::F; break; } return ObjElement::OUNKNOWN; } // MtlElement toMtlElement(const std::string &s) { // if (s == "#") return MtlElement::MHASH; // if (s == "newmtl") return MtlElement::NEWMTL; // if (s == "Ns") return MtlElement::NS; // if (s == "Ka") return MtlElement::KA; // if (s == "Ks") return MtlElement::KS; // if (s == "Kd") return MtlElement::KD; // if (s == "Ni") return MtlElement::NI; // if (s == "d") return MtlElement::D; // if (s == "illum") return MtlElement::ILLUM; // if (s == "map_Kd") return MtlElement::MAP_KD; // if (s == "map_Ka") return MtlElement::MAP_KA; // // if (s == "map_Ke") return MtlElement::MAP_KE; // return MtlElement::MUNKNOWN; // } inline MtlElement toMtlElement(const char* s) { switch (s[0]) { case '#': return MtlElement::MHASH; case 'n': if (strcmp(s, "newmtl") == 0) return MtlElement::NEWMTL; break; case 'N': if (s[1] == 's' && s[2] == '\0') return MtlElement::NS; if (s[1] == 'i' && s[2] == '\0') return MtlElement::NI; break; case 'K': if (s[1] == 'a' && s[2] == '\0') return MtlElement::KA; if (s[1] == 's' && s[2] == '\0') return MtlElement::KS; if (s[1] == 'd' && s[2] == '\0') return MtlElement::KD; break; case 'd': if (s[1] == '\0') return MtlElement::D; break; case 'i': if (strcmp(s, "illum") == 0) return MtlElement::ILLUM; break; case 'm': if (strcmp(s, "map_Kd") == 0) return MtlElement::MAP_KD; if (strcmp(s, "map_Ka") == 0) return MtlElement::MAP_KA; // if (strcmp(s, "map_Ke") == 0) return MtlElement::MAP_KE; break; } return MtlElement::MUNKNOWN; } inline int Object::NormalizeIndex(int idx, int baseCount) { // idx is the raw value returned by parser: // 0 -> means "not present" or invalid in our convention // >0 -> 1-based index -> convert to 0-based // <0 -> negative index -> relative to baseCount: baseCount + idx if (idx == 0) return -1; // absent / invalid if (idx > 0) return idx - 1; // 1-based -> 0-based return baseCount + idx; // negative -> count from end } Object::Object() { m_vertices = std::vector(); m_normals = std::vector(); m_texCoords = std::vector(); } void Object::LoadMaterials(const std::filesystem::path& filename) { std::ifstream file(filename); if (!file.is_open()) { std::cerr << "Failed to open MTL file: " << filename << std::endl; return; } std::string currentMaterialName; std::shared_ptr currentMaterial; char line[1024]; // buffer per line while (file.getline(line, sizeof(line))) { Parser p(line); char* prefix = p.TakeWord(); if (!prefix) continue; switch (toMtlElement(prefix)) { case MtlElement::MHASH: // comment continue; case MtlElement::NEWMTL: { // If a material was being built, commit it first if (currentMaterial) { AddMaterial(currentMaterialName, std::move(currentMaterial)); currentMaterial = nullptr; } char* materialName = p.TakeWord(); if (materialName) { currentMaterialName = materialName; currentMaterial = std::make_shared(); } break; } case MtlElement::NS: // specular weight { float weight = p.TakeFloat(); if (currentMaterial) currentMaterial->SetSpecularWeight(weight); break; } case MtlElement::KA: // ambient color { float r = p.TakeFloat(); float g = p.TakeFloat(); float b = p.TakeFloat(); if (currentMaterial) currentMaterial->SetAmbientColor(glm::vec3(r, g, b)); break; } case MtlElement::KS: // specular color { float r = p.TakeFloat(); float g = p.TakeFloat(); float b = p.TakeFloat(); if (currentMaterial) currentMaterial->SetSpecularColor(glm::vec3(r, g, b)); break; } case MtlElement::KD: // diffuse color { float r = p.TakeFloat(); float g = p.TakeFloat(); float b = p.TakeFloat(); if (currentMaterial) currentMaterial->SetDiffuseColor(glm::vec3(r, g, b)); break; } case MtlElement::D: // opacity { float d = p.TakeFloat(); if (currentMaterial) currentMaterial->SetOpacity(d); break; } case MtlElement::ILLUM: // illumination model { int illum = p.TakeInt(); if (currentMaterial) currentMaterial->SetIllumination(illum); break; } case MtlElement::MAP_KD: // diffuse texture map { // take rest of line as texture path (can contain spaces) char* texPath = p.TakeUntil('\0'); if (texPath && currentMaterial) { // trim trailing spaces size_t len = std::strlen(texPath); while (len > 0 && (texPath[len - 1] == ' ' || texPath[len - 1] == '\t')) texPath[--len] = '\0'; currentMaterial->SetDiffuseTexture(Texture::LoadFile(texPath)); } break; } case MtlElement::MAP_KA: // ambient texture map { char* texPath = p.TakeUntil('\0'); if (texPath && currentMaterial) { size_t len = std::strlen(texPath); while (len > 0 && (texPath[len - 1] == ' ' || texPath[len - 1] == '\t')) texPath[--len] = '\0'; // optional: handle ambient texture // currentMaterial->SetAmbientTexture(Texture::LoadFile(texPath)); } break; } default: // ignore unknown tokens break; } } // Commit last material if pending if (currentMaterial) { AddMaterial(currentMaterialName, std::move(currentMaterial)); } file.close(); } void Object::AddMaterial(std::string name, std::shared_ptr material) { m_materials.insert(std::make_pair(std::move(name), std::move(material))); } std::shared_ptr Object::GetMaterial(std::string name) { auto material = m_materials.find(name); if (material == m_materials.end()) return nullptr; return material->second; } void Object::CreateNewMesh(const std::string& materialName) { Mesh mesh; mesh.materialName = materialName; m_meshes.push_back(mesh); } Mesh& Object::GetLastMesh() { if (m_meshes.empty()) { auto material = std::make_shared(); material->SetAmbientColor(glm::vec3(0.52f, 0.52f, 0.52f)); AddMaterial(DEFAULT_MATERIAL_NAME, std::move(material)); CreateNewMesh(DEFAULT_MATERIAL_NAME); } return m_meshes.back(); } Object Object::LoadFile(const std::string& filename) { std::ifstream file(filename); if (!file.is_open()) { std::cerr << "Failed to open OBJ file: " << filename << std::endl; return {}; } Object obj; char line[1024]; // static buffer for each line (enough for OBJ lines) while (file.getline(line, sizeof(line))) { Parser p(line); char* prefix = p.TakeWord(); if (!prefix) continue; switch (toElement(prefix)) { case ObjElement::OHASH: // comment continue; case ObjElement::MTLLIB: { char* mtlFile = p.TakeWord(); if (mtlFile) { std::filesystem::path fullPath = filename; std::filesystem::path mtlPath = fullPath.replace_filename(mtlFile); obj.LoadMaterials(mtlPath); } break; } case ObjElement::USEMTL: { char* materialName = p.TakeWord(); if (materialName) { auto& mesh = obj.GetLastMesh(); if (mesh.materialName != materialName) { Mesh newMesh; newMesh.materialName = materialName; obj.m_meshes.push_back(newMesh); } } break; } case ObjElement::O: // object name { char* name = p.TakeWord(); if (name) obj.m_name = name; break; } case ObjElement::V: // vertex { float x = p.TakeFloat(); float y = p.TakeFloat(); float z = p.TakeFloat(); float w = p.TakeFloat(); if (w != 0.0f && w != 1.0f) { x /= w; y /= w; z /= w; } obj.m_vertices.emplace_back(x, y, z); break; } case ObjElement::VN: // normal { float x = p.TakeFloat(); float y = p.TakeFloat(); float z = p.TakeFloat(); obj.m_normals.emplace_back(x, y, z); break; } case ObjElement::VT: // texcoord { float u = p.TakeFloat(); float v = p.TakeFloat(); obj.m_texCoords.emplace_back(u, 1.0f - v); break; } case ObjElement::F: // face { auto& mesh = obj.GetLastMesh(); int raw_vi, raw_ti, raw_ni; while (p.TakeFaceIndices(raw_vi, raw_ti, raw_ni)) { // Convert raw OBJ indices to 0-based / -1 sentinel int vi = Object::NormalizeIndex(raw_vi, (int)obj.m_vertices.size()); int ti = Object::NormalizeIndex(raw_ti, (int)obj.m_texCoords.size()); int ni = Object::NormalizeIndex(raw_ni, (int)obj.m_normals.size()); if (vi < 0) { // malformed token (no vertex) — skip continue; } glm::vec3 vert = obj.m_vertices[vi]; glm::vec3 norm(0.0f); glm::vec2 texCoord(0.0f); if (ni >= 0) norm = obj.m_normals[ni]; if (ti >= 0) texCoord = obj.m_texCoords[ti]; mesh.m_vertexBuffer.emplace_back(vert, norm, texCoord); mesh.m_indexBuffer.push_back(mesh.m_vertexBuffer.size() - 1); } break; } default: // ignore unknown tokens break; } } std::cout << "Object name: " << obj.m_name << std::endl; std::cout << "Vertices count: " << obj.m_vertices.size() << std::endl; std::cout << "Normals count: " << obj.m_normals.size() << std::endl; std::cout << "TexCoords count: " << obj.m_texCoords.size() << std::endl; std::cout << "Meshes count: " << obj.m_meshes.size() << std::endl; std::cout << "Materials count: " << obj.m_materials.size() << std::endl; file.close(); for (auto &mesh : obj.m_meshes) { mesh.Upload(); } return obj; } void Object::Render(Shader& shader) { for (auto &mesh : m_meshes) { auto material = GetMaterial(mesh.materialName); shader.setFloat("ambientStrength", 0.2f); shader.setFloat("shininess", material->GetSpecularWeight()); shader.setFloat("opacity", material->GetOpacity()); shader.setBool("useSpecular", material->GetIllumination() >= 2); shader.setFloat("specularStrength", 1.0f); shader.setVec3("ambientColor", material->GetAmbientColor()); shader.setVec3("diffuseColor", material->GetDiffuseColor()); shader.setVec3("specularColor", material->GetSpecularColor()); if (material->HasDiffuseTexture()) { shader.setBool("useTexture", true); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, material->GetDiffuseTexture()->GetID()); shader.setInt("diffuseTex", 0); } else { shader.setBool("useTexture", false); } mesh.Render(); } }