Skip to content

Commit b041bdf

Browse files
Copilotsheazywi
andauthored
Implement Hazel-style asset system: serializers, async systems, complete RuntimeAssetManager
Agent-Logs-Url: https://github.com/starbounded-dev/LuxEngine/sessions/a7b976e8-d569-4fb6-889e-44131bcfcc6a Co-authored-by: sheazywi <73042839+sheazywi@users.noreply.github.com>
1 parent 86de67f commit b041bdf

20 files changed

Lines changed: 1111 additions & 24 deletions

Core/Source/Lux/Asset/AssetImporter.cpp

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,77 @@
44
#include "TextureImporter.h"
55
#include "SceneImporter.h"
66
#include "AudioImporter.h"
7+
#include "TextureSerializer.h"
8+
#include "MeshSerializer.h"
9+
#include "MaterialSerializer.h"
710

811
#include <map>
12+
#include <memory>
913

1014
namespace Lux {
1115

12-
using AssetImportFunction = std::function<Ref<Asset>(AssetHandle, const AssetMetadata&)>;
13-
static std::map<AssetType, AssetImportFunction> s_AssetImportFunctions = {
14-
{AssetType::Texture, TextureImporter::ImportTexture },
15-
{ AssetType::Scene, SceneImporter::ImportScene },
16-
{ AssetType::Audio, AudioImporter::ImportAudio },/*
17-
{ AssetType::ObjModel, ObjModelImporter::ImportObjModel },
18-
{ AssetType::ScriptFile, SceneImporter::ImportScript }*/
19-
};
16+
// Serializer-based dispatch table (Hazel-style).
17+
// Each entry owns its AssetSerializer instance.
18+
static std::map<AssetType, std::unique_ptr<AssetSerializer>> s_Serializers;
19+
20+
static void InitSerializers()
21+
{
22+
// Texture
23+
s_Serializers[AssetType::Texture] = std::make_unique<TextureSerializer>();
24+
s_Serializers[AssetType::EnvMap] = std::make_unique<TextureSerializer>();
25+
26+
// Mesh
27+
s_Serializers[AssetType::MeshSource] = std::make_unique<MeshSourceSerializer>();
28+
s_Serializers[AssetType::Mesh] = std::make_unique<MeshSerializer>();
29+
s_Serializers[AssetType::StaticMesh] = std::make_unique<StaticMeshSerializer>();
30+
31+
// Material
32+
s_Serializers[AssetType::Material] = std::make_unique<MaterialSerializer>();
33+
}
2034

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

39+
// Lazy-initialise the serializer table once.
40+
static std::once_flag s_InitFlag;
41+
std::call_once(s_InitFlag, InitSerializers);
42+
43+
// ── Serializer-based types ────────────────────────────────────────────
44+
auto serializerIt = s_Serializers.find(metadata.Type);
45+
if (serializerIt != s_Serializers.end())
2546
{
26-
LUX_PROFILE_SCOPE_COLOR("AssetImporter::ImportAsset Scope", 0x27628A);
47+
// Build a metadata copy that carries the handle (in case the
48+
// metadata came in without it already set).
49+
AssetMetadata meta = metadata;
50+
if (meta.Handle == 0)
51+
meta.Handle = handle;
2752

28-
if (s_AssetImportFunctions.find(metadata.Type) == s_AssetImportFunctions.end())
29-
{
30-
LUX_CORE_ERROR("No importer available for asset type: {}", (uint16_t)metadata.Type);
31-
return nullptr;
32-
}
53+
Ref<Asset> asset;
54+
if (serializerIt->second->TryLoadData(meta, asset))
55+
return asset;
56+
57+
LUX_CORE_ERROR("AssetImporter: serializer failed for type {} (handle {})",
58+
(uint16_t)metadata.Type, (uint64_t)handle);
59+
return nullptr;
3360
}
3461

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

69+
auto legacyIt = s_LegacyImportFunctions.find(metadata.Type);
70+
if (legacyIt == s_LegacyImportFunctions.end())
3771
{
38-
LUX_PROFILE_SCOPE_COLOR("AssetImporter::ImportAsset 2 Scope", 0xD1C48A);
39-
40-
return result(handle, metadata);
72+
LUX_CORE_ERROR("AssetImporter: no importer for asset type {} (handle {})",
73+
(uint16_t)metadata.Type, (uint64_t)handle);
74+
return nullptr;
4175
}
4276

77+
return legacyIt->second(handle, metadata);
4378
}
4479

4580
}

Core/Source/Lux/Asset/AssetImporter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
#pragma once
22

33
#include "AssetMetadata.h"
4+
#include "AssetSerializer.h"
45

56
namespace Lux
67
{
8+
// Routes asset load requests to the appropriate AssetSerializer (or legacy
9+
// importer function) based on the asset type stored in the metadata.
710
class AssetImporter
811
{
912
public:

Core/Source/Lux/Asset/AssetMetadata.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace Lux
88
{
99
struct AssetMetadata
1010
{
11+
AssetHandle Handle = 0;
1112
AssetType Type = AssetType::None;
1213
std::filesystem::path FilePath = "";
1314

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
#include "AssetMetadata.h"
4+
5+
namespace Lux
6+
{
7+
// Abstract interface that every asset type's serializer must satisfy.
8+
// Editor serializers load data from disk (e.g. via YAML / Assimp).
9+
// Runtime serializers read from packed binary streams produced at build time.
10+
class AssetSerializer
11+
{
12+
public:
13+
virtual ~AssetSerializer() = default;
14+
15+
// Serialize the asset back to its source representation (YAML, binary, etc.)
16+
virtual void Serialize(const AssetMetadata& metadata, const Ref<Asset>& asset) const = 0;
17+
18+
// Attempt to load the asset described by metadata.
19+
// Returns true and sets asset on success; returns false on failure.
20+
virtual bool TryLoadData(const AssetMetadata& metadata, Ref<Asset>& asset) const = 0;
21+
};
22+
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
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+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#pragma once
2+
3+
#include "Lux/Core/Base.h"
4+
#include "Lux/Renderer/Mesh.h"
5+
6+
#include <filesystem>
7+
8+
namespace Lux
9+
{
10+
// Imports a mesh file from disk using the Assimp library and populates
11+
// a MeshSource asset with vertices, indices, submeshes and basic material handles.
12+
class AssimpMeshImporter
13+
{
14+
public:
15+
explicit AssimpMeshImporter(const std::filesystem::path& path);
16+
17+
// Load the mesh source. Returns nullptr on failure.
18+
Ref<MeshSource> ImportToMeshSource();
19+
20+
private:
21+
std::filesystem::path m_Path;
22+
};
23+
}

0 commit comments

Comments
 (0)