Loading...
Searching...
No Matches
ModelLoader.cpp
Go to the documentation of this file.
1#include "hellfire/assets/ModelLoader.h"
2#include <filesystem>
3#include <fstream>
4#include <iostream>
5#include <glm/gtc/quaternion.hpp>
6#include <glm/gtx/matrix_decompose.hpp>
7
8#include "assimp/Importer.hpp"
9#include "hellfire/ecs/RenderableComponent.h"
10#include "../ecs/Entity.h"
11#include "hellfire/ecs/TransformComponent.h"
12#include "hellfire/ecs/components/MeshComponent.h"
13#include "hellfire/scene/Scene.h"
14
15namespace fs = std::filesystem;
16
17#define MODEL_LOADER_DEBUG 0
18
19namespace hellfire::Addons {
20 // Static member definitions
24
25 EntityID ModelLoader::load_model(Scene *scene, const std::filesystem::path &filepath, unsigned int import_flags) {
26 const auto start_time = std::chrono::high_resolution_clock::now();
27 std::cout << "Loading model: " << filepath << std::endl;
28
29 Assimp::Importer importer;
30
31 // Use provided flags or default to runtime
32 if (import_flags == 0) {
33 import_flags = ImportFlags::HIGH_QUALITY;
34 }
35
36 // Parse model file
37 const aiScene *ai_scene = importer.ReadFile(filepath.string(), import_flags);
38
39 if (!ai_scene || ai_scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !ai_scene->mRootNode) {
40 std::cerr << "ModelLoader: Cannot load " << filepath << " - "
41 << importer.GetErrorString() << std::endl;
42 return 0;
43 }
44
45 // Debug information
47 debug_scene_info(ai_scene, filepath.string());
48#endif
49
50 // Pre-process materials for caching
51 preprocess_materials(ai_scene, filepath.string());
52
53 // Process the scene
54 const EntityID imported_model = process_node(scene, ai_scene->mRootNode, ai_scene, filepath.string());
56
57 const auto end_time = std::chrono::high_resolution_clock::now();
58 const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
59 std::cout << "Model loaded in: " << duration.count() << "ms" << std::endl;
60
61 return imported_model;
62 }
63
64 void ModelLoader::debug_scene_info(const aiScene *scene, const std::string &filepath) {
65 std::cout << "=== Scene Debug Info for " << fs::path(filepath).filename() << " ===" << std::endl;
66 std::cout << "Size: " << fs::file_size(filepath) / 1024 << "(KB)" << std::endl;
67 std::cout << "Materials: " << scene->mNumMaterials << std::endl;
68 std::cout << "Meshes: " << scene->mNumMeshes << std::endl;
69 std::cout << "Embedded Textures: " << scene->mNumTextures << std::endl;
70
71 // Print embedded texture info
72 for (unsigned int i = 0; i < scene->mNumTextures; i++) {
73 const aiTexture *tex = scene->mTextures[i];
74 std::cout << " Embedded texture " << i << ": "
75 << tex->mWidth << "x" << tex->mHeight
76 << " format: " << tex->achFormatHint << std::endl;
77 }
78
79 std::cout << "=============================================" << std::endl;
80 }
81
82 void ModelLoader::preprocess_materials(const aiScene *scene, const std::string &filepath) {
84 std::cout << "Preprocessing " << scene->mNumMaterials << " materials..." << std::endl;
85#endif
86
87 for (unsigned int i = 0; i < scene->mNumMaterials; i++) {
88 const aiMaterial *ai_material = scene->mMaterials[i];
89
90 // Create material cache key
91 std::string cache_key = create_material_key(ai_material, filepath, i);
92
93 // Skip if already cached
94 if (material_cache.find(cache_key) != material_cache.end()) {
95 continue;
96 }
97
98 // Create and cache material
99 const auto material = create_material(ai_material, scene, filepath);
100 material_cache[cache_key] = material;
101 }
102 }
103
104 // This method converts assimp matrix struct to glm matrix struct
105 glm::mat4 aiMatrix4x4ToGlm(const aiMatrix4x4 &from) {
106 glm::mat4 to;
107 to[0][0] = from.a1;
108 to[1][0] = from.a2;
109 to[2][0] = from.a3;
110 to[3][0] = from.a4;
111 to[0][1] = from.b1;
112 to[1][1] = from.b2;
113 to[2][1] = from.b3;
114 to[3][1] = from.b4;
115 to[0][2] = from.c1;
116 to[1][2] = from.c2;
117 to[2][2] = from.c3;
118 to[3][2] = from.c4;
119 to[0][3] = from.d1;
120 to[1][3] = from.d2;
121 to[2][3] = from.d3;
122 to[3][3] = from.d4;
123 return to;
124 }
125
126 EntityID ModelLoader::process_node(Scene *scene, aiNode *node, const aiScene *ai_scene, const std::string &filepath,
127 EntityID parent_id) {
128 bool should_create_entity = node->mNumMeshes > 0 ||
129 node->mNumChildren > 0 ||
130 !is_identity_transform(node->mTransformation);
131
132 EntityID entity_id = 0;
133 Entity *entity = nullptr;
134
135 if (should_create_entity) {
136 std::string node_name = node->mName.length > 0
137 ? node->mName.C_Str()
138 : ("Node_" + std::to_string(reinterpret_cast<uintptr_t>(node)));
139
140 entity_id = scene->create_entity(node_name);
141 entity = scene->get_entity(entity_id);
142
143 // Apply transform
144 glm::mat4 transform = aiMatrix4x4ToGlm(node->mTransformation);
145 glm::vec3 translation, scale, skew;
146 glm::quat rotation;
147 glm::vec4 perspective;
148 glm::decompose(transform, scale, rotation, translation, skew, perspective);
149
150 entity->transform()->set_position(translation);
151 entity->transform()->set_scale(scale);
152 entity->transform()->set_rotation(rotation);
153
154 if (parent_id != 0) {
155 scene->set_parent(entity_id, parent_id);
156 }
157 }
158
159 // Process meshes
160 if (entity_id != 0) {
161 for (unsigned int i = 0; i < node->mNumMeshes; i++) {
162 unsigned int mesh_index = node->mMeshes[i];
163 aiMesh *ai_mesh = ai_scene->mMeshes[mesh_index];
164
165 std::shared_ptr<Mesh> processed_mesh = process_mesh(ai_mesh, ai_scene, filepath);
166
167 // Get material
168 std::shared_ptr<Material> material = nullptr;
169 if (ai_mesh->mMaterialIndex >= 0) {
170 const aiMaterial *ai_material = ai_scene->mMaterials[ai_mesh->mMaterialIndex];
171 std::string material_key = create_material_key(ai_material, filepath, ai_mesh->mMaterialIndex);
172
173 auto cached_material = material_cache.find(material_key);
174 if (cached_material != material_cache.end()) {
175 material = cached_material->second;
176 } else {
177 material = create_material(ai_material, ai_scene, filepath);
178 material_cache[material_key] = material;
179 }
180 }
181
182 // Determine where to attach mesh components
183 EntityID mesh_entity_id;
184 Entity *mesh_entity;
185
186 if (node->mNumMeshes == 1) {
187 // Single mesh: attach to current node
188 mesh_entity_id = entity_id;
189 mesh_entity = entity;
190 } else {
191 // Multiple meshes: create submesh entity
192 std::string mesh_name = ai_mesh->mName.length > 0
193 ? ai_mesh->mName.C_Str()
194 : (std::string(node->mName.C_Str()) + "_Mesh_" + std::to_string(i));
195
196 mesh_entity_id = scene->create_entity(mesh_name);
197 mesh_entity = scene->get_entity(mesh_entity_id);
198 scene->set_parent(mesh_entity_id, entity_id);
199 }
200
201 // Add mesh and material components
202 auto *mesh_comp = mesh_entity->add_component<MeshComponent>();
203 mesh_comp->set_mesh(processed_mesh);
204
205 auto *renderable = mesh_entity->add_component<RenderableComponent>();
206 if (material) {
207 renderable->set_material(material);
208 }
209 }
210 }
211
212 // Process children recursively
213 for (unsigned int i = 0; i < node->mNumChildren; i++) {
214 process_node(scene, node->mChildren[i], ai_scene, filepath,
215 entity_id != 0 ? entity_id : parent_id);
216 }
217
218 return entity_id;
219 }
220
221 bool ModelLoader::is_identity_transform(const aiMatrix4x4 &matrix) {
222 const float epsilon = 0.0001f;
223 return std::abs(matrix.a1 - 1.0f) < epsilon && std::abs(matrix.b2 - 1.0f) < epsilon &&
224 std::abs(matrix.c3 - 1.0f) < epsilon && std::abs(matrix.d4 - 1.0f) < epsilon &&
225 std::abs(matrix.a2) < epsilon && std::abs(matrix.a3) < epsilon && std::abs(matrix.a4) < epsilon &&
226 std::abs(matrix.b1) < epsilon && std::abs(matrix.b3) < epsilon && std::abs(matrix.b4) < epsilon &&
227 std::abs(matrix.c1) < epsilon && std::abs(matrix.c2) < epsilon && std::abs(matrix.c4) < epsilon &&
228 std::abs(matrix.d1) < epsilon && std::abs(matrix.d2) < epsilon && std::abs(matrix.d3) < epsilon;
229 }
230
231 void ModelLoader::process_mesh_vertices(aiMesh *mesh, std::vector<Vertex> &vertices,
232 std::vector<unsigned int> &indices) {
233 vertices.reserve(mesh->mNumVertices);
234 indices.reserve(mesh->mNumFaces * 3);
235
236 for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
237 Vertex &v = vertices.emplace_back();
238 // Position
239 const auto &pos = mesh->mVertices[i];
240 v.position = glm::vec3(pos.x, pos.y, pos.z);
241
242 // Normal
243 if (mesh->HasNormals()) {
244 const auto &norm = mesh->mNormals[i];
245 v.normal = glm::vec3(norm.x, norm.y, norm.z);
246 } else {
247 v.normal = glm::vec3(0.0f, 1.0f, 0.0f); // Default up
248 }
249
250 if (mesh->HasTangentsAndBitangents()) {
251 const auto &bitan = mesh->mBitangents[i];
252 const auto &tan = mesh->mTangents[i];
253 v.tangent = glm::vec3(tan.x, tan.y, tan.z);
254 v.bitangent = glm::vec3(bitan.x, bitan.y, bitan.z);
255 } else {
256 v.tangent = glm::vec3(1.0f, 0.0f, 0.0f);
257 v.bitangent = glm::vec3(0.0f, 1.0f, 0.0f);
258 }
259
260 // Color
261 if (mesh->HasVertexColors(0)) {
262 const auto &col = mesh->mColors[0][i];
263 v.color = glm::vec3(col.r, col.g, col.b);
264 } else {
265 v.color = glm::vec3(1.0f); // Default white
266 }
267
268 // Texture coordinates
269 if (mesh->HasTextureCoords(0)) {
270 const auto &tex = mesh->mTextureCoords[0][i];
271 v.texCoords = glm::vec2(tex.x, tex.y);
272 } else {
273 v.texCoords = glm::vec2(0.0f);
274 }
275 }
276
277 for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
278 const aiFace &face = mesh->mFaces[i];
279 for (unsigned int j = 0; j < face.mNumIndices; j++) {
280 indices.push_back(face.mIndices[j]);
281 }
282 }
283 }
284
285 std::string ModelLoader::create_mesh_key(aiMesh *mesh, const std::string &filepath) {
286 return filepath + "_mesh_" + std::string(mesh->mName.C_Str()) + "_" + std::to_string(mesh->mMaterialIndex);
287 }
288
289 std::string ModelLoader::create_material_key(const aiMaterial *ai_material, const std::string &filepath,
290 unsigned int material_index) {
291 aiString material_name;
292 ai_material->Get(AI_MATKEY_NAME, material_name);
293 return filepath + "_mat_" + std::to_string(material_index) + "_" +
294 (material_name.length > 0 ? material_name.C_Str() : "unnamed");
295 }
296
297 std::shared_ptr<Mesh> ModelLoader::process_mesh(aiMesh *mesh, const aiScene *scene, const std::string &filepath) {
298 // Create mesh key and check cache
299 const std::string mesh_key = create_mesh_key(mesh, filepath);
300 const auto cached = mesh_cache.find(mesh_key);
301 if (cached != mesh_cache.end()) {
302 std::cout << "Using cached mesh: " << mesh_key << std::endl;
303 return cached->second;
304 }
305
306 std::vector<Vertex> vertices;
307 std::vector<unsigned int> indices;
308 process_mesh_vertices(mesh, vertices, indices);
309
310 // Create mesh WITHOUT material
311 auto processed_mesh = std::make_shared<Mesh>(vertices, indices);
312
313 // Cache the mesh
314 mesh_cache[mesh_key] = processed_mesh;
315
316 return processed_mesh;
317 }
318
319
320 std::shared_ptr<Material> ModelLoader::create_material(const aiMaterial *ai_material, const aiScene *scene,
321 const std::string &filepath
322 ) {
323 // Get material name
324 aiString material_name;
325 ai_material->Get(AI_MATKEY_NAME, material_name);
326 const std::string name = material_name.length > 0 ? material_name.C_Str() : "ImportedMaterial";
327
328 // Create Lambert Material
329 auto material = MaterialBuilder::create(name);
330
331 // Load ALL properties
332 load_essential_material_properties(ai_material, *material);
333
334 // Load textures
335 load_material_textures(ai_material, *material, scene, filepath);
336
337 return material;
338 }
339
340 void ModelLoader::load_essential_material_properties(const aiMaterial *ai_material, Material &material) {
341 aiColor3D color;
342 float value;
343
344 // Load color properties and convert them to the engine's material system
345 if (ai_material->Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) {
346 material.set_diffuse_color(glm::vec3(color.r, color.g, color.b));
347 }
348
349 if (ai_material->Get(AI_MATKEY_COLOR_AMBIENT, color) == AI_SUCCESS) {
350 material.set_ambient_color(glm::vec3(color.r, color.g, color.b));
351 }
352
353 if (ai_material->Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS) {
354 material.set_specular_color(glm::vec3(color.r, color.g, color.b));
355 }
356
357 if (ai_material->Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) {
358 material.set_emissive_color(glm::vec3(color.r, color.g, color.b));
359 }
360
361 // Load scalar properties using constants
362 if (ai_material->Get(AI_MATKEY_OPACITY, value) == AI_SUCCESS) {
363 material.set_opacity(value);
364 }
365
366 if (ai_material->Get(AI_MATKEY_SHININESS, value) == AI_SUCCESS) {
367 material.set_shininess(std::max(value, 1.0f));
368 }
369
370 if (ai_material->Get(AI_MATKEY_METALLIC_FACTOR, value) == AI_SUCCESS) {
371 material.set_metallic(value);
372 }
373
374 if (ai_material->Get(AI_MATKEY_ROUGHNESS_FACTOR, value) == AI_SUCCESS) {
375 material.set_roughness(value);
376 }
377 }
378
379 std::vector<std::string> ModelLoader::get_texture_search_paths(const std::string &filepath) {
380 const std::filesystem::path model_dir = std::filesystem::path(filepath).parent_path();
381
382 return {
383 model_dir.string() + "/textures/",
384 model_dir.string() + "/Textures/",
385 model_dir.string() + "/texture/",
386 model_dir.string() + "/materials/",
387 model_dir.string() + "/source/",
388 model_dir.string() + "/../textures/",
389 model_dir.string() + "/",
390 "assets/models/",
391 "assets/textures/"
392 };
393 }
394
395 void ModelLoader::load_material_textures(const aiMaterial *ai_material, Material &material, const aiScene *scene,
396 const std::string &filepath) {
397 auto load_texture = [&](aiTextureType ai_type, TextureType dcr_type) {
398 if (ai_material->GetTextureCount(ai_type) == 0) return;
399
400 aiString texture_path;
401 if (ai_material->GetTexture(ai_type, 0, &texture_path) != AI_SUCCESS) return;
402
403 const std::string path_str = texture_path.C_Str();
404
405 // Try embedded texture first
406 if (try_load_embedded_texture_unified(path_str, scene, dcr_type, material)) {
407 return;
408 }
409
410 // Try external texture
411 if (try_load_external_texture_unified(path_str, filepath, dcr_type, material)) {
412 return;
413 }
414
415 std::cerr << "Failed to load texture: " << path_str << std::endl;
416 };
417
418 // Load textures using the enum-based system
419 load_texture(aiTextureType_DIFFUSE, TextureType::DIFFUSE);
420 load_texture(aiTextureType_NORMALS, TextureType::NORMAL);
421 load_texture(aiTextureType_SPECULAR, TextureType::SPECULAR);
422 load_texture(aiTextureType_METALNESS, TextureType::METALNESS);
423 load_texture(aiTextureType_DIFFUSE_ROUGHNESS, TextureType::ROUGHNESS);
424 load_texture(aiTextureType_AMBIENT_OCCLUSION, TextureType::AMBIENT_OCCLUSION);
425 load_texture(aiTextureType_EMISSIVE, TextureType::EMISSIVE);
426 }
427
428 bool ModelLoader::try_load_embedded_texture_unified(const std::string &path_str,
429 const aiScene *scene,
430 TextureType type,
431 Material &material) {
432 if (path_str.empty() || path_str[0] != '*') {
433 return false;
434 }
435
436 try {
437 const int texture_index = std::stoi(path_str.substr(1));
438
439 if (!scene || texture_index >= static_cast<int>(scene->mNumTextures)) {
440 std::cerr << "Embedded texture index " << texture_index << " is out of range!" << std::endl;
441 return false;
442 }
443
444 const aiTexture *embedded_texture = scene->mTextures[texture_index];
445
446 // Handle compressed embedded textures
447 if (embedded_texture->mHeight == 0) {
448 std::string extension = embedded_texture->achFormatHint;
449 if (extension.empty() || extension.length() > 4) {
450 const unsigned char *data = reinterpret_cast<const unsigned char *>(embedded_texture->pcData);
451 if (data && embedded_texture->mWidth >= 2) {
452 if (data[0] == 0xFF && data[1] == 0xD8) extension = "jpg";
453 else if (data[0] == 0x89 && data[1] == 0x50) extension = "png";
454 else if (data[0] == 'B' && data[1] == 'M') extension = "bmp";
455 else extension = "bin";
456 } else {
457 extension = "bin";
458 }
459 }
460
461 const std::string temp_filename = "temp_texture_" + std::to_string(texture_index) + "." + extension;
462
463 std::ofstream temp_file(temp_filename, std::ios::binary);
464 if (!temp_file) {
465 std::cerr << "Failed to create temp file: " << temp_filename << std::endl;
466 return false;
467 }
468
469 temp_file.write(reinterpret_cast<const char *>(embedded_texture->pcData), embedded_texture->mWidth);
470 temp_file.close();
471
472 try {
473 // Use the unified texture setting API
474 material.set_texture(temp_filename, type, 0);
475
476
477 std::filesystem::remove(temp_filename);
478 return true;
479 } catch (const std::exception &e) {
480 std::cerr << "Exception loading embedded texture: " << e.what() << std::endl;
481 }
482
483 std::filesystem::remove(temp_filename);
484 return false;
485 }
486 // Handle uncompressed embedded textures
487 else {
488 std::cout << "Uncompressed embedded texture found - needs direct GPU upload implementation" <<
489 std::endl;
490 return false;
491 }
492 } catch (const std::exception &e) {
493 std::cerr << "Error processing embedded texture: " << e.what() << std::endl;
494 return false;
495 }
496 }
497
498 bool ModelLoader::try_load_external_texture_unified(const std::string &path_str,
499 const std::string &filepath,
500 TextureType type,
501 Material &material) {
502 const std::string texture_filename = std::filesystem::path(path_str).filename().string();
503 const std::filesystem::path model_dir = std::filesystem::path(filepath).parent_path();
504
505 const std::vector<std::string> possible_paths = {
506 path_str,
507 model_dir.string() + "/" + path_str,
508 model_dir.string() + "/textures/" + texture_filename,
509 model_dir.string() + "/Textures/" + texture_filename,
510 model_dir.string() + "/texture/" + texture_filename,
511 model_dir.string() + "/materials/" + texture_filename,
512 model_dir.string() + "/source/" + texture_filename,
513 model_dir.string() + "/../textures/" + texture_filename,
514 model_dir.string() + "/" + texture_filename,
515 "assets/models/" + texture_filename,
516 "assets/textures/" + texture_filename
517 };
518
519 for (const auto &test_path: possible_paths) {
520 if (std::filesystem::exists(test_path)) {
521 auto texture = load_cached_texture(test_path, type);
522 if (texture) {
523 // Use the unified texture setting API
524 material.set_texture(texture, 0);
525 return true;
526 }
527 }
528 }
529 return false;
530 }
531
532 std::shared_ptr<Texture> ModelLoader::load_cached_texture(const std::string &path, TextureType type) {
533 const auto it = texture_cache.find(path);
534 if (it != texture_cache.end()) {
535 std::cout << "Using cached texture: " << path << std::endl;
536 return it->second;
537 }
538
539 try {
540 auto texture = std::make_shared<Texture>(path, type);
541 if (texture->is_valid()) {
542 texture_cache[path] = texture;
543 return texture;
544 }
545 } catch (const std::exception &e) {
546 std::cerr << "Failed to load texture " << path << ": " << e.what() << std::endl;
547 }
548
549 return nullptr;
550 }
551
552 std::string ModelLoader::capitalize_first(const std::string &str) {
553 if (str.empty()) return str;
554 std::string result = str;
555 result[0] = std::toupper(result[0]);
556 return result;
557 }
558
560 mesh_cache.clear();
561 material_cache.clear();
562 texture_cache.clear();
563 std::cout << "ModelLoader caches cleared" << std::endl;
564 }
565
567 std::cout << "=== ModelLoader Cache Statistics ===" << std::endl;
568 std::cout << "Cached meshes: " << mesh_cache.size() << std::endl;
569 std::cout << "Cached materials: " << material_cache.size() << std::endl;
570 std::cout << "====================================" << std::endl;
571 }
572}
#define MODEL_LOADER_DEBUG
static std::shared_ptr< Texture > load_cached_texture(const std::string &path, TextureType type)
static std::unordered_map< std::string, std::shared_ptr< Texture > > texture_cache
static bool try_load_external_texture_unified(const std::string &path_str, const std::string &filepath, TextureType type, Material &material)
static std::unordered_map< std::string, std::shared_ptr< Mesh > > mesh_cache
static std::string capitalize_first(const std::string &str)
static std::unordered_map< std::string, std::shared_ptr< Material > > material_cache
static EntityID load_model(Scene *scene, const std::filesystem::path &filepath, unsigned int import_flags=0)
TransformComponent * transform()
Definition Entity.cpp:42
void set_roughness(float roughness)
Definition Material.h:177
void set_metallic(float metallic)
Definition Material.h:173
void set_opacity(float opacity)
Definition Material.h:181
void set_shininess(float shininess)
Definition Material.h:169
Manages a collection of entities and their hierarchical relationships.
Definition Scene.h:24
void set_parent(EntityID child_id, EntityID parent_id)
Sets the parent of an entity.
Definition Scene.cpp:105
void update_world_matrices()
Updates world transformation matrices for all entities Propagates transformations through the entity ...
Definition Scene.cpp:168
Entity * get_entity(EntityID id)
Retrieves an entity by its ID.
Definition Scene.cpp:81
EntityID create_entity(const std::string &name="GameObject")
Creates a new entity in the scene.
Definition Scene.cpp:29
void set_position(float x, float y, float z)
void set_rotation(float x, float y, float z)
glm::mat4 aiMatrix4x4ToGlm(const aiMatrix4x4 &from)
TextureType
Definition Texture.h:13
Definition Vertex.h:5
static constexpr unsigned int HIGH_QUALITY
Definition ModelLoader.h:55