首先,需要知道AOI(Area of Interest)是什么。AOI通常用于游戏或者分布式系统中,用来管理玩家或对象的视野范围,只关注感兴趣区域内的其他对象,减少不必要的通信和数据传输。
如文章标题,我们要求结合网格和十字链表两种方法来实现AOI。网格法是将地图划分成多个格子,每个格子管理其中的对象,这样可以快速查找周围的对象。十字链表可能是指每个对象维护两个链表,一个横向(x轴)和一个纵向(y轴)的链表,方便快速插入和删除,同时遍历附近的节点。
接下来,我需要确定如何将这两种结构结合起来。可能的思路是,使用网格划分区域,每个网格内的对象用十字链表来管理。这样在查找周围对象时,可以快速定位到附近的网格,然后遍历这些网格中的十字链表,找到在视野范围内的对象。
首先,网格的实现。每个网格需要记录该区域的坐标范围,以及该区域内的对象链表。或者,可能每个对象的位置信息会被映射到对应的网格中,而每个网格维护两个方向的链表?或者,十字链表是全局的,按照坐标顺序排列,而网格用于快速定位邻近区域?
可能需要更详细的结构设计。例如,每个对象在加入AOI管理时,会被分配到对应的网格中。同时,这些对象在全局的十字链表中按x和y坐标排序,方便快速遍历附近的节点。当需要检测视野内的对象时,先通过网格确定可能处于视野范围内的区域,然后利用十字链表快速遍历这些区域内的对象,进行精确的距离判断。或者,十字链表本身用于维护每个轴上的对象顺序,这样在遍历时,可以根据坐标范围快速跳过不在视野内的对象。比如,在x轴上按坐标排序的链表,当查找某个x范围内的对象时,可以快速定位链表的起始和结束节点,然后遍历这部分节点,同时在y轴上进行同样的判断。
现在,需要具体设计这两个结构。网格法的AOI通常将整个地图划分为多个小格子,每个对象属于某个格子,当对象移动时,检查是否跨越格子边界,如果是,则从一个格子移除,加入另一个格子。查找周围对象时,只需检查周围几个格子内的对象。这种方法效率高,但可能有精度问题,比如视野范围跨越多个格子时,需要检查多个格子。而十字链表可能是指,每个对象在x和y方向上有前驱和后继指针,形成一个有序的链表。这样,当需要查找某个坐标范围内的对象时,可以从当前位置向前和向后遍历,直到超出范围。这种方法可以高效地遍历特定区域内的对象,但插入和删除操作可能较慢,因为需要维持有序性。
结合这两种方法的话,可能需要用网格来管理空间分区,每个网格内的对象使用十字链表来维护。或者,使用十字链表来维护整个对象的全局顺序,而网格用于快速缩小搜索范围。可能需要进一步理清思路。假设网格用于空间划分,每个网格包含一个十字链表,管理该网格内的对象。当查找周围对象时,先确定目标周围的网格,然后遍历这些网格中的十字链表,检查每个对象是否在视野范围内。这可能不够高效,因为十字链表在这里可能没有发挥作用,反而每个网格内的对象可能用普通链表即可。
另一种思路是,全局使用十字链表来维护所有对象在x和y轴上的顺序,同时使用网格来加速区域查询。例如,当需要查找某个区域内的对象时,利用网格快速定位到附近的格子,然后在这些格子中的十字链表中进行范围查询。这可能比较复杂,需要同时维护两种结构。或者,十字链表本身作为AOI的核心结构,每个对象在x和y轴上按坐标排序,形成两个方向的链表。这样,当需要查找某个区域内的对象时,可以沿着x链表找到起始点,然后遍历直到超出x范围,同时检查y坐标是否满足。这种方法不需要网格,但可能效率较低,尤其是在大规模对象的情况下。此时,网格可以作为辅助结构,例如将对象所在的网格记录下来,当对象移动时,如果跨网格,则更新网格信息,这样在查找时,可以快速定位到周围的网格,然后利用十字链表遍历这些网格中的对象。
这可能是一个可行的方案。具体来说,每个对象有一个位置,并被分配到对应的网格中。同时,所有对象在x和y轴上形成有序的十字链表。当需要查找视野内的对象时,先确定视野覆盖的网格,然后针对这些网格中的每个对象,检查是否在十字链表的视野范围内。
现在,我需要具体设计这两个结构。例如,网格部分,每个网格包含一个十字链表的头节点,或者每个网格中的对象在x和y方向上有链表连接?或者,整个空间使用一个十字链表,而网格用于加速邻近区域的查询?假设整个空间使用一个大的十字链表,所有对象按x和y坐标排序,形成双向链表。当要查找某个区域内的对象时,可以从中间位置开始,向左右或上下遍历,直到超出范围。而网格的作用可能在这里是帮助快速定位到链表中的某个节点,从而减少遍历的时间。
不过这可能比较复杂。可能更合理的做法是,每个网格维护自己的十字链表,即在该网格内的对象按x和y坐标排序。这样,当需要查找周围的网格时,可以合并这些网格的十字链表,并快速遍历。但这样的话,每个网格的十字链表可能很小,维护起来可能没有太大必要。或许应该将整个空间分为网格,每个网格中的对象用普通的链表或集合保存,当需要查找时,遍历周围网格的对象,然后用十字链表结构来快速过滤出在视野范围内的对象。
或许,正确的做法是,使用网格来分割空间,每个网格内的对象用十字链表来管理。例如,每个网格有一个水平链表和一个垂直链表,对象在加入网格时,按坐标插入到链表的适当位置。当需要查询某个区域时,首先确定覆盖的网格,然后对这些网格中的十字链表进行范围查询,比如在水平链表上找到x范围内的对象,同时在垂直链表上检查y范围。
或者,十字链表是全局的,所有对象按x和y坐标排序,形成两个方向的有序链表。同时,每个对象记录所在的网格。当对象移动时,如果跨网格,则更新网格信息,并从链表中删除再插入新的位置。这样,当需要查询视野范围内的对象时,可以先用网格确定周围的网格,然后利用十字链表快速遍历这些网格中的对象,或者直接遍历整个十字链表中视野范围内的部分。这可能结合了两种方法的优点:网格用于快速定位邻近区域,而十字链表用于快速遍历有序的对象,减少检查的数量。
现在,我需要考虑如何具体实现这两种结构的结合。例如,每个对象在加入时,根据其坐标被分配到对应的网格,同时被插入到全局的x和y链表中,保持有序。当对象移动时,检查是否还在原网格,如果不在,则移动到新网格,并在链表中调整位置。结合两种方法:首先使用网格快速筛选出可能位于视野范围内的网格,然后在这些网格中使用十字链表的有序性进行范围查询,比如在x链表中找到起点和终点,然后遍历这些节点,并检查y坐标是否符合条件。
这样的结合可能提高查询效率,因为网格减少了需要检查的网格数量,而十字链表的有序性使得在每个网格中可以快速定位到起始点,减少遍历次数。
接下来,我需要考虑如何用C++实现这样的结构。首先,定义Object类,每个对象有x和y坐标,以及前后左右指针用于十字链表。同时,每个对象可能属于一个网格,网格可以用一个二维的网格坐标来表示,比如每个网格的大小是固定的,比如每个格子是100x100单位。
Grid类可能需要管理多个网格,每个网格有一个对象列表。或者,每个网格维护两个链表,x链表和y链表,按坐标排序。或者,整个空间维护一个全局的十字链表,而每个对象同时属于某个网格。
将地图划分为网格,每个网格中的对象按x和y坐标维护两个链表(即十字链表)。这样,每个网格内的对象在x和y方向上是局部有序的。当需要查询某个区域时,首先收集所有相关的网格,然后对每个网格中的十字链表进行范围查询,这样每个网格内的查询可以利用有序链表快速定位。例如,假设视野范围是某个矩形区域,首先确定覆盖的网格,然后对每个网格中的x链表进行遍历,找到x在范围内的对象,同时检查y是否符合条件。这可能减少每个网格内的遍历次数,因为链表是有序的,可以快速跳过不符合条件的对象。这样的结构结合了网格的空间划分和十字链表的有序性,可能提高查询效率。
现在,开始编写代码的大纲。
首先,定义对象的结构,每个对象需要存储坐标,以及前后指针用于x和y链表:
struct AOIObject {
int id;
float x;
float y;
// 网格中的坐标
int gridX;
int gridY;
// 十字链表指针
AOIObject* xPrev;
AOIObject* xNext;
AOIObject* yPrev;
AOIObject* yNext;
};
然后,网格的结构,每个网格维护x和y链表的头尾指针:
struct Grid {
AOIObject* xHead;
AOIObject* xTail;
AOIObject* yHead;
AOIObject* yTail;
};
AOI管理类需要管理所有的网格,以及提供插入、删除、移动对象的方法:
class AOIManager {
public:
AOIManager(float gridWidth, float gridHeight, float mapWidth, float mapHeight);
void AddObject(AOIObject* obj);
void RemoveObject(AOIObject* obj);
void UpdateObject(AOIObject* obj, float newX, float newY);
std::vector GetNearbyObjects(AOIObject* obj, float radius);
private:
float gridWidth_;
float gridHeight_;
int gridCols_;
int gridRows_;
std::vector> grids_;
// 其他辅助方法...
};
当添加对象时,计算其所在的网格,然后插入到对应的Grid的x和y链表中,保持有序:
void AOIManager::AddObject(AOIObject* obj) {
// 计算网格坐标
int gridX = static_cast(obj->x / gridWidth_);
int gridY = static_cast(obj->y / gridHeight_);
obj->gridX = gridX;
obj->gridY = gridY;
// 获取对应的网格
Grid& grid = grids_[gridY][gridX];
// 插入到x链表,按x坐标排序
AOIObject* current = grid.xHead;
AOIObject* prev = nullptr;
while (current && current->x < obj->x) {
prev = current;
current = current->xNext;
}
if (prev) {
prev->xNext = obj;
obj->xPrev = prev;
}
else {
grid.xHead = obj;
obj->xPrev = nullptr;
}
if (current) {
current->xPrev = obj;
obj->xNext = current;
}
else {
grid.xTail = obj;
obj->xNext = nullptr;
}
// 同样的逻辑插入到y链表,按y坐标排序
// ...
}
这里需要处理x和y两个方向的链表插入,保持有序。类似代码需要处理y链表。
当对象移动时,检查是否跨网格:
void AOIManager::UpdateObject(AOIObject* obj, float newX, float newY) {
int newGridX = static_cast(newX / gridWidth_);
int newGridY = static_cast(newY / gridHeight_);
if (newGridX != obj->gridX || newGridY != obj->gridY) {
// 需要移动网格
RemoveObject(obj);
obj->x = newX;
obj->y = newY;
AddObject(obj);
}
else {
// 同一网格内,只需更新坐标,并调整链表中的位置
obj->x = newX;
obj->y = newY;
// 调整x链表中的位置
AdjustPositionInGridX(obj);
// 调整y链表中的位置
AdjustPositionInGridY(obj);
}
}
调整链表位置的函数需要处理从链表中取出对象,然后重新插入到正确的位置。例如:
void AOIManager::AdjustPositionInGridX(AOIObject* obj) {
Grid& grid = grids_[obj->gridY][obj->gridX];
// 从原位置断开
if (obj->xPrev) {
obj->xPrev->xNext = obj->xNext;
}
else {
grid.xHead = obj->xNext;
}
if (obj->xNext) {
obj->xNext->xPrev = obj->xPrev;
}
else {
grid.xTail = obj->xPrev;
}
// 重新插入到正确的位置
AOIObject* current = grid.xHead;
AOIObject* prev = nullptr;
while (current && current->x < obj->x) {
prev = current;
current = current->xNext;
}
if (prev) {
prev->xNext = obj;
obj->xPrev = prev;
}
else {
grid.xHead = obj;
obj->xPrev = nullptr;
}
if (current) {
current->xPrev = obj;
obj->xNext = current;
}
else {
grid.xTail = obj;
obj->xNext = nullptr;
}
}
同样的逻辑适用于y链表。
查询附近的对象时,首先确定视野范围覆盖的网格:
std::vector AOIManager::GetNearbyObjects(AOIObject* obj, float radius) {
float x = obj->x;
float y = obj->y;
float left = x - radius;
float right = x + radius;
float top = y + radius;
float bottom = y - radius;
int startGridX = static_cast(left / gridWidth_);
int endGridX = static_cast(right / gridWidth_);
int startGridY = static_cast(bottom / gridHeight_);
int endGridY = static_cast(top / gridHeight_);
startGridX = std::max(0, startGridX);
endGridX = std::min(gridCols_ - 1, endGridX);
startGridY = std::max(0, startGridY);
endGridY = std::min(gridRows_ - 1, endGridY);
std::vector result;
for (int gy = startGridY; gy <= endGridY; ++gy)
{
for (int gx = startGridX; gx <= endGridX; ++gx)
{
Grid& grid = grids_[gy][gx];
// 遍历该网格的x链表,找到在[left, right]范围内的对象
AOIObject* current = grid.xHead;
while (current)
{
if (current->x >= left && current->x <= right)
{
// 检查y是否在[bottom, top]范围内
if (current->y >= bottom && current->y <= top)
{
// 计算实际距离是否在半径内(如果是圆形视野的话)
float dx = current->x - x;
float dy = current->y - y;
if (dx*dx + dy*dy <= radius*radius) {
result.push_back(current);
}
}
}
else if (current->x > right) {
break; // x链表是有序的,后面的都大于right,无需继续
}
current = current->xNext;
}
}
}
return result;
}
这样,每个网格中的x链表是有序的,所以一旦当前节点的x超过right就可以停止遍历该网格的链表。这减少了需要检查的节点数量。y坐标的判断则是在每个符合条件的x节点上进行。这可能是一个有效的结合网格和十字链表的AOI实现。每个网格内的x和y方向链表是有序的,因此在查询时,可以快速跳过不符合条件的对象,提高效率。
现在,需要验证这个设计是否正确,是否存在问题。例如,当对象移动时,如果它还在同一个网格中,那么需要调整在链表中的位置,这需要正确地从链表中移除并重新插入。如果网格的大小设置得比较大,那么每个网格中的对象数量可能较多,链表的插入和删除操作可能比较耗时。如果网格设置较小,则链表操作较快,但需要处理更多的网格在查询时。
可能存在的优化点:当对象在同一个网格内移动时,调整链表的位置,但由于网格内的链表是有序的,移动可能导致频繁的链表调整。如果网格足够小,对象在同一网格内的移动可能不会导致频繁的链表重排,因为坐标变化不大。或者,如果网格较大,可能链表中的顺序调整会更频繁。
总的来说,这样的设计结合了网格的空间划分和链表的有序性,可以在查询时快速过滤对象,减少需要检查的数量,同时网格帮助缩小搜索范围。
现在,编写一个具体的例子,比如创建一些对象,并测试AOI功能。例如:
int main() {
AOIManager aoi(100.0f, 100.0f, 1000.0f, 1000.0f); // 每个网格100x100,地图大小1000x1000
AOIObject obj1{1, 50.0f, 50.0f};
AOIObject obj2{2, 150.0f, 150.0f};
AOIObject obj3{3, 250.0f, 250.0f};
aoi.AddObject(&obj1);
aoi.AddObject(&obj2);
aoi.AddObject(&obj3);
// 查询obj1附近半径100的对象
auto nearby = aoi.GetNearbyObjects(&obj1, 100.0f);
for (auto obj : nearby) {
std::cout << "Nearby object id: " << obj->id << std::endl;
}
// 移动obj2到更远的位置
aoi.UpdateObject(&obj2, 300.0f, 300.0f);
nearby = aoi.GetNearbyObjects(&obj1, 100.0f);
// 现在应该只有obj1自己,或者包括其他对象?
// 需要具体计算距离
return 0;
}
这只是一个简单示例,实际需要根据坐标和半径计算距离是否正确。
可能的问题:在网格划分时,是否正确地处理了边界?例如,当对象的x坐标正好是网格的边界时,是否分配到正确的网格?在计算gridX和gridY时,应该使用整数除法,例如,假设网格的索引从0开始,每个网格的宽度是100,那么坐标099属于gridX=0,100199属于gridX=1,等等。因此,计算gridX时应为static_cast(x / gridWidth_),这可能需要处理浮点数精度问题。
另外,在GetNearbyObjects函数中,是否考虑了所有可能的覆盖网格?比如,当视野半径覆盖多个网格时,是否正确遍历所有相关的网格?
综上,这样的代码框架结合了网格和十字链表两种结构,网格用于空间划分,每个网格内的对象按x和y坐标维护有序链表,以提高查询效率。这应该满足我们的需求。
#include
#include
#include
#include
struct AOIObject {
int id;
float x, y;
int gridX, gridY;
AOIObject* xPrev = nullptr;
AOIObject* xNext = nullptr;
AOIObject* yPrev = nullptr;
AOIObject* yNext = nullptr;
AOIObject(int id, float x, float y) : id(id), x(x), y(y) {}
};
struct Grid {
AOIObject* xHead = nullptr;
AOIObject* xTail = nullptr;
AOIObject* yHead = nullptr;
AOIObject* yTail = nullptr;
};
class AOIManager {
private:
float gridWidth, gridHeight;
int gridCols, gridRows;
std::vector> grids;
public:
AOIManager(float gw, float gh, float mapWidth, float mapHeight)
: gridWidth(gw), gridHeight(gh)
{
gridCols = static_cast(ceil(mapWidth / gw));
gridRows = static_cast(ceil(mapHeight / gh));
grids.resize(gridRows, std::vector(gridCols));
}
// 插入对象到网格并维护有序链表
void AddObject(AOIObject* obj) {
UpdateGridPosition(obj);
InsertToGrid(obj);
}
// 从网格中移除对象
void RemoveObject(AOIObject* obj) {
RemoveFromGrid(obj);
}
// 更新对象位置
void UpdateObject(AOIObject* obj, float newX, float newY) {
RemoveFromGrid(obj);
obj->x = newX;
obj->y = newY;
UpdateGridPosition(obj);
InsertToGrid(obj);
}
// 获取周围对象
std::vector GetNearbyObjects(AOIObject* obj, float radius) {
std::vector result;
float x = obj->x, y = obj->y;
int minGX = std::max(0, static_cast((x - radius) / gridWidth));
int maxGX = std::min(gridCols-1, static_cast((x + radius) / gridWidth));
int minGY = std::max(0, static_cast((y - radius) / gridHeight));
int maxGY = std::min(gridRows-1, static_cast((y + radius) / gridHeight));
for (int gy = minGY; gy <= maxGY; ++gy) {
for (int gx = minGX; gx <= maxGX; ++gx) {
CheckGrid(gy, gx, x, y, radius, result);
}
}
return result;
}
private:
// 更新对象所属网格
void UpdateGridPosition(AOIObject* obj) {
obj->gridX = static_cast(obj->x / gridWidth);
obj->gridY = static_cast(obj->y / gridHeight);
obj->gridX = std::clamp(obj->gridX, 0, gridCols-1);
obj->gridY = std::clamp(obj->gridY, 0, gridRows-1);
}
// 将对象插入到对应网格的有序链表中
void InsertToGrid(AOIObject* obj) {
Grid& grid = grids[obj->gridY][obj->gridX];
// X轴插入
if (!grid.xHead || obj->x < grid.xHead->x) {
obj->xNext = grid.xHead;
if (grid.xHead) grid.xHead->xPrev = obj;
grid.xHead = obj;
} else {
AOIObject* current = grid.xHead;
while (current->xNext && current->xNext->x < obj->x) {
current = current->xNext;
}
obj->xNext = current->xNext;
if (current->xNext) current->xNext->xPrev = obj;
current->xNext = obj;
obj->xPrev = current;
}
// Y轴插入(类似X轴逻辑)
// ...
}
// 从链表中移除对象
void RemoveFromGrid(AOIObject* obj) {
Grid& grid = grids[obj->gridY][obj->gridX];
// X轴移除
if (obj->xPrev) obj->xPrev->xNext = obj->xNext;
else grid.xHead = obj->xNext;
if (obj->xNext) obj->xNext->xPrev = obj->xPrev;
else grid.xTail = obj->xPrev;
// Y轴移除
// ...
}
// 检查指定网格内的对象
void CheckGrid(int gy, int gx, float x, float y, float r,
std::vector& result) {
Grid& grid = grids[gy][gx];
AOIObject* current = grid.xHead;
float rSq = r * r;
while (current) {
float dx = current->x - x;
if (dx > r) break; // X轴超出范围
float dy = current->y - y;
if (dx*dx + dy*dy <= rSq) {
result.push_back(current);
}
current = current->xNext;
}
}
};
int main() {
AOIManager aoi(100.0f, 100.0f, 1000.0f, 1000.0f);
AOIObject obj1(1, 50.0f, 50.0f);
AOIObject obj2(2, 150.0f, 150.0f);
AOIObject obj3(3, 160.0f, 160.0f);
aoi.AddObject(&obj1);
aoi.AddObject(&obj2);
aoi.AddObject(&obj3);
// 查询obj1周围半径100的对象
auto nearby = aoi.GetNearbyObjects(&obj1, 100.0f);
std::cout << "Nearby objects count: " << nearby.size() << "\n";
for (auto obj : nearby) {
std::cout << "Object " << obj->id
<< " at (" << obj->x << ", " << obj->y << ")\n";
}
return 0;
}
示例代码只实现了X轴的链表,而Y轴的插入和删除逻辑被省略了。此外,我们的扩展建议包括优化链表操作、添加进入离开视野的回调,以及支持不同形状的检测区域。我需要逐一分析这些需求,并考虑如何在代码中实现。
首先,第一个扩展建议是“添加Y轴链表处理”。在之前的代码中,InsertToGrid和RemoveFromGrid函数只处理了X轴的链表,而Y轴的逻辑被注释掉了。所以,我需要补全这部分代码,确保在插入和删除对象时,同时维护X和Y两个方向的有序链表。这需要仔细检查插入和删除的逻辑,确保两个链表的指针正确更新,避免出现悬空指针或链表断裂的情况。
接下来是“优化链表插入/删除操作”。当前代码中使用的是线性搜索来找到插入位置,这在最坏情况下时间复杂度是O(n)。如果对象的位置经常变化,这样的效率可能不够。优化方法可以考虑使用跳表或者平衡二叉树,但考虑到实现的复杂性,可能在示例代码中保持简单,或者采用更高效的搜索方式,比如二分查找,但由于链表结构不支持随机访问,可能需要调整数据结构,比如使用双向链表的同时记录中间节点,但这会增加复杂度。因此,可能暂时保持原样,但添加注释说明可能的优化方向。
第三个建议是“添加对象离开/进入视野的回调”。这需要AOIManager能够跟踪对象的移动,并在对象进入或离开其他对象的视野时触发回调函数。实现这一点需要在对象的位置更新时,比较新旧网格周围的区域,找出新增和移除的对象,然后触发相应的回调。这涉及到维护每个对象的观察者列表,或者全局的事件处理机制。考虑到代码示例的简洁性,可能需要设计一个回调接口,让用户注册进入和离开的事件处理函数,并在GetNearbyObjects或UpdateObject时触发这些回调。
最后一个建议是“支持不同形状的检测区域”。当前的GetNearbyObjects使用圆形区域,将来可能希望支持矩形、扇形等其他形状。这需要修改CheckGrid函数中的距离计算部分,根据不同的形状进行判断。例如,矩形区域只需要检查坐标是否在特定范围内,而扇形可能需要计算角度。这部分可以通过策略模式,将区域检测逻辑抽象为不同的类,根据传入的参数选择不同的检测策略。但在示例代码中,可能需要保持简单,添加多个检测函数,如GetNearbyInRectangle或GetNearbyInSector。
现在,我需要考虑如何逐步实现这些扩展,并在代码中展示出来。首先,补全Y轴链表的处理,确保插入和删除时同时更新X和Y链表。然后,优化插入操作,可能保持线性搜索但确保代码正确性。接着,添加回调机制,在对象移动时比较新旧附近的物体,触发进入和离开事件。最后,修改GetNearbyObjects以支持不同形状的区域,比如添加一个形状参数,或者不同的函数。
在实现回调时,需要注意避免在回调函数中修改AOIManager的状态,以免引起不可预见的错误。此外,处理进入和离开事件需要记录之前的附近对象列表,并在更新后比较差异。这可能需要为每个对象维护一个上一次查询的结果,或者在每次更新时比较新旧两个结果。
对于不同形状的区域,可能需要修改CheckGrid函数中的条件判断。例如,矩形区域只需检查x和y的差值是否在范围内,而无需计算平方距离。扇形区域则需要计算角度和距离,这可能更复杂。
在编写代码时,需要保持示例的简洁性和可读性,避免过度设计,同时展示关键部分的实现。例如,在补全Y轴链表时,可以复制X轴的逻辑,但需要修改变量为y方向的。在添加回调时,可以定义两个函数指针或使用C++的std::function来允许用户注册回调函数。
可能遇到的挑战包括正确处理链表的双向指针,确保在插入和删除时不会遗漏任何指针的更新,以及在处理回调时高效地比较对象集合的变化。此外,支持不同形状的区域需要灵活的条件判断,可能需要使用策略模式或模板方法,但在示例中可能直接通过参数控制。
总结步骤:
需要确保每一步的代码正确,并进行测试,例如在main函数中添加移动对象并触发回调的案例。同时,需要处理可能的边界条件,例如对象移动到网格边界时的处理,或者多个对象在同一位置的情况。
#include
#include
#include
#include
#include
#include
// 区域形状基类
class AOIStrategy {
public:
virtual bool Check(float dx, float dy) const = 0;
virtual ~AOIStrategy() = default;
};
// 圆形区域策略
class CircleStrategy : public AOIStrategy {
float radiusSq;
public:
explicit CircleStrategy(float r) : radiusSq(r*r) {}
bool Check(float dx, float dy) const override {
return dx*dx + dy*dy <= radiusSq;
}
};
// 矩形区域策略
class RectangleStrategy : public AOIStrategy {
float width, height;
public:
RectangleStrategy(float w, float h) : width(w), height(h) {}
bool Check(float dx, float dy) const override {
return std::abs(dx) <= width/2 && std::abs(dy) <= height/2;
}
};
struct AOIObject {
int id;
float x, y;
int gridX, gridY;
AOIObject* xPrev = nullptr;
AOIObject* xNext = nullptr;
AOIObject* yPrev = nullptr;
AOIObject* yNext = nullptr;
AOIObject(int id, float x, float y) : id(id), x(x), y(y) {}
};
struct Grid {
AOIObject* xHead = nullptr;
AOIObject* xTail = nullptr;
AOIObject* yHead = nullptr;
AOIObject* yTail = nullptr;
};
class AOIManager {
private:
float gridWidth, gridHeight;
int gridCols, gridRows;
std::vector> grids;
// 事件回调
std::function onEnter;
std::function onLeave;
// 优化插入的有序性维护
AOIObject* InsertSorted(AOIObject* head, AOIObject* obj, bool isXAxis) {
if (!head) return obj;
AOIObject** nextPtr = isXAxis ? &head->xNext : &head->yNext;
AOIObject** prevPtr = isXAxis ? &head->xPrev : &head->yPrev;
float compareVal = isXAxis ? obj->x : obj->y;
// 头节点处理
if ((isXAxis ? head->x : head->y) > compareVal) {
obj->xNext = head;
head->xPrev = obj;
return obj;
}
AOIObject* current = head;
while (current->xNext && (isXAxis ? current->xNext->x : current->xNext->y) < compareVal) {
current = isXAxis ? current->xNext : current->yNext;
}
obj->xNext = current->xNext;
if (current->xNext) {
current->xNext->xPrev = obj;
}
current->xNext = obj;
obj->xPrev = current;
return head;
}
public:
AOIManager(float gw, float gh, float mapWidth, float mapHeight)
: gridWidth(gw), gridHeight(gh)
{
gridCols = static_cast(ceil(mapWidth / gw));
gridRows = static_cast(ceil(mapHeight / gh));
grids.resize(gridRows, std::vector(gridCols));
}
// 注册事件回调
void SetCallbacks(std::function enter,
std::function leave) {
onEnter = enter;
onLeave = leave;
}
// 带事件通知的更新方法
void UpdateObjectWithEvent(AOIObject* obj, float newX, float newY) {
auto oldNearby = GetNearbyObjects(obj, 100.0f); // 假设视野半径100
RemoveFromGrid(obj);
obj->x = newX;
obj->y = newY;
UpdateGridPosition(obj);
InsertToGrid(obj);
auto newNearby = GetNearbyObjects(obj, 100.0f);
// 计算进入和离开的对象
std::unordered_set oldSet(oldNearby.begin(), oldNearby.end());
std::unordered_set newSet(newNearby.begin(), newNearby.end());
for (auto* o : newNearby) {
if (!oldSet.count(o) && o != obj && onEnter) {
onEnter(obj, o);
}
}
for (auto* o : oldNearby) {
if (!newSet.count(o) && o != obj && onLeave) {
onLeave(obj, o);
}
}
}
// 支持不同形状的查询
std::vector QueryArea(AOIObject* center, const AOIStrategy& strategy) {
std::vector result;
float x = center->x, y = center->y;
int minGX = std::max(0, static_cast((x - strategy.GetLeft()) / gridWidth));
int maxGX = std::min(gridCols-1, static_cast((x + strategy.GetRight()) / gridWidth));
int minGY = std::max(0, static_cast((y - strategy.GetBottom()) / gridHeight));
int maxGY = std::min(gridRows-1, static_cast((y + strategy.GetTop()) / gridHeight));
for (int gy = minGY; gy <= maxGY; ++gy) {
for (int gx = minGX; gx <= maxGX; ++gx) {
CheckGridWithStrategy(gy, gx, x, y, strategy, result);
}
}
return result;
}
private:
// 带策略的网格检查
void CheckGridWithStrategy(int gy, int gx, float x, float y,
const AOIStrategy& strategy,
std::vector& result) {
Grid& grid = grids[gy][gx];
// 同时利用X和Y轴有序性进行快速筛选
AOIObject* current = grid.xHead;
while (current) {
float dx = current->x - x;
float dy = current->y - y;
if (strategy.Check(dx, dy)) {
result.push_back(current);
}
current = current->xNext;
}
}
// 完整实现Y轴插入
void InsertToGrid(AOIObject* obj) {
Grid& grid = grids[obj->gridY][obj->gridX];
// X轴插入
grid.xHead = InsertSorted(grid.xHead, obj, true);
// Y轴插入
grid.yHead = InsertSorted(grid.yHead, obj, false);
}
// 完整实现Y轴移除
void RemoveFromGrid(AOIObject* obj) {
Grid& grid = grids[obj->gridY][obj->gridX];
// X轴移除
if (obj->xPrev) obj->xPrev->xNext = obj->xNext;
else grid.xHead = obj->xNext;
if (obj->xNext) obj->xNext->xPrev = obj->xPrev;
// Y轴移除
if (obj->yPrev) obj->yPrev->yNext = obj->yNext;
else grid.yHead = obj->yNext;
if (obj->yNext) obj->yNext->yPrev = obj->yPrev;
}
// 其他原有方法保持不变...
};
// 示例用法
int main() {
AOIManager aoi(100.0f, 100.0f, 1000.0f, 1000.0f);
// 设置事件回调
aoi.SetCallbacks(
[](AOIObject* a, AOIObject* b) {
std::cout << "Object " << a->id << " enter " << b->id << "\n";
},
[](AOIObject* a, AOIObject* b) {
std::cout << "Object " << a->id << " leave " << b->id << "\n";
}
);
AOIObject obj1(1, 50.0f, 50.0f);
AOIObject obj2(2, 150.0f, 150.0f);
aoi.AddObject(&obj1);
aoi.AddObject(&obj2);
// 移动对象并触发事件
aoi.UpdateObjectWithEvent(&obj1, 180.0f, 180.0f);
// 使用不同形状查询
RectangleStrategy rect(80.0f, 80.0f);
auto inRect = aoi.QueryArea(&obj1, rect);
std::cout << "Objects in rectangle: " << inRect.size() << "\n";
return 0;
}
InsertToGrid
和RemoveFromGrid
中同时维护X/Y方向的有序链表InsertSorted
处理排序逻辑std::function
支持自定义进入/离开事件UpdateObjectWithEvent
中通过新旧集合对比触发事件AOIStrategy
基类实现不同形状检测QueryArea
方法支持任意形状查询事件管理:
// 注册自定义回调
aoi.SetCallbacks(
[](AOIObject* watcher, AOIObject* target) {
// 处理进入视野逻辑
},
[](AOIObject* watcher, AOIObject* target) {
// 处理离开视野逻辑
}
);
自定义区域形状:
// 实现扇形区域策略
class SectorStrategy : public AOIStrategy {
float radius;
float angle;
public:
SectorStrategy(float r, float a) : radius(r), angle(a) {}
bool Check(float dx, float dy) const override {
float dist = sqrt(dx*dx + dy*dy);
if (dist > radius) return false;
float objAngle = atan2(dy, dx);
return fabs(objAngle) <= angle/2;
}
};
// 使用自定义策略
SectorStrategy sector(100.0f, M_PI/3); // 60度扇形
auto inSector = aoi.QueryArea(&obj1, sector);
性能调优:
// 调整网格大小平衡查询与维护开销
AOIManager aoi(50.0f, 50.0f, 1000.0f, 1000.0f); // 更细密的网格
// 使用空间索引加速查询
void OptimizedInsert(AOIObject* obj) {
// 结合空间索引(如四叉树)进行多层索引
}