9#include <assimp/Importer.hpp>
10#include <assimp/scene.h>
11#include <assimp/postprocess.h>
13#include "glm/detail/type_mat4x4.hpp"
14#include "glm/gtc/quaternion.hpp"
15#include "glm/gtx/matrix_decompose.hpp"
16#include "hellfire/graphics/Mesh.h"
17#include "hellfire/graphics/Vertex.h"
18#include "hellfire/graphics/material/MaterialData.h"
19#include "hellfire/serializers/MaterialSerializer.h"
20#include "hellfire/serializers/MeshSerializer.h"
21#include "hellfire/serializers/TextureSerializer.h"
25 const std::filesystem::path &output_dir) :
registry_(registry),
27 create_directories(output_dir_);
33 source_path_ = source_path;
34 source_dir_ = source_path.parent_path();
37 Assimp::Importer importer;
38 ai_scene_ = importer.ReadFile(source_path.string(), build_import_flags(settings));
40 if (!ai_scene_ || !ai_scene_->mRootNode) {
46 process_node_hierarchy(ai_scene_->mRootNode, result);
55 unsigned int flags = aiProcess_ValidateDataStructure;
57 if (settings.triangulate)
58 flags |= aiProcess_Triangulate;
59 if (settings.generate_normals)
60 flags |= aiProcess_GenSmoothNormals;
61 if (settings.generate_tangents)
62 flags |= aiProcess_CalcTangentSpace;
63 if (settings.flip_uvs)
64 flags |= aiProcess_FlipUVs;
65 if (settings.optimize_meshes)
66 flags |= aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph;
68 flags |= aiProcess_JoinIdenticalVertices;
69 flags |= aiProcess_ImproveCacheLocality;
75 const size_t current_index = result.nodes.size();
76 result.nodes.push_back(convert_node(node));
77 auto &imported_node = result.nodes.back();
80 if (parent_index != SIZE_MAX) {
81 result.nodes[parent_index].child_indices.push_back(current_index);
87 for (
unsigned int i = 0; i < node->mNumMeshes; i++) {
88 const unsigned int mesh_idx = node->mMeshes[i];
89 const aiMesh *ai_mesh = ai_scene_->mMeshes[mesh_idx];
92 imported_mesh
.name = ai_mesh->mName.length > 0
93 ? ai_mesh->mName.C_Str()
94 : make_unique_name(
base_name_,
"mesh", mesh_idx);
97 imported_mesh.mesh_asset = process_mesh(ai_mesh, mesh_idx);
98 result.created_mesh_assets.push_back(imported_mesh
.mesh_asset);
101 if (ai_mesh->mMaterialIndex < ai_scene_->mNumMaterials) {
102 const aiMaterial *material = ai_scene_->mMaterials[ai_mesh->mMaterialIndex];
103 imported_mesh.material_asset = process_material(material, ai_mesh->mMaterialIndex);
106 result.created_material_assets.push_back(imported_mesh
.material_asset);
110 imported_node.mesh_indices.push_back(result.meshes.size());
111 result.meshes.push_back(imported_mesh);
115 for (
unsigned int i = 0; i < node->mNumChildren; i++) {
116 process_node_hierarchy(node->mChildren[i], result, current_index);
123 result
.name = node->mName.length > 0 ? node->mName.C_Str() :
"Node";
125 glm::mat4 transform = convert_matrix(node->mTransformation);
127 glm::vec4 perspective;
130 decompose(transform, result.scale, rotation, result.position, skew, perspective);
131 result.rotation = eulerAngles(rotation);
137 std::vector<Vertex> vertices;
138 std::vector<
unsigned int> indices;
140 vertices.reserve(ai_mesh->mNumVertices);
141 indices.reserve(ai_mesh->mNumFaces * 3);
144 for (
unsigned int i = 0; i < ai_mesh->mNumVertices; i++) {
148 ai_mesh->mVertices[i].x,
149 ai_mesh->mVertices[i].y,
150 ai_mesh->mVertices[i].z
153 if (ai_mesh->HasNormals()) {
155 ai_mesh->mNormals[i].x,
156 ai_mesh->mNormals[i].y,
157 ai_mesh->mNormals[i].z
161 if (ai_mesh->HasTangentsAndBitangents()) {
163 ai_mesh->mTangents[i].x,
164 ai_mesh->mTangents[i].y,
165 ai_mesh->mTangents[i].z
168 ai_mesh->mBitangents[i].x,
169 ai_mesh->mBitangents[i].y,
170 ai_mesh->mBitangents[i].z
174 if (ai_mesh->HasTextureCoords(0)) {
176 ai_mesh->mTextureCoords[0][i].x,
177 ai_mesh->mTextureCoords[0][i].y
181 if (ai_mesh->HasVertexColors(0)) {
183 ai_mesh->mColors[0][i].r,
184 ai_mesh->mColors[0][i].g,
185 ai_mesh->mColors[0][i].b
188 v.color = glm::vec3(1.0f);
191 vertices.push_back(v);
195 for (
unsigned int i = 0; i < ai_mesh->mNumFaces; i++) {
196 const aiFace &face = ai_mesh->mFaces[i];
197 for (
unsigned int j = 0; j < face.mNumIndices; j++) {
198 indices.push_back(face.mIndices[j]);
203 Mesh mesh(vertices, indices,
true);
204 const std::string filename = make_unique_name(
base_name_,
"mesh", mesh_index) +
".hfmesh";
205 const auto filepath = output_dir_ / filename;
209 std::cerr <<
"Failed to save mesh: " << filepath << std::endl;
221 if (ai_mat->Get(AI_MATKEY_NAME, name) == AI_SUCCESS) {
222 data.name = name.C_Str();
224 data.name = make_unique_name(
base_name_,
"material", material_index);
229 if (ai_mat->Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS)
230 data.diffuse_color = {color.r, color.g, color.b};
231 if (ai_mat->Get(AI_MATKEY_COLOR_AMBIENT, color) == AI_SUCCESS)
232 data.ambient_color = {color.r, color.g, color.b};
233 if (ai_mat->Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS)
234 data.specular_color = {color.r, color.g, color.b};
235 if (ai_mat->Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS)
236 data.emissive_color = {color.r, color.g, color.b};
240 if (ai_mat->Get(AI_MATKEY_OPACITY, value) == AI_SUCCESS)
242 if (ai_mat->Get(AI_MATKEY_SHININESS, value) == AI_SUCCESS)
244 if (ai_mat->Get(AI_MATKEY_METALLIC_FACTOR, value) == AI_SUCCESS)
246 if (ai_mat->Get(AI_MATKEY_ROUGHNESS_FACTOR, value) == AI_SUCCESS)
250 auto try_load_texture = [&](aiTextureType ai_type,
TextureType hf_type,
const char* type_name) {
251 unsigned int count = ai_mat->GetTextureCount(ai_type);
252 if (count == 0)
return;
254 std::cout <<
" Found " << count <<
" texture(s) for type: " << type_name << std::endl;
257 if (ai_mat->GetTexture(ai_type, 0, &tex_path) != AI_SUCCESS)
return;
259 std::cout <<
" -> Path: " << tex_path.C_Str() << std::endl;
261 AssetID tex_asset = process_texture(tex_path.C_Str(), hf_type);
263 data.texture_assets[hf_type] = tex_asset;
267 try_load_texture(aiTextureType_DIFFUSE, TextureType::DIFFUSE,
"DIFFUSE");
268 try_load_texture(aiTextureType_NORMALS, TextureType::NORMAL,
"NORMALS");
269 try_load_texture(aiTextureType_SPECULAR, TextureType::SPECULAR,
"SPECULAR");
270 try_load_texture(aiTextureType_METALNESS, TextureType::METALNESS,
"METALNESS");
271 try_load_texture(aiTextureType_DIFFUSE_ROUGHNESS, TextureType::ROUGHNESS,
"ROUGHNESS");
272 try_load_texture(aiTextureType_AMBIENT_OCCLUSION, TextureType::AMBIENT_OCCLUSION,
"AO");
273 try_load_texture(aiTextureType_EMISSIVE, TextureType::EMISSIVE,
"EMISSIVE");
275 try_load_texture(aiTextureType_BASE_COLOR, TextureType::DIFFUSE,
"BASE_COLOR (glTF)");
276 try_load_texture(aiTextureType_NORMAL_CAMERA, TextureType::NORMAL,
"NORMAL_CAMERA");
279 const std::string filename = data.name +
".hfmat";
280 const auto filepath = output_dir_ / filename;
283 std::cerr <<
"Failed to save material: " << filepath << std::endl;
291 std::filesystem::path resolved_path;
293 if (is_embedded_texture(texture_ref)) {
294 size_t index = std::stoul(texture_ref.substr(1));
297 auto path_opt = resolve_texture_path(texture_ref);
299 std::cerr <<
"Could not resolve texture: " << texture_ref << std::endl;
302 resolved_path = *path_opt;
306 tex_meta
.type = type;
313 if (
const auto existing = registry_.get_uuid_by_path(resolved_path)) {
321 std::optional<std::filesystem::path>
ModelImporter::resolve_texture_path(
const std::string &texture_ref)
const {
322 const std::filesystem::path tex_filename = std::filesystem::path(texture_ref).filename();
324 const std::vector search_paths = {
325 source_dir_ / texture_ref,
326 source_dir_ / tex_filename,
327 source_dir_ /
"textures" / tex_filename,
328 source_dir_ /
"Textures" / tex_filename,
329 source_dir_ /
"materials" / tex_filename,
330 source_dir_.parent_path() /
"textures" / tex_filename,
333 for (
const auto &path: search_paths) {
335 return canonical(path);
343 return !path.empty() && path[0] ==
'*';
347 if (index >= ai_scene_->mNumTextures) {
351 const aiTexture *tex = ai_scene_->mTextures[index];
353 std::string extension = tex->achFormatHint;
354 if (extension.empty()) {
356 const auto *data =
reinterpret_cast<
const unsigned char *>(tex->pcData);
357 if (data[0] == 0xFF && data[1] == 0xD8) extension =
"jpg";
358 else if (data[0] == 0x89 && data[1] == 0x50) extension =
"png";
359 else extension =
"bin";
362 const std::string filename = make_unique_name(
base_name_,
"texture", index)
364 auto filepath = output_dir_ / filename;
366 std::ofstream file(filepath, std::ios::binary);
367 if (!file)
return {};
370 const size_t size = (tex->mHeight == 0)
372 : tex->mWidth * tex->mHeight * 4;
374 file.write(
reinterpret_cast<
const char *>(tex->pcData), size);
381 m.a1, m.b1, m.c1, m.d1,
382 m.a2, m.b2, m.c2, m.d2,
383 m.a3, m.b3, m.c3, m.d3,
384 m.a4, m.b4, m.c4, m.d4
389 constexpr float epsilon = 0.0001f;
390 return std::abs(m.a1 - 1.0f) < epsilon && std::abs(m.b2 - 1.0f) < epsilon
391 && std::abs(m.c3 - 1.0f) < epsilon && std::abs(m.d4 - 1.0f) < epsilon
392 && std::abs(m.a2) < epsilon && std::abs(m.a3) < epsilon && std::abs(m.a4) < epsilon
393 && std::abs(m.b1) < epsilon && std::abs(m.b3) < epsilon && std::abs(m.b4) < epsilon
394 && std::abs(m.c1) < epsilon && std::abs(m.c2) < epsilon && std::abs(m.c4) < epsilon
395 && std::abs(m.d1) < epsilon && std::abs(m.d2) < epsilon && std::abs(m.d3) < epsilon;
398 std::string
ModelImporter::make_unique_name(
const std::string &base,
const std::string &suffix,
399 size_t index)
const {
400 return base +
"_" + suffix +
"_" + std::to_string(index);
Registry for storing assets.
AssetID register_asset(const std::filesystem::path &filepath, AssetType type)
static bool save(const std::filesystem::path &filepath, const MaterialData &material)
static bool save(const std::filesystem::path &filepath, const Mesh &mesh)
Converts external model formats (FBX, GLTF, OBJ) into internal assets this runs once during asset imp...
unsigned int build_import_flags(const ImportSettings &settings) const
std::filesystem::path extract_embedded_texture(size_t index)
ImportedNode convert_node(const aiNode *node) const
ModelImporter(AssetRegistry ®istry, const std::filesystem::path &output_dir)
ImportResult import(const std::filesystem::path &source_path, const ImportSettings &settings={})
AssetID process_mesh(const aiMesh *mesh, size_t mesh_index)
void process_node_hierarchy(const aiNode *node, ImportResult &result, size_t parent_index=SIZE_MAX)
AssetID process_material(const aiMaterial *ai_mat, size_t material_index)
AssetRegistry & registry_
static bool save_metadata(const std::filesystem::path &texture_path, const TextureMetadata &meta)
constexpr AssetID INVALID_ASSET_ID
Complete result of importing a model file.
std::string error_message
Represents an imported mesh with its material binding.
Represents a node in the imported model hierarchy.
Serializable material data (separate from runtime Material class)
Metadata for texture assets.