AOI之十字链表法

AOI之十字链表法1.简介AOI主要有九宫格、灯塔和十字链表的算法实现。本文阐述十字链表的实现2. 基本原理若是二维地图,将地图内的对象按照坐标值,从小到大分在x轴和y轴两个链表上。如果是三维地图,则还需要维护多一个z轴的链表3.基本接口 Add:对象进入场景Move:对象在场景内移动Leave:对象离开场景4.代码如下scene.h#ifndef__CScene_H__#define__CScene_…

大家好,又见面了,我是你们的朋友全栈君。

1. 简介

AOI主要有九宫格、灯塔和十字链表的算法实现。本文阐述十字链表的实现

2. 基本原理

若是二维地图,将地图内的对象按照坐标值,从小到大分在x轴和y轴两个链表上。如果是三维地图,则还需要维护多一个z轴的链表

3. 基本接口

 Add:对象进入场景

Move:对象在场景内移动

Leave:对象离开场景

4. 代码如下,注释非常详尽

scene.h

#ifndef __CScene_H__
#define __CScene_H__
#include <map>
#include <list>
using namespace std;

// 注意:本示例的AOI范围指的是以对象为中心的正方形

// 地图内的对象
class CEntity 
{
public:
	int x;			// X坐标
	int y;			// Y坐标
	int id;			// 对象的唯一ID
	int radius;		// 对象的AOI半径

	list<CEntity*>::iterator	x_pos;	// X链上的指针
	list<CEntity*>::iterator	y_pos;	// Y链上的指针

	CEntity(int _id, int _x, int _y, int _radius) : id(_id), x(_x), y(_y), radius(_radius) 
	{
	}
};

typedef map<int, CEntity*>	EntityMap;
typedef list<CEntity*>		EntityList;

// 场景
class CScene 
{
public:
    CScene();
    ~CScene();

	// 添加一个新的对象插入到指定坐标(x,y)
    void Add(int id, int x, int y, int distance = 2);

	// 移动一个对象,新位置点(x,y)
    void Move(int id, int x, int y);

	// 删除一个对象
    void Leave(int id);

private:
	// 获取指定对象的AOI对象列表
	void GetRangeSet(CEntity* pEntity, EntityMap* pEntityMap);

	// 更新对象CEntity到坐标(x,y)
    void UpdateEntityPosition(CEntity* pEntity, int x, int y);

private:
	EntityMap		m_mapEntity;	// 本场景所有的对象
	EntityList		m_listXEntity;	// X链
	EntityList		m_listYEntity;	// Y链
};

#endif  // __CScene_H__

scene.cpp

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "scene.h"

CScene::CScene() 
{
}

CScene::~CScene() 
{
	// 删除场景内的所有对象
    EntityMap::iterator tmp;
    for (EntityMap::iterator iter = m_mapEntity.begin(); iter != m_mapEntity.end(); ) 
	{
        tmp = iter;
        ++iter;
        delete tmp->second;
    }
}

// 添加一个新的对象插入到指定坐标(x,y)
void CScene::Add(int id, int x, int y, int distance) 
{
	// 已经存在该ID,直接返回
    if (m_mapEntity.find(id) != m_mapEntity.end()) 
	{
        return;
    }

	// 新创建一个对象
    CEntity* pEntity = new CEntity(id, x, y, distance);
    m_mapEntity[pEntity->id] = pEntity;

	EntityList::iterator iter;

	// 在X链上找到插入位置	
	for (iter = m_listXEntity.begin(); iter != m_listXEntity.end();iter++)
	{
		if ((*iter)->x > x)
		{
			break;
		}
	}
	
	// 在iter位置前面插入,同时记录下在X链上的位置
	pEntity->x_pos = m_listXEntity.insert(iter,pEntity);

	// 在Y链上找到插入位置	
	for (iter = m_listYEntity.begin(); iter != m_listYEntity.end();iter++)
	{
		if ((*iter)->y > y)
		{
			break;
		}
	}

	// 在iter位置前面插入,同时记录下在Y链上的位置
	pEntity->y_pos = m_listYEntity.insert(iter,pEntity);

	// 获取AOI列表
	EntityMap newMap;
	GetRangeSet(pEntity, &newMap);

	// 1. 把pEntity的进入AOI消息通知他们 
	// 2. 通知pEntity,他们进入了AOI
	for (EntityMap::iterator iter = newMap.begin(); iter != newMap.end(); ++iter) 
	{
		printf("send [%d:(%d,%d)]\t ENTER msg to obj [%d:(%d,%d)]\n",pEntity->id, pEntity->x, pEntity->y,iter->first, iter->second->x, iter->second->y);
		printf("send [%d:(%d,%d)]\t ENTER msg to obj [%d:(%d,%d)]\n",iter->first, iter->second->x, iter->second->y,pEntity->id, pEntity->x, pEntity->y);
	}
}

