Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 53 additions & 18 deletions Core/Source/Lux/Asset/AssetImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,77 @@
#include "TextureImporter.h"
#include "SceneImporter.h"
#include "AudioImporter.h"
#include "TextureSerializer.h"
#include "MeshSerializer.h"
#include "MaterialSerializer.h"

#include <map>
#include <memory>

namespace Lux {

using AssetImportFunction = std::function<Ref<Asset>(AssetHandle, const AssetMetadata&)>;
static std::map<AssetType, AssetImportFunction> s_AssetImportFunctions = {
{AssetType::Texture, TextureImporter::ImportTexture },
{ AssetType::Scene, SceneImporter::ImportScene },
{ AssetType::Audio, AudioImporter::ImportAudio },/*
{ AssetType::ObjModel, ObjModelImporter::ImportObjModel },
{ AssetType::ScriptFile, SceneImporter::ImportScript }*/
};
// Serializer-based dispatch table (Hazel-style).
// Each entry owns its AssetSerializer instance.
static std::map<AssetType, std::unique_ptr<AssetSerializer>> s_Serializers;

static void InitSerializers()
{
// Texture
s_Serializers[AssetType::Texture] = std::make_unique<TextureSerializer>();
s_Serializers[AssetType::EnvMap] = std::make_unique<TextureSerializer>();

// Mesh
s_Serializers[AssetType::MeshSource] = std::make_unique<MeshSourceSerializer>();
s_Serializers[AssetType::Mesh] = std::make_unique<MeshSerializer>();
s_Serializers[AssetType::StaticMesh] = std::make_unique<StaticMeshSerializer>();

// Material
s_Serializers[AssetType::Material] = std::make_unique<MaterialSerializer>();
}

Ref<Asset> AssetImporter::ImportAsset(AssetHandle handle, const AssetMetadata& metadata)
{
LUX_PROFILE_FUNCTION_COLOR("AssetImporter::ImportAsset", 0xF2FA8A);

// Lazy-initialise the serializer table once.
static std::once_flag s_InitFlag;
std::call_once(s_InitFlag, InitSerializers);

// ── Serializer-based types ────────────────────────────────────────────
auto serializerIt = s_Serializers.find(metadata.Type);
if (serializerIt != s_Serializers.end())
{
LUX_PROFILE_SCOPE_COLOR("AssetImporter::ImportAsset Scope", 0x27628A);
// Build a metadata copy that carries the handle (in case the
// metadata came in without it already set).
AssetMetadata meta = metadata;
if (meta.Handle == 0)
meta.Handle = handle;

if (s_AssetImportFunctions.find(metadata.Type) == s_AssetImportFunctions.end())
{
LUX_CORE_ERROR("No importer available for asset type: {}", (uint16_t)metadata.Type);
return nullptr;
}
Ref<Asset> asset;
if (serializerIt->second->TryLoadData(meta, asset))
return asset;

LUX_CORE_ERROR("AssetImporter: serializer failed for type {} (handle {})",
(uint16_t)metadata.Type, (uint64_t)handle);
return nullptr;
}

auto& result = s_AssetImportFunctions.at(metadata.Type);//(metadata.Type)(handle, metadata);
// ── Legacy function-pointer importers ─────────────────────────────────
using AssetImportFunction = std::function<Ref<Asset>(AssetHandle, const AssetMetadata&)>;
static const std::map<AssetType, AssetImportFunction> s_LegacyImportFunctions = {
{ AssetType::Scene, SceneImporter::ImportScene },
{ AssetType::Audio, AudioImporter::ImportAudio },
};

auto legacyIt = s_LegacyImportFunctions.find(metadata.Type);
if (legacyIt == s_LegacyImportFunctions.end())
{
LUX_PROFILE_SCOPE_COLOR("AssetImporter::ImportAsset 2 Scope", 0xD1C48A);

return result(handle, metadata);
LUX_CORE_ERROR("AssetImporter: no importer for asset type {} (handle {})",
(uint16_t)metadata.Type, (uint64_t)handle);
return nullptr;
}

return legacyIt->second(handle, metadata);
}

}
3 changes: 3 additions & 0 deletions Core/Source/Lux/Asset/AssetImporter.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#pragma once

#include "AssetMetadata.h"
#include "AssetSerializer.h"

namespace Lux
{
// Routes asset load requests to the appropriate AssetSerializer (or legacy
// importer function) based on the asset type stored in the metadata.
class AssetImporter
{
public:
Expand Down
1 change: 1 addition & 0 deletions Core/Source/Lux/Asset/AssetMetadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Lux
{
struct AssetMetadata
{
AssetHandle Handle = 0;
AssetType Type = AssetType::None;
std::filesystem::path FilePath = "";

Expand Down
22 changes: 22 additions & 0 deletions Core/Source/Lux/Asset/AssetSerializer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include "AssetMetadata.h"

namespace Lux
{
// Abstract interface that every asset type's serializer must satisfy.
// Editor serializers load data from disk (e.g. via YAML / Assimp).
// Runtime serializers read from packed binary streams produced at build time.
class AssetSerializer
{
public:
virtual ~AssetSerializer() = default;

// Serialize the asset back to its source representation (YAML, binary, etc.)
virtual void Serialize(const AssetMetadata& metadata, const Ref<Asset>& asset) const = 0;

// Attempt to load the asset described by metadata.
// Returns true and sets asset on success; returns false on failure.
virtual bool TryLoadData(const AssetMetadata& metadata, Ref<Asset>& asset) const = 0;
};
}
213 changes: 213 additions & 0 deletions Core/Source/Lux/Asset/AssimpMeshImporter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
#include "lpch.h"
#include "AssimpMeshImporter.h"

#include "Lux/Core/Math/AABB.h"
#include "Lux/Renderer/VertexBuffer.h"
#include "Lux/Renderer/IndexBuffer.h"

#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>

#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtc/type_ptr.hpp>

namespace Lux
{
static constexpr uint32_t s_InvalidParentIndex = 0xffffffffu;

static glm::mat4 AssimpMat4ToGlm(const aiMatrix4x4& m)
{
glm::mat4 result;
result[0][0] = m.a1; result[1][0] = m.a2; result[2][0] = m.a3; result[3][0] = m.a4;
result[0][1] = m.b1; result[1][1] = m.b2; result[2][1] = m.b3; result[3][1] = m.b4;
result[0][2] = m.c1; result[1][2] = m.c2; result[2][2] = m.c3; result[3][2] = m.c4;
result[0][3] = m.d1; result[1][3] = m.d2; result[2][3] = m.d3; result[3][3] = m.d4;
return result;
}

AssimpMeshImporter::AssimpMeshImporter(const std::filesystem::path& path)
: m_Path(path)
{
}

void AssimpMeshImporter::TraverseNodes(Ref<MeshSource> meshSource,
aiNode* node,
const glm::mat4& parentTransform,
uint32_t parentIndex)
{
glm::mat4 localTransform = AssimpMat4ToGlm(node->mTransformation);
glm::mat4 worldTransform = parentTransform * localTransform;

MeshNode luxNode;
luxNode.Name = node->mName.C_Str();
luxNode.LocalTransform = localTransform;
luxNode.Parent = parentIndex;

uint32_t nodeIndex = (uint32_t)meshSource->m_Nodes.size();
meshSource->m_Nodes.push_back(luxNode);

if (parentIndex != s_InvalidParentIndex)
meshSource->m_Nodes[parentIndex].Children.push_back(nodeIndex);

auto& currentNode = meshSource->m_Nodes[nodeIndex];

for (uint32_t i = 0; i < node->mNumMeshes; i++)
{
uint32_t submeshIndex = node->mMeshes[i];
currentNode.Submeshes.push_back(submeshIndex);
meshSource->m_Submeshes[submeshIndex].Transform = worldTransform;
meshSource->m_Submeshes[submeshIndex].LocalTransform = localTransform;
meshSource->m_Submeshes[submeshIndex].NodeName = node->mName.C_Str();
}

for (uint32_t i = 0; i < node->mNumChildren; i++)
TraverseNodes(meshSource, node->mChildren[i], worldTransform, nodeIndex);
}

Ref<MeshSource> AssimpMeshImporter::ImportToMeshSource()
{
Ref<MeshSource> meshSource = Ref<MeshSource>::Create();
meshSource->m_FilePath = m_Path.string();

Assimp::Importer importer;
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false);

constexpr uint32_t meshImportFlags =
aiProcess_CalcTangentSpace |
aiProcess_Triangulate |
aiProcess_SortByPType |
aiProcess_GenNormals |
aiProcess_GenUVCoords |
aiProcess_OptimizeMeshes |
aiProcess_JoinIdenticalVertices |
aiProcess_LimitBoneWeights |
aiProcess_ValidateDataStructure |
aiProcess_GlobalScale;

const aiScene* scene = importer.ReadFile(m_Path.string(), meshImportFlags);
if (!scene || !scene->HasMeshes())
{
LUX_CORE_ERROR("AssimpMeshImporter: Failed to import mesh from '{}'", m_Path.string());
LUX_CORE_ERROR(" Assimp error: {}", importer.GetErrorString());
return nullptr;
}

// ── Reserve submeshes ─────────────────────────────────────────────────
meshSource->m_Submeshes.reserve(scene->mNumMeshes);

uint32_t vertexCount = 0;
uint32_t indexCount = 0;

meshSource->m_BoundingBox.Min = { FLT_MAX, FLT_MAX, FLT_MAX };
meshSource->m_BoundingBox.Max = { -FLT_MAX, -FLT_MAX, -FLT_MAX };

for (uint32_t m = 0; m < scene->mNumMeshes; m++)
{
aiMesh* mesh = scene->mMeshes[m];

Submesh& submesh = meshSource->m_Submeshes.emplace_back();
submesh.BaseVertex = vertexCount;
submesh.BaseIndex = indexCount;
submesh.MaterialIndex = mesh->mMaterialIndex;
submesh.IndexCount = mesh->mNumFaces * 3;
submesh.VertexCount = mesh->mNumVertices;
submesh.MeshName = mesh->mName.C_Str();

vertexCount += mesh->mNumVertices;
indexCount += submesh.IndexCount;

// ── AABB per submesh ──────────────────────────────────────────────
AABB& aabb = submesh.BoundingBox;
aabb.Min = { FLT_MAX, FLT_MAX, FLT_MAX };
aabb.Max = { -FLT_MAX, -FLT_MAX, -FLT_MAX };

for (uint32_t v = 0; v < mesh->mNumVertices; v++)
{
Vertex vertex;
vertex.Position = { mesh->mVertices[v].x, mesh->mVertices[v].y, mesh->mVertices[v].z };
vertex.Normal = { mesh->mNormals[v].x, mesh->mNormals[v].y, mesh->mNormals[v].z };

if (mesh->HasTangentsAndBitangents())
{
vertex.Tangent = { mesh->mTangents[v].x, mesh->mTangents[v].y, mesh->mTangents[v].z };
vertex.Binormal = { mesh->mBitangents[v].x, mesh->mBitangents[v].y, mesh->mBitangents[v].z };
}

if (mesh->HasTextureCoords(0))
vertex.Texcoord = { mesh->mTextureCoords[0][v].x, mesh->mTextureCoords[0][v].y };
else
vertex.Texcoord = { 0.0f, 0.0f };

aabb.Min.x = glm::min(vertex.Position.x, aabb.Min.x);
aabb.Min.y = glm::min(vertex.Position.y, aabb.Min.y);
aabb.Min.z = glm::min(vertex.Position.z, aabb.Min.z);
aabb.Max.x = glm::max(vertex.Position.x, aabb.Max.x);
aabb.Max.y = glm::max(vertex.Position.y, aabb.Max.y);
aabb.Max.z = glm::max(vertex.Position.z, aabb.Max.z);

meshSource->m_Vertices.push_back(vertex);
}

meshSource->m_BoundingBox.Min.x = glm::min(aabb.Min.x, meshSource->m_BoundingBox.Min.x);
meshSource->m_BoundingBox.Min.y = glm::min(aabb.Min.y, meshSource->m_BoundingBox.Min.y);
meshSource->m_BoundingBox.Min.z = glm::min(aabb.Min.z, meshSource->m_BoundingBox.Min.z);
meshSource->m_BoundingBox.Max.x = glm::max(aabb.Max.x, meshSource->m_BoundingBox.Max.x);
meshSource->m_BoundingBox.Max.y = glm::max(aabb.Max.y, meshSource->m_BoundingBox.Max.y);
meshSource->m_BoundingBox.Max.z = glm::max(aabb.Max.z, meshSource->m_BoundingBox.Max.z);

// ── Indices ───────────────────────────────────────────────────────
for (uint32_t f = 0; f < mesh->mNumFaces; f++)
{
const aiFace& face = mesh->mFaces[f];
LUX_CORE_ASSERT(face.mNumIndices == 3, "Only triangles are supported!");
Index idx;
idx.V1 = face.mIndices[0];
idx.V2 = face.mIndices[1];
idx.V3 = face.mIndices[2];
meshSource->m_Indices.push_back(idx);
}
}

// ── Node hierarchy ────────────────────────────────────────────────────
// Insert sentinel root so every real node has a valid parentIndex
meshSource->m_Nodes.emplace_back(); // root placeholder
TraverseNodes(meshSource, scene->mRootNode, glm::mat4(1.0f), s_InvalidParentIndex);

// ── Materials (allocate zero-material placeholders) ───────────────────
meshSource->m_Materials.resize(scene->mNumMaterials, 0);

// ── Triangle cache ────────────────────────────────────────────────────
for (uint32_t i = 0; i < (uint32_t)meshSource->m_Submeshes.size(); i++)
{
const Submesh& sm = meshSource->m_Submeshes[i];
for (uint32_t f = 0; f < sm.IndexCount / 3; f++)
{
const Index& idx = meshSource->m_Indices[sm.BaseIndex / 3 + f];
meshSource->m_TriangleCache[i].emplace_back(
meshSource->m_Vertices[sm.BaseVertex + idx.V1],
meshSource->m_Vertices[sm.BaseVertex + idx.V2],
meshSource->m_Vertices[sm.BaseVertex + idx.V3]);
}
}

// ── GPU buffers ───────────────────────────────────────────────────────
meshSource->m_VertexBuffer = VertexBuffer::Create(
Buffer(meshSource->m_Vertices.data(),
(uint32_t)(meshSource->m_Vertices.size() * sizeof(Vertex))));

meshSource->m_IndexBuffer = IndexBuffer::Create(
Buffer(meshSource->m_Indices.data(),
(uint32_t)(meshSource->m_Indices.size() * sizeof(Index))));

LUX_CORE_INFO("AssimpMeshImporter: Loaded '{}' – {} submeshes, {} vertices, {} indices",
m_Path.filename().string(),
meshSource->m_Submeshes.size(),
meshSource->m_Vertices.size(),
meshSource->m_Indices.size() * 3);

return meshSource;
}
}
26 changes: 26 additions & 0 deletions Core/Source/Lux/Asset/AssimpMeshImporter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include "Lux/Core/Base.h"
#include "Lux/Renderer/Mesh.h"

#include <assimp/scene.h>
#include <filesystem>

namespace Lux
{
// Imports a mesh file from disk using the Assimp library and populates
// a MeshSource asset with vertices, indices, submeshes and basic material handles.
class AssimpMeshImporter
{
public:
explicit AssimpMeshImporter(const std::filesystem::path& path);

// Load the mesh source. Returns nullptr on failure.
Ref<MeshSource> ImportToMeshSource();

private:
void TraverseNodes(Ref<MeshSource> meshSource, aiNode* node, const glm::mat4& parentTransform, uint32_t parentIndex);

std::filesystem::path m_Path;
};
}
Loading