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"
24 const std::filesystem::path &output_dir) :
registry_(registry),
26 create_directories(output_dir_);
32 source_path_ = source_path;
33 source_dir_ = source_path.parent_path();
36 Assimp::Importer importer;
37 ai_scene_ = importer.ReadFile(source_path.string(), build_import_flags(settings));
39 if (!ai_scene_ || !ai_scene_->mRootNode) {
45 process_node_hierarchy(ai_scene_->mRootNode, result);
54 unsigned int flags = aiProcess_ValidateDataStructure;
56 if (settings.triangulate)
57 flags |= aiProcess_Triangulate;
58 if (settings.generate_normals)
59 flags |= aiProcess_GenSmoothNormals;
60 if (settings.generate_tangents)
61 flags |= aiProcess_CalcTangentSpace;
62 if (settings.flip_uvs)
63 flags |= aiProcess_FlipUVs;
64 if (settings.optimize_meshes)
65 flags |= aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph;
67 flags |= aiProcess_JoinIdenticalVertices;
68 flags |= aiProcess_ImproveCacheLocality;
74 const size_t current_index = result.nodes.size();
75 result.nodes.push_back(convert_node(node));
76 auto &imported_node = result.nodes.back();
79 if (parent_index != SIZE_MAX) {
80 result.nodes[parent_index].child_indices.push_back(current_index);
86 for (
unsigned int i = 0; i < node->mNumMeshes; i++) {
87 const unsigned int mesh_idx = node->mMeshes[i];
88 const aiMesh *ai_mesh = ai_scene_->mMeshes[mesh_idx];
91 imported_mesh
.name = ai_mesh->mName.length > 0
92 ? ai_mesh->mName.C_Str()
93 : make_unique_name(
base_name_,
"mesh", mesh_idx);
96 imported_mesh.mesh_asset = process_mesh(ai_mesh, mesh_idx);
97 result.created_mesh_assets.push_back(imported_mesh
.mesh_asset);
100 if (ai_mesh->mMaterialIndex < ai_scene_->mNumMaterials) {
101 const aiMaterial *material = ai_scene_->mMaterials[ai_mesh->mMaterialIndex];
102 imported_mesh.material_asset = process_material(material, ai_mesh->mMaterialIndex);
105 result.created_material_assets.push_back(imported_mesh
.material_asset);
109 imported_node.mesh_indices.push_back(result.meshes.size());
110 result.meshes.push_back(imported_mesh);
114 for (
unsigned int i = 0; i < node->mNumChildren; i++) {
115 process_node_hierarchy(node->mChildren[i], result, current_index);
122 result
.name = node->mName.length > 0 ? node->mName.C_Str() :
"Node";
124 glm::mat4 transform = convert_matrix(node->mTransformation);
126 glm::vec4 perspective;
129 decompose(transform, result.scale, rotation, result.position, skew, perspective);
130 result.rotation = eulerAngles(rotation);
136 std::vector<Vertex> vertices;
137 std::vector<
unsigned int> indices;
139 vertices.reserve(ai_mesh->mNumVertices);
140 indices.reserve(ai_mesh->mNumFaces * 3);
143 for (
unsigned int i = 0; i < ai_mesh->mNumVertices; i++) {
147 ai_mesh->mVertices[i].x,
148 ai_mesh->mVertices[i].y,
149 ai_mesh->mVertices[i].z
152 if (ai_mesh->HasNormals()) {
154 ai_mesh->mNormals[i].x,
155 ai_mesh->mNormals[i].y,
156 ai_mesh->mNormals[i].z
160 if (ai_mesh->HasTangentsAndBitangents()) {
162 ai_mesh->mTangents[i].x,
163 ai_mesh->mTangents[i].y,
164 ai_mesh->mTangents[i].z
167 ai_mesh->mBitangents[i].x,
168 ai_mesh->mBitangents[i].y,
169 ai_mesh->mBitangents[i].z
173 if (ai_mesh->HasTextureCoords(0)) {
175 ai_mesh->mTextureCoords[0][i].x,
176 ai_mesh->mTextureCoords[0][i].y
180 if (ai_mesh->HasVertexColors(0)) {
182 ai_mesh->mColors[0][i].r,
183 ai_mesh->mColors[0][i].g,
184 ai_mesh->mColors[0][i].b
187 v.color = glm::vec3(1.0f);
190 vertices.push_back(v);
194 for (
unsigned int i = 0; i < ai_mesh->mNumFaces; i++) {
195 const aiFace &face = ai_mesh->mFaces[i];
196 for (
unsigned int j = 0; j < face.mNumIndices; j++) {
197 indices.push_back(face.mIndices[j]);
202 Mesh mesh(vertices, indices,
true);
203 const std::string filename = make_unique_name(
base_name_,
"mesh", mesh_index) +
".hfmesh";
204 const auto filepath = output_dir_ / filename;
208 std::cerr <<
"Failed to save mesh: " << filepath << std::endl;
220 if (ai_mat->Get(AI_MATKEY_NAME, name) == AI_SUCCESS) {
221 data.name = name.C_Str();
223 data.name = make_unique_name(
base_name_,
"material", material_index);
228 if (ai_mat->Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS)
229 data.diffuse_color = {color.r, color.g, color.b};
230 if (ai_mat->Get(AI_MATKEY_COLOR_AMBIENT, color) == AI_SUCCESS)
231 data.ambient_color = {color.r, color.g, color.b};
232 if (ai_mat->Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS)
233 data.specular_color = {color.r, color.g, color.b};
234 if (ai_mat->Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS)
235 data.emissive_color = {color.r, color.g, color.b};
239 if (ai_mat->Get(AI_MATKEY_OPACITY, value) == AI_SUCCESS)
241 if (ai_mat->Get(AI_MATKEY_SHININESS, value) == AI_SUCCESS)
243 if (ai_mat->Get(AI_MATKEY_METALLIC_FACTOR, value) == AI_SUCCESS)
245 if (ai_mat->Get(AI_MATKEY_ROUGHNESS_FACTOR, value) == AI_SUCCESS)
249 auto try_load_texture = [&](aiTextureType ai_type,
TextureType hf_type) {
250 if (ai_mat->GetTextureCount(ai_type) == 0)
return;
253 if (ai_mat->GetTexture(ai_type, 0, &tex_path) != AI_SUCCESS)
return;
255 AssetID tex_asset = process_texture(tex_path.C_Str(), hf_type);
257 data.texture_assets[hf_type] = tex_asset;
261 try_load_texture(aiTextureType_DIFFUSE, TextureType::DIFFUSE);
262 try_load_texture(aiTextureType_NORMALS, TextureType::NORMAL);
263 try_load_texture(aiTextureType_SPECULAR, TextureType::SPECULAR);
264 try_load_texture(aiTextureType_METALNESS, TextureType::METALNESS);
265 try_load_texture(aiTextureType_DIFFUSE_ROUGHNESS, TextureType::ROUGHNESS);
266 try_load_texture(aiTextureType_AMBIENT_OCCLUSION, TextureType::AMBIENT_OCCLUSION);
267 try_load_texture(aiTextureType_EMISSIVE, TextureType::EMISSIVE);
270 const std::string filename = data.name +
".hfmat";
271 const auto filepath = output_dir_ / filename;
274 std::cerr <<
"Failed to save material: " << filepath << std::endl;
282 std::filesystem::path resolved_path;
284 if (is_embedded_texture(texture_ref)) {
285 size_t index = std::stoul(texture_ref.substr(1));
288 auto path_opt = resolve_texture_path(texture_ref);
290 std::cerr <<
"Could not resolve texture: " << texture_ref << std::endl;
293 resolved_path = *path_opt;
297 if (
auto existing = registry_.get_uuid_by_path(resolved_path)) {
305 std::optional<std::filesystem::path>
ModelImporter::resolve_texture_path(
const std::string &texture_ref)
const {
306 const std::filesystem::path tex_filename = std::filesystem::path(texture_ref).filename();
308 const std::vector search_paths = {
309 source_dir_ / texture_ref,
310 source_dir_ / tex_filename,
311 source_dir_ /
"textures" / tex_filename,
312 source_dir_ /
"Textures" / tex_filename,
313 source_dir_ /
"materials" / tex_filename,
314 source_dir_.parent_path() /
"textures" / tex_filename,
317 for (
const auto &path: search_paths) {
319 return canonical(path);
327 return !path.empty() && path[0] ==
'*';
331 if (index >= ai_scene_->mNumTextures) {
335 const aiTexture *tex = ai_scene_->mTextures[index];
337 std::string extension = tex->achFormatHint;
338 if (extension.empty()) {
340 const auto *data =
reinterpret_cast<
const unsigned char *>(tex->pcData);
341 if (data[0] == 0xFF && data[1] == 0xD8) extension =
"jpg";
342 else if (data[0] == 0x89 && data[1] == 0x50) extension =
"png";
343 else extension =
"bin";
346 const std::string filename = make_unique_name(
base_name_,
"texture", index)
348 const auto filepath = output_dir_ / filename;
350 std::ofstream file(filepath, std::ios::binary);
351 if (!file)
return {};
354 const size_t size = (tex->mHeight == 0)
356 : tex->mWidth * tex->mHeight * 4;
358 file.write(
reinterpret_cast<
const char *>(tex->pcData), size);
365 m.a1, m.b1, m.c1, m.d1,
366 m.a2, m.b2, m.c2, m.d2,
367 m.a3, m.b3, m.c3, m.d3,
368 m.a4, m.b4, m.c4, m.d4
373 constexpr float epsilon = 0.0001f;
374 return std::abs(m.a1 - 1.0f) < epsilon && std::abs(m.b2 - 1.0f) < epsilon
375 && std::abs(m.c3 - 1.0f) < epsilon && std::abs(m.d4 - 1.0f) < epsilon
376 && std::abs(m.a2) < epsilon && std::abs(m.a3) < epsilon && std::abs(m.a4) < epsilon
377 && std::abs(m.b1) < epsilon && std::abs(m.b3) < epsilon && std::abs(m.b4) < epsilon
378 && std::abs(m.c1) < epsilon && std::abs(m.c2) < epsilon && std::abs(m.c4) < epsilon
379 && std::abs(m.d1) < epsilon && std::abs(m.d2) < epsilon && std::abs(m.d3) < epsilon;
382 std::string
ModelImporter::make_unique_name(
const std::string &base,
const std::string &suffix,
383 size_t index)
const {
384 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_
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)