// 更新对象CEntity到坐标(x,y)
void CScene::UpdateEntityPosition(CEntity* pEntity, int x, int y) 
{
	// 原来的坐标
    int old_x = pEntity->x;
    int old_y = pEntity->y;

	// 新坐标
    pEntity->x = x;
    pEntity->y = y;

    EntityList::iterator iter;

	// 查找新的X坐标
	
	// 如果新坐标的x比旧坐标的x大,就往后面遍历
    if (pEntity->x > old_x) 
	{
        if (pEntity->x_pos != m_listXEntity.end()) 
		{
			// 取得原X指针位置
            iter = pEntity->x_pos;

			// 往后面移动一个节点,作为搜索起始节点
            ++iter;
            
			// 删除X链的旧节点
			m_listXEntity.erase(pEntity->x_pos);

			// 往后面遍历
            while (iter != m_listXEntity.end()) 
			{
				// 找到第一个x值大于新坐标x指的节点
                if ((*iter)->x > pEntity->x) 
				{
                    break;
                }
                ++iter;
            }

			// X链新位置上插入,并记录位置
			pEntity->x_pos = m_listXEntity.insert(iter, pEntity);
        }
    } 
	else if (x < old_x)  // 如果新坐标的x比旧坐标的x小,就往前面遍历
	{
        if (pEntity->x_pos != m_listXEntity.begin()) 
		{
			// 取得原X指针位置
            iter = pEntity->x_pos;

			// 往前面移动一个节点,作为搜索起始节点
            --iter;

			// 删除旧节点
            m_listXEntity.erase(pEntity->x_pos);

			// 往前面遍历
            while (iter != m_listXEntity.begin()) 
			{
				// 找到第一个x值小于新坐标x指的节点
                if ((*iter)->x < pEntity->x) 
				{
					// 要指向该节点的下一个节点,因为iter是第一个比pEntity的x值小的,那么后面一个节点才是大于等于x值的,应该插在这个节点前面才对
                    ++iter;
                    break;
                }
                --iter;
            }

			// X链新位置上插入,并记录位置
			pEntity->x_pos = m_listXEntity.insert(iter, pEntity);
        }
		else
		{
			// 不需处理,因为已经是最前面了,x,y坐标改了就行
		}
    }

    // 如果新坐标的y值比旧坐标的y值大,就往后面遍历
    if (y > old_y) 
	{
        if (pEntity->y_pos != m_listYEntity.end()) 
		{
			// 取得原y指针位置
            iter = pEntity->y_pos;

			// 往后面移动一个节点,作为搜索起始节点
            ++iter;

			// 删除Y链的旧节点
            m_listYEntity.erase(pEntity->y_pos);

			// 往后面遍历
            while (iter != m_listYEntity.end()) 
			{
				// 找到第一个y值大于新坐标y值的节点
                if ((*iter)->y > pEntity->y) 
				{
                    break;
                }
                ++iter;
            }

			// Y链新位置上插入,并记录位置
			pEntity->y_pos = m_listYEntity.insert(iter, pEntity);
        }
    } 
	else if (y < old_y) // 如果新坐标的x比旧坐标的x小,就往前面遍历
	{
        if (pEntity->y_pos != m_listYEntity.begin()) 
		{
			// 取得原y指针位置
            iter = pEntity->y_pos;

			// 往前面移动一个节点,作为搜索起始节点
            --iter;

			// 删除Y链的旧节点
            m_listYEntity.erase(pEntity->y_pos);

			// 往前面遍历
            while (iter != m_listYEntity.begin()) 
			{
				// 找到第一个y值小于新坐标y值的节点
                if (pEntity->y - (*iter)->y > 0) 
				{
					// 要指向该节点的下一个节点,因为iter是第一个比pEntity的y值小的,那么后面一个节点才是大于等于y值的,,应该插在这个节点前面才对
					++iter;
                    break;
                }
                --iter;
            }

			// Y链新位置上插入,并记录位置
			pEntity->y_pos = m_listYEntity.insert(iter, pEntity);
        }
    }
}

