|
#pragma once |
|
|
|
#include <cstdint> |
|
#include <cmath> |
|
#include <map> |
|
#include <string> |
|
#include <unordered_set> |
|
|
|
#include <algorithm> |
|
#include <unordered_map> |
|
|
|
#define CCAOI_POSITION_TEMPLATE template<typename POSITION = double> |
|
#define CCAOI_ENTITY_TEMPLATE template<typename POSITION = double, typename EID = std::string> |
|
#define CCAOI_BASIC_TEMPLATE CCAOI_ENTITY_TEMPLATE |
|
|
|
CCAOI_ENTITY_TEMPLATE |
|
struct ccaoi_entity_t { |
|
POSITION x, y; |
|
EID eid; // 实体`ID`(玩家/NPC/其他...) |
|
}; |
|
|
|
CCAOI_POSITION_TEMPLATE |
|
struct ccaoi_range_t { /* 当前二维地图的有效区间 */ |
|
POSITION min_x, max_x; |
|
POSITION min_y, max_y; |
|
}; |
|
|
|
/* note: return `true` when x between r.min_x ~ r.max_x, y between r.min_y ~ r.max_y. */ |
|
CCAOI_POSITION_TEMPLATE |
|
inline bool ccaoi_in_range(const ccaoi_range_t<POSITION> &r, POSITION x, POSITION y) noexcept |
|
{ |
|
return (r.min_x <= x and r.max_x >= x) and (r.min_y <= y and r.max_y >= y); |
|
} |
|
|
|
CCAOI_POSITION_TEMPLATE |
|
inline bool ccaoi_in_around(const ccaoi_range_t<POSITION> &r, POSITION x, POSITION y) noexcept |
|
{ |
|
return ccaoi_in_range(r, x, y); |
|
} |
|
|
|
CCAOI_POSITION_TEMPLATE |
|
inline void ccaoi_wrap_around(ccaoi_range_t<POSITION> &r, POSITION x1, POSITION x2, POSITION y1, POSITION y2, POSITION radius) noexcept |
|
{ |
|
r.min_x = std::min(x1, x2) - radius; r.max_x = std::max(x1, x2) + radius; |
|
r.min_y = std::min(y1, y2) - radius; r.max_y = std::max(y1, y2) + radius; |
|
} |
|
|
|
/* ── ccaoi_normal: O(n) 全表扫描实现 ── */ |
|
CCAOI_BASIC_TEMPLATE |
|
class ccaoi_normal |
|
{ |
|
using ENTITY = struct ccaoi_entity_t< POSITION, EID >; |
|
using MAP = std::unordered_map< EID, ENTITY >; |
|
using RANGE = struct ccaoi_range_t< POSITION >; |
|
public: |
|
using CCAOI_CALLBACK = void(*)(const ENTITY&, void*); |
|
private: |
|
MAP _map; // 实体表 |
|
POSITION _radius; // 半径长度 |
|
RANGE _r; // aoi范围 |
|
void _find_around(const ENTITY &self, const RANGE &r, CCAOI_CALLBACK cb, void *udata) noexcept |
|
{ |
|
for (auto &iter : _map) |
|
{ |
|
const auto &val = iter.second; |
|
if (&val == &self) continue; |
|
if (ccaoi_in_around<POSITION>(r, val.x, val.y)) |
|
cb(val, udata); |
|
} |
|
} |
|
public: |
|
explicit ccaoi_normal(RANGE r, POSITION radius): _radius(radius), _r(r) {} |
|
|
|
const ENTITY* find(const EID &eid) const noexcept |
|
{ |
|
auto it = _map.find(eid); |
|
return it != _map.end() ? &it->second : nullptr; |
|
} |
|
/* 进入 */ |
|
bool enter(const EID &eid, POSITION x, POSITION y, CCAOI_CALLBACK cb = nullptr, void *udata = nullptr) noexcept |
|
{ |
|
if (!ccaoi_in_range(_r, x, y)) |
|
return false; |
|
|
|
auto result = _map.emplace(eid, ENTITY{x, y, eid}); |
|
if (!result.second) |
|
return false; |
|
|
|
if (cb) { |
|
RANGE r; ccaoi_wrap_around(r, x, x, y, y, _radius); |
|
_find_around(result.first->second, r, cb, udata); |
|
} |
|
return true; |
|
} |
|
|
|
/* 移动 */ |
|
bool move(const EID &eid, POSITION x, POSITION y, CCAOI_CALLBACK cb = nullptr, void *udata = nullptr) noexcept |
|
{ |
|
if (!ccaoi_in_range(_r, x, y)) |
|
return false; |
|
|
|
auto iter = _map.find(eid); |
|
if (iter == _map.end()) |
|
return false; |
|
|
|
ENTITY &e = iter->second; |
|
if (cb) { |
|
RANGE r; ccaoi_wrap_around(r, e.x, x, e.y, y, _radius); |
|
_find_around(e, r, cb, udata); |
|
} |
|
|
|
e.x = x; e.y = y; |
|
return true; |
|
} |
|
|
|
/* 离开 */ |
|
bool leave(const EID &eid, CCAOI_CALLBACK cb = nullptr, void *udata = nullptr) noexcept |
|
{ |
|
const auto iter = _map.find(eid); |
|
if (iter == _map.end()) |
|
return false; |
|
|
|
if (cb) { |
|
const ENTITY &e = iter->second; |
|
RANGE r; ccaoi_wrap_around(r, e.x, e.x, e.y, e.y, _radius); |
|
_find_around(e, r, cb, udata); |
|
} |
|
|
|
_map.erase(eid); |
|
return true; |
|
} |
|
}; |
|
|
|
/* ── ccaoi_grid: 九宫格 O(1) 空间索引实现 ── |
|
* 格子内存储 const ENTITY* 而非 EID,消除 _find_around 的二次 hash 查找。 |
|
* enter/move/leave 时只搜索目标实体周围 3×3 格子内的实体, |
|
* 再通过 ccaoi_in_around 精确过滤,与 ccaoi_normal 语义完全一致。 |
|
*/ |
|
CCAOI_BASIC_TEMPLATE |
|
class ccaoi_grid |
|
{ |
|
using ENTITY = struct ccaoi_entity_t< POSITION, EID >; |
|
using MAP = std::unordered_map< EID, ENTITY >; |
|
using RANGE = struct ccaoi_range_t< POSITION >; |
|
public: |
|
using CCAOI_CALLBACK = void(*)(const ENTITY&, void*); |
|
private: |
|
using cell_key_t = std::pair<int64_t, int64_t>; |
|
using cell_set_t = std::unordered_set<const ENTITY*>; |
|
using grid_t = std::map<cell_key_t, cell_set_t>; |
|
|
|
MAP _map; |
|
POSITION _radius; |
|
RANGE _r; |
|
grid_t _grid; |
|
|
|
int64_t _cell_coord(POSITION v) const noexcept |
|
{ |
|
return static_cast<int64_t>(std::floor( |
|
static_cast<double>(v) / static_cast<double>(_radius))); |
|
} |
|
|
|
cell_key_t _cell_of(POSITION x, POSITION y) const noexcept |
|
{ |
|
return {_cell_coord(x), _cell_coord(y)}; |
|
} |
|
|
|
void _grid_add(const ENTITY &e) noexcept |
|
{ |
|
_grid[_cell_of(e.x, e.y)].insert(&e); |
|
} |
|
|
|
void _grid_remove(const ENTITY &e) noexcept |
|
{ |
|
auto it = _grid.find(_cell_of(e.x, e.y)); |
|
if (it != _grid.end()) { |
|
it->second.erase(&e); |
|
if (it->second.empty()) |
|
_grid.erase(it); |
|
} |
|
} |
|
|
|
void _find_around(const EID &eid, const RANGE &r, CCAOI_CALLBACK cb, void *udata) noexcept |
|
{ |
|
int64_t min_cx = _cell_coord(r.min_x); |
|
int64_t max_cx = _cell_coord(r.max_x); |
|
int64_t min_cy = _cell_coord(r.min_y); |
|
int64_t max_cy = _cell_coord(r.max_y); |
|
|
|
auto lo = _grid.lower_bound({min_cx, min_cy}); |
|
auto hi = _grid.upper_bound({max_cx, max_cy}); |
|
for (auto it = lo; it != hi; ++it) { |
|
if (it->first.second < min_cy || it->first.second > max_cy) continue; |
|
for (const auto *val : it->second) { |
|
if (val->eid == eid) continue; |
|
if (ccaoi_in_around<POSITION>(r, val->x, val->y)) |
|
cb(*val, udata); |
|
} |
|
} |
|
} |
|
|
|
public: |
|
explicit ccaoi_grid(RANGE r, POSITION radius): _radius(radius), _r(r) {} |
|
|
|
const ENTITY* find(const EID &eid) const noexcept |
|
{ |
|
auto it = _map.find(eid); |
|
return it != _map.end() ? &it->second : nullptr; |
|
} |
|
|
|
bool enter(const EID &eid, POSITION x, POSITION y, CCAOI_CALLBACK cb = nullptr, void *udata = nullptr) noexcept |
|
{ |
|
if (!ccaoi_in_range(_r, x, y)) |
|
return false; |
|
auto result = _map.emplace(eid, ENTITY{x, y, eid}); |
|
if (!result.second) |
|
return false; |
|
_grid_add(result.first->second); |
|
|
|
if (cb) { |
|
RANGE r; ccaoi_wrap_around(r, x, x, y, y, _radius); |
|
_find_around(eid, r, cb, udata); |
|
} |
|
return true; |
|
} |
|
|
|
bool move(const EID &eid, POSITION x, POSITION y, CCAOI_CALLBACK cb = nullptr, void *udata = nullptr) noexcept |
|
{ |
|
if (!ccaoi_in_range(_r, x, y)) |
|
return false; |
|
auto iter = _map.find(eid); |
|
if (iter == _map.end()) |
|
return false; |
|
|
|
ENTITY &e = iter->second; |
|
POSITION old_x = e.x, old_y = e.y; |
|
|
|
if (cb) { |
|
RANGE r; ccaoi_wrap_around(r, old_x, x, old_y, y, _radius); |
|
_find_around(eid, r, cb, udata); |
|
} |
|
|
|
_grid_remove(e); |
|
e.x = x; e.y = y; |
|
_grid_add(e); |
|
return true; |
|
} |
|
|
|
bool leave(const EID &eid, CCAOI_CALLBACK cb = nullptr, void *udata = nullptr) noexcept |
|
{ |
|
const auto iter = _map.find(eid); |
|
if (iter == _map.end()) |
|
return false; |
|
|
|
const ENTITY &e = iter->second; |
|
if (cb) { |
|
RANGE r; ccaoi_wrap_around(r, e.x, e.x, e.y, e.y, _radius); |
|
_find_around(eid, r, cb, udata); |
|
} |
|
|
|
_grid_remove(e); |
|
_map.erase(eid); |
|
return true; |
|
} |
|
}; |