diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b44b682..e851180 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,6 +39,7 @@ add_executable (vulkan_engine core/config.h core/vk_engine.h core/vk_engine.cpp + core/vk_engine_ui.cpp core/vk_raytracing.h core/vk_raytracing.cpp core/ibl_manager.h @@ -76,6 +77,7 @@ add_executable (vulkan_engine # scene scene/vk_scene.h scene/vk_scene.cpp + scene/vk_scene_picking.cpp scene/vk_loader.h scene/vk_loader.cpp scene/tangent_space.h diff --git a/src/core/asset_manager.cpp b/src/core/asset_manager.cpp index 8e83058..91a3415 100644 --- a/src/core/asset_manager.cpp +++ b/src/core/asset_manager.cpp @@ -131,20 +131,20 @@ std::shared_ptr AssetManager::createMesh(const MeshCreateInfo &info) switch (info.geometry.type) { - case MeshGeometryDesc::Type::Provided: - vertsSpan = info.geometry.vertices; - indsSpan = info.geometry.indices; - break; - case MeshGeometryDesc::Type::Cube: - primitives::buildCube(tmpVerts, tmpInds); - vertsSpan = tmpVerts; - indsSpan = tmpInds; - break; - case MeshGeometryDesc::Type::Sphere: - primitives::buildSphere(tmpVerts, tmpInds, info.geometry.sectors, info.geometry.stacks); - vertsSpan = tmpVerts; - indsSpan = tmpInds; - break; + case MeshGeometryDesc::Type::Provided: + vertsSpan = info.geometry.vertices; + indsSpan = info.geometry.indices; + break; + case MeshGeometryDesc::Type::Cube: + primitives::buildCube(tmpVerts, tmpInds); + vertsSpan = tmpVerts; + indsSpan = tmpInds; + break; + case MeshGeometryDesc::Type::Sphere: + primitives::buildSphere(tmpVerts, tmpInds, info.geometry.sectors, info.geometry.stacks); + vertsSpan = tmpVerts; + indsSpan = tmpInds; + break; } // Ensure tangents exist for primitives (and provided geometry if needed) @@ -153,80 +153,112 @@ std::shared_ptr AssetManager::createMesh(const MeshCreateInfo &info) geom::generate_tangents(tmpVerts, tmpInds); } + std::shared_ptr mesh; + if (info.material.kind == MeshMaterialDesc::Kind::Default) { - return createMesh(info.name, vertsSpan, indsSpan, {}); + mesh = createMesh(info.name, vertsSpan, indsSpan, {}); } - - const auto &opt = info.material.options; - - // Fallbacks are bound now; real textures will patch in via TextureCache - AllocatedBuffer matBuffer = createMaterialBufferWithConstants(opt.constants); - - GLTFMetallic_Roughness::MaterialResources res{}; - res.colorImage = _engine->_errorCheckerboardImage; // visible fallback for albedo - res.colorSampler = _engine->_samplerManager->defaultLinear(); - res.metalRoughImage = _engine->_whiteImage; - res.metalRoughSampler = _engine->_samplerManager->defaultLinear(); - res.normalImage = _engine->_flatNormalImage; - res.normalSampler = _engine->_samplerManager->defaultLinear(); - res.dataBuffer = matBuffer.buffer; - res.dataBufferOffset = 0; - - auto mat = createMaterial(opt.pass, res); - - // Register dynamic texture bindings using the central TextureCache - if (_engine && _engine->_context && _engine->_context->textures) + else { - TextureCache *cache = _engine->_context->textures; - auto buildKey = [&](std::string_view path, bool srgb) -> TextureCache::TextureKey { - TextureCache::TextureKey k{}; - if (!path.empty()) - { - k.kind = TextureCache::TextureKey::SourceKind::FilePath; - k.path = assetPath(path); - k.srgb = srgb; - k.mipmapped = true; - std::string id = std::string("PRIM:") + k.path + (srgb ? "#sRGB" : "#UNORM"); - k.hash = texcache::fnv1a64(id); - } - return k; - }; + const auto &opt = info.material.options; - if (!opt.albedoPath.empty()) + // Fallbacks are bound now; real textures will patch in via TextureCache + AllocatedBuffer matBuffer = createMaterialBufferWithConstants(opt.constants); + + GLTFMetallic_Roughness::MaterialResources res{}; + res.colorImage = _engine->_errorCheckerboardImage; // visible fallback for albedo + res.colorSampler = _engine->_samplerManager->defaultLinear(); + res.metalRoughImage = _engine->_whiteImage; + res.metalRoughSampler = _engine->_samplerManager->defaultLinear(); + res.normalImage = _engine->_flatNormalImage; + res.normalSampler = _engine->_samplerManager->defaultLinear(); + res.dataBuffer = matBuffer.buffer; + res.dataBufferOffset = 0; + + auto mat = createMaterial(opt.pass, res); + + // Register dynamic texture bindings using the central TextureCache + if (_engine && _engine->_context && _engine->_context->textures) { - auto key = buildKey(opt.albedoPath, opt.albedoSRGB); - if (key.hash != 0) + TextureCache *cache = _engine->_context->textures; + auto buildKey = [&](std::string_view path, bool srgb) -> TextureCache::TextureKey { + TextureCache::TextureKey k{}; + if (!path.empty()) + { + k.kind = TextureCache::TextureKey::SourceKind::FilePath; + k.path = assetPath(path); + k.srgb = srgb; + k.mipmapped = true; + std::string id = std::string("PRIM:") + k.path + (srgb ? "#sRGB" : "#UNORM"); + k.hash = texcache::fnv1a64(id); + } + return k; + }; + + if (!opt.albedoPath.empty()) { - VkSampler samp = _engine->_samplerManager->defaultLinear(); - auto handle = cache->request(key, samp); - cache->watchBinding(handle, mat->data.materialSet, 1u, samp, _engine->_errorCheckerboardImage.imageView); + auto key = buildKey(opt.albedoPath, opt.albedoSRGB); + if (key.hash != 0) + { + VkSampler samp = _engine->_samplerManager->defaultLinear(); + auto handle = cache->request(key, samp); + cache->watchBinding(handle, mat->data.materialSet, 1u, samp, _engine->_errorCheckerboardImage.imageView); + } + } + if (!opt.metalRoughPath.empty()) + { + auto key = buildKey(opt.metalRoughPath, opt.metalRoughSRGB); + if (key.hash != 0) + { + VkSampler samp = _engine->_samplerManager->defaultLinear(); + auto handle = cache->request(key, samp); + cache->watchBinding(handle, mat->data.materialSet, 2u, samp, _engine->_whiteImage.imageView); + } + } + if (!opt.normalPath.empty()) + { + auto key = buildKey(opt.normalPath, opt.normalSRGB); + if (key.hash != 0) + { + VkSampler samp = _engine->_samplerManager->defaultLinear(); + auto handle = cache->request(key, samp); + cache->watchBinding(handle, mat->data.materialSet, 3u, samp, _engine->_flatNormalImage.imageView); + } } } - if (!opt.metalRoughPath.empty()) + + mesh = createMesh(info.name, vertsSpan, indsSpan, mat); + _meshMaterialBuffers.emplace(info.name, matBuffer); + } + + if (!mesh) + { + return {}; + } + + // Tag primitive meshes with more appropriate default bounds types for picking, + // then apply any explicit override from MeshCreateInfo. + for (auto &surf : mesh->surfaces) + { + switch (info.geometry.type) { - auto key = buildKey(opt.metalRoughPath, opt.metalRoughSRGB); - if (key.hash != 0) - { - VkSampler samp = _engine->_samplerManager->defaultLinear(); - auto handle = cache->request(key, samp); - cache->watchBinding(handle, mat->data.materialSet, 2u, samp, _engine->_whiteImage.imageView); - } + case MeshGeometryDesc::Type::Sphere: + surf.bounds.type = BoundsType::Sphere; + break; + case MeshGeometryDesc::Type::Cube: + case MeshGeometryDesc::Type::Provided: + default: + surf.bounds.type = BoundsType::Box; + break; } - if (!opt.normalPath.empty()) + + if (info.boundsType.has_value()) { - auto key = buildKey(opt.normalPath, opt.normalSRGB); - if (key.hash != 0) - { - VkSampler samp = _engine->_samplerManager->defaultLinear(); - auto handle = cache->request(key, samp); - cache->watchBinding(handle, mat->data.materialSet, 3u, samp, _engine->_flatNormalImage.imageView); - } + surf.bounds.type = *info.boundsType; } } - auto mesh = createMesh(info.name, vertsSpan, indsSpan, mat); - _meshMaterialBuffers.emplace(info.name, matBuffer); return mesh; } @@ -348,6 +380,7 @@ static Bounds compute_bounds(std::span vertices) b.origin = glm::vec3(0.0f); b.extents = glm::vec3(0.5f); b.sphereRadius = glm::length(b.extents); + b.type = BoundsType::Box; return b; } glm::vec3 minpos = vertices[0].position; @@ -360,6 +393,7 @@ static Bounds compute_bounds(std::span vertices) b.origin = (maxpos + minpos) / 2.f; b.extents = (maxpos - minpos) / 2.f; b.sphereRadius = glm::length(b.extents); + b.type = BoundsType::Box; return b; } diff --git a/src/core/asset_manager.h b/src/core/asset_manager.h index d5f4c78..5fc398c 100644 --- a/src/core/asset_manager.h +++ b/src/core/asset_manager.h @@ -62,6 +62,9 @@ public: std::string name; MeshGeometryDesc geometry; MeshMaterialDesc material; + // Optional override for collision / picking bounds type for this mesh. + // When unset, a reasonable default is chosen based on geometry.type. + std::optional boundsType; }; void init(VulkanEngine *engine); diff --git a/src/core/vk_engine.cpp b/src/core/vk_engine.cpp index e9af8ae..2c41586 100644 --- a/src/core/vk_engine.cpp +++ b/src/core/vk_engine.cpp @@ -49,8 +49,6 @@ #include "core/texture_cache.h" #include "core/ibl_manager.h" -// Query a conservative streaming texture budget based on VMA-reported -// device-local heap budgets. Uses ~35% of total device-local budget. static size_t query_texture_budget_bytes(DeviceManager* dev) { if (!dev) return 512ull * 1024ull * 1024ull; // fallback @@ -90,6 +88,7 @@ static size_t query_texture_budget_bytes(DeviceManager* dev) // // ImGui helpers: keep UI code tidy and grouped in small functions. // These render inside a single consolidated Debug window using tab items. +// (Original definitions are now compiled out; see core/vk_engine_ui.cpp.) // namespace { // Background / compute playground @@ -142,7 +141,7 @@ namespace { const glm::vec3 pos = origin + glm::vec3(ix*spacing, 0.5f, iy*spacing); glm::mat4 M = glm::translate(glm::mat4(1.0f), pos); - eng->_sceneManager->addMeshInstance(base+".inst", mesh, M); + eng->_sceneManager->addMeshInstance(base+".inst", mesh, M, BoundsType::Sphere); eng->_iblTestNames.push_back(base+".inst"); eng->_iblTestNames.push_back(base+".mesh"); eng->_iblTestNames.push_back(base+".mat"); @@ -156,7 +155,7 @@ namespace { auto mesh = eng->_assetManager->createMesh("ibltest.chrome.mesh", std::span(verts.data(), verts.size()), std::span(inds.data(), inds.size()), mat); glm::mat4 M = glm::translate(glm::mat4(1.0f), origin + glm::vec3(5.5f, 0.5f, 0.0f)); - eng->_sceneManager->addMeshInstance("ibltest.chrome.inst", mesh, M); + eng->_sceneManager->addMeshInstance("ibltest.chrome.inst", mesh, M, BoundsType::Sphere); eng->_iblTestNames.insert(eng->_iblTestNames.end(), {"ibltest.chrome.inst","ibltest.chrome.mesh","ibltest.chrome.mat"}); } { @@ -165,7 +164,7 @@ namespace { auto mesh = eng->_assetManager->createMesh("ibltest.glass.mesh", std::span(verts.data(), verts.size()), std::span(inds.data(), inds.size()), mat); glm::mat4 M = glm::translate(glm::mat4(1.0f), origin + glm::vec3(5.5f, 0.5f, 2.0f)); - eng->_sceneManager->addMeshInstance("ibltest.glass.inst", mesh, M); + eng->_sceneManager->addMeshInstance("ibltest.glass.inst", mesh, M, BoundsType::Sphere); eng->_iblTestNames.insert(eng->_iblTestNames.end(), {"ibltest.glass.inst","ibltest.glass.mesh","ibltest.glass.mat"}); } } @@ -619,6 +618,43 @@ static void dump_vma_json(DeviceManager* dev, const char* tag) } } +size_t VulkanEngine::query_texture_budget_bytes() const +{ + DeviceManager *dev = _deviceManager.get(); + if (!dev) return 512ull * 1024ull * 1024ull; // fallback + VmaAllocator alloc = dev->allocator(); + if (!alloc) return 512ull * 1024ull * 1024ull; + + const VkPhysicalDeviceMemoryProperties *memProps = nullptr; + vmaGetMemoryProperties(alloc, &memProps); + if (!memProps) return 512ull * 1024ull * 1024ull; + + VmaBudget budgets[VK_MAX_MEMORY_HEAPS] = {}; + vmaGetHeapBudgets(alloc, budgets); + + unsigned long long totalBudget = 0; + unsigned long long totalUsage = 0; + for (uint32_t i = 0; i < memProps->memoryHeapCount; ++i) + { + if (memProps->memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) + { + totalBudget += budgets[i].budget; + totalUsage += budgets[i].usage; + } + } + if (totalBudget == 0) return 512ull * 1024ull * 1024ull; + + // Reserve ~65% of VRAM for attachments, swapchain, meshes, AS, etc. + unsigned long long cap = static_cast(double(totalBudget) * 0.35); + + // If usage is already near the cap, still allow current textures to live; eviction will trim. + // Clamp to at least 128 MB, at most totalBudget. + unsigned long long minCap = 128ull * 1024ull * 1024ull; + if (cap < minCap) cap = minCap; + if (cap > totalBudget) cap = totalBudget; + return static_cast(cap); +} + void VulkanEngine::init() { // We initialize SDL and create a window with it. @@ -814,7 +850,8 @@ void VulkanEngine::init_default_data() _sceneManager->addMeshInstance("default.cube", cubeMesh, glm::translate(glm::mat4(1.f), glm::vec3(-2.f, 0.f, -2.f))); _sceneManager->addMeshInstance("default.sphere", sphereMesh, - glm::translate(glm::mat4(1.f), glm::vec3(2.f, 0.f, -2.f))); + glm::translate(glm::mat4(1.f), glm::vec3(2.f, 0.f, -2.f)), + BoundsType::Sphere); } _mainDeletionQueue.push_function([&]() { @@ -1069,7 +1106,7 @@ void VulkanEngine::draw() // Prior to building passes, pump texture loads for this frame. if (_textureCache) { - size_t budget = query_texture_budget_bytes(_deviceManager.get()); + size_t budget = query_texture_budget_bytes(); _textureCache->set_gpu_budget_bytes(budget); _textureCache->evictToBudget(budget); _textureCache->pumpLoads(*_resourceManager, get_current_frame()); @@ -1375,8 +1412,8 @@ void VulkanEngine::run() if (ImGui::Begin("Debug")) { const ImGuiTabBarFlags tf = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs; - if (ImGui::BeginTabBar("DebugTabs", tf)) - { + if (ImGui::BeginTabBar("DebugTabs", tf)) + { if (ImGui::BeginTabItem("Overview")) { ui_overview(this); @@ -1412,20 +1449,21 @@ void VulkanEngine::run() ui_postfx(this); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Scene")) - { - ui_scene(this); - ImGui::EndTabItem(); + if (ImGui::BeginTabItem("Scene")) + { + ui_scene(this); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Textures")) + { + ui_textures(this); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); } - if (ImGui::BeginTabItem("Textures")) - { - ui_textures(this); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - } ImGui::End(); } + ImGui::Render(); draw(); diff --git a/src/core/vk_engine.h b/src/core/vk_engine.h index 7db1b82..27b6fd9 100644 --- a/src/core/vk_engine.h +++ b/src/core/vk_engine.h @@ -167,6 +167,9 @@ public: //run main loop void run(); + // Query a conservative streaming texture budget for the texture cache. + size_t query_texture_budget_bytes() const; + bool resize_requested{false}; bool freeze_rendering{false}; diff --git a/src/scene/vk_loader.cpp b/src/scene/vk_loader.cpp index e8485bc..8c45481 100644 --- a/src/scene/vk_loader.cpp +++ b/src/scene/vk_loader.cpp @@ -592,12 +592,14 @@ std::optional > loadGltf(VulkanEngine *engine, std:: newSurface.bounds.origin = (maxpos + minpos) / 2.f; newSurface.bounds.extents = (maxpos - minpos) / 2.f; newSurface.bounds.sphereRadius = glm::length(newSurface.bounds.extents); + newSurface.bounds.type = BoundsType::Box; } else { newSurface.bounds.origin = glm::vec3(0.0f); newSurface.bounds.extents = glm::vec3(0.5f); newSurface.bounds.sphereRadius = glm::length(newSurface.bounds.extents); + newSurface.bounds.type = BoundsType::Box; } newmesh->surfaces.push_back(newSurface); } diff --git a/src/scene/vk_loader.h b/src/scene/vk_loader.h index c9c0d39..d11970f 100644 --- a/src/scene/vk_loader.h +++ b/src/scene/vk_loader.h @@ -11,11 +11,25 @@ class VulkanEngine; +// Basic collision / selection shape associated with a render surface. +// origin/extents are always the local-space AABB center and half-size; +// sphereRadius is a conservative bounding-sphere radius in local space. +// type controls how precise ray tests should be. +enum class BoundsType : uint8_t +{ + None = 0, // not pickable + Box, // oriented box using origin/extents (default) + Sphere, // sphere using origin + sphereRadius + Capsule, // capsule aligned with local Y (derived from extents) + Mesh // full mesh (BVH / ray query); currently falls back to Box +}; + struct Bounds { glm::vec3 origin; float sphereRadius; glm::vec3 extents; + BoundsType type = BoundsType::Box; }; struct GLTFMaterial diff --git a/src/scene/vk_scene.cpp b/src/scene/vk_scene.cpp index eb1e126..bb3b2b6 100644 --- a/src/scene/vk_scene.cpp +++ b/src/scene/vk_scene.cpp @@ -16,180 +16,6 @@ #include #include "core/config.h" -namespace -{ - // Quick conservative ray / bounding-sphere test in world space. - // Returns false when the ray misses the sphere; on hit, outT is the - // closest positive intersection distance along the ray direction. - bool intersect_ray_sphere(const glm::vec3 &rayOrigin, - const glm::vec3 &rayDir, - const Bounds &bounds, - const glm::mat4 &worldTransform, - float &outT) - { - // Sphere center is bounds.origin transformed to world. - glm::vec3 centerWorld = glm::vec3(worldTransform * glm::vec4(bounds.origin, 1.0f)); - - // Approximate world-space radius by scaling with the maximum axis scale. - glm::vec3 sx = glm::vec3(worldTransform[0]); - glm::vec3 sy = glm::vec3(worldTransform[1]); - glm::vec3 sz = glm::vec3(worldTransform[2]); - float maxScale = std::max({glm::length(sx), glm::length(sy), glm::length(sz)}); - float radiusWorld = bounds.sphereRadius * maxScale; - if (radiusWorld <= 0.0f) - { - return false; - } - - glm::vec3 oc = rayOrigin - centerWorld; - float b = glm::dot(oc, rayDir); - float c = glm::dot(oc, oc) - radiusWorld * radiusWorld; - float disc = b * b - c; - if (disc < 0.0f) - { - return false; - } - float s = std::sqrt(disc); - float t0 = -b - s; - float t1 = -b + s; - float t = t0 >= 0.0f ? t0 : t1; - if (t < 0.0f) - { - return false; - } - outT = t; - return true; - } - - // Ray / oriented-bounds intersection in world space using object-local AABB. - // Uses a quick sphere test first; on success refines with OBB slabs. - // Returns true when hit; outWorldHit is the closest hit point in world space. - bool intersect_ray_bounds(const glm::vec3 &rayOrigin, - const glm::vec3 &rayDir, - const Bounds &bounds, - const glm::mat4 &worldTransform, - glm::vec3 &outWorldHit) - { - if (glm::length2(rayDir) < 1e-8f) - { - return false; - } - - // Early reject using bounding sphere in world space. - float sphereT = 0.0f; - if (!intersect_ray_sphere(rayOrigin, rayDir, bounds, worldTransform, sphereT)) - { - return false; - } - - // Transform ray into local space of the bounds for precise box test. - glm::mat4 invM = glm::inverse(worldTransform); - glm::vec3 localOrigin = glm::vec3(invM * glm::vec4(rayOrigin, 1.0f)); - glm::vec3 localDir = glm::vec3(invM * glm::vec4(rayDir, 0.0f)); - - if (glm::length2(localDir) < 1e-8f) - { - return false; - } - localDir = glm::normalize(localDir); - - glm::vec3 minB = bounds.origin - bounds.extents; - glm::vec3 maxB = bounds.origin + bounds.extents; - - float tMin = 0.0f; - float tMax = std::numeric_limits::max(); - - for (int axis = 0; axis < 3; ++axis) - { - float o = localOrigin[axis]; - float d = localDir[axis]; - if (std::abs(d) < 1e-8f) - { - // Ray parallel to slab: must be inside to intersect. - if (o < minB[axis] || o > maxB[axis]) - { - return false; - } - } - else - { - float invD = 1.0f / d; - float t1 = (minB[axis] - o) * invD; - float t2 = (maxB[axis] - o) * invD; - if (t1 > t2) - { - std::swap(t1, t2); - } - - tMin = std::max(tMin, t1); - tMax = std::min(tMax, t2); - - if (tMax < tMin) - { - return false; - } - } - } - - if (tMax < 0.0f) - { - return false; - } - - float tHit = (tMin >= 0.0f) ? tMin : tMax; - glm::vec3 localHit = localOrigin + tHit * localDir; - glm::vec3 worldHit = glm::vec3(worldTransform * glm::vec4(localHit, 1.0f)); - - if (glm::dot(worldHit - rayOrigin, rayDir) <= 0.0f) - { - return false; - } - - outWorldHit = worldHit; - return true; - } - - // Test whether the clip-space box corners of an object intersect a 2D NDC rectangle. - // ndcMin/ndcMax are in [-1,1]x[-1,1]. Returns true if any visible corner projects inside. - bool box_overlaps_ndc_rect(const RenderObject &obj, - const glm::mat4 &viewproj, - const glm::vec2 &ndcMin, - const glm::vec2 &ndcMax) - { - const glm::vec3 o = obj.bounds.origin; - const glm::vec3 e = obj.bounds.extents; - const glm::mat4 m = viewproj * obj.transform; // world -> clip - - const std::array corners{ - glm::vec3{+1, +1, +1}, glm::vec3{+1, +1, -1}, glm::vec3{+1, -1, +1}, glm::vec3{+1, -1, -1}, - glm::vec3{-1, +1, +1}, glm::vec3{-1, +1, -1}, glm::vec3{-1, -1, +1}, glm::vec3{-1, -1, -1}, - }; - - for (const glm::vec3 &c : corners) - { - glm::vec3 pLocal = o + c * e; - glm::vec4 clip = m * glm::vec4(pLocal, 1.f); - if (clip.w <= 0.0f) - { - continue; - } - float x = clip.x / clip.w; - float y = clip.y / clip.w; - float z = clip.z / clip.w; // Vulkan Z0: 0..1 - if (z < 0.0f || z > 1.0f) - { - continue; - } - if (x >= ndcMin.x && x <= ndcMax.x && - y >= ndcMin.y && y <= ndcMax.y) - { - return true; - } - } - return false; - } -} // namespace - void SceneManager::init(EngineContext *context) { _context = context; @@ -289,6 +115,10 @@ void SceneManager::update_scene() obj.vertexBufferAddress = inst.mesh->meshBuffers.vertexBufferAddress; obj.material = &surf.material->data; obj.bounds = surf.bounds; + if (inst.boundsTypeOverride.has_value()) + { + obj.bounds.type = *inst.boundsTypeOverride; + } obj.transform = inst.transform; obj.sourceMesh = inst.mesh.get(); obj.surfaceIndex = surfaceIndex++; @@ -409,159 +239,6 @@ void SceneManager::update_scene() stats.scene_update_time = elapsed.count() / 1000.f; } -bool SceneManager::pick(const glm::vec2 &mousePosPixels, RenderObject &outObject, glm::vec3 &outWorldPos) -{ - if (_context == nullptr) - { - return false; - } - - SwapchainManager *swapchain = _context->getSwapchain(); - if (swapchain == nullptr) - { - return false; - } - - VkExtent2D extent = swapchain->windowExtent(); - if (extent.width == 0 || extent.height == 0) - { - return false; - } - - float width = static_cast(extent.width); - float height = static_cast(extent.height); - - // Convert from window coordinates (top-left origin) to NDC in [-1, 1]. - float ndcX = (2.0f * mousePosPixels.x / width) - 1.0f; - float ndcY = 1.0f - (2.0f * mousePosPixels.y / height); - - float fovRad = glm::radians(mainCamera.fovDegrees); - float tanHalfFov = std::tan(fovRad * 0.5f); - float aspect = width / height; - - // Build ray in camera space using -Z forward convention. - glm::vec3 dirCamera(ndcX * aspect * tanHalfFov, - ndcY * tanHalfFov, - -1.0f); - dirCamera = glm::normalize(dirCamera); - - glm::vec3 rayOrigin = mainCamera.position; - glm::mat4 camRotation = mainCamera.getRotationMatrix(); - glm::vec3 rayDir = glm::normalize(glm::vec3(camRotation * glm::vec4(dirCamera, 0.0f))); - - bool anyHit = false; - float bestDist2 = std::numeric_limits::max(); - glm::vec3 bestHitPos{}; - - auto testList = [&](const std::vector &list) - { - for (const RenderObject &obj: list) - { - glm::vec3 hitPos{}; - if (!intersect_ray_bounds(rayOrigin, rayDir, obj.bounds, obj.transform, hitPos)) - { - continue; - } - - float d2 = glm::length2(hitPos - rayOrigin); - if (d2 < bestDist2) - { - bestDist2 = d2; - bestHitPos = hitPos; - outObject = obj; - anyHit = true; - } - } - }; - - testList(mainDrawContext.OpaqueSurfaces); - testList(mainDrawContext.TransparentSurfaces); - - if (anyHit) - { - outWorldPos = bestHitPos; - } - - return anyHit; -} - -bool SceneManager::resolveObjectID(uint32_t id, RenderObject &outObject) const -{ - if (id == 0) - { - return false; - } - - auto findIn = [&](const std::vector &list) -> bool - { - for (const RenderObject &obj : list) - { - if (obj.objectID == id) - { - outObject = obj; - return true; - } - } - return false; - }; - - if (findIn(mainDrawContext.OpaqueSurfaces)) - { - return true; - } - if (findIn(mainDrawContext.TransparentSurfaces)) - { - return true; - } - return false; -} - -void SceneManager::selectRect(const glm::vec2 &p0, const glm::vec2 &p1, std::vector &outObjects) const -{ - if (!_context || !_context->getSwapchain()) - { - return; - } - - VkExtent2D extent = _context->getSwapchain()->windowExtent(); - if (extent.width == 0 || extent.height == 0) - { - return; - } - - float width = static_cast(extent.width); - float height = static_cast(extent.height); - - // Convert from window coordinates (top-left origin) to NDC in [-1, 1]. - auto toNdc = [&](const glm::vec2 &p) -> glm::vec2 - { - float ndcX = (2.0f * p.x / width) - 1.0f; - float ndcY = 1.0f - (2.0f * p.y / height); - return glm::vec2{ndcX, ndcY}; - }; - - glm::vec2 ndc0 = toNdc(p0); - glm::vec2 ndc1 = toNdc(p1); - glm::vec2 ndcMin = glm::min(ndc0, ndc1); - glm::vec2 ndcMax = glm::max(ndc0, ndc1); - - const glm::mat4 vp = sceneData.viewproj; - - auto testList = [&](const std::vector &list) - { - for (const RenderObject &obj : list) - { - if (box_overlaps_ndc_rect(obj, vp, ndcMin, ndcMax)) - { - outObjects.push_back(obj); - } - } - }; - - testList(mainDrawContext.OpaqueSurfaces); - testList(mainDrawContext.TransparentSurfaces); -} - void SceneManager::loadScene(const std::string &name, std::shared_ptr scene) { if (scene) @@ -590,10 +267,15 @@ void SceneManager::cleanup() loadedNodes.clear(); } -void SceneManager::addMeshInstance(const std::string &name, std::shared_ptr mesh, const glm::mat4 &transform) +void SceneManager::addMeshInstance(const std::string &name, std::shared_ptr mesh, + const glm::mat4 &transform, std::optional boundsType) { if (!mesh) return; - dynamicMeshInstances[name] = MeshInstance{std::move(mesh), transform}; + MeshInstance inst{}; + inst.mesh = std::move(mesh); + inst.transform = transform; + inst.boundsTypeOverride = boundsType; + dynamicMeshInstances[name] = std::move(inst); } bool SceneManager::removeMeshInstance(const std::string &name) diff --git a/src/scene/vk_scene.h b/src/scene/vk_scene.h index f2b1301..581731f 100644 --- a/src/scene/vk_scene.h +++ b/src/scene/vk_scene.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "scene/vk_loader.h" @@ -75,10 +76,12 @@ public: { std::shared_ptr mesh; glm::mat4 transform{1.f}; + std::optional boundsTypeOverride; }; void addMeshInstance(const std::string &name, std::shared_ptr mesh, - const glm::mat4 &transform = glm::mat4(1.f)); + const glm::mat4 &transform = glm::mat4(1.f), + std::optional boundsType = {}); bool removeMeshInstance(const std::string &name); void clearMeshInstances();