// 移动一个对象,新位置点(x,y)
void CScene::Move(int id, int x, int y) 
{
	// 在对象列表中查找该对象
    EntityMap::iterator iterEntity = m_mapEntity.find(id);
    if (iterEntity == m_mapEntity.end()) 
	{
        return;
    }

	// 该对象指针
    CEntity* pEntity = iterEntity->second;
    
	// 旧AOI列表,新AOI列表
	EntityMap oldMap, newMap;

    // 获取旧的AOI列表
    GetRangeSet(pEntity, &oldMap);

    // 更新对象位置到新坐标(x,y)
    UpdateEntityPosition(pEntity, x, y);

    // 获取新的AOI列表
    GetRangeSet(pEntity, &newMap);

	// 遍历旧AOI列表里面的对象
	// 若在新AOI列表里面存在,说明该对象是两次AOI列表里面的交集,把pEntity的移动消息通知他们
	// 若在新AOI列表里面不存在,则说明是要删除的对象(操作如下 1.把pEntity的离开AOI消息通知他们 2. 通知pEntity,他们离开了AOI)
    for (EntityMap::iterator iter = oldMap.begin(); iter != oldMap.end(); ++iter) 
	{
        if (newMap.find(iter->first) != newMap.end()) 
		{
			printf("send [%d:(%d,%d)]\t MOVE msg to obj [%d:(%d,%d)]\n",pEntity->id, pEntity->x, pEntity->y, iter->first, iter->second->x, iter->second->y);
        }
		else
		{
			printf("send [%d:(%d,%d)]\t LEAVE msg to obj [%d:(%d,%d)]\n",pEntity->id, pEntity->x, pEntity->y,iter->first, iter->second->x, iter->second->y);
			printf("send [%d:(%d,%d)]\t LEAVE msg to obj [%d:(%d,%d)]\n",iter->first, iter->second->x, iter->second->y,pEntity->id, pEntity->x, pEntity->y);
		}
    }

    // 遍历新AOI列表里面的对象
	// 若在交集move_set中不存在,则说明是新添加的对象(操作如下 1.把pEntity的进入AOI消息通知他们 2. 通知pEntity,他们进入了AOI)
    for (EntityMap::iterator iter = newMap.begin(); iter != newMap.end(); ++iter) 
	{
        if (oldMap.find(iter->first) == oldMap.end()) 
		{
			printf("send [%d:(%d,%d)]\t ENTER msg to obj [%d:(%d,%d)]\n",pEntity->id, pEntity->x, pEntity->y,iter->first, iter->second->x, iter->second->y);
			printf("send [%d:(%d,%d)]\t ENTER msg to obj [%d:(%d,%d)]\n",iter->first, iter->second->x, iter->second->y,pEntity->id, pEntity->x, pEntity->y);
        }
    }
}

