- Add Behavior enum (IDLE..FLEE, 11 values) and Trigger enum (DONE, BLOCKED, TARGET) as runtime IntEnum classes (closes #297, closes #298) - Add entity label system: labels property (frozenset), add_label(), remove_label(), has_label(), constructor kwarg (closes #296) - Add cell_pos integer logical position decoupled from float draw_pos; grid_pos now aliases cell_pos; SpatialHash::updateCell() for cell-based bucket management; FOV/visibility uses cell_position (closes #295) - Add step callback and default_behavior properties to Entity for grid.step() turn management (closes #299) - Update updateVisibility, visible_entities, ColorLayer::updatePerspective to use cell_position instead of float position BREAKING: grid_pos no longer derives from float x/y position. Use cell_pos/grid_pos for logical position, draw_pos for render position. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
79 lines
2.9 KiB
C++
79 lines
2.9 KiB
C++
#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);
|
|
|
|
// Update entity position using integer cell coordinates (#295)
|
|
// Removes from old bucket and inserts into new based on cell_position
|
|
void updateCell(std::shared_ptr<UIEntity> entity, int old_x, int old_y);
|
|
|
|
// Query all entities at a specific cell (uses cell_position for matching)
|
|
// O(n) where n = entities in the bucket containing this cell
|
|
std::vector<std::shared_ptr<UIEntity>> queryCell(int x, int y) const;
|
|
|
|
// 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;
|
|
|
|
// Clear all entities from the hash
|
|
void clear();
|
|
|
|
// Get statistics for debugging
|
|
size_t bucketCount() const { return buckets.size(); }
|
|
|
|
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;
|
|
};
|