From ac35de61040261adc7852b1f9bbcf40d5be9bf15 Mon Sep 17 00:00:00 2001 From: hydrogendeuteride Date: Wed, 19 Nov 2025 16:24:51 +0900 Subject: [PATCH] ADD: BVH Selection ongoing --- src/scene/mesh_bvh.cpp | 147 +++++++++++++++++++++++++++++++++++++++++ src/scene/mesh_bvh.h | 58 ++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 src/scene/mesh_bvh.cpp create mode 100644 src/scene/mesh_bvh.h diff --git a/src/scene/mesh_bvh.cpp b/src/scene/mesh_bvh.cpp new file mode 100644 index 0000000..788a9cc --- /dev/null +++ b/src/scene/mesh_bvh.cpp @@ -0,0 +1,147 @@ +#include "mesh_bvh.h" + +#include +#include + +#include + +#include "glm/gtx/norm.hpp" + +std::unique_ptr build_mesh_bvh(const MeshAsset &mesh, + std::span vertices, + std::span indices) +{ + if (vertices.empty() || indices.size() < 3 || mesh.surfaces.empty()) + { + return {}; + } + + size_t totalTriangles = 0; + for (const GeoSurface &surf: mesh.surfaces) + { + totalTriangles += static_cast(surf.count / 3); + } + if (totalTriangles == 0) + { + return {}; + } + + auto result = std::make_unique(); + result->primitives.reserve(totalTriangles); + result->primitiveRefs.reserve(totalTriangles); + + for (uint32_t s = 0; s < mesh.surfaces.size(); ++s) + { + const GeoSurface &surf = mesh.surfaces[s]; + uint32_t start = surf.startIndex; + uint32_t end = surf.startIndex + surf.count; + if (start >= indices.size()) + { + continue; + } + if (end > indices.size()) + { + end = static_cast(indices.size()); + } + + for (uint32_t idx = start; idx + 2 < end; idx += 3) + { + uint32_t i0 = indices[idx + 0]; + uint32_t i1 = indices[idx + 1]; + uint32_t i2 = indices[idx + 2]; + if (i0 >= vertices.size() || i1 >= vertices.size() || i2 >= vertices.size()) + { + continue; + } + + const glm::vec3 &p0 = vertices[i0].position; + const glm::vec3 &p1 = vertices[i1].position; + const glm::vec3 &p2 = vertices[i2].position; + + PrimitiveF prim{}; + prim.bounds.expand(Vec3(p0.x, p0.y, p0.z)); + prim.bounds.expand(Vec3(p1.x, p1.y, p1.z)); + prim.bounds.expand(Vec3(p2.x, p2.y, p2.z)); + result->primitives.push_back(prim); + + MeshBVHPrimitiveRef ref{}; + ref.surfaceIndex = s; + ref.firstIndex = idx; + result->primitiveRefs.push_back(ref); + } + } + + if (result->primitives.empty()) + { + return {}; + } + + unsigned int threadCount = std::thread::hardware_concurrency(); + if (threadCount == 0) + { + threadCount = 1; + } + + tf::Executor executor{threadCount}; + result->nodes = buildLBVH(executor, result->primitives, MortonSortMethod::RadixSort); + + return result; +} + +bool intersect_ray_mesh_bvh(const MeshBVH &bvh, + const glm::mat4 &worldTransform, + const glm::vec3 &rayOriginWorld, + const glm::vec3 &rayDirWorld, + MeshBVHPickHit &outHit) +{ + outHit = {}; + + if (bvh.nodes.empty() || bvh.primitives.empty()) + { + return false; + } + + if (glm::length2(rayDirWorld) < 1e-8f) + { + return false; + } + + glm::mat4 invM = glm::inverse(worldTransform); + glm::vec3 originLocal = glm::vec3(invM * glm::vec4(rayOriginWorld, 1.0f)); + glm::vec3 dirLocal = glm::vec3(invM * glm::vec4(rayDirWorld, 0.0f)); + + if (glm::length2(dirLocal) < 1e-8f) + { + return false; + } + dirLocal = glm::normalize(dirLocal); + + Ray ray(Vec3(originLocal.x, originLocal.y, originLocal.z), + Vec3(dirLocal.x, dirLocal.y, dirLocal.z)); + + uint32_t primIdx = 0; + float tLocal = 0.0f; + if (!traverseBVHClosestHit(bvh.nodes, bvh.primitives, ray, primIdx, tLocal)) + { + return false; + } + if (primIdx >= bvh.primitiveRefs.size()) + { + return false; + } + + const MeshBVHPrimitiveRef &ref = bvh.primitiveRefs[primIdx]; + + glm::vec3 localHit = originLocal + dirLocal * tLocal; + glm::vec3 worldHit = glm::vec3(worldTransform * glm::vec4(localHit, 1.0f)); + + outHit.hit = true; + outHit.localPos = localHit; + outHit.worldPos = worldHit; + outHit.surfaceIndex = ref.surfaceIndex; + outHit.firstIndex = ref.firstIndex; + outHit.t = glm::length(worldHit - rayOriginWorld); + + return true; +} + diff --git a/src/scene/mesh_bvh.h b/src/scene/mesh_bvh.h new file mode 100644 index 0000000..eb5c12a --- /dev/null +++ b/src/scene/mesh_bvh.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include + +struct MeshAsset; + +// For each BVH primitive, record which surface and which triangle +// (by starting index into the index buffer) it represents. +struct MeshBVHPrimitiveRef +{ + uint32_t surfaceIndex = 0; + uint32_t firstIndex = 0; +}; + +// CPU-side BVH for a mesh, built in mesh-local space. +struct MeshBVH +{ + std::vector primitives; + std::vector nodes; + std::vector primitiveRefs; +}; + +// Build a mesh-local BVH for a triangle mesh. +// vertices/indices must match the GPU data uploaded for 'mesh'. +// Returns nullptr if no triangles are found. +std::unique_ptr build_mesh_bvh(const MeshAsset &mesh, + std::span vertices, + std::span indices); + +struct MeshBVHPickHit +{ + bool hit = false; + + float t = 0.0f; // world-space distance along ray + glm::vec3 localPos{0.0f}; // hit position in mesh-local space + glm::vec3 worldPos{0.0f}; // hit position in world space + + uint32_t surfaceIndex = 0; // hit GeoSurface index + uint32_t firstIndex = 0; // index into mesh index buffer (triangle start) +}; + +// Ray–mesh BVH intersection in world space. +// Returns true on hit and fills outHit. +bool intersect_ray_mesh_bvh(const MeshBVH &bvh, + const glm::mat4 &worldTransform, + const glm::vec3 &rayOriginWorld, + const glm::vec3 &rayDirWorld, + MeshBVHPickHit &outHit); +