feat: Implement SpatialHash for O(1) entity spatial queries (closes #115)
Add SpatialHash class for efficient spatial queries on entities: - New SpatialHash.h/cpp with bucket-based spatial hashing - Grid.entities_in_radius(x, y, radius) method for O(k) queries - Automatic spatial hash updates on entity add/remove/move Benchmark results at 2,000 entities: - Single query: 16.2× faster (0.044ms → 0.003ms) - N×N visibility: 104.8× faster (74ms → 1ms) This enables efficient range queries for AI, visibility, and collision detection without scanning all entities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8f2407b518
commit
7d57ce2608
6 changed files with 477 additions and 15 deletions
81
src/SpatialHash.h
Normal file
81
src/SpatialHash.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#pragma once
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
|
||||
class UIEntity;
|
||||
|
||||
/**
|
||||
* SpatialHash - O(1) average spatial queries for entities (#115)
|
||||
*
|
||||
* Divides the grid into buckets and tracks which entities are in each bucket.
|
||||
* Queries only check entities in nearby buckets instead of all entities.
|
||||
*
|
||||
* Performance characteristics:
|
||||
* - Insert: O(1)
|
||||
* - Remove: O(n) where n = entities in bucket (typically small)
|
||||
* - Update position: O(n) where n = entities in bucket
|
||||
* - Query radius: O(k) where k = entities in checked buckets (vs O(N) for all entities)
|
||||
*/
|
||||
class SpatialHash {
|
||||
public:
|
||||
// Default bucket size of 32 cells balances memory and query performance
|
||||
explicit SpatialHash(int bucket_size = 32);
|
||||
|
||||
// Insert entity into spatial hash based on current position
|
||||
void insert(std::shared_ptr<UIEntity> entity);
|
||||
|
||||
// Remove entity from spatial hash
|
||||
void remove(std::shared_ptr<UIEntity> entity);
|
||||
|
||||
// Update entity position - call when entity moves
|
||||
// This removes from old bucket and inserts into new bucket if needed
|
||||
void update(std::shared_ptr<UIEntity> entity, float old_x, float old_y);
|
||||
|
||||
// Query all entities within radius of a point
|
||||
// Returns entities whose positions are within the circular radius
|
||||
std::vector<std::shared_ptr<UIEntity>> queryRadius(float x, float y, float radius) const;
|
||||
|
||||
// Query all entities within a rectangular region
|
||||
std::vector<std::shared_ptr<UIEntity>> queryRect(float x, float y, float width, float height) const;
|
||||
|
||||
// Clear all entities from the hash
|
||||
void clear();
|
||||
|
||||
// Get statistics for debugging
|
||||
size_t bucketCount() const { return buckets.size(); }
|
||||
size_t totalEntities() const;
|
||||
|
||||
private:
|
||||
int bucket_size;
|
||||
|
||||
// Hash function for bucket coordinates
|
||||
struct PairHash {
|
||||
size_t operator()(const std::pair<int, int>& p) const {
|
||||
// Combine hash of both coordinates
|
||||
return std::hash<int>()(p.first) ^ (std::hash<int>()(p.second) << 16);
|
||||
}
|
||||
};
|
||||
|
||||
// Map from bucket coordinates to list of entities in that bucket
|
||||
// Using weak_ptr to avoid preventing entity deletion
|
||||
std::unordered_map<std::pair<int, int>, std::vector<std::weak_ptr<UIEntity>>, PairHash> buckets;
|
||||
|
||||
// Get bucket coordinates for a world position
|
||||
std::pair<int, int> getBucket(float x, float y) const {
|
||||
return {
|
||||
static_cast<int>(std::floor(x / bucket_size)),
|
||||
static_cast<int>(std::floor(y / bucket_size))
|
||||
};
|
||||
}
|
||||
|
||||
// Get all bucket coordinates that overlap with a radius query
|
||||
std::vector<std::pair<int, int>> getBucketsInRadius(float x, float y, float radius) const;
|
||||
|
||||
// Get all bucket coordinates that overlap with a rectangle
|
||||
std::vector<std::pair<int, int>> getBucketsInRect(float x, float y, float width, float height) const;
|
||||
|
||||
// Clean expired weak_ptrs from a bucket
|
||||
void cleanBucket(std::vector<std::weak_ptr<UIEntity>>& bucket);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue