EDIT:
Here is an Example Test Video.
Stack Exchange network consists of 183 Q&A communities including Stack Overflow, the largest, most trusted online community for developers to learn, share their knowledge, and build their careers.
Visit Stack ExchangeStack Internal
Knowledge at work
Bring the best of human thought and AI automation together at your work.
Explore Stack InternalI have found a solution to my design. Only took 5 days and no responses to my original question!.. You can obviously tell I am a hobbyist programmer haha
I have opted to have a single global vector of data sources that are held within the OctreeBounds class and not within the nodes themselves.
Each node now merely holds an index to the starting position of the node's respective data source, the size of the total data sources at the location, and a size of total data sources managed by this node and subsequent child nodes.
Ordering the data in the vector and validation of the start position indices is extremely important as whenever we add or remove something from the global vector, we have to traverse the tree and update the start position indices of each node to ensure they point to the updated position in the global vector.
This is by far more efficient than having to traverse an entire node recursively to gather all child data sources, that previously resulted in a huge number of vector range inserts.
In summary, this new design is to dramatically increase the speed of queries to the Octree. If we contain an entire node within the query region, we can simply insert all data sources in one fell swoop using the total data source size without having to recurse into each child and gather their data sources then again recursively for all that children's children, etc.
This now looks something like this within the OctreeNode class:
void Query(const Frustum& frustum, std::vector<OctreeData>& result, bool& should_delete_node) { // If there are no data sources in itself or children // why bother testing this node? if (TotalCount() == 0) { should_delete_node = CheckShouldDeleteNode(); return; } switch (frustum.Contains(m_NodeBounds)) { case FrustumContainResult::Contains: { L_PROFILE_SCOPE_ACCUMULATIVE("Octree Query - GatherAllDataSources NEW"); // Mmmm grab all of our contiguous data in one fell swoop, yummy result.insert( result.end(), m_Octree->m_DataSources.begin() + m_DataSourceIndex, m_Octree->m_DataSources.begin() + m_DataSourceIndex + TotalCount() ); break; } case FrustumContainResult::Intersects: { // Test intersection of each data source pertaining to ONLY this node for (int i = 0; i < m_DataSourceSize; i++) { try { const auto& data = m_Octree->m_DataSources.at(m_DataSourceIndex + i); if (frustum.Contains(data->Bounds) != FrustumContainResult::DoesNotContain) result.push_back(data); } catch (const std::out_of_range& ex) { L_CORE_ERROR("Octree Query - Data Out of Range Could Not Test Intersection."); L_CORE_ERROR(ex.what()); } } // If Node is not split, just break if (!IsNodeSplit()) break; // Query children for (int i = 0; i < m_ChildrenNodes.size(); i++) { if (m_ChildrenNodes[i]) { bool should_delete = false; m_ChildrenNodes[i]->Query(frustum, result, should_delete); if (should_delete) { m_ChildrenNodes[i]->Clear(); m_ChildrenNodes[i].reset(); m_ChildrenNodes[i] = nullptr; } } } break; } } } If anyone sees this and thinks I can implement a better design, please let me know.
| Task | Old | New | Diff | Performance Increase |
|---|---|---|---|---|
| Overall Octree Query | 0.036ms | 0.012ms | -0.024ms | +67% |
| Gather All Data Sources | 0.021ms | 0.004ms | -0.017ms | +81% |
| Approach | Culling Speed (ms) | Efficiency Compared to Brute Force |
|---|---|---|
| Brute Force Culling | 0.018ms | Baseline |
| Octree Query (Before) | 0.036ms | 2.0x slower |
| Octree Query (After) | 0.012ms | 1.5x faster |