From e396cf7d1f3fffdb98e5b2fa01ba19f7b3e62a7a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:23:44 +0000 Subject: [PATCH 1/5] Initial plan From a0180b55f65eace19e19de516ff9f353f64625cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:32:15 +0000 Subject: [PATCH 2/5] Add volume management system for scene traversal and culling acceleration - Add AABB helper methods (GetCenter, GetExtent, IsValid, Contains) - Add Sphere intersection functions (Sphere-Plane, Sphere-AABB, Sphere-Frustum) - Create BoundingVolume abstraction unifying AABB and Sphere bounds - Create VolumeManager using OctTree for spatial partitioning - Integrate VolumeManager into RenderScene for primitive tracking - Update MeshRenderer to sync volume bounds on transform changes - Add comprehensive unit tests Co-authored-by: bluesky013 <35895395+bluesky013@users.noreply.github.com> --- runtime/core/include/core/shapes/AABB.h | 10 + .../core/include/core/shapes/BoundingVolume.h | 87 ++++++ runtime/core/include/core/shapes/Shapes.h | 3 + .../core/include/core/tree/VolumeManager.h | 122 ++++++++ runtime/core/src/shapes/Shapes.cpp | 34 +++ .../core/include/render/RenderPrimitive.h | 3 + .../render/core/include/render/RenderScene.h | 5 + runtime/render/core/src/RenderScene.cpp | 6 + runtime/render/core/src/mesh/MeshRenderer.cpp | 4 + test/core/VolumeTest.cpp | 264 ++++++++++++++++++ 10 files changed, 538 insertions(+) create mode 100644 runtime/core/include/core/shapes/BoundingVolume.h create mode 100644 runtime/core/include/core/tree/VolumeManager.h create mode 100644 test/core/VolumeTest.cpp diff --git a/runtime/core/include/core/shapes/AABB.h b/runtime/core/include/core/shapes/AABB.h index aee89949..102f0b70 100644 --- a/runtime/core/include/core/shapes/AABB.h +++ b/runtime/core/include/core/shapes/AABB.h @@ -18,6 +18,16 @@ namespace sky { inline constexpr AABB(Vector3 min_, Vector3 max_) : min(min_), max(max_) {} static AABB Transform(const AABB& box, const Matrix4 &matrix); + + inline Vector3 GetCenter() const { return (min + max) / 2.f; } + inline Vector3 GetExtent() const { return (max - min) / 2.f; } + inline bool IsValid() const { return min.x <= max.x && min.y <= max.y && min.z <= max.z; } + inline bool Contains(const Vector3 &point) const + { + return point.x >= min.x && point.x <= max.x && + point.y >= min.y && point.y <= max.y && + point.z >= min.z && point.z <= max.z; + } }; void Merge(const AABB &a, const AABB &b, AABB &out); diff --git a/runtime/core/include/core/shapes/BoundingVolume.h b/runtime/core/include/core/shapes/BoundingVolume.h new file mode 100644 index 00000000..f706029f --- /dev/null +++ b/runtime/core/include/core/shapes/BoundingVolume.h @@ -0,0 +1,87 @@ +// +// Created for volume management system +// + +#pragma once + +#include +#include +#include +#include +#include + +namespace sky { + + class BoundingVolume { + public: + enum class Type : uint8_t { + NONE, + BOX, + SPHERE + }; + + BoundingVolume() = default; + explicit BoundingVolume(const AABB &aabb) : type(Type::BOX), aabb(aabb) {} + explicit BoundingVolume(const Sphere &sphere) : type(Type::SPHERE), sphere(sphere) {} + + Type GetType() const { return type; } + + const AABB &GetAABB() const { return aabb; } + const Sphere &GetSphere() const { return sphere; } + + void SetAABB(const AABB &box) + { + type = Type::BOX; + aabb = box; + } + + void SetSphere(const Sphere &sph) + { + type = Type::SPHERE; + sphere = sph; + } + + AABB ToAABB() const + { + switch (type) { + case Type::BOX: + return aabb; + case Type::SPHERE: + return AABB{sphere.center - Vector3(sphere.radius), + sphere.center + Vector3(sphere.radius)}; + default: + return {}; + } + } + + bool IntersectsFrustum(const Frustum &frustum) const + { + switch (type) { + case Type::BOX: + return Intersection(aabb, frustum); + case Type::SPHERE: + return Intersection(sphere, frustum); + default: + return false; + } + } + + bool IntersectsAABB(const AABB &other) const + { + switch (type) { + case Type::BOX: + return Intersection(aabb, other); + case Type::SPHERE: + return Intersection(sphere, other); + default: + return false; + } + } + + private: + Type type = Type::NONE; + AABB aabb; + Sphere sphere; + }; + +} // namespace sky diff --git a/runtime/core/include/core/shapes/Shapes.h b/runtime/core/include/core/shapes/Shapes.h index dfc451c9..8f2f2551 100644 --- a/runtime/core/include/core/shapes/Shapes.h +++ b/runtime/core/include/core/shapes/Shapes.h @@ -13,6 +13,9 @@ namespace sky { bool Intersection(const AABB &lhs, const AABB &rhs); bool Intersection(const AABB &aabb, const Frustum &frustum); std::pair Intersection(const AABB &aabb, const Plane &plane); + bool Intersection(const Sphere &sphere, const Frustum &frustum); + bool Intersection(const Sphere &sphere, const AABB &aabb); + std::pair Intersection(const Sphere &sphere, const Plane &plane); // utils Plane CreatePlaneByVertices(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3); diff --git a/runtime/core/include/core/tree/VolumeManager.h b/runtime/core/include/core/tree/VolumeManager.h new file mode 100644 index 00000000..7e173b3e --- /dev/null +++ b/runtime/core/include/core/tree/VolumeManager.h @@ -0,0 +1,122 @@ +// +// Created for volume management system +// + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace sky { + + using VolumeID = uint32_t; + static constexpr VolumeID INVALID_VOLUME_ID = ~(0U); + + struct VolumeEntry { + VolumeID id = INVALID_VOLUME_ID; + AABB worldBound; + void *userData = nullptr; + + mutable Octree::ElementIndex octreeIndex; + }; + + template <> + struct OctreeTraits { + static constexpr uint32_t MAX_DEPTH = 8; + static constexpr uint32_t MAX_ELEMENT_LEAF = 8; + + using BoundType = AABB; + + static const AABB &GetBounds(const VolumeEntry &entry) + { + return entry.worldBound; + } + + static void IndexChanged(const VolumeEntry &entry, const Octree::ElementIndex &index) + { + entry.octreeIndex = index; + } + }; + + class VolumeManager { + public: + explicit VolumeManager(float worldExtent = 2048.f, const Vector3 &origin = VEC3_ZERO) + : octree(worldExtent, origin) + { + } + + ~VolumeManager() = default; + + VolumeID AddVolume(const AABB &worldBound, void *userData = nullptr) + { + VolumeID id = nextID++; + auto &entry = entries.emplace_back(VolumeEntry{id, worldBound, userData}); + octree.AddElement(entry); + return id; + } + + void RemoveVolume(VolumeID id) + { + for (size_t i = 0; i < entries.size(); ++i) { + if (entries[i].id == id) { + octree.RemoveElement(entries[i].octreeIndex); + entries[i] = entries.back(); + entries.pop_back(); + return; + } + } + } + + void UpdateVolume(VolumeID id, const AABB &newBound) + { + for (auto &entry : entries) { + if (entry.id == id) { + octree.RemoveElement(entry.octreeIndex); + entry.worldBound = newBound; + octree.AddElement(entry); + return; + } + } + } + + template + void FrustumCull(const Frustum &frustum, const Func &callback) const + { + for (const auto &entry : entries) { + if (Intersection(entry.worldBound, frustum)) { + callback(entry); + } + } + } + + template + void QueryByAABB(const AABB &queryBound, const Func &callback) + { + octree.ForeachWithBoundTest(queryBound, [&callback](const VolumeEntry &entry) { + callback(entry); + }); + } + + size_t GetVolumeCount() const { return entries.size(); } + + const VolumeEntry *FindVolume(VolumeID id) const + { + for (const auto &entry : entries) { + if (entry.id == id) { + return &entry; + } + } + return nullptr; + } + + private: + VolumeID nextID = 0; + std::vector entries; + Octree octree; + }; + +} // namespace sky diff --git a/runtime/core/src/shapes/Shapes.cpp b/runtime/core/src/shapes/Shapes.cpp index e9730390..d067e72f 100644 --- a/runtime/core/src/shapes/Shapes.cpp +++ b/runtime/core/src/shapes/Shapes.cpp @@ -47,6 +47,40 @@ namespace sky { }); } + std::pair Intersection(const Sphere &sphere, const Plane &plane) + { + float dist = plane.normal.Dot(sphere.center) - plane.distance; + if (std::abs(dist) <= sphere.radius) { + return {true, 0}; + } + return {false, dist > 0.f ? 1 : -1}; + } + + bool Intersection(const Sphere &sphere, const AABB &aabb) + { + float sqDist = 0.f; + for (int i = 0; i < 3; ++i) { + float v = sphere.center[i]; + if (v < aabb.min[i]) { + float d = aabb.min[i] - v; + sqDist += d * d; + } + if (v > aabb.max[i]) { + float d = v - aabb.max[i]; + sqDist += d * d; + } + } + return sqDist <= sphere.radius * sphere.radius; + } + + bool Intersection(const Sphere &sphere, const Frustum &frustum) + { + return std::all_of(frustum.planes.begin(), frustum.planes.end(), + [&sphere](const Plane &plane) { + return Intersection(sphere, plane).second <= 0; + }); + } + Plane CreatePlaneByVertices(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3) { diff --git a/runtime/render/core/include/render/RenderPrimitive.h b/runtime/render/core/include/render/RenderPrimitive.h index e86f9540..5009840a 100644 --- a/runtime/render/core/include/render/RenderPrimitive.h +++ b/runtime/render/core/include/render/RenderPrimitive.h @@ -58,6 +58,9 @@ namespace sky { AABB localBound {Vector3(std::numeric_limits::min()), Vector3(std::numeric_limits::max())}; AABB worldBound {Vector3(std::numeric_limits::min()), Vector3(std::numeric_limits::max())}; + // volume management + uint32_t volumeId = ~(0U); + // geometry RenderVertexFlags vertexFlags; RenderGeometryPtr geometry; diff --git a/runtime/render/core/include/render/RenderScene.h b/runtime/render/core/include/render/RenderScene.h index 0dfd9d52..e02a10b9 100644 --- a/runtime/render/core/include/render/RenderScene.h +++ b/runtime/render/core/include/render/RenderScene.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -35,6 +36,9 @@ namespace sky { const RenderPipelineFlags &GetRenderPipelineFlags() const { return renderFlags; } + VolumeManager &GetVolumeManager() { return volumeManager; } + const VolumeManager &GetVolumeManager() const { return volumeManager; } + template T *GetFeature() const { @@ -59,6 +63,7 @@ namespace sky { PmrHashMap viewMap; PmrVector primitives; + VolumeManager volumeManager; RenderPipelineFlags renderFlags; }; diff --git a/runtime/render/core/src/RenderScene.cpp b/runtime/render/core/src/RenderScene.cpp index 01c65f1c..c8b24ab6 100644 --- a/runtime/render/core/src/RenderScene.cpp +++ b/runtime/render/core/src/RenderScene.cpp @@ -84,6 +84,7 @@ namespace sky { auto iter = std::find(primitives.begin(), primitives.end(), primitive); SKY_ASSERT(iter == primitives.end()); primitives.emplace_back(primitive); + primitive->volumeId = volumeManager.AddVolume(primitive->worldBound, primitive); } void RenderScene::RemovePrimitive(RenderPrimitive *primitive) @@ -92,6 +93,11 @@ namespace sky { return; } + if (primitive->volumeId != INVALID_VOLUME_ID) { + volumeManager.RemoveVolume(primitive->volumeId); + primitive->volumeId = INVALID_VOLUME_ID; + } + primitives.erase(std::remove_if(primitives.begin(), primitives.end(), [primitive](const auto &v) { return primitive == v; }), primitives.end()); diff --git a/runtime/render/core/src/mesh/MeshRenderer.cpp b/runtime/render/core/src/mesh/MeshRenderer.cpp index 4cda00bb..9be79d9f 100644 --- a/runtime/render/core/src/mesh/MeshRenderer.cpp +++ b/runtime/render/core/src/mesh/MeshRenderer.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -183,6 +184,9 @@ namespace sky { for (auto &prim : primitives) { prim->worldBound = AABB::Transform(prim->localBound, matrix); + if (scene != nullptr && prim->volumeId != INVALID_VOLUME_ID) { + scene->GetVolumeManager().UpdateVolume(prim->volumeId, prim->worldBound); + } } } diff --git a/test/core/VolumeTest.cpp b/test/core/VolumeTest.cpp new file mode 100644 index 00000000..4bad622b --- /dev/null +++ b/test/core/VolumeTest.cpp @@ -0,0 +1,264 @@ +// +// Tests for volume management system +// + +#include +#include +#include +#include +#include +#include +#include + +using namespace sky; + +// ============================================ +// AABB Helper Tests +// ============================================ +TEST(VolumeTest, AABBHelpers) +{ + AABB aabb(Vector3(-1, -2, -3), Vector3(1, 2, 3)); + + auto center = aabb.GetCenter(); + EXPECT_FLOAT_EQ(center.x, 0.f); + EXPECT_FLOAT_EQ(center.y, 0.f); + EXPECT_FLOAT_EQ(center.z, 0.f); + + auto extent = aabb.GetExtent(); + EXPECT_FLOAT_EQ(extent.x, 1.f); + EXPECT_FLOAT_EQ(extent.y, 2.f); + EXPECT_FLOAT_EQ(extent.z, 3.f); + + EXPECT_TRUE(aabb.IsValid()); + EXPECT_TRUE(aabb.Contains(Vector3(0, 0, 0))); + EXPECT_TRUE(aabb.Contains(Vector3(1, 2, 3))); + EXPECT_FALSE(aabb.Contains(Vector3(1.1f, 0, 0))); + + AABB invalid(Vector3(1, 0, 0), Vector3(-1, 0, 0)); + EXPECT_FALSE(invalid.IsValid()); +} + +// ============================================ +// Sphere-Plane Intersection Tests +// ============================================ +TEST(VolumeTest, SpherePlaneIntersection) +{ + Plane plane{VEC3_Y, 0.f}; + + // Sphere intersects plane + Sphere sphere1{Vector3(0, 0.5f, 0), 1.f}; + auto [intersects1, side1] = Intersection(sphere1, plane); + EXPECT_TRUE(intersects1); + EXPECT_EQ(side1, 0); + + // Sphere fully above plane + Sphere sphere2{Vector3(0, 5, 0), 1.f}; + auto [intersects2, side2] = Intersection(sphere2, plane); + EXPECT_FALSE(intersects2); + EXPECT_EQ(side2, 1); + + // Sphere fully below plane + Sphere sphere3{Vector3(0, -5, 0), 1.f}; + auto [intersects3, side3] = Intersection(sphere3, plane); + EXPECT_FALSE(intersects3); + EXPECT_EQ(side3, -1); +} + +// ============================================ +// Sphere-AABB Intersection Tests +// ============================================ +TEST(VolumeTest, SphereAABBIntersection) +{ + AABB aabb(Vector3(-1, -1, -1), Vector3(1, 1, 1)); + + // Sphere centered at origin + Sphere sphere1{VEC3_ZERO, 0.5f}; + EXPECT_TRUE(Intersection(sphere1, aabb)); + + // Sphere barely touching + Sphere sphere2{Vector3(2, 0, 0), 1.f}; + EXPECT_TRUE(Intersection(sphere2, aabb)); + + // Sphere not touching + Sphere sphere3{Vector3(3, 0, 0), 1.f}; + EXPECT_FALSE(Intersection(sphere3, aabb)); + + // Sphere at corner + Sphere sphere4{Vector3(2, 2, 2), 1.73f}; + EXPECT_TRUE(Intersection(sphere4, aabb)); +} + +// ============================================ +// Sphere-Frustum Intersection Tests +// ============================================ +TEST(VolumeTest, SphereFrustumIntersection) +{ + const auto mtx = MakePerspective(90.f / 180.f * 3.14f, 1.0, 0.1f, 100.f); + const auto frustum = CreateFrustumByViewProjectMatrix(mtx); + + // Inside frustum + Sphere sphere1{Vector3(0, 0, -5), 1.f}; + EXPECT_TRUE(Intersection(sphere1, frustum)); + + // Behind camera + Sphere sphere2{Vector3(0, 0, 5), 1.f}; + EXPECT_FALSE(Intersection(sphere2, frustum)); + + // Beyond far plane + Sphere sphere3{Vector3(0, 0, -150), 1.f}; + EXPECT_FALSE(Intersection(sphere3, frustum)); +} + +// ============================================ +// BoundingVolume Tests +// ============================================ +TEST(VolumeTest, BoundingVolumeAABB) +{ + AABB aabb(Vector3(-1, -1, -1), Vector3(1, 1, 1)); + BoundingVolume vol(aabb); + + EXPECT_EQ(vol.GetType(), BoundingVolume::Type::BOX); + EXPECT_FLOAT_EQ(vol.GetAABB().min.x, -1.f); + + auto converted = vol.ToAABB(); + EXPECT_FLOAT_EQ(converted.min.x, -1.f); + EXPECT_FLOAT_EQ(converted.max.x, 1.f); + + AABB other(Vector3(0, 0, 0), Vector3(2, 2, 2)); + EXPECT_TRUE(vol.IntersectsAABB(other)); + + AABB noOverlap(Vector3(5, 5, 5), Vector3(6, 6, 6)); + EXPECT_FALSE(vol.IntersectsAABB(noOverlap)); +} + +TEST(VolumeTest, BoundingVolumeSphere) +{ + Sphere sphere{VEC3_ZERO, 2.f}; + BoundingVolume vol(sphere); + + EXPECT_EQ(vol.GetType(), BoundingVolume::Type::SPHERE); + + auto converted = vol.ToAABB(); + EXPECT_FLOAT_EQ(converted.min.x, -2.f); + EXPECT_FLOAT_EQ(converted.max.x, 2.f); + + AABB other(Vector3(1, 0, 0), Vector3(3, 1, 1)); + EXPECT_TRUE(vol.IntersectsAABB(other)); +} + +TEST(VolumeTest, BoundingVolumeFrustumCulling) +{ + const auto mtx = MakePerspective(90.f / 180.f * 3.14f, 1.0, 0.1f, 100.f); + const auto frustum = CreateFrustumByViewProjectMatrix(mtx); + + BoundingVolume boxVol(AABB(Vector3(-1, -1, -6), Vector3(1, 1, -4))); + EXPECT_TRUE(boxVol.IntersectsFrustum(frustum)); + + BoundingVolume sphereVol(Sphere{Vector3(0, 0, -5), 1.f}); + EXPECT_TRUE(sphereVol.IntersectsFrustum(frustum)); + + BoundingVolume behindVol(AABB(Vector3(-1, -1, 4), Vector3(1, 1, 6))); + EXPECT_FALSE(behindVol.IntersectsFrustum(frustum)); +} + +// ============================================ +// VolumeManager Tests +// ============================================ +TEST(VolumeTest, VolumeManagerAddRemove) +{ + VolumeManager mgr(1024.f); + + AABB aabb1(Vector3(-1, -1, -1), Vector3(1, 1, 1)); + AABB aabb2(Vector3(10, 10, 10), Vector3(12, 12, 12)); + + auto id1 = mgr.AddVolume(aabb1); + auto id2 = mgr.AddVolume(aabb2); + + EXPECT_EQ(mgr.GetVolumeCount(), 2u); + EXPECT_NE(id1, id2); + + auto *found = mgr.FindVolume(id1); + ASSERT_NE(found, nullptr); + EXPECT_FLOAT_EQ(found->worldBound.min.x, -1.f); + + mgr.RemoveVolume(id1); + EXPECT_EQ(mgr.GetVolumeCount(), 1u); + EXPECT_EQ(mgr.FindVolume(id1), nullptr); + + auto *found2 = mgr.FindVolume(id2); + ASSERT_NE(found2, nullptr); +} + +TEST(VolumeTest, VolumeManagerUpdate) +{ + VolumeManager mgr(1024.f); + + AABB aabb(Vector3(-1, -1, -1), Vector3(1, 1, 1)); + auto id = mgr.AddVolume(aabb); + + AABB newAABB(Vector3(5, 5, 5), Vector3(7, 7, 7)); + mgr.UpdateVolume(id, newAABB); + + auto *found = mgr.FindVolume(id); + ASSERT_NE(found, nullptr); + EXPECT_FLOAT_EQ(found->worldBound.min.x, 5.f); + EXPECT_FLOAT_EQ(found->worldBound.max.x, 7.f); +} + +TEST(VolumeTest, VolumeManagerFrustumCull) +{ + VolumeManager mgr(1024.f); + + // Add some volumes at different positions + AABB inside(Vector3(-1, -1, -6), Vector3(1, 1, -4)); + AABB behind(Vector3(-1, -1, 4), Vector3(1, 1, 6)); + AABB farAway(Vector3(-1, -1, -200), Vector3(1, 1, -198)); + + mgr.AddVolume(inside); + mgr.AddVolume(behind); + mgr.AddVolume(farAway); + + const auto mtx = MakePerspective(90.f / 180.f * 3.14f, 1.0, 0.1f, 100.f); + const auto frustum = CreateFrustumByViewProjectMatrix(mtx); + + std::vector visibleIds; + mgr.FrustumCull(frustum, [&visibleIds](const VolumeEntry &entry) { + visibleIds.push_back(entry.id); + }); + + EXPECT_EQ(visibleIds.size(), 1u); +} + +TEST(VolumeTest, VolumeManagerAABBQuery) +{ + VolumeManager mgr(1024.f); + + AABB vol1(Vector3(-1, -1, -1), Vector3(1, 1, 1)); + AABB vol2(Vector3(10, 10, 10), Vector3(12, 12, 12)); + AABB vol3(Vector3(0, 0, 0), Vector3(3, 3, 3)); + + mgr.AddVolume(vol1); + mgr.AddVolume(vol2); + mgr.AddVolume(vol3); + + AABB queryBox(Vector3(-2, -2, -2), Vector3(2, 2, 2)); + std::vector hitIds; + mgr.QueryByAABB(queryBox, [&hitIds](const VolumeEntry &entry) { + hitIds.push_back(entry.id); + }); + + EXPECT_EQ(hitIds.size(), 2u); +} + +TEST(VolumeTest, VolumeManagerUserData) +{ + VolumeManager mgr(1024.f); + + int myData = 42; + AABB aabb(Vector3(-1, -1, -1), Vector3(1, 1, 1)); + auto id = mgr.AddVolume(aabb, &myData); + + auto *found = mgr.FindVolume(id); + ASSERT_NE(found, nullptr); + EXPECT_EQ(*static_cast(found->userData), 42); +} From a4a51570355b60971d864a55a3dcb00648177c61 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:34:43 +0000 Subject: [PATCH 3/5] Fix VolumeManager: use pointer-based octree and add octree-accelerated frustum culling - Use unordered_map for entries to ensure stable pointers - Store pointers in octree to avoid stale index issues - Add ForeachWithNodeTest to OctTree for custom node-level culling - FrustumCull now skips entire octree subtrees outside the frustum Co-authored-by: bluesky013 <35895395+bluesky013@users.noreply.github.com> --- runtime/core/include/core/tree/OctTree.h | 31 ++++++++ .../core/include/core/tree/VolumeManager.h | 72 +++++++++---------- 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/runtime/core/include/core/tree/OctTree.h b/runtime/core/include/core/tree/OctTree.h index c85d06f7..5c56b073 100644 --- a/runtime/core/include/core/tree/OctTree.h +++ b/runtime/core/include/core/tree/OctTree.h @@ -124,6 +124,37 @@ namespace sky { ForeachWithBoundTestWith(0, bounds, func); } + template + void ForeachWithNodeTestWith(NodeIndex index, const NodeTest &nodeTest, const Func &func) + { + if (nodes[index].numElements > 0) { + AABB nodeBox{ + nodes[index].origin - Vector3(nodes[index].ext), + nodes[index].origin + Vector3(nodes[index].ext) + }; + if (!nodeTest(nodeBox)) { + return; + } + + for (auto &element : elements[index]) { + func(element); + } + + if (!nodes[index].IsLeaf()) { + auto childStart = nodes[index].idxChild; + for (uint8_t i = 0; i < 8; ++i) { + ForeachWithNodeTestWith(childStart + i, nodeTest, func); + } + } + } + } + + template + void ForeachWithNodeTest(const NodeTest &nodeTest, const Func &func) + { + ForeachWithNodeTestWith(0, nodeTest, func); + } + explicit Octree(float maxExtent, const Vector3 &ori = VEC3_ZERO) : leafExtent(maxExtent * std::pow(0.5f, static_cast(MAX_DEPTH))) { diff --git a/runtime/core/include/core/tree/VolumeManager.h b/runtime/core/include/core/tree/VolumeManager.h index 7e173b3e..228ddd2b 100644 --- a/runtime/core/include/core/tree/VolumeManager.h +++ b/runtime/core/include/core/tree/VolumeManager.h @@ -4,8 +4,8 @@ #pragma once -#include #include +#include #include #include #include @@ -21,24 +21,24 @@ namespace sky { AABB worldBound; void *userData = nullptr; - mutable Octree::ElementIndex octreeIndex; + mutable Octree::ElementIndex octreeIndex; }; template <> - struct OctreeTraits { + struct OctreeTraits { static constexpr uint32_t MAX_DEPTH = 8; static constexpr uint32_t MAX_ELEMENT_LEAF = 8; using BoundType = AABB; - static const AABB &GetBounds(const VolumeEntry &entry) + static const AABB &GetBounds(VolumeEntry *const &entry) { - return entry.worldBound; + return entry->worldBound; } - static void IndexChanged(const VolumeEntry &entry, const Octree::ElementIndex &index) + static void IndexChanged(VolumeEntry *const &entry, const Octree::ElementIndex &index) { - entry.octreeIndex = index; + entry->octreeIndex = index; } }; @@ -54,50 +54,50 @@ namespace sky { VolumeID AddVolume(const AABB &worldBound, void *userData = nullptr) { VolumeID id = nextID++; - auto &entry = entries.emplace_back(VolumeEntry{id, worldBound, userData}); - octree.AddElement(entry); + auto [it, ok] = entries.emplace(id, VolumeEntry{id, worldBound, userData}); + octree.AddElement(&it->second); return id; } void RemoveVolume(VolumeID id) { - for (size_t i = 0; i < entries.size(); ++i) { - if (entries[i].id == id) { - octree.RemoveElement(entries[i].octreeIndex); - entries[i] = entries.back(); - entries.pop_back(); - return; - } + auto it = entries.find(id); + if (it != entries.end()) { + octree.RemoveElement(it->second.octreeIndex); + entries.erase(it); } } void UpdateVolume(VolumeID id, const AABB &newBound) { - for (auto &entry : entries) { - if (entry.id == id) { - octree.RemoveElement(entry.octreeIndex); - entry.worldBound = newBound; - octree.AddElement(entry); - return; - } + auto it = entries.find(id); + if (it != entries.end()) { + octree.RemoveElement(it->second.octreeIndex); + it->second.worldBound = newBound; + octree.AddElement(&it->second); } } template - void FrustumCull(const Frustum &frustum, const Func &callback) const + void FrustumCull(const Frustum &frustum, const Func &callback) { - for (const auto &entry : entries) { - if (Intersection(entry.worldBound, frustum)) { - callback(entry); + octree.ForeachWithNodeTest( + [&frustum](const AABB &nodeBox) { + return Intersection(nodeBox, frustum); + }, + [&callback, &frustum](VolumeEntry *entry) { + if (Intersection(entry->worldBound, frustum)) { + callback(*entry); + } } - } + ); } template void QueryByAABB(const AABB &queryBound, const Func &callback) { - octree.ForeachWithBoundTest(queryBound, [&callback](const VolumeEntry &entry) { - callback(entry); + octree.ForeachWithBoundTest(queryBound, [&callback](VolumeEntry *entry) { + callback(*entry); }); } @@ -105,18 +105,14 @@ namespace sky { const VolumeEntry *FindVolume(VolumeID id) const { - for (const auto &entry : entries) { - if (entry.id == id) { - return &entry; - } - } - return nullptr; + auto it = entries.find(id); + return it != entries.end() ? &it->second : nullptr; } private: VolumeID nextID = 0; - std::vector entries; - Octree octree; + std::unordered_map entries; + Octree octree; }; } // namespace sky From 26d79de9bf8c42161683027dc35e6b0d68a3c10c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:35:40 +0000 Subject: [PATCH 4/5] Final review - no additional code changes needed Co-authored-by: bluesky013 <35895395+bluesky013@users.noreply.github.com> --- _codeql_detected_source_root | 1 + 1 file changed, 1 insertion(+) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 00000000..945c9b46 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file From d2311ee90e0f56357b2ed50b572235cc6cbe4c4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:35:58 +0000 Subject: [PATCH 5/5] Clean up codeql artifact and update gitignore Co-authored-by: bluesky013 <35895395+bluesky013@users.noreply.github.com> --- .gitignore | 1 + _codeql_detected_source_root | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 120000 _codeql_detected_source_root diff --git a/.gitignore b/.gitignore index 9309668d..a38f0748 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ /active_project /test/assets/assets.db /test/products/ +_codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root deleted file mode 120000 index 945c9b46..00000000 --- a/_codeql_detected_source_root +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file