1#include "MeshManager.hpp"
5#include "components/ErrorUtils.hpp"
6#include "components/ThreadPool.hpp"
10#include <unordered_set>
19 : m_r_context(context), m_p_resources(resources), m_vfs(vfs) {
20 log(
"MeshManager initialized");
23 MeshManager::~MeshManager() {
24 m_vulkanMeshes.clear();
25 log(
"MeshManager destroyed");
29 log(
"Constructing model: %s...", name.c_str());
31 std::string tempName = name;
34 if (!m_freeModelIds.empty()) {
35 newId = m_freeModelIds.back();
36 m_freeModelIds.pop_back();
38 newId = m_nextModelId++;
39 if (newId >= MAX_MODELS) {
43 meshComponent.id = newId;
44 meshComponent.textureNames.clear();
46 for (
const auto& submesh : meshComponent.meshData.submeshes) {
47 if (!submesh.texturePath.empty()) {
48 meshComponent.textureNames.push_back(submesh.texturePath);
52 log(
"Creating Vulkan mesh for %s", tempName.c_str());
53 if(m_vulkanMeshes.find(meshComponent.meshData.meshPath) == m_vulkanMeshes.end()){
54 m_vulkanMeshes.emplace(meshComponent.meshData.meshPath, std::make_unique<VulkanMesh>(m_r_context));
55 m_vulkanMeshes.at(meshComponent.meshData.meshPath)->upload(meshComponent.meshData);
56 m_vulkanMeshes.at(meshComponent.meshData.meshPath)->addInstance();
58 m_vulkanMeshes.at(meshComponent.meshData.meshPath)->addInstance();
59 log(
"Reusing same mesh model");
63 log(
"Mesh upload successful");
64 }
catch (
const std::exception& e) {
65 log(LogLevel::ERROR,
"Mesh upload failed");
66 m_vulkanMeshes.erase(meshComponent.meshData.meshPath);
82 log(
"Loading mesh data from: %s", realPath.c_str());
84 if (!m_vfs->file_exists(realPath)) {
89 meshData.meshPath = path;
90 }
catch (
const std::exception& e) {
91 log(LogLevel::ERROR,
"Mesh load failed: %s", path.c_str());
95 meshComponent.meshData = std::move(meshData);
97 glm::vec3 min = glm::vec3(FLT_MAX);
98 glm::vec3 max = glm::vec3(-FLT_MAX);
100 for(
auto& submesh : meshComponent.meshData.submeshes) {
101 for(
auto& vertex : submesh.vertices) {
102 min = glm::min(min, vertex.position);
103 max = glm::max(max, vertex.position);
107 glm::vec3 center = (min + max) * 0.5f;
108 float radius = glm::length(max - center);
110 meshComponent.localCenter = center;
111 meshComponent.localRadius = radius;
113 return meshComponent;
117 if (meshComponent.id == UINT32_MAX) {
118 if (!m_freeModelIds.empty()) {
119 meshComponent.id = m_freeModelIds.back();
120 m_freeModelIds.pop_back();
122 meshComponent.id = m_nextModelId++;
126 std::string& installedPath = m_installedPaths[meshComponent.id];
127 std::string requestedPath = meshComponent.meshData.meshPath;
129 if (installedPath != requestedPath) [[unlikely]] {
130 log(
"Swapping mesh %s -> %s for model %d", installedPath.c_str(), requestedPath.c_str(), meshComponent.id);
134 installedPath = requestedPath;
136 bool alreadyExisted = m_vulkanMeshes.count(requestedPath) > 0;
140 if (alreadyExisted && m_vulkanMeshes.count(requestedPath)) {
141 m_vulkanMeshes.at(requestedPath)->addInstance();
147 if (m_vulkanMeshes.count(requestedPath)) {
148 return m_vulkanMeshes.at(requestedPath);
151 static std::unique_ptr<VulkanMesh> nullMesh =
nullptr;
156 const std::string& path = meshComponent.meshData.meshPath;
157 if (m_vulkanMeshes.count(path)) {
158 if (m_meshBoundsCache.count(path)) {
159 auto& bounds = m_meshBoundsCache[path];
160 meshComponent.localCenter = bounds.first;
161 meshComponent.localRadius = bounds.second;
166 if (!(meshComponent.meshData.meshPath ==
"" || meshComponent.meshData.meshPath.empty() || path ==
GetAssetDir())) [[unlikely]] {
168 if (meshComponent.meshData.meshPath.empty() || path ==
GetAssetDir() || !m_vfs->file_exists(
GetAssetPath(path))) {
169 log(LogLevel::WARNING,
"Skipping registration for invalid path: %s", path.c_str());
175 meshComponent.meshData = std::move(loadedAsset.meshData);
176 meshComponent.localCenter = loadedAsset.localCenter;
177 meshComponent.localRadius = loadedAsset.localRadius;
180 m_meshBoundsCache[path] = { loadedAsset.localCenter, loadedAsset.localRadius };
182 meshComponent.textureNames = std::move(loadedAsset.textureNames);
187 meshComponent.textureNames.clear();
188 std::unordered_set<std::string> uniqueTextures;
190 for (
const auto& submesh : meshComponent.meshData.submeshes) {
191 if (!submesh.texturePath.empty()) {
192 uniqueTextures.insert(submesh.texturePath);
193 meshComponent.textureNames.push_back(submesh.texturePath);
197 log(
"Lazy-loading %zu submesh textures for %s", uniqueTextures.size(), path.c_str());
199 if (!uniqueTextures.empty()) {
200 std::vector<std::string> texturesToLoad(uniqueTextures.begin(), uniqueTextures.end());
201 m_p_resources->loadTexturesBatched(texturesToLoad);
202 log(
"Batch Loaded textures");
206 log(
"Initializing Vulkan mesh for: %s", path.c_str());
208 auto newVulkanMesh = std::make_unique<VulkanMesh>(m_r_context);
209 newVulkanMesh->upload(meshComponent.meshData);
210 newVulkanMesh->addInstance();
212 m_vulkanMeshes.emplace(path, std::move(newVulkanMesh));
214 log(
"Successfully registered mesh: %s", path.c_str());
216 }
catch (
const std::exception& e) {
217 log(LogLevel::ERROR,
"Failed to register VulkanMesh: %s", path.c_str());
222 if (pendingComponents.empty())
return;
224 std::vector<std::future<std::pair<MeshComponent*, MeshComponent>>> futures;
227 const std::string path = comp->meshData.meshPath;
228 futures.push_back(GetThreadPool().enqueue([
this, comp, path]() {
229 return std::make_pair(comp, this->
loadMesh(path));
233 std::unordered_set<std::string> uniqueTextures;
235 for (
auto& future : futures) {
236 auto result = future.get();
240 uint32_t originalId = originalComp->id;
241 RenderType originalRenderType = originalComp->renderType;
242 vex::rgba originalColor = originalComp->color;
243 auto originalOverrides = originalComp->textureOverrides;
245 *originalComp = std::move(loadedData);
247 originalComp->id = originalId;
248 originalComp->renderType = originalRenderType;
249 originalComp->color = originalColor;
250 originalComp->textureOverrides = originalOverrides;
252 for (
const auto& texPath : originalComp->textureNames) {
253 if (!texPath.empty()) {
254 uniqueTextures.insert(texPath);
259 if (!uniqueTextures.empty()) {
260 std::vector<std::string> texturesToLoad(uniqueTextures.begin(), uniqueTextures.end());
261 m_p_resources->loadTexturesBatched(texturesToLoad);
265 const std::string& path = comp->meshData.meshPath;
267 if (m_vulkanMeshes.find(path) == m_vulkanMeshes.end()) {
268 auto newVulkanMesh = std::make_unique<VulkanMesh>(m_r_context);
269 newVulkanMesh->upload(comp->meshData);
270 newVulkanMesh->addInstance();
272 m_meshBoundsCache[path] = { comp->localCenter, comp->localRadius };
273 m_vulkanMeshes.emplace(path, std::move(newVulkanMesh));
275 log(
"Async bulk loaded & registered mesh: %s", path.c_str());
277 m_vulkanMeshes[path]->addInstance();
280 comp->forceRefresh();
285 m_freeModelIds.clear();
287 m_vulkanMeshes.clear();
288 m_installedPaths.clear();
289 m_meshBoundsCache.clear();
295 if (!m_freeModelIds.empty()) {
296 meshComponent.id = m_freeModelIds.back();
297 m_freeModelIds.pop_back();
299 meshComponent.id = m_nextModelId++;
302 m_installedPaths[meshComponent.id] = meshComponent.meshData.meshPath;
303 const std::string& path = meshComponent.meshData.meshPath;
305 bool alreadyExisted = m_vulkanMeshes.count(path) > 0;
309 if (alreadyExisted) {
310 m_vulkanMeshes.at(path)->addInstance();
315 if(oldPC.shape == ShapeType::MESH){
326 m_freeModelIds.push_back(meshComponent.id);
328 if (m_vulkanMeshes.count(meshComponent.meshData.meshPath)) {
329 auto& vulkanMesh = m_vulkanMeshes.at(meshComponent.meshData.meshPath);
330 vulkanMesh->removeInstance();
332 log(
"Reference count decreased for: %s (Total: %d)", meshComponent.meshData.meshPath.c_str(), vulkanMesh->getNumOfInstances());
334 if (vulkanMesh->getNumOfInstances() <= 0) {
335 log(
"Cleaning up unused mesh: %s", meshComponent.meshData.meshPath.c_str());
337 for (
const auto& tex : meshComponent.textureNames) {
338 m_p_resources->unloadTexture(tex);
341 m_vulkanMeshes.erase(meshComponent.meshData.meshPath);
347 if (path.empty())
return;
349 if (m_vulkanMeshes.count(path)) {
350 auto& vulkanMesh = m_vulkanMeshes.at(path);
351 vulkanMesh->removeInstance();
353 log(
"Ref count decreased for: %s (Remaining: %d)", path.c_str(), vulkanMesh->getNumOfInstances());
355 if (vulkanMesh->getNumOfInstances() <= 0) {
356 log(
"Cleaning up unused mesh: %s", path.c_str());
358 for (
const auto& tex : ownerComp.textureNames) {
359 m_p_resources->unloadTexture(tex);
361 ownerComp.textureNames.clear();
362 m_vulkanMeshes.erase(path);
368 log(
"Freed model id");
369 m_freeModelIds.push_back(meshComponent.id);
372 log(
"Erased VulkanMesh data");
373 m_vulkanMeshes.erase(meshComponent.meshData.meshPath);
374 for(
size_t i = 0; i < meshComponent.textureNames.size(); i++){
375 m_p_resources->unloadTexture(meshComponent.textureNames[i]);
377 log(
"Unloaded textures");
Contains basic components like transform, camera, name components..
This file defines MeshData struct and all other structs needed for it.
This file defines PhysicsSystem class.
MeshComponent loadMesh(const std::string &path)
Loads mesh from a file, creates vulkan mesh and returns a MeshComponent.
void clearState()
Clears the state of the mesh manager, resetting model IDs and clearing the mesh map.
void registerVulkanMesh(MeshComponent &meshComponent)
Registers a Vulkan mesh component.
void onMeshComponentDestroy(vex::Registry ®istry, vex::Entity entity)
Internally handles the destruction of a mesh component called by entt callbacks.
std::unique_ptr< VulkanMesh > & getVulkanMeshByMesh(MeshComponent &meshComponent)
Retrieves a Vulkan mesh by mesh component.
ModelObject * createModel(const std::string &name, MeshComponent meshComponent, TransformComponent transformComponent, vex::Entity parent)
Creates a model object from a mesh component, transform component, and parent entity.
void destroyModel(std::string &name, MeshComponent meshComponent)
Destroys a model object by name and mesh component.
void onMeshComponentConstruct(vex::Registry ®istry, vex::Entity entity)
Internally handles the construction of a mesh component called by entt callbacks.
void loadMeshesAsync(const std::vector< MeshComponent * > &pendingComponents)
Loads meshes asynchronously from the given paths.
MeshManager(VulkanContext &context, std::unique_ptr< VulkanResources > &resources, VirtualFileSystem *vfs)
Constructor for MeshManager.
void releaseMeshReference(const std::string &path, MeshComponent &ownerComp)
Internally handles the release of a mesh reference called by entt callbacks.
ModelObject class represents a model object in the engine. Thanks to engine separation from the backe...
Central registry for managing entities and their components in the ECS system.
bool has(Entity entity)
Checks if an entity has a component of type T.
T & add_or_replace(Entity entity, Args &&... args)
Adds or replaces a component for an entity.
T & get(Entity entity)
Retrieves a component from an entity.
This class provides abstraction of file system needed for loading packed and unpacked assets.
This files defines global variables defining vulkan limits.
std::string VEX_EXPORT GetAssetDir()
Gets the current asset directory override.
RenderType
Enum class for different types of rendering. It internally switches used pipeline.
uint32_t Entity
Type alias representing a unique entity identifier in the ECS system.
void VEX_EXPORT throw_error(const std::string &msg)
Throws an error or terminates the application.
void VEX_EXPORT log(const char *fmt,...)
Logs a formatted message.
std::string VEX_EXPORT GetAssetPath(const std::string &relativePath)
Resolves the absolute path of an asset based on the current build configuration.
void VEX_EXPORT handle_exception(const std::exception &e)
Handles an exception based on build configuration.
constexpr Entity NULL_ENTITY
Special entity value indicating an invalid or null entity.
Struct containing raw meshData, mesh id, texture paths and material properties. It just template and ...
void forceRefresh()
(used internally by the engine, DO NOT CALL) forces the component to be refreshed.
Mesh data structure for loading and managing mesh data.
void loadFromFile(const std::string &path, VirtualFileSystem *vfs)
Loads mesh data from a file using the Virtual File System.
Structure representing a physics component.
static PhysicsComponent Mesh(MeshComponent &mesh, BodyType bodyType=BodyType::STATIC, float mass=1.0f, float friction=0.5f, float bounce=0.1f)
Creates a mesh-shaped physics component.
Struct holding all vulkan data, like device, surface, swapchain, images, views, and more.
Represents an RGBA color value. Used mainly for fancy rendering in editor.