|
| 1 | +#include "lpch.h" |
| 2 | +#include "AssimpMeshImporter.h" |
| 3 | + |
| 4 | +#include "Lux/Core/Math/AABB.h" |
| 5 | +#include "Lux/Renderer/VertexBuffer.h" |
| 6 | +#include "Lux/Renderer/IndexBuffer.h" |
| 7 | + |
| 8 | +#include <assimp/Importer.hpp> |
| 9 | +#include <assimp/scene.h> |
| 10 | +#include <assimp/postprocess.h> |
| 11 | + |
| 12 | +#define GLM_ENABLE_EXPERIMENTAL |
| 13 | +#include <glm/glm.hpp> |
| 14 | +#include <glm/gtx/quaternion.hpp> |
| 15 | +#include <glm/gtc/type_ptr.hpp> |
| 16 | + |
| 17 | +namespace Lux |
| 18 | +{ |
| 19 | + static glm::mat4 AssimpMat4ToGlm(const aiMatrix4x4& m) |
| 20 | + { |
| 21 | + glm::mat4 result; |
| 22 | + result[0][0] = m.a1; result[1][0] = m.a2; result[2][0] = m.a3; result[3][0] = m.a4; |
| 23 | + result[0][1] = m.b1; result[1][1] = m.b2; result[2][1] = m.b3; result[3][1] = m.b4; |
| 24 | + result[0][2] = m.c1; result[1][2] = m.c2; result[2][2] = m.c3; result[3][2] = m.c4; |
| 25 | + result[0][3] = m.d1; result[1][3] = m.d2; result[2][3] = m.d3; result[3][3] = m.d4; |
| 26 | + return result; |
| 27 | + } |
| 28 | + |
| 29 | + AssimpMeshImporter::AssimpMeshImporter(const std::filesystem::path& path) |
| 30 | + : m_Path(path) |
| 31 | + { |
| 32 | + } |
| 33 | + |
| 34 | + static void TraverseNodes(Ref<MeshSource> meshSource, |
| 35 | + aiNode* node, |
| 36 | + const glm::mat4& parentTransform, |
| 37 | + uint32_t parentIndex, |
| 38 | + uint32_t level = 0) |
| 39 | + { |
| 40 | + glm::mat4 localTransform = AssimpMat4ToGlm(node->mTransformation); |
| 41 | + glm::mat4 worldTransform = parentTransform * localTransform; |
| 42 | + |
| 43 | + MeshNode luxNode; |
| 44 | + luxNode.Name = node->mName.C_Str(); |
| 45 | + luxNode.LocalTransform = localTransform; |
| 46 | + luxNode.Parent = parentIndex; |
| 47 | + |
| 48 | + uint32_t nodeIndex = (uint32_t)meshSource->m_Nodes.size(); |
| 49 | + meshSource->m_Nodes.push_back(luxNode); |
| 50 | + |
| 51 | + if (parentIndex != 0xffffffff) |
| 52 | + meshSource->m_Nodes[parentIndex].Children.push_back(nodeIndex); |
| 53 | + |
| 54 | + auto& currentNode = meshSource->m_Nodes[nodeIndex]; |
| 55 | + |
| 56 | + for (uint32_t i = 0; i < node->mNumMeshes; i++) |
| 57 | + { |
| 58 | + uint32_t submeshIndex = node->mMeshes[i]; |
| 59 | + currentNode.Submeshes.push_back(submeshIndex); |
| 60 | + meshSource->m_Submeshes[submeshIndex].Transform = worldTransform; |
| 61 | + meshSource->m_Submeshes[submeshIndex].LocalTransform = localTransform; |
| 62 | + meshSource->m_Submeshes[submeshIndex].NodeName = node->mName.C_Str(); |
| 63 | + } |
| 64 | + |
| 65 | + for (uint32_t i = 0; i < node->mNumChildren; i++) |
| 66 | + TraverseNodes(meshSource, node->mChildren[i], worldTransform, nodeIndex, level + 1); |
| 67 | + } |
| 68 | + |
| 69 | + Ref<MeshSource> AssimpMeshImporter::ImportToMeshSource() |
| 70 | + { |
| 71 | + Ref<MeshSource> meshSource = Ref<MeshSource>::Create(); |
| 72 | + meshSource->m_FilePath = m_Path.string(); |
| 73 | + |
| 74 | + Assimp::Importer importer; |
| 75 | + importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false); |
| 76 | + |
| 77 | + constexpr uint32_t meshImportFlags = |
| 78 | + aiProcess_CalcTangentSpace | |
| 79 | + aiProcess_Triangulate | |
| 80 | + aiProcess_SortByPType | |
| 81 | + aiProcess_GenNormals | |
| 82 | + aiProcess_GenUVCoords | |
| 83 | + aiProcess_OptimizeMeshes | |
| 84 | + aiProcess_JoinIdenticalVertices | |
| 85 | + aiProcess_LimitBoneWeights | |
| 86 | + aiProcess_ValidateDataStructure | |
| 87 | + aiProcess_GlobalScale; |
| 88 | + |
| 89 | + const aiScene* scene = importer.ReadFile(m_Path.string(), meshImportFlags); |
| 90 | + if (!scene || !scene->HasMeshes()) |
| 91 | + { |
| 92 | + LUX_CORE_ERROR("AssimpMeshImporter: Failed to import mesh from '{}'", m_Path.string()); |
| 93 | + LUX_CORE_ERROR(" Assimp error: {}", importer.GetErrorString()); |
| 94 | + return nullptr; |
| 95 | + } |
| 96 | + |
| 97 | + // ── Reserve submeshes ───────────────────────────────────────────────── |
| 98 | + meshSource->m_Submeshes.reserve(scene->mNumMeshes); |
| 99 | + |
| 100 | + uint32_t vertexCount = 0; |
| 101 | + uint32_t indexCount = 0; |
| 102 | + |
| 103 | + meshSource->m_BoundingBox.Min = { FLT_MAX, FLT_MAX, FLT_MAX }; |
| 104 | + meshSource->m_BoundingBox.Max = { -FLT_MAX, -FLT_MAX, -FLT_MAX }; |
| 105 | + |
| 106 | + for (uint32_t m = 0; m < scene->mNumMeshes; m++) |
| 107 | + { |
| 108 | + aiMesh* mesh = scene->mMeshes[m]; |
| 109 | + |
| 110 | + Submesh& submesh = meshSource->m_Submeshes.emplace_back(); |
| 111 | + submesh.BaseVertex = vertexCount; |
| 112 | + submesh.BaseIndex = indexCount; |
| 113 | + submesh.MaterialIndex = mesh->mMaterialIndex; |
| 114 | + submesh.IndexCount = mesh->mNumFaces * 3; |
| 115 | + submesh.VertexCount = mesh->mNumVertices; |
| 116 | + submesh.MeshName = mesh->mName.C_Str(); |
| 117 | + |
| 118 | + vertexCount += mesh->mNumVertices; |
| 119 | + indexCount += submesh.IndexCount; |
| 120 | + |
| 121 | + // ── AABB per submesh ────────────────────────────────────────────── |
| 122 | + AABB& aabb = submesh.BoundingBox; |
| 123 | + aabb.Min = { FLT_MAX, FLT_MAX, FLT_MAX }; |
| 124 | + aabb.Max = { -FLT_MAX, -FLT_MAX, -FLT_MAX }; |
| 125 | + |
| 126 | + for (uint32_t v = 0; v < mesh->mNumVertices; v++) |
| 127 | + { |
| 128 | + Vertex vertex; |
| 129 | + vertex.Position = { mesh->mVertices[v].x, mesh->mVertices[v].y, mesh->mVertices[v].z }; |
| 130 | + vertex.Normal = { mesh->mNormals[v].x, mesh->mNormals[v].y, mesh->mNormals[v].z }; |
| 131 | + |
| 132 | + if (mesh->HasTangentsAndBitangents()) |
| 133 | + { |
| 134 | + vertex.Tangent = { mesh->mTangents[v].x, mesh->mTangents[v].y, mesh->mTangents[v].z }; |
| 135 | + vertex.Binormal = { mesh->mBitangents[v].x, mesh->mBitangents[v].y, mesh->mBitangents[v].z }; |
| 136 | + } |
| 137 | + |
| 138 | + if (mesh->HasTextureCoords(0)) |
| 139 | + vertex.Texcoord = { mesh->mTextureCoords[0][v].x, mesh->mTextureCoords[0][v].y }; |
| 140 | + else |
| 141 | + vertex.Texcoord = { 0.0f, 0.0f }; |
| 142 | + |
| 143 | + aabb.Min.x = glm::min(vertex.Position.x, aabb.Min.x); |
| 144 | + aabb.Min.y = glm::min(vertex.Position.y, aabb.Min.y); |
| 145 | + aabb.Min.z = glm::min(vertex.Position.z, aabb.Min.z); |
| 146 | + aabb.Max.x = glm::max(vertex.Position.x, aabb.Max.x); |
| 147 | + aabb.Max.y = glm::max(vertex.Position.y, aabb.Max.y); |
| 148 | + aabb.Max.z = glm::max(vertex.Position.z, aabb.Max.z); |
| 149 | + |
| 150 | + meshSource->m_Vertices.push_back(vertex); |
| 151 | + } |
| 152 | + |
| 153 | + meshSource->m_BoundingBox.Min.x = glm::min(aabb.Min.x, meshSource->m_BoundingBox.Min.x); |
| 154 | + meshSource->m_BoundingBox.Min.y = glm::min(aabb.Min.y, meshSource->m_BoundingBox.Min.y); |
| 155 | + meshSource->m_BoundingBox.Min.z = glm::min(aabb.Min.z, meshSource->m_BoundingBox.Min.z); |
| 156 | + meshSource->m_BoundingBox.Max.x = glm::max(aabb.Max.x, meshSource->m_BoundingBox.Max.x); |
| 157 | + meshSource->m_BoundingBox.Max.y = glm::max(aabb.Max.y, meshSource->m_BoundingBox.Max.y); |
| 158 | + meshSource->m_BoundingBox.Max.z = glm::max(aabb.Max.z, meshSource->m_BoundingBox.Max.z); |
| 159 | + |
| 160 | + // ── Indices ─────────────────────────────────────────────────────── |
| 161 | + for (uint32_t f = 0; f < mesh->mNumFaces; f++) |
| 162 | + { |
| 163 | + const aiFace& face = mesh->mFaces[f]; |
| 164 | + LUX_CORE_ASSERT(face.mNumIndices == 3, "Only triangles are supported!"); |
| 165 | + Index idx; |
| 166 | + idx.V1 = face.mIndices[0]; |
| 167 | + idx.V2 = face.mIndices[1]; |
| 168 | + idx.V3 = face.mIndices[2]; |
| 169 | + meshSource->m_Indices.push_back(idx); |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + // ── Node hierarchy ──────────────────────────────────────────────────── |
| 174 | + // Insert sentinel root so every real node has a valid parentIndex |
| 175 | + meshSource->m_Nodes.emplace_back(); // root placeholder |
| 176 | + TraverseNodes(meshSource, scene->mRootNode, glm::mat4(1.0f), 0xffffffff); |
| 177 | + |
| 178 | + // ── Materials (allocate zero-material placeholders) ─────────────────── |
| 179 | + meshSource->m_Materials.resize(scene->mNumMaterials, 0); |
| 180 | + |
| 181 | + // ── Triangle cache ──────────────────────────────────────────────────── |
| 182 | + for (uint32_t i = 0; i < (uint32_t)meshSource->m_Submeshes.size(); i++) |
| 183 | + { |
| 184 | + const Submesh& sm = meshSource->m_Submeshes[i]; |
| 185 | + for (uint32_t f = 0; f < sm.IndexCount / 3; f++) |
| 186 | + { |
| 187 | + const Index& idx = meshSource->m_Indices[sm.BaseIndex / 3 + f]; |
| 188 | + meshSource->m_TriangleCache[i].emplace_back( |
| 189 | + meshSource->m_Vertices[sm.BaseVertex + idx.V1], |
| 190 | + meshSource->m_Vertices[sm.BaseVertex + idx.V2], |
| 191 | + meshSource->m_Vertices[sm.BaseVertex + idx.V3]); |
| 192 | + } |
| 193 | + } |
| 194 | + |
| 195 | + // ── GPU buffers ─────────────────────────────────────────────────────── |
| 196 | + meshSource->m_VertexBuffer = VertexBuffer::Create( |
| 197 | + Buffer(meshSource->m_Vertices.data(), |
| 198 | + (uint32_t)(meshSource->m_Vertices.size() * sizeof(Vertex)))); |
| 199 | + |
| 200 | + meshSource->m_IndexBuffer = IndexBuffer::Create( |
| 201 | + Buffer(meshSource->m_Indices.data(), |
| 202 | + (uint32_t)(meshSource->m_Indices.size() * sizeof(Index)))); |
| 203 | + |
| 204 | + LUX_CORE_INFO("AssimpMeshImporter: Loaded '{}' – {} submeshes, {} vertices, {} indices", |
| 205 | + m_Path.filename().string(), |
| 206 | + meshSource->m_Submeshes.size(), |
| 207 | + meshSource->m_Vertices.size(), |
| 208 | + meshSource->m_Indices.size() * 3); |
| 209 | + |
| 210 | + return meshSource; |
| 211 | + } |
| 212 | +} |
0 commit comments