refactor: mesh load method
This commit is contained in:
		| @@ -142,9 +142,9 @@ void Engine::setup() | |||||||
| 	world_ = std::make_unique<World>(); | 	world_ = std::make_unique<World>(); | ||||||
| 	Logger::info("World created"); | 	Logger::info("World created"); | ||||||
|  |  | ||||||
| 	// Create camera at position (0, 1, 3) with -20 degrees X rotation | 	// Create camera | ||||||
| 	camera_ = std::make_shared<Camera>(); | 	camera_ = std::make_shared<Camera>(); | ||||||
| 	camera_->set_position(glm::vec3(0.0f, 1.0f, 3.0f)); | 	camera_->set_position(glm::vec3(0.0f, 1.0f, 10.0f)); | ||||||
| 	camera_->set_rotation(glm::vec3(glm::radians(-20.0f), 0.0f, 0.0f)); | 	camera_->set_rotation(glm::vec3(glm::radians(-20.0f), 0.0f, 0.0f)); | ||||||
| 	camera_->set_projection( | 	camera_->set_projection( | ||||||
| 	    glm::perspective(glm::radians(45.0f), | 	    glm::perspective(glm::radians(45.0f), | ||||||
|   | |||||||
							
								
								
									
										255
									
								
								src/mesh.cpp
									
									
									
									
									
								
							
							
						
						
									
										255
									
								
								src/mesh.cpp
									
									
									
									
									
								
							| @@ -14,6 +14,125 @@ | |||||||
| #include "texture.h" | #include "texture.h" | ||||||
| #include "vertex.h" | #include "vertex.h" | ||||||
|  |  | ||||||
|  | // Helper structures and functions for OBJ loading | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | struct LoadContext { | ||||||
|  | 	std::string						  base_dir; | ||||||
|  | 	std::unordered_map<std::string, std::shared_ptr<Texture>> texture_cache; | ||||||
|  | 	const std::vector<tinyobj::material_t>*			  materials; | ||||||
|  | 	const std::shared_ptr<Shader>&				  shader; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Vertex create_vertex(const tinyobj::attrib_t& attrib, | ||||||
|  | 		     const tinyobj::index_t&  idx) | ||||||
|  | { | ||||||
|  | 	Vertex vertex; | ||||||
|  |  | ||||||
|  | 	// Position | ||||||
|  | 	vertex.position.x = attrib.vertices[3 * idx.vertex_index + 0]; | ||||||
|  | 	vertex.position.y = attrib.vertices[3 * idx.vertex_index + 1]; | ||||||
|  | 	vertex.position.z = attrib.vertices[3 * idx.vertex_index + 2]; | ||||||
|  |  | ||||||
|  | 	// Color (default to white) | ||||||
|  | 	vertex.color = glm::vec3(1.0f, 1.0f, 1.0f); | ||||||
|  |  | ||||||
|  | 	// Texture coordinates | ||||||
|  | 	if (idx.texcoord_index >= 0) { | ||||||
|  | 		vertex.tex_coord.x = | ||||||
|  | 		    attrib.texcoords[2 * idx.texcoord_index + 0]; | ||||||
|  | 		vertex.tex_coord.y = 1.0f | ||||||
|  | 		    - attrib.texcoords[2 * idx.texcoord_index + 1]; // Flip Y | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return vertex; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void group_vertices_by_material( | ||||||
|  |     const tinyobj::shape_t& shape, const tinyobj::attrib_t& attrib, | ||||||
|  |     std::unordered_map<int, std::vector<Vertex>>&   out_vertices, | ||||||
|  |     std::unordered_map<int, std::vector<uint16_t>>& out_indices) | ||||||
|  | { | ||||||
|  | 	size_t index_offset = 0; | ||||||
|  | 	for (size_t f = 0; f < shape.mesh.num_face_vertices.size(); ++f) { | ||||||
|  | 		int fv		= shape.mesh.num_face_vertices[f]; | ||||||
|  | 		int material_id = shape.mesh.material_ids[f]; | ||||||
|  |  | ||||||
|  | 		for (size_t v = 0; v < static_cast<size_t>(fv); ++v) { | ||||||
|  | 			const tinyobj::index_t& idx = | ||||||
|  | 			    shape.mesh.indices[index_offset + v]; | ||||||
|  |  | ||||||
|  | 			Vertex vertex = create_vertex(attrib, idx); | ||||||
|  | 			out_vertices[material_id].push_back(vertex); | ||||||
|  | 			out_indices[material_id].push_back( | ||||||
|  | 			    static_cast<uint16_t>( | ||||||
|  | 				out_vertices[material_id].size() - 1)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		index_offset += fv; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::shared_ptr<Texture> load_material_texture(int	    material_id, | ||||||
|  | 					       LoadContext& context) | ||||||
|  | { | ||||||
|  | 	if (material_id < 0 | ||||||
|  | 	    || material_id >= static_cast<int>(context.materials->size())) { | ||||||
|  | 		return nullptr; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	const auto& mat		 = (*context.materials)[material_id]; | ||||||
|  | 	std::string texture_name = mat.diffuse_texname; | ||||||
|  |  | ||||||
|  | 	// Try ambient texture if diffuse is not available | ||||||
|  | 	if (texture_name.empty()) { | ||||||
|  | 		texture_name = mat.ambient_texname; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (texture_name.empty()) { | ||||||
|  | 		return nullptr; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Check cache first | ||||||
|  | 	auto it = context.texture_cache.find(texture_name); | ||||||
|  | 	if (it != context.texture_cache.end()) { | ||||||
|  | 		return it->second; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Load texture | ||||||
|  | 	std::string texture_path = context.base_dir + texture_name; | ||||||
|  | 	auto	    texture	 = Texture::load(texture_path.c_str()); | ||||||
|  |  | ||||||
|  | 	if (texture) { | ||||||
|  | 		context.texture_cache[texture_name] = texture; | ||||||
|  | 		Logger::info(sstr("Loaded texture: ", texture_path)); | ||||||
|  | 	} else { | ||||||
|  | 		Logger::warn(sstr("Failed to load texture: ", texture_path)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return texture; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void create_buffers_for_materials( | ||||||
|  |     const std::unordered_map<int, std::vector<Vertex>>&	  material_vertices, | ||||||
|  |     const std::unordered_map<int, std::vector<uint16_t>>& material_indices, | ||||||
|  |     LoadContext& context, std::shared_ptr<Mesh>& mesh) | ||||||
|  | { | ||||||
|  | 	for (const auto& [material_id, vertices] : material_vertices) { | ||||||
|  | 		const auto& indices = material_indices.at(material_id); | ||||||
|  |  | ||||||
|  | 		auto buffer  = std::make_shared<Buffer>(vertices, indices); | ||||||
|  | 		auto texture = load_material_texture(material_id, context); | ||||||
|  | 		auto material_shader = | ||||||
|  | 		    context.shader ? context.shader : state::default_shader; | ||||||
|  |  | ||||||
|  | 		Material mat(texture, material_shader); | ||||||
|  | 		mesh->add_buffer(buffer, mat); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace | ||||||
|  |  | ||||||
| void Mesh::add_buffer(const std::shared_ptr<Buffer>& buffer, | void Mesh::add_buffer(const std::shared_ptr<Buffer>& buffer, | ||||||
| 		      const Material&		     material) | 		      const Material&		     material) | ||||||
| { | { | ||||||
| @@ -26,152 +145,44 @@ std::shared_ptr<Mesh> Mesh::load(const char*			filename, | |||||||
| { | { | ||||||
| 	Logger::info(sstr("Loading mesh from: ", filename)); | 	Logger::info(sstr("Loading mesh from: ", filename)); | ||||||
|  |  | ||||||
| 	// Load the OBJ file | 	// Load OBJ file | ||||||
| 	tinyobj::attrib_t		 attrib; | 	tinyobj::attrib_t		 attrib; | ||||||
| 	std::vector<tinyobj::shape_t>	 shapes; | 	std::vector<tinyobj::shape_t>	 shapes; | ||||||
| 	std::vector<tinyobj::material_t> materials; | 	std::vector<tinyobj::material_t> materials; | ||||||
| 	std::string			 warn_err; | 	std::string			 err; | ||||||
|  |  | ||||||
| 	// Get the directory of the OBJ file for loading materials |  | ||||||
| 	std::filesystem::path obj_path(filename); | 	std::filesystem::path obj_path(filename); | ||||||
| 	std::string	      base_dir = obj_path.parent_path().string(); | 	std::string	      base_dir = obj_path.parent_path().string(); | ||||||
| 	if (!base_dir.empty()) { | 	if (!base_dir.empty()) { | ||||||
| 		base_dir += "/"; | 		base_dir += "/"; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn_err, | 	if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, | ||||||
| 			      filename, base_dir.c_str())) { | 			      base_dir.c_str())) { | ||||||
| 		Logger::error( | 		Logger::error( | ||||||
| 		    sstr("Failed to load OBJ file: ", filename, " - ", | 		    sstr("Failed to load OBJ file: ", filename, " - ", err)); | ||||||
| 			 warn_err)); |  | ||||||
| 		return nullptr; | 		return nullptr; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (!warn_err.empty()) { | 	if (!err.empty()) { | ||||||
| 		Logger::warn(sstr("TinyObjLoader warning: ", warn_err)); | 		Logger::warn(sstr("TinyObjLoader warning: ", err)); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Create the mesh | 	// Setup load context | ||||||
| 	auto mesh = std::make_shared<Mesh>(); | 	LoadContext context{base_dir, {}, &materials, shader}; | ||||||
|  |  | ||||||
| 	// Cache loaded textures to avoid loading the same texture multiple |  | ||||||
| 	// times |  | ||||||
| 	std::unordered_map<std::string, std::shared_ptr<Texture>> texture_cache; |  | ||||||
|  |  | ||||||
| 	// Process each shape | 	// Process each shape | ||||||
|  | 	auto mesh = std::make_shared<Mesh>(); | ||||||
| 	for (const auto& shape : shapes) { | 	for (const auto& shape : shapes) { | ||||||
| 		Logger::info(sstr("Processing shape: ", shape.name)); | 		Logger::info(sstr("Processing shape: ", shape.name)); | ||||||
|  |  | ||||||
| 		// Group faces by material |  | ||||||
| 		std::unordered_map<int, std::vector<Vertex>> material_vertices; | 		std::unordered_map<int, std::vector<Vertex>> material_vertices; | ||||||
| 		std::unordered_map<int, std::vector<uint16_t>> material_indices; | 		std::unordered_map<int, std::vector<uint16_t>> material_indices; | ||||||
|  |  | ||||||
| 		// Process all faces in this shape | 		group_vertices_by_material(shape, attrib, material_vertices, | ||||||
| 		size_t index_offset = 0; | 					   material_indices); | ||||||
| 		for (size_t f = 0; f < shape.mesh.num_face_vertices.size(); | 		create_buffers_for_materials(material_vertices, | ||||||
| 		     f++) { | 					     material_indices, context, mesh); | ||||||
| 			int fv		= shape.mesh.num_face_vertices[f]; |  | ||||||
| 			int material_id = shape.mesh.material_ids[f]; |  | ||||||
|  |  | ||||||
| 			// Process each vertex in the face |  | ||||||
| 			for (size_t v = 0; v < static_cast<size_t>(fv); v++) { |  | ||||||
| 				tinyobj::index_t idx = |  | ||||||
| 				    shape.mesh.indices[index_offset + v]; |  | ||||||
|  |  | ||||||
| 				Vertex vertex; |  | ||||||
|  |  | ||||||
| 				// Position |  | ||||||
| 				vertex.position.x = |  | ||||||
| 				    attrib.vertices[3 * idx.vertex_index + 0]; |  | ||||||
| 				vertex.position.y = |  | ||||||
| 				    attrib.vertices[3 * idx.vertex_index + 1]; |  | ||||||
| 				vertex.position.z = |  | ||||||
| 				    attrib.vertices[3 * idx.vertex_index + 2]; |  | ||||||
|  |  | ||||||
| 				// Color (default to white) |  | ||||||
| 				vertex.color = glm::vec3(1.0f, 1.0f, 1.0f); |  | ||||||
|  |  | ||||||
| 				// Texture coordinates |  | ||||||
| 				if (idx.texcoord_index >= 0) { |  | ||||||
| 					vertex.tex_coord.x = |  | ||||||
| 					    attrib.texcoords |  | ||||||
| 						[2 * idx.texcoord_index + 0]; |  | ||||||
| 					vertex.tex_coord.y = 1.0f |  | ||||||
| 					    - attrib.texcoords |  | ||||||
| 						  [2 * idx.texcoord_index |  | ||||||
| 						   + 1]; // Flip Y |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				material_vertices[material_id].push_back( |  | ||||||
| 				    vertex); |  | ||||||
| 				material_indices[material_id].push_back( |  | ||||||
| 				    static_cast<uint16_t>( |  | ||||||
| 					material_vertices[material_id].size() |  | ||||||
| 					- 1)); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			index_offset += fv; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Create buffers for each material |  | ||||||
| 		for (const auto& [material_id, vertices] : material_vertices) { |  | ||||||
| 			const auto& indices = material_indices[material_id]; |  | ||||||
|  |  | ||||||
| 			// Create buffer |  | ||||||
| 			auto buffer = |  | ||||||
| 			    std::make_shared<Buffer>(vertices, indices); |  | ||||||
|  |  | ||||||
| 			// Create material |  | ||||||
| 			std::shared_ptr<Texture> texture = nullptr; |  | ||||||
|  |  | ||||||
| 			// Load texture if material has one |  | ||||||
| 			if (material_id >= 0 |  | ||||||
| 			    && material_id |  | ||||||
| 				< static_cast<int>(materials.size())) { |  | ||||||
| 				const auto& mat = materials[material_id]; |  | ||||||
| 				std::string texture_name = mat.diffuse_texname; |  | ||||||
|  |  | ||||||
| 				// Try ambient texture if diffuse is not |  | ||||||
| 				// available |  | ||||||
| 				if (texture_name.empty()) { |  | ||||||
| 					texture_name = mat.ambient_texname; |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				if (!texture_name.empty()) { |  | ||||||
| 					// Check cache first |  | ||||||
| 					auto it = |  | ||||||
| 					    texture_cache.find(texture_name); |  | ||||||
| 					if (it != texture_cache.end()) { |  | ||||||
| 						texture = it->second; |  | ||||||
| 					} else { |  | ||||||
| 						// Load texture |  | ||||||
| 						std::string texture_path = |  | ||||||
| 						    base_dir + texture_name; |  | ||||||
| 						texture = Texture::load( |  | ||||||
| 						    texture_path.c_str()); |  | ||||||
| 						if (texture) { |  | ||||||
| 							texture_cache |  | ||||||
| 							    [texture_name] = |  | ||||||
| 								texture; |  | ||||||
| 							Logger::info(sstr( |  | ||||||
| 							    "Loaded texture: ", |  | ||||||
| 							    texture_path)); |  | ||||||
| 						} else { |  | ||||||
| 							Logger::warn(sstr( |  | ||||||
| 							    "Failed to load texture: ", |  | ||||||
| 							    texture_path)); |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Use provided shader or default shader |  | ||||||
| 			auto material_shader = |  | ||||||
| 			    shader ? shader : state::default_shader; |  | ||||||
| 			Material mat(texture, material_shader); |  | ||||||
|  |  | ||||||
| 			mesh->add_buffer(buffer, mat); |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	Logger::info(sstr("Mesh loaded successfully with ", mesh->num_buffers(), | 	Logger::info(sstr("Mesh loaded successfully with ", mesh->num_buffers(), | ||||||
|   | |||||||
| @@ -14,9 +14,9 @@ public: | |||||||
| 	Mesh()	= default; | 	Mesh()	= default; | ||||||
| 	~Mesh() = default; | 	~Mesh() = default; | ||||||
|  |  | ||||||
| 	static std::shared_ptr<Mesh> load( | 	static std::shared_ptr<Mesh> | ||||||
| 	    const char*			     filename, | 	load(const char*		    filename, | ||||||
| 	    const std::shared_ptr<Shader>& shader = nullptr); | 	     const std::shared_ptr<Shader>& shader = nullptr); | ||||||
|  |  | ||||||
| 	void add_buffer(const std::shared_ptr<Buffer>& buffer, | 	void add_buffer(const std::shared_ptr<Buffer>& buffer, | ||||||
| 			const Material&		       material); | 			const Material&		       material); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user