// 获取指定对象的AOI对象列表
void CScene::GetRangeSet(CEntity* pEntity, EntityMap* pEntityMap) 
{
	if (pEntity == NULL || pEntityMap == NULL)
	{
		return;
	}

	// X链上的AOI范围内的对象,注意:并不是最终的AOI对象,因为还要检测Y链上的位置是否在AOI范围内
    EntityMap x_map;


    EntityList::iterator iter;
    
	// X链上往后面遍历
	iter = pEntity->x_pos;
	while(iter != m_listXEntity.end())
	{
		++iter;
		
		// 到了最后,跳出
		if (iter == m_listXEntity.end()) 
		{
			break;
		}

		// 正向距离已经超过半径,跳出
		if ((*iter)->x - pEntity->x > pEntity->radius) 
		{
			break;
		}

		// 添加进X方向的AOI集合
		x_map[(*iter)->id] = *iter;
	}

	// X链上往前面遍历
	iter = pEntity->x_pos;
	while(iter != m_listXEntity.begin())
	{
		--iter;

		// 负向距离已经超过半径,跳出
		if (pEntity->x - (*iter)->x > pEntity->radius) 
		{
			break;
		}

		// 添加进X方向的AOI集合
		x_map[(*iter)->id] = *iter;

		// 到了最前面,跳出,注意:不能放在加入AOI集合的前面,因为begin()位置也是有效对象
		if (iter == m_listXEntity.begin()) 
		{
			break;
		}
	}
	
	// Y链上往后面遍历
	iter = pEntity->y_pos;
	while(iter != m_listYEntity.end())
	{
		++iter;

		// 到了最后,跳出
		if (iter == m_listYEntity.end()) 
		{
			break;
		}

		// 正向距离已经超过半径,跳出
		if ((*iter)->y - pEntity->y > pEntity->radius) 
		{
			break;
		}

		// X方向AOI集合中存在的,则添加该对象到最终的AOI列表中
		if (x_map.find((*iter)->id) != x_map.end()) 
		{
			(*pEntityMap)[(*iter)->id] = *iter;
		}
	}


	// Y链上往前面遍历
	iter = pEntity->y_pos;
	while(iter != m_listYEntity.begin())
	{
		--iter;

		// 负向距离已经超过半径,跳出
		if (pEntity->y  - (*iter)->y > pEntity->radius) 
		{
			break;
		}

		// X方向AOI集合中存在的,则添加该对象到最终的AOI列表中
		if (x_map.find((*iter)->id) != x_map.end()) 
		{
			(*pEntityMap)[(*iter)->id] = *iter;
		}

		// 到了最前面,跳出,注意:不能放在加入AOI集合的前面,因为begin()位置也是有效对象
		if (iter == m_listYEntity.begin()) 
		{
			break;
		}
	}
}

// 删除一个对象
void CScene::Leave(int id) 
{
	// 对象列表中是否存在该ID
    EntityMap::iterator iterEntity = m_mapEntity.find(id);
    if (iterEntity == m_mapEntity.end()) 
	{
        return;
    }

	// 找到该对象
    CEntity* pEntity = iterEntity->second;

    // 获取该对象的AOI列表
	EntityMap leaveMap;
    GetRangeSet(pEntity, &leaveMap);

	// 通知AOI列表中的每个对象,pEntity要离开了
	// 通知pEntity,你看不到AOI列表中的每个对象了
	for (EntityMap::iterator iter = leaveMap.begin(); iter != leaveMap.end(); ++iter) 
	{
		printf("send [%d:(%d,%d)]\t LEAVE msg to obj [%d:(%d,%d)]\n",pEntity->id, pEntity->x, pEntity->y,iter->first, iter->second->x, iter->second->y);
		printf("send [%d:(%d,%d)]\t LEAVE msg to obj [%d:(%d,%d)]\n",iter->first, iter->second->x, iter->second->y,pEntity->id, pEntity->x, pEntity->y);
	}

	// X链,Y链上删除
    m_listXEntity.erase(pEntity->x_pos);
    m_listYEntity.erase(pEntity->y_pos);

	// 对象列表中删除
    m_mapEntity.erase(iterEntity);

	// 释放对象
    delete pEntity;
}

测试代码test.cpp

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "scene.h"

