ADD: Bounding, Some error

This commit is contained in:
2025-11-18 13:53:02 +09:00
parent 94ba704f99
commit 6e0e86b683
9 changed files with 204 additions and 423 deletions

View File

@@ -39,6 +39,7 @@ add_executable (vulkan_engine
core/config.h core/config.h
core/vk_engine.h core/vk_engine.h
core/vk_engine.cpp core/vk_engine.cpp
core/vk_engine_ui.cpp
core/vk_raytracing.h core/vk_raytracing.h
core/vk_raytracing.cpp core/vk_raytracing.cpp
core/ibl_manager.h core/ibl_manager.h
@@ -76,6 +77,7 @@ add_executable (vulkan_engine
# scene # scene
scene/vk_scene.h scene/vk_scene.h
scene/vk_scene.cpp scene/vk_scene.cpp
scene/vk_scene_picking.cpp
scene/vk_loader.h scene/vk_loader.h
scene/vk_loader.cpp scene/vk_loader.cpp
scene/tangent_space.h scene/tangent_space.h

View File

@@ -131,20 +131,20 @@ std::shared_ptr<MeshAsset> AssetManager::createMesh(const MeshCreateInfo &info)
switch (info.geometry.type) switch (info.geometry.type)
{ {
case MeshGeometryDesc::Type::Provided: case MeshGeometryDesc::Type::Provided:
vertsSpan = info.geometry.vertices; vertsSpan = info.geometry.vertices;
indsSpan = info.geometry.indices; indsSpan = info.geometry.indices;
break; break;
case MeshGeometryDesc::Type::Cube: case MeshGeometryDesc::Type::Cube:
primitives::buildCube(tmpVerts, tmpInds); primitives::buildCube(tmpVerts, tmpInds);
vertsSpan = tmpVerts; vertsSpan = tmpVerts;
indsSpan = tmpInds; indsSpan = tmpInds;
break; break;
case MeshGeometryDesc::Type::Sphere: case MeshGeometryDesc::Type::Sphere:
primitives::buildSphere(tmpVerts, tmpInds, info.geometry.sectors, info.geometry.stacks); primitives::buildSphere(tmpVerts, tmpInds, info.geometry.sectors, info.geometry.stacks);
vertsSpan = tmpVerts; vertsSpan = tmpVerts;
indsSpan = tmpInds; indsSpan = tmpInds;
break; break;
} }
// Ensure tangents exist for primitives (and provided geometry if needed) // Ensure tangents exist for primitives (and provided geometry if needed)
@@ -153,80 +153,112 @@ std::shared_ptr<MeshAsset> AssetManager::createMesh(const MeshCreateInfo &info)
geom::generate_tangents(tmpVerts, tmpInds); geom::generate_tangents(tmpVerts, tmpInds);
} }
std::shared_ptr<MeshAsset> mesh;
if (info.material.kind == MeshMaterialDesc::Kind::Default) if (info.material.kind == MeshMaterialDesc::Kind::Default)
{ {
return createMesh(info.name, vertsSpan, indsSpan, {}); mesh = createMesh(info.name, vertsSpan, indsSpan, {});
} }
else
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)
{ {
TextureCache *cache = _engine->_context->textures; const auto &opt = info.material.options;
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()) // 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); TextureCache *cache = _engine->_context->textures;
if (key.hash != 0) 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 key = buildKey(opt.albedoPath, opt.albedoSRGB);
auto handle = cache->request(key, samp); if (key.hash != 0)
cache->watchBinding(handle, mat->data.materialSet, 1u, samp, _engine->_errorCheckerboardImage.imageView); {
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); case MeshGeometryDesc::Type::Sphere:
if (key.hash != 0) surf.bounds.type = BoundsType::Sphere;
{ break;
VkSampler samp = _engine->_samplerManager->defaultLinear(); case MeshGeometryDesc::Type::Cube:
auto handle = cache->request(key, samp); case MeshGeometryDesc::Type::Provided:
cache->watchBinding(handle, mat->data.materialSet, 2u, samp, _engine->_whiteImage.imageView); default:
} surf.bounds.type = BoundsType::Box;
break;
} }
if (!opt.normalPath.empty())
if (info.boundsType.has_value())
{ {
auto key = buildKey(opt.normalPath, opt.normalSRGB); surf.bounds.type = *info.boundsType;
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);
}
} }
} }
auto mesh = createMesh(info.name, vertsSpan, indsSpan, mat);
_meshMaterialBuffers.emplace(info.name, matBuffer);
return mesh; return mesh;
} }
@@ -348,6 +380,7 @@ static Bounds compute_bounds(std::span<Vertex> vertices)
b.origin = glm::vec3(0.0f); b.origin = glm::vec3(0.0f);
b.extents = glm::vec3(0.5f); b.extents = glm::vec3(0.5f);
b.sphereRadius = glm::length(b.extents); b.sphereRadius = glm::length(b.extents);
b.type = BoundsType::Box;
return b; return b;
} }
glm::vec3 minpos = vertices[0].position; glm::vec3 minpos = vertices[0].position;
@@ -360,6 +393,7 @@ static Bounds compute_bounds(std::span<Vertex> vertices)
b.origin = (maxpos + minpos) / 2.f; b.origin = (maxpos + minpos) / 2.f;
b.extents = (maxpos - minpos) / 2.f; b.extents = (maxpos - minpos) / 2.f;
b.sphereRadius = glm::length(b.extents); b.sphereRadius = glm::length(b.extents);
b.type = BoundsType::Box;
return b; return b;
} }

View File

@@ -62,6 +62,9 @@ public:
std::string name; std::string name;
MeshGeometryDesc geometry; MeshGeometryDesc geometry;
MeshMaterialDesc material; 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> boundsType;
}; };
void init(VulkanEngine *engine); void init(VulkanEngine *engine);

View File

@@ -49,8 +49,6 @@
#include "core/texture_cache.h" #include "core/texture_cache.h"
#include "core/ibl_manager.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) static size_t query_texture_budget_bytes(DeviceManager* dev)
{ {
if (!dev) return 512ull * 1024ull * 1024ull; // fallback 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. // ImGui helpers: keep UI code tidy and grouped in small functions.
// These render inside a single consolidated Debug window using tab items. // These render inside a single consolidated Debug window using tab items.
// (Original definitions are now compiled out; see core/vk_engine_ui.cpp.)
// //
namespace { namespace {
// Background / compute playground // Background / compute playground
@@ -142,7 +141,7 @@ namespace {
const glm::vec3 pos = origin + glm::vec3(ix*spacing, 0.5f, iy*spacing); const glm::vec3 pos = origin + glm::vec3(ix*spacing, 0.5f, iy*spacing);
glm::mat4 M = glm::translate(glm::mat4(1.0f), pos); 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+".inst");
eng->_iblTestNames.push_back(base+".mesh"); eng->_iblTestNames.push_back(base+".mesh");
eng->_iblTestNames.push_back(base+".mat"); eng->_iblTestNames.push_back(base+".mat");
@@ -156,7 +155,7 @@ namespace {
auto mesh = eng->_assetManager->createMesh("ibltest.chrome.mesh", std::span<Vertex>(verts.data(), verts.size()), auto mesh = eng->_assetManager->createMesh("ibltest.chrome.mesh", std::span<Vertex>(verts.data(), verts.size()),
std::span<uint32_t>(inds.data(), inds.size()), mat); std::span<uint32_t>(inds.data(), inds.size()), mat);
glm::mat4 M = glm::translate(glm::mat4(1.0f), origin + glm::vec3(5.5f, 0.5f, 0.0f)); 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"}); 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<Vertex>(verts.data(), verts.size()), auto mesh = eng->_assetManager->createMesh("ibltest.glass.mesh", std::span<Vertex>(verts.data(), verts.size()),
std::span<uint32_t>(inds.data(), inds.size()), mat); std::span<uint32_t>(inds.data(), inds.size()), mat);
glm::mat4 M = glm::translate(glm::mat4(1.0f), origin + glm::vec3(5.5f, 0.5f, 2.0f)); 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"}); 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<unsigned long long>(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<size_t>(cap);
}
void VulkanEngine::init() void VulkanEngine::init()
{ {
// We initialize SDL and create a window with it. // We initialize SDL and create a window with it.
@@ -814,7 +850,8 @@ void VulkanEngine::init_default_data()
_sceneManager->addMeshInstance("default.cube", cubeMesh, _sceneManager->addMeshInstance("default.cube", cubeMesh,
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)));
_sceneManager->addMeshInstance("default.sphere", sphereMesh, _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([&]() { _mainDeletionQueue.push_function([&]() {
@@ -1069,7 +1106,7 @@ void VulkanEngine::draw()
// Prior to building passes, pump texture loads for this frame. // Prior to building passes, pump texture loads for this frame.
if (_textureCache) 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->set_gpu_budget_bytes(budget);
_textureCache->evictToBudget(budget); _textureCache->evictToBudget(budget);
_textureCache->pumpLoads(*_resourceManager, get_current_frame()); _textureCache->pumpLoads(*_resourceManager, get_current_frame());
@@ -1375,8 +1412,8 @@ void VulkanEngine::run()
if (ImGui::Begin("Debug")) if (ImGui::Begin("Debug"))
{ {
const ImGuiTabBarFlags tf = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs; const ImGuiTabBarFlags tf = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs;
if (ImGui::BeginTabBar("DebugTabs", tf)) if (ImGui::BeginTabBar("DebugTabs", tf))
{ {
if (ImGui::BeginTabItem("Overview")) if (ImGui::BeginTabItem("Overview"))
{ {
ui_overview(this); ui_overview(this);
@@ -1412,20 +1449,21 @@ void VulkanEngine::run()
ui_postfx(this); ui_postfx(this);
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
if (ImGui::BeginTabItem("Scene")) if (ImGui::BeginTabItem("Scene"))
{ {
ui_scene(this); ui_scene(this);
ImGui::EndTabItem(); 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::End();
} }
ImGui::Render(); ImGui::Render();
draw(); draw();

View File

@@ -167,6 +167,9 @@ public:
//run main loop //run main loop
void run(); void run();
// Query a conservative streaming texture budget for the texture cache.
size_t query_texture_budget_bytes() const;
bool resize_requested{false}; bool resize_requested{false};
bool freeze_rendering{false}; bool freeze_rendering{false};

View File

@@ -592,12 +592,14 @@ std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::
newSurface.bounds.origin = (maxpos + minpos) / 2.f; newSurface.bounds.origin = (maxpos + minpos) / 2.f;
newSurface.bounds.extents = (maxpos - minpos) / 2.f; newSurface.bounds.extents = (maxpos - minpos) / 2.f;
newSurface.bounds.sphereRadius = glm::length(newSurface.bounds.extents); newSurface.bounds.sphereRadius = glm::length(newSurface.bounds.extents);
newSurface.bounds.type = BoundsType::Box;
} }
else else
{ {
newSurface.bounds.origin = glm::vec3(0.0f); newSurface.bounds.origin = glm::vec3(0.0f);
newSurface.bounds.extents = glm::vec3(0.5f); newSurface.bounds.extents = glm::vec3(0.5f);
newSurface.bounds.sphereRadius = glm::length(newSurface.bounds.extents); newSurface.bounds.sphereRadius = glm::length(newSurface.bounds.extents);
newSurface.bounds.type = BoundsType::Box;
} }
newmesh->surfaces.push_back(newSurface); newmesh->surfaces.push_back(newSurface);
} }

View File

@@ -11,11 +11,25 @@
class VulkanEngine; 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 struct Bounds
{ {
glm::vec3 origin; glm::vec3 origin;
float sphereRadius; float sphereRadius;
glm::vec3 extents; glm::vec3 extents;
BoundsType type = BoundsType::Box;
}; };
struct GLTFMaterial struct GLTFMaterial

View File

@@ -16,180 +16,6 @@
#include <cmath> #include <cmath>
#include "core/config.h" #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<float>::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<glm::vec3, 8> 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) void SceneManager::init(EngineContext *context)
{ {
_context = context; _context = context;
@@ -289,6 +115,10 @@ void SceneManager::update_scene()
obj.vertexBufferAddress = inst.mesh->meshBuffers.vertexBufferAddress; obj.vertexBufferAddress = inst.mesh->meshBuffers.vertexBufferAddress;
obj.material = &surf.material->data; obj.material = &surf.material->data;
obj.bounds = surf.bounds; obj.bounds = surf.bounds;
if (inst.boundsTypeOverride.has_value())
{
obj.bounds.type = *inst.boundsTypeOverride;
}
obj.transform = inst.transform; obj.transform = inst.transform;
obj.sourceMesh = inst.mesh.get(); obj.sourceMesh = inst.mesh.get();
obj.surfaceIndex = surfaceIndex++; obj.surfaceIndex = surfaceIndex++;
@@ -409,159 +239,6 @@ void SceneManager::update_scene()
stats.scene_update_time = elapsed.count() / 1000.f; 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<float>(extent.width);
float height = static_cast<float>(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<float>::max();
glm::vec3 bestHitPos{};
auto testList = [&](const std::vector<RenderObject> &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<RenderObject> &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<RenderObject> &outObjects) const
{
if (!_context || !_context->getSwapchain())
{
return;
}
VkExtent2D extent = _context->getSwapchain()->windowExtent();
if (extent.width == 0 || extent.height == 0)
{
return;
}
float width = static_cast<float>(extent.width);
float height = static_cast<float>(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<RenderObject> &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<LoadedGLTF> scene) void SceneManager::loadScene(const std::string &name, std::shared_ptr<LoadedGLTF> scene)
{ {
if (scene) if (scene)
@@ -590,10 +267,15 @@ void SceneManager::cleanup()
loadedNodes.clear(); loadedNodes.clear();
} }
void SceneManager::addMeshInstance(const std::string &name, std::shared_ptr<MeshAsset> mesh, const glm::mat4 &transform) void SceneManager::addMeshInstance(const std::string &name, std::shared_ptr<MeshAsset> mesh,
const glm::mat4 &transform, std::optional<BoundsType> boundsType)
{ {
if (!mesh) return; 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) bool SceneManager::removeMeshInstance(const std::string &name)

View File

@@ -3,6 +3,7 @@
#include <scene/camera.h> #include <scene/camera.h>
#include <unordered_map> #include <unordered_map>
#include <memory> #include <memory>
#include <optional>
#include <glm/vec2.hpp> #include <glm/vec2.hpp>
#include "scene/vk_loader.h" #include "scene/vk_loader.h"
@@ -75,10 +76,12 @@ public:
{ {
std::shared_ptr<MeshAsset> mesh; std::shared_ptr<MeshAsset> mesh;
glm::mat4 transform{1.f}; glm::mat4 transform{1.f};
std::optional<BoundsType> boundsTypeOverride;
}; };
void addMeshInstance(const std::string &name, std::shared_ptr<MeshAsset> mesh, void addMeshInstance(const std::string &name, std::shared_ptr<MeshAsset> mesh,
const glm::mat4 &transform = glm::mat4(1.f)); const glm::mat4 &transform = glm::mat4(1.f),
std::optional<BoundsType> boundsType = {});
bool removeMeshInstance(const std::string &name); bool removeMeshInstance(const std::string &name);
void clearMeshInstances(); void clearMeshInstances();