int main() 
{
	// 新建一个场景
    CScene scene;

    printf("步骤一:添加5个对象\n");
	scene.Add(1,1,5);	// 对象1,坐标(1,5)
	scene.Add(2,2,2);	// 对象2,坐标(2,2)
	scene.Add(3,3,1);	// 对象3,坐标(3,1)
	scene.Add(4,5,3);	// 对象4,坐标(5,3)
	scene.Add(5,6,6);	// 对象5,坐标(6,6)

    printf("步骤二:添加一个新的对象到坐标(3,3)\n");
	scene.Add(6,3,3);	// 对象6,坐标(3,3)

	printf("步骤三:把对象3移动到坐标(4,4)\n");
	scene.Move(6,4,4);

    printf("步骤四:移除这6个对象\n");
    for (int i = 1; i <= 6; ++i) 
	{
        scene.Leave(i);
    }

	system("pause");
    return 0;
}

分析:

步骤一完成后结果如下:

AOI之十字链表法

步骤二完成后结果如下(红色框表示AOI范围):

AOI之十字链表法

步骤三完成后结果如下(红色框表示移动后的AOI范围):

AOI之十字链表法

笔者这里的结果打印如下:

AOI之十字链表法

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/150316.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Python面对对象相关知识总结

    很有一段时间没使用python了,前两天研究微信公众号使用了下python的django服务,感觉好多知识都遗忘了,毕竟之前没有深入的实践,长期不使用就忘得快。本博的主要目的就是对Python中我认为

    2021年12月29日
    40
  • 关于 python 的缩进「建议收藏」

    关于 python 的缩进「建议收藏」python对缩进是敏感的,而大多教程对应缩进也只是几句话带过,对新手十分不友好,本文就把python常见的缩进问题做了一些整理。

    2022年4月19日
    70
  • word2vec原理概述

    word2vec原理概述最近阅读了Mikolov两篇关于word2vec的论文,结合Goldberg对这两篇论文的解读,作如下概述。概述在较早的论文“EfficientEstimationofWordRepresentationsinVectorSpace”中,Mikolov讨论了FeedforwardNeuralNetLanguageModel(NNLM)、RecurrentNeural

    2022年5月17日
    31
  • Python上位机软件图形界面实战(2)[通俗易懂]

    Python上位机软件图形界面实战(2)[通俗易懂]前言上位机图形界面开发设计用QTDesigner就可以了。但是qtdesigner生成的是.ui文件,我们需要将.ui转换为我们用的py文件。这里就要用到昨天设置Pyuic来生成。由于只是初步开发所以设计的界面没有美化,只是体验一下功能就可以了。1Pyuic的修改今天做的时候才发现昨天的Pyuic没设置好。下来在昨天的基础上只修改这两行。-mPyQt5.uic.pyuic$F…

    2022年5月29日
    49
  • Fiddler+雷电模拟器进行APP抓包

    Fiddler+雷电模拟器进行APP抓包1、Fiddler下载地址:下载最新版Fiddler,强烈建议在官网下载:https://www.telerik.com/download/fiddler雷电模拟器下载地址:选择3.0正式版(注意,高版本无法抓包,只能下载3.0正式版)https://www.ldmnq.com/other/version-history-and-release-notes.html?log=3正常傻瓜式安装,下一步,下一步,安装完毕后,先不用急于打开软件。3.下载并安装Fiddler证书生成器:http:

    2022年5月30日
    196
  • layui弹窗间的传值(layui弹出层传值)(窗口传值)[通俗易懂]

    layui弹窗间的传值(layui弹出层传值)(窗口传值)[通俗易懂]主要有两部分1、从主窗口传值到弹出层2、从弹出层传值到主窗口1、从主窗口传值到弹出层首先时jschangefileone函数时按钮绑定事件,按钮点击后调用这个函数然后弹出弹出层,加载changefile.html界面然后success提前加载changefile的form数据(从主窗口传值到弹出层)//bootstraptable的修改,点击按钮的时候自动选中该行,因此可以获取到整行…

    2022年6月12日
    139

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号