寻路网格 recastnavigation-1.5.1 服务器处理多人不同动态障碍物寻路,复用寻路数据 方案思路。

本文详细介绍了在SLG游戏中处理动态障碍物的方法,包括使用dtTileCache添加和更新圆形、矩形及旋转矩形障碍物,以及通过调整poly标志来控制动态障碍物的开关。文章还探讨了优化方案,如通过记录障碍物与poly的关系,动态开关特定区域的poly,以提高性能和减少延迟。此外,文章还讨论了遇到的问题,如大障碍物部分不生效,以及如何解决相邻动态障碍物块连接问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 缘起:

有个slg项目,所有玩家在一个大地图上移动, 有公用的动态障碍物

参考样例代码 Sample_TempObstacles里调用  dtTileCache 的 addObstacle、addBoxObstacle

(注:接口只是放入排队,调用后最好要调用update 确保动态障碍 走到 buildNavMeshTile,以便生效),可以添加圆形、矩形 3种情况的动态障碍物,基本能满足需求了。 

但有个需求,玩家还有动态的障碍块, 每个玩家进度不同, 解锁情况也不一样。  

以下是之前实现的思路记录, 比较乱,要结论直接看 最后面参考官网代码修改后的 DetourTileCache
 

目录

思路:

最后结论方案


  • 思路:

思路1. 每个玩家维护一份寻路数据, 但内存开销大,初始化慢。
且有公共的动态障碍 会有变化,需要全部,刷一遍,效率慢, 不太可行

思路2. 公用一份寻路数据, 由于个人的动态障碍,位置是固定的,只不过解锁进度不一样, 所以可以初始化这些动态障碍,存下ref,  等到某个玩家寻路时,再手动开关 这个ref(不是删除,而是改状态),
临时开关动态障碍物方法:
1.dtTileCache::getObstacleByRef 找到 ob,   

2. 需要关闭 则 ob->state = DT_OBSTACLE_EMPTY,需要开启 则 ob->state = DT_OBSTACLE_PROCESSED,

3.然后把ob->touched里关联的tile  执行一下buildNavMeshTile。 
方案是有效的,但块数多了之后,切换2个差异很大的玩家,会卡很久, 放弃。 
  
P.S. 尝试途中发现 很大的矩形障碍物,会有部分不生效的问题,因为 ob->touched默认只有8个 DT_MAX_TOUCHED_TILES,  所以要么调大这个参数,要么把动态障碍物分割成多块。
P.S. 动态障碍物有数量上限, 写在导出的.bin文件的头信息里,可以改导出工具,当然也可以自己强制写死。

思路3. 应该可行的方案

看demo时,发现有可以单独开关一个 poly的功能接口, (CrowdTool.cpp的 1039行左右:
 

				unsigned short flags = 0;
				if (dtStatusSucceed(nav->getPolyFlags(ref, &flags)))
				{
					flags ^= SAMPLE_POLYFLAGS_DISABLED;
					nav->setPolyFlags(ref, flags);
				}

想到可以找到这个障碍物下的poly,然后控制它的状态。  
怎么取呢?
1. 添加的动态障碍不能变成真的阻挡物,一般情况下要能同行,怎么做呢? 发现本身就支持
dtTileCache::buildNavMeshTile 里 dtMarkBoxArea 调用的接口最后一个参数,可以传层级,默认是0(会变成障碍), 我改成3 (SAMPLE_POLYAREA_DOOR), 寻路时过滤排除条件SAMPLE_POLYFLAGS_DOOR,则能实现绕路, 正常可以通过,然后 单独开关对应的poly状态,则可绕路。    
所以我扩展了 dtTileCacheObstacle 结构体,增加 unsigned char areaId;  方便添加动态障碍时传递


2. 如何找到对应动态障碍物覆盖到的poly? 

思路,找到关联的tile  即 ob->touched, 然后找到tile下的poly 判断其flag是否包含 SAMPLE_POLYFLAGS_DOOR(0x04), 但有可能取到的不是自己的。    发现有个navQuery->findPolysAroundShape( 接口可以使用, demo里也有例子。)  不过这个接口实际发现问题, 看图就明白了, 后面再说这个问题。

 大概代码:
 


enum SamplePolyFlags
{
    SAMPLE_POLYFLAGS_WALK = 0x01,      // Ability to walk (ground, grass, road)
    SAMPLE_POLYFLAGS_SWIM = 0x02,      // Ability to swim (water).
    SAMPLE_POLYFLAGS_DOOR = 0x04,      // Ability to move through doors.
    SAMPLE_POLYFLAGS_JUMP = 0x08,      // Ability to jump.
    SAMPLE_POLYFLAGS_DISABLED = 0x10,  // Disabled polygon
    SAMPLE_POLYFLAGS_ALL = 0xffff      // All abilities.
};

void dtTileCache::setPolyState(dtNavMesh* navmesh, dtPolyRef ref, bool bEnable) {
    unsigned short flags = 0;
    if (dtStatusSucceed(navmesh->getPolyFlags(ref, &flags)))
    {
        if (bEnable) {
            if ((flags & SAMPLE_POLYFLAGS_DISABLED) != 0) {
                flags ^= SAMPLE_POLYFLAGS_DISABLED;
            }
        }
        else {
            if ((flags & SAMPLE_POLYFLAGS_DISABLED) == 0) {
                flags ^= SAMPLE_POLYFLAGS_DISABLED;
            }
        }
        navmesh->setPolyFlags(ref, flags);
    }
}





        if(tLastUpdate  已超时 (或者说地图障碍有变化才需要)) {
			dtPolyRef m_startRef;
			static float nearestPt[3];
			bool isOverPoly = false;
			dtQueryFilter m_filter;
			m_filter.setIncludeFlags(SAMPLE_POLYFLAGS_DOOR);

            // 找到m_startRef
			auto status = navQuery->findNearestPoly(startPos, m_polyPickExt, &m_filter, &m_startRef, nearestPt, &isOverPoly);
            // queryPoly 多边形数组 范围内的指定poly
			navQuery->findPolysAroundShape(m_startRef, queryPoly, 4, &m_filter,
				m_polys, m_parent, 0, &m_npolys, MAX_POLYS);

			// 对应的poly记录到缓存, 以便一定时间后才去刷新一下
			stObParam.setPoly.clear();
			for (int i = 0; i < m_npolys; ++i) {
				stObParam.setPoly.emplace(m_polys[i]);
			}

			stObParam.tLastUpdate = tNow;

        }

        for (auto polyRef: stObParam.setPoly) {
            setPolyState(navmesh, polyRef, !bOpen);
        }



实际发现一般的块没问题,开关效率也挺高的。  
就是前面提到的问题,就像图中所示, 中间隔了障碍物的,似乎找不到所有的poly块,   个人考虑了一个思路: 遍历相关Tile下的poly 作为起始搜索 对应区域,排除已经搜索到的。      这个就各自发挥看看有什么更好的方案。我先去试验了。


ps1. 发现 一个 navQuery->queryPolygons(, 如果是平的方方正正的矩形(DT_OBSTACLE_ORIENTED_BOX), 用这个加ob参数就能直接取到对应区域的吧? ,由于项目里不是,所以没实验。


最后实验出来查找poly块方案:
找出动态障碍的矩形区域所有有关poly 然后以其为起点,调用findPolysAroundShape:


            navQuery->queryPolygons(s_posCenter, s_polyPickExt, &s_filter, s_polys1, &npolys1, MAX_POLYS);

            std::set<dtPolyRef> setUsed;
            static dtPolyRef s_polys2[MAX_POLYS];
            int npolys = 0;
            for (int i = 0; i < npolys1; ++i) {
                auto startRef = s_polys1[i];
                //printf("test start:%u\n", startRef);
                if (setUsed.count(startRef) == 0) {
                    setUsed.emplace(startRef);
                    //printf("test start:%u  to found \n", startRef);

                    navQuery->findPolysAroundShape(startRef, queryPoly, 4, &s_filter,
						s_polys2, s_parent, 0, &npolys, MAX_POLYS);
                    //printf("test start:%u  found poly:%d \n", startRef, npolys);
                    for (int j = 0; j < npolys; ++j) {
                        setUsed.emplace(s_polys2[j]);
                        stObParam.setPoly.emplace(s_polys2[j]);
                    }
                }
            }



注:.把大块的动态障碍区域分割成小块一点(否则有些开关不生效)

注:还有一个问题,两块区域相连,会被连载一起, 获得的poly很可能跨了2个区域。 【个人处理方案:不同动态障碍物块矩形区域缩小一点点,避免2块相连】

  • 最后结论方案

 1. 添加动态障碍物,层级传SAMPLE_POLYFLAGS_DOOR,得到障碍物 ref,  记录下来和游戏里的对应关系

        比如:

    dtObstacleRef ob_id = 0;
    m_tileCache->addBoxObstacle(pc, he, static_cast<float>(CONST_PI/4), &ob_id, SAMPLE_POLYFLAGS_DOOR);
    m_tileCache->update(1.0f, m_navMesh);
    m_tileCache->update(1.0f, m_navMesh);
    m_obstacle_ids.insert(ob_id);

2. 上面调用update里 会 触发 dtTileCache::buildNavMeshTile,里面会用SAMPLE_POLYFLAGS_DOOR层 构建出障碍物网格,网格属性里会带这个层标识。

这个函数比较耗时,不过一般是第一次初始化调用一下就行了。

3. 前面取到的ob_id 调用,记录下 ob_id 对应的实际矩形区域,(为了搜索 poly时用)

m_tileCache->setObstacleRange(ob_id, rectPos);

4.  查找ob_id对应的poly块,核心函数 updateDoorObstaclePolys 

(如果地图没有其他动态障碍物 变更,则只调用一次就行。我这边项目还混杂其他公共的障碍物变更所以,定时调用一下)

里面就是 用步骤3 ob_id 记录的实际矩形区域,构建一个查询区域,调用navQuery->queryPolygons (这个是粗略的找出规整矩形里得polyref,这些不一定是实际需要的,只是为了不遗漏),然后遍历这些poly,作为起点,调用 navQuery->findPolysAroundShape(参考了demo里的例子)

这样 ob_id 和 poly的对应关系就都找到了。


5. 寻路前 根据需要 切换 ob_id 的开 或 关

即调用 dtTileCache::setObstaclePolysState, 开表示不让走,关 则 可通行,
这个函数是为了避免修改频繁做过一些状态缓存优化,
实际调用 navmesh->setPolyEnable(polyRef, bEnablePoly); 开关每个 poly块,这个不费时。
 


#define  SAMPLE_POLYFLAGS_DISABLED  0x10  // Disabled polygon
dtStatus dtNavMesh::setPolyEnable(dtPolyRef ref, bool bEnable)
{
    if (!ref) return DT_FAILURE;
    unsigned int salt, it, ip;
    decodePolyId(ref, salt, it, ip);
    if (it >= (unsigned int)m_maxTiles) return DT_FAILURE | DT_INVALID_PARAM;
    if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return DT_FAILURE | DT_INVALID_PARAM;
    dtMeshTile* tile = &m_tiles[it];
    if (ip >= (unsigned int)tile->header->polyCount) return DT_FAILURE | DT_INVALID_PARAM;
    dtPoly* poly = &tile->polys[ip];

    if (bEnable) {
        if ((poly->flags & SAMPLE_POLYFLAGS_DISABLED) != 0) {
			poly->flags ^= SAMPLE_POLYFLAGS_DISABLED;
        }
    }
    else {
        if ((poly->flags & SAMPLE_POLYFLAGS_DISABLED) == 0) {
			poly->flags ^= SAMPLE_POLYFLAGS_DISABLED;
        }
    }

    return DT_SUCCESS;
}

6.寻路时排除掉SAMPLE_POLYFLAGS_DISABLED  flag的层,就是把 poly 开启的 作为障碍过滤掉。


    dtQueryFilter m_filter;
    m_filter.setExcludeFlags(SAMPLE_POLYFLAGS_DISABLED);

附2个文件代码:
DetourTileCache.h

#ifndef DETOURTILECACHE_H
#define DETOURTILECACHE_H

#include "DetourStatus.h"
#include "DetourNavMesh.h"
#include "DetourNavMeshQuery.h"
#include <map>
#include <set>


typedef unsigned int dtObstacleRef;

typedef unsigned int dtCompressedTileRef;

/// Flags for addTile
enum dtCompressedTileFlags
{
	DT_COMPRESSEDTILE_FREE_DATA = 0x01,					///< Navmesh owns the tile memory and should free it.
};

struct dtCompressedTile
{
	unsigned int salt;						///< Counter describing modifications to the tile.
	struct dtTileCacheLayerHeader* header;
	unsigned char* compressed;
	int compressedSize;
	unsigned char* data;
	int dataSize;
	unsigned int flags;
	dtCompressedTile* next;
};

enum ObstacleState
{
	DT_OBSTACLE_EMPTY,
	DT_OBSTACLE_PROCESSING,
	DT_OBSTACLE_PROCESSED,
	DT_OBSTACLE_REMOVING,
};

enum ObstacleType
{
	DT_OBSTACLE_CYLINDER,
	DT_OBSTACLE_BOX, // AABB
	DT_OBSTACLE_ORIENTED_BOX, // OBB
};

struct dtObstacleCylinder
{
	float pos[ 3 ];
	float radius;
	float height;
};

struct dtObstacleBox
{
	float bmin[ 3 ];
	float bmax[ 3 ];
};

struct dtObstacleOrientedBox
{
	float center[ 3 ];
	float halfExtents[ 3 ];
	float rotAux[ 2 ]; //{ cos(0.5f*angle)*sin(-0.5f*angle); cos(0.5f*angle)*cos(0.5f*angle) - 0.5 }
};

static const int DT_MAX_TOUCHED_TILES = 64;
struct dtTileCacheObstacle
{
	union
	{
		dtObstacleCylinder cylinder;
		dtObstacleBox box;
		dtObstacleOrientedBox orientedBox;
	};

	dtCompressedTileRef touched[DT_MAX_TOUCHED_TILES];
	dtCompressedTileRef pending[DT_MAX_TOUCHED_TILES];
	unsigned short salt;
	unsigned char type;
	unsigned char state;
	unsigned char ntouched;
	unsigned char npending;
	unsigned char areaId;
	dtTileCacheObstacle* next;
};

struct dtTileCacheParams
{
	float orig[3];
	float cs, ch;
	int width, height;
	float walkableHeight;
	float walkableRadius;
	float walkableClimb;
	float maxSimplificationError;
	int maxTiles;
	int maxObstacles;
};

struct dtTileCacheMeshProcess
{
	virtual ~dtTileCacheMeshProcess() { }

	virtual void process(struct dtNavMeshCreateParams* params,
						 unsigned char* polyAreas, unsigned short* polyFlags) = 0;
};

struct dtRectObstacleParam
{
	float queryPoly[4 * 3];
	bool bOpen = false;
	std::set<dtPolyRef> setPoly;
};

class dtTileCache
{
public:
	dtTileCache();
	~dtTileCache();
	
	struct dtTileCacheAlloc* getAlloc() { return m_talloc; }
	struct dtTileCacheCompressor* getCompressor() { return m_tcomp; }
	const dtTileCacheParams* getParams() const { return &m_params; }
	
	inline int getTileCount() const { return m_params.maxTiles; }
	inline const dtCompressedTile* getTile(const int i) const { return &m_tiles[i]; }
	
	inline int getObstacleCount() const { return m_params.maxObstacles; }
	inline const dtTileCacheObstacle* getObstacle(const int i) const { return &m_obstacles[i]; }
	
	const dtTileCacheObstacle* getObstacleByRef(dtObstacleRef ref);
	
	dtObstacleRef getObstacleRef(const dtTileCacheObstacle* obmin) const;
	
	dtStatus init(const dtTileCacheParams* params,
				  struct dtTileCacheAlloc* talloc,
				  struct dtTileCacheCompressor* tcomp,
				  struct dtTileCacheMeshProcess* tmproc);
	
	int getTilesAt(const int tx, const int ty, dtCompressedTileRef* tiles, const int maxTiles) const ;
	
	dtCompressedTile* getTileAt(const int tx, const int ty, const int tlayer);
	dtCompressedTileRef getTileRef(const dtCompressedTile* tile) const;
	const dtCompressedTile* getTileByRef(dtCompressedTileRef ref) const;
	
	dtStatus addTile(unsigned char* data, const int dataSize, unsigned char flags, dtCompressedTileRef* result);
	
	dtStatus removeTile(dtCompressedTileRef ref, unsigned char** data, int* dataSize);
	
	// Cylinder obstacle.
	dtStatus addObstacle(const float* pos, const float radius, const float height, dtObstacleRef* result, const unsigned char areaId = 0);

	// Aabb obstacle.
	dtStatus addBoxObstacle(const float* bmin, const float* bmax, dtObstacleRef* result, const unsigned char areaId = 0);

	// Box obstacle: can be rotated in Y.
	dtStatus addBoxObstacle(const float* center, const float* halfExtents, const float yRadians, dtObstacleRef* result, const unsigned char areaId = 0);
	
	dtStatus removeObstacle(const dtObstacleRef ref);
	
	dtStatus queryTiles(const float* bmin, const float* bmax,
						dtCompressedTileRef* results, int* resultCount, const int maxResults) const;
	
	/// Updates the tile cache by rebuilding tiles touched by unfinished obstacle requests.
	///  @param[in]		dt			The time step size. Currently not used.
	///  @param[in]		navmesh		The mesh to affect when rebuilding tiles.
	///  @param[out]	upToDate	Whether the tile cache is fully up to date with obstacle requests and tile rebuilds.
	///  							If the tile cache is up to date another (immediate) call to update will have no effect;
	///  							otherwise another call will continue processing obstacle requests and tile rebuilds.
	dtStatus update(const float dt, class dtNavMesh* navmesh, bool* upToDate = 0);
	
	dtStatus buildNavMeshTilesAt(const int tx, const int ty, class dtNavMesh* navmesh);
	
	dtStatus buildNavMeshTile(const dtCompressedTileRef ref, class dtNavMesh* navmesh);
	/*
	 * add by tianyh 2018/4/18 16:12
	 * @param [in] dtCompressedTileRef
	 * @params [in] navmesh
	 * @param [out] nav_data
	 * @param [out] nav_data_size
	 */
    dtStatus buildNavMeshTile(const dtCompressedTileRef ref, class dtNavMesh* navmesh, unsigned char*& nav_data, int& data_size);

    void setPolyState(dtNavMesh* navmesh, dtPolyRef ref, bool bEnable);

	void updateDoorObstaclePolys(dtNavMeshQuery* navQuery);

    void setObstaclePolysState(dtNavMeshQuery* navQuery, dtNavMesh* navmesh, dtObstacleRef obRef, bool bOpen, time_t tNow);

    void setObstacleRange(dtObstacleRef obRef, const float rectPos[]);
	
	void calcTightTileBounds(const struct dtTileCacheLayerHeader* header, float* bmin, float* bmax) const;
	
	void getObstacleBounds(const struct dtTileCacheObstacle* ob, float* bmin, float* bmax) const;
	

	/// Encodes a tile id.
	inline dtCompressedTileRef encodeTileId(unsigned int salt, unsigned int it) const
	{
		return ((dtCompressedTileRef)salt << m_tileBits) | (dtCompressedTileRef)it;
	}
	
	/// Decodes a tile salt.
	inline unsigned int decodeTileIdSalt(dtCompressedTileRef ref) const
	{
		const dtCompressedTileRef saltMask = ((dtCompressedTileRef)1<<m_saltBits)-1;
		return (unsigned int)((ref >> m_tileBits) & saltMask);
	}
	
	/// Decodes a tile id.
	inline unsigned int decodeTileIdTile(dtCompressedTileRef ref) const
	{
		const dtCompressedTileRef tileMask = ((dtCompressedTileRef)1<<m_tileBits)-1;
		return (unsigned int)(ref & tileMask);
	}

	/// Encodes an obstacle id.
	inline dtObstacleRef encodeObstacleId(unsigned int salt, unsigned int it) const
	{
		return ((dtObstacleRef)salt << 16) | (dtObstacleRef)it;
	}
	
	/// Decodes an obstacle salt.
	inline unsigned int decodeObstacleIdSalt(dtObstacleRef ref) const
	{
		const dtObstacleRef saltMask = ((dtObstacleRef)1<<16)-1;
		return (unsigned int)((ref >> 16) & saltMask);
	}
	
	/// Decodes an obstacle id.
	inline unsigned int decodeObstacleIdObstacle(dtObstacleRef ref) const
	{
		const dtObstacleRef tileMask = ((dtObstacleRef)1<<16)-1;
		return (unsigned int)(ref & tileMask);
	}
	
	inline void setLastUpdateTime(time_t t64Time)
	{
		m_t64LastUpdateTime = t64Time;
	}
	
private:
	// Explicitly disabled copy constructor and copy assignment operator.
	dtTileCache(const dtTileCache&);
	dtTileCache& operator=(const dtTileCache&);

	enum ObstacleRequestAction
	{
		REQUEST_ADD,
		REQUEST_REMOVE,
	};
	
	struct ObstacleRequest
	{
		int action;
		dtObstacleRef ref;
	};
	
	int m_tileLutSize;						///< Tile hash lookup size (must be pot).
	int m_tileLutMask;						///< Tile hash lookup mask.
	
	dtCompressedTile** m_posLookup;			///< Tile hash lookup.
	dtCompressedTile* m_nextFreeTile;		///< Freelist of tiles.
	dtCompressedTile* m_tiles;				///< List of tiles.
	
	unsigned int m_saltBits;				///< Number of salt bits in the tile ID.
	unsigned int m_tileBits;				///< Number of tile bits in the tile ID.
	
	dtTileCacheParams m_params;
	
	dtTileCacheAlloc* m_talloc;
	dtTileCacheCompressor* m_tcomp;
	dtTileCacheMeshProcess* m_tmproc;
	
	dtTileCacheObstacle* m_obstacles;
	dtTileCacheObstacle* m_nextFreeObstacle;
	
	static const int MAX_REQUESTS = 64;
	ObstacleRequest m_reqs[MAX_REQUESTS];
	int m_nreqs;
	
	static const int MAX_UPDATE = 64;
	dtCompressedTileRef m_update[MAX_UPDATE];
	int m_nupdate;
	time_t m_t64LastUpdateTime = 0;

	std::map<dtObstacleRef, dtRectObstacleParam> m_mapObstacleParams;
	std::map<dtPolyRef, std::set<dtObstacleRef> > m_mapDoorPoly2Obstacles;
};

dtTileCache* dtAllocTileCache();
void dtFreeTileCache(dtTileCache* tc);

#endif

DetourTileCache.cpp

#include "DetourTileCache.h"
#include "DetourTileCacheBuilder.h"
#include "DetourNavMeshBuilder.h"
#include "DetourNavMesh.h"
#include "DetourCommon.h"
#include "DetourMath.h"
#include "DetourAlloc.h"
#include "DetourAssert.h"
#include <string.h>
#include <new>
#include "platform/typedefine.h"
#include "toolkit/TimeTools.h"

dtTileCache* dtAllocTileCache()
{
	void* mem = dtAlloc(sizeof(dtTileCache), DT_ALLOC_PERM);
	if (!mem) return 0;
	return new(mem) dtTileCache;
}

void dtFreeTileCache(dtTileCache* tc)
{
	if (!tc) return;
	tc->~dtTileCache();
	dtFree(tc);
}

static bool contains(const dtCompressedTileRef* a, const int n, const dtCompressedTileRef v)
{
	for (int i = 0; i < n; ++i)
		if (a[i] == v)
			return true;
	return false;
}

inline int computeTileHash(int x, int y, const int mask)
{
	const unsigned int h1 = 0x8da6b343; // Large multiplicative constants;
	const unsigned int h2 = 0xd8163841; // here arbitrarily chosen primes
	unsigned int n = h1 * x + h2 * y;
	return (int)(n & mask);
}


struct NavMeshTileBuildContext
{
	inline NavMeshTileBuildContext(struct dtTileCacheAlloc* a) : layer(0), lcset(0), lmesh(0), alloc(a) {}
	inline ~NavMeshTileBuildContext() { purge(); }
	void purge()
	{
		dtFreeTileCacheLayer(alloc, layer);
		layer = 0;
		dtFreeTileCacheContourSet(alloc, lcset);
		lcset = 0;
		dtFreeTileCachePolyMesh(alloc, lmesh);
		lmesh = 0;
	}
	struct dtTileCacheLayer* layer;
	struct dtTileCacheContourSet* lcset;
	struct dtTileCachePolyMesh* lmesh;
	struct dtTileCacheAlloc* alloc;
};


dtTileCache::dtTileCache() :
	m_tileLutSize(0),
	m_tileLutMask(0),
	m_posLookup(0),
	m_nextFreeTile(0),	
	m_tiles(0),	
	m_saltBits(0),
	m_tileBits(0),
	m_talloc(0),
	m_tcomp(0),
	m_tmproc(0),
	m_obstacles(0),
	m_nextFreeObstacle(0),
	m_nreqs(0),
	m_nupdate(0),
	m_t64LastUpdateTime(0)
{
	memset(&m_params, 0, sizeof(m_params));
	memset(m_reqs, 0, sizeof(ObstacleRequest) * MAX_REQUESTS);
}
	
dtTileCache::~dtTileCache()
{
	for (int i = 0; i < m_params.maxTiles; ++i)
	{
		if (m_tiles[i].flags & DT_COMPRESSEDTILE_FREE_DATA)
		{
			dtFree(m_tiles[i].data);
			m_tiles[i].data = 0;
		}
	}
	dtFree(m_obstacles);
	m_obstacles = 0;
	dtFree(m_posLookup);
	m_posLookup = 0;
	dtFree(m_tiles);
	m_tiles = 0;
	m_nreqs = 0;
	m_nupdate = 0;
}

const dtCompressedTile* dtTileCache::getTileByRef(dtCompressedTileRef ref) const
{
	if (!ref)
		return 0;
	unsigned int tileIndex = decodeTileIdTile(ref);
	unsigned int tileSalt = decodeTileIdSalt(ref);
	if ((int)tileIndex >= m_params.maxTiles)
		return 0;
	const dtCompressedTile* tile = &m_tiles[tileIndex];
	if (tile->salt != tileSalt)
		return 0;
	return tile;
}


dtStatus dtTileCache::init(const dtTileCacheParams* params,
						   dtTileCacheAlloc* talloc,
						   dtTileCacheCompressor* tcomp,
						   dtTileCacheMeshProcess* tmproc)
{
	m_talloc = talloc;
	m_tcomp = tcomp;
	m_tmproc = tmproc;
	m_nreqs = 0;
	memcpy(&m_params, params, sizeof(m_params));
	
	// Alloc space for obstacles.
	m_obstacles = (dtTileCacheObstacle*)dtAlloc(sizeof(dtTileCacheObstacle)*m_params.maxObstacles, DT_ALLOC_PERM);
	if (!m_obstacles)
		return DT_FAILURE | DT_OUT_OF_MEMORY;
	memset(m_obstacles, 0, sizeof(dtTileCacheObstacle)*m_params.maxObstacles);
	m_nextFreeObstacle = 0;
	for (int i = m_params.maxObstacles-1; i >= 0; --i)
	{
		m_obstacles[i].salt = 1;
		m_obstacles[i].next = m_nextFreeObstacle;
		m_obstacles[i].areaId = 0;
		m_nextFreeObstacle = &m_obstacles[i];
	}
	
	// Init tiles
	m_tileLutSize = dtNextPow2(m_params.maxTiles/4);
	if (!m_tileLutSize) m_tileLutSize = 1;
	m_tileLutMask = m_tileLutSize-1;
	
	m_tiles = (dtCompressedTile*)dtAlloc(sizeof(dtCompressedTile)*m_params.maxTiles, DT_ALLOC_PERM);
	if (!m_tiles)
		return DT_FAILURE | DT_OUT_OF_MEMORY;
	m_posLookup = (dtCompressedTile**)dtAlloc(sizeof(dtCompressedTile*)*m_tileLutSize, DT_ALLOC_PERM);
	if (!m_posLookup)
		return DT_FAILURE | DT_OUT_OF_MEMORY;
	memset(m_tiles, 0, sizeof(dtCompressedTile)*m_params.maxTiles);
	memset(m_posLookup, 0, sizeof(dtCompressedTile*)*m_tileLutSize);
	m_nextFreeTile = 0;
	for (int i = m_params.maxTiles-1; i >= 0; --i)
	{
		m_tiles[i].salt = 1;
		m_tiles[i].next = m_nextFreeTile;
		m_nextFreeTile = &m_tiles[i];
	}
	
	// Init ID generator values.
	m_tileBits = dtIlog2(dtNextPow2((unsigned int)m_params.maxTiles));
	// Only allow 31 salt bits, since the salt mask is calculated using 32bit uint and it will overflow.
	m_saltBits = dtMin((unsigned int)31, 32 - m_tileBits);
	if (m_saltBits < 10)
		return DT_FAILURE | DT_INVALID_PARAM;
	
	return DT_SUCCESS;
}

int dtTileCache::getTilesAt(const int tx, const int ty, dtCompressedTileRef* tiles, const int maxTiles) const 
{
	int n = 0;
	
	// Find tile based on hash.
	int h = computeTileHash(tx,ty,m_tileLutMask);
	dtCompressedTile* tile = m_posLookup[h];
	while (tile)
	{
		if (tile->header &&
			tile->header->tx == tx &&
			tile->header->ty == ty)
		{
			if (n < maxTiles)
				tiles[n++] = getTileRef(tile);
		}
		tile = tile->next;
	}
	
	return n;
}

dtCompressedTile* dtTileCache::getTileAt(const int tx, const int ty, const int tlayer)
{
	// Find tile based on hash.
	int h = computeTileHash(tx,ty,m_tileLutMask);
	dtCompressedTile* tile = m_posLookup[h];
	while (tile)
	{
		if (tile->header &&
			tile->header->tx == tx &&
			tile->header->ty == ty &&
			tile->header->tlayer == tlayer)
		{
			return tile;
		}
		tile = tile->next;
	}
	return 0;
}

dtCompressedTileRef dtTileCache::getTileRef(const dtCompressedTile* tile) const
{
	if (!tile) return 0;
	const unsigned int it = (unsigned int)(tile - m_tiles);
	return (dtCompressedTileRef)encodeTileId(tile->salt, it);
}

dtObstacleRef dtTileCache::getObstacleRef(const dtTileCacheObstacle* ob) const
{
	if (!ob) return 0;
	const unsigned int idx = (unsigned int)(ob - m_obstacles);
	return encodeObstacleId(ob->salt, idx);
}

const dtTileCacheObstacle* dtTileCache::getObstacleByRef(dtObstacleRef ref)
{
	if (!ref)
		return 0;
	unsigned int idx = decodeObstacleIdObstacle(ref);
	if ((int)idx >= m_params.maxObstacles)
		return 0;
	const dtTileCacheObstacle* ob = &m_obstacles[idx];
	unsigned int salt = decodeObstacleIdSalt(ref);
	if (ob->salt != salt)
		return 0;
	return ob;
}

dtStatus dtTileCache::addTile(unsigned char* data, const int dataSize, unsigned char flags, dtCompressedTileRef* result)
{
	// Make sure the data is in right format.
	dtTileCacheLayerHeader* header = (dtTileCacheLayerHeader*)data;
	if (header->magic != DT_TILECACHE_MAGIC)
		return DT_FAILURE | DT_WRONG_MAGIC;
	if (header->version != DT_TILECACHE_VERSION)
		return DT_FAILURE | DT_WRONG_VERSION;
	
	// Make sure the location is free.
	if (getTileAt(header->tx, header->ty, header->tlayer))
		return DT_FAILURE;
	
	// Allocate a tile.
	dtCompressedTile* tile = 0;
	if (m_nextFreeTile)
	{
		tile = m_nextFreeTile;
		m_nextFreeTile = tile->next;
		tile->next = 0;
	}
	
	// Make sure we could allocate a tile.
	if (!tile)
		return DT_FAILURE | DT_OUT_OF_MEMORY;
	
	// Insert tile into the position lut.
	int h = computeTileHash(header->tx, header->ty, m_tileLutMask);
	tile->next = m_posLookup[h];
	m_posLookup[h] = tile;
	
	// Init tile.
	const int headerSize = dtAlign4(sizeof(dtTileCacheLayerHeader));
	tile->header = (dtTileCacheLayerHeader*)data;
	tile->data = data;
	tile->dataSize = dataSize;
	tile->compressed = tile->data + headerSize;
	tile->compressedSize = tile->dataSize - headerSize;
	tile->flags = flags;
	
	if (result)
		*result = getTileRef(tile);
	
	return DT_SUCCESS;
}

dtStatus dtTileCache::removeTile(dtCompressedTileRef ref, unsigned char** data, int* dataSize)
{
	if (!ref)
		return DT_FAILURE | DT_INVALID_PARAM;
	unsigned int tileIndex = decodeTileIdTile(ref);
	unsigned int tileSalt = decodeTileIdSalt(ref);
	if ((int)tileIndex >= m_params.maxTiles)
		return DT_FAILURE | DT_INVALID_PARAM;
	dtCompressedTile* tile = &m_tiles[tileIndex];
	if (tile->salt != tileSalt)
		return DT_FAILURE | DT_INVALID_PARAM;
	
	// Remove tile from hash lookup.
	const int h = computeTileHash(tile->header->tx,tile->header->ty,m_tileLutMask);
	dtCompressedTile* prev = 0;
	dtCompressedTile* cur = m_posLookup[h];
	while (cur)
	{
		if (cur == tile)
		{
			if (prev)
				prev->next = cur->next;
			else
				m_posLookup[h] = cur->next;
			break;
		}
		prev = cur;
		cur = cur->next;
	}
	
	// Reset tile.
	if (tile->flags & DT_COMPRESSEDTILE_FREE_DATA)
	{
		// Owns data
		dtFree(tile->data);
		tile->data = 0;
		tile->dataSize = 0;
		if (data) *data = 0;
		if (dataSize) *dataSize = 0;
	}
	else
	{
		if (data) *data = tile->data;
		if (dataSize) *dataSize = tile->dataSize;
	}
	
	tile->header = 0;
	tile->data = 0;
	tile->dataSize = 0;
	tile->compressed = 0;
	tile->compressedSize = 0;
	tile->flags = 0;
	
	// Update salt, salt should never be zero.
	tile->salt = (tile->salt+1) & ((1<<m_saltBits)-1);
	if (tile->salt == 0)
		tile->salt++;
	
	// Add to free list.
	tile->next = m_nextFreeTile;
	m_nextFreeTile = tile;
	
	return DT_SUCCESS;
}


dtStatus dtTileCache::addObstacle(const float* pos, const float radius, const float height, dtObstacleRef* result, const unsigned char areaId/* = 0*/)
{
	if (m_nreqs >= MAX_REQUESTS)
		return DT_FAILURE | DT_BUFFER_TOO_SMALL;
	
	dtTileCacheObstacle* ob = 0;
	if (m_nextFreeObstacle)
	{
		ob = m_nextFreeObstacle;
		m_nextFreeObstacle = ob->next;
		ob->next = 0;
	}
	if (!ob)
		return DT_FAILURE | DT_OUT_OF_MEMORY;
	
	unsigned short salt = ob->salt;
	memset(ob, 0, sizeof(dtTileCacheObstacle));
	ob->salt = salt;
	ob->areaId = areaId;
	ob->state = DT_OBSTACLE_PROCESSING;
	ob->type = DT_OBSTACLE_CYLINDER;
	dtVcopy(ob->cylinder.pos, pos);
	ob->cylinder.radius = radius;
	ob->cylinder.height = height;
	
	ObstacleRequest* req = &m_reqs[m_nreqs++];
	memset(req, 0, sizeof(ObstacleRequest));
	req->action = REQUEST_ADD;
	req->ref = getObstacleRef(ob);
	
	if (result)
		*result = req->ref;
	
	return DT_SUCCESS;
}

dtStatus dtTileCache::addBoxObstacle(const float* bmin, const float* bmax, dtObstacleRef* result, const unsigned char areaId/* = 0*/)
{
	if (m_nreqs >= MAX_REQUESTS)
		return DT_FAILURE | DT_BUFFER_TOO_SMALL;
	
	dtTileCacheObstacle* ob = 0;
	if (m_nextFreeObstacle)
	{
		ob = m_nextFreeObstacle;
		m_nextFreeObstacle = ob->next;
		ob->next = 0;
	}
	if (!ob)
		return DT_FAILURE | DT_OUT_OF_MEMORY;
	
	unsigned short salt = ob->salt;
	memset(ob, 0, sizeof(dtTileCacheObstacle));
	ob->salt = salt;
	ob->areaId = areaId;
	ob->state = DT_OBSTACLE_PROCESSING;
	ob->type = DT_OBSTACLE_BOX;
	dtVcopy(ob->box.bmin, bmin);
	dtVcopy(ob->box.bmax, bmax);
	
	ObstacleRequest* req = &m_reqs[m_nreqs++];
	memset(req, 0, sizeof(ObstacleRequest));
	req->action = REQUEST_ADD;
	req->ref = getObstacleRef(ob);
	
	if (result)
		*result = req->ref;
	
	return DT_SUCCESS;
}

dtStatus dtTileCache::addBoxObstacle(const float* center, const float* halfExtents, const float yRadians, dtObstacleRef* result, const unsigned char areaId/* = 0*/)
{
	if (m_nreqs >= MAX_REQUESTS)
		return DT_FAILURE | DT_BUFFER_TOO_SMALL;

	dtTileCacheObstacle* ob = 0;
	if (m_nextFreeObstacle)
	{
		ob = m_nextFreeObstacle;
		m_nextFreeObstacle = ob->next;
		ob->next = 0;
	}
	if (!ob)
		return DT_FAILURE | DT_OUT_OF_MEMORY;

	unsigned short salt = ob->salt;
	memset(ob, 0, sizeof(dtTileCacheObstacle));
	ob->salt = salt;
	ob->areaId = areaId;
	ob->state = DT_OBSTACLE_PROCESSING;
	ob->type = DT_OBSTACLE_ORIENTED_BOX;
	dtVcopy(ob->orientedBox.center, center);
	dtVcopy(ob->orientedBox.halfExtents, halfExtents);

	float coshalf= cosf(0.5f*yRadians);
	float sinhalf = sinf(-0.5f*yRadians);
	ob->orientedBox.rotAux[0] = coshalf*sinhalf;
	ob->orientedBox.rotAux[1] = coshalf*coshalf - 0.5f;

	ObstacleRequest* req = &m_reqs[m_nreqs++];
	memset(req, 0, sizeof(ObstacleRequest));
	req->action = REQUEST_ADD;
	req->ref = getObstacleRef(ob);

	if (result)
		*result = req->ref;

	return DT_SUCCESS;
}

dtStatus dtTileCache::removeObstacle(const dtObstacleRef ref)
{
	if (!ref)
		return DT_SUCCESS;
	if (m_nreqs >= MAX_REQUESTS)
		return DT_FAILURE | DT_BUFFER_TOO_SMALL;
	
	ObstacleRequest* req = &m_reqs[m_nreqs++];
	memset(req, 0, sizeof(ObstacleRequest));
	req->action = REQUEST_REMOVE;
	req->ref = ref;
	
	return DT_SUCCESS;
}

dtStatus dtTileCache::queryTiles(const float* bmin, const float* bmax,
								 dtCompressedTileRef* results, int* resultCount, const int maxResults) const 
{
	const int MAX_TILES = 32;
	dtCompressedTileRef tiles[MAX_TILES];
	
	int n = 0;
	
	const float tw = m_params.width * m_params.cs;
	const float th = m_params.height * m_params.cs;
	const int tx0 = (int)dtMathFloorf((bmin[0]-m_params.orig[0]) / tw);
	const int tx1 = (int)dtMathFloorf((bmax[0]-m_params.orig[0]) / tw);
	const int ty0 = (int)dtMathFloorf((bmin[2]-m_params.orig[2]) / th);
	const int ty1 = (int)dtMathFloorf((bmax[2]-m_params.orig[2]) / th);
	
	for (int ty = ty0; ty <= ty1; ++ty)
	{
		for (int tx = tx0; tx <= tx1; ++tx)
		{
			const int ntiles = getTilesAt(tx,ty,tiles,MAX_TILES);
			
			for (int i = 0; i < ntiles; ++i)
			{
				const dtCompressedTile* tile = &m_tiles[decodeTileIdTile(tiles[i])];
				float tbmin[3], tbmax[3];
				calcTightTileBounds(tile->header, tbmin, tbmax);
				
				if (dtOverlapBounds(bmin,bmax, tbmin,tbmax))
				{
					if (n < maxResults)
						results[n++] = tiles[i];
				}
			}
		}
	}
	
	*resultCount = n;
	
	return DT_SUCCESS;
}

dtStatus dtTileCache::update(const float /*dt*/, dtNavMesh* navmesh,
							 bool* upToDate)
{
	if (m_nupdate == 0)
	{
		// Process requests.
		for (int i = 0; i < m_nreqs; ++i)
		{
			ObstacleRequest* req = &m_reqs[i];
			
			unsigned int idx = decodeObstacleIdObstacle(req->ref);
			if ((int)idx >= m_params.maxObstacles)
				continue;
			dtTileCacheObstacle* ob = &m_obstacles[idx];
			unsigned int salt = decodeObstacleIdSalt(req->ref);
			if (ob->salt != salt)
				continue;
			
			if (req->action == REQUEST_ADD)
			{
				// Find touched tiles.
				float bmin[3], bmax[3];
				getObstacleBounds(ob, bmin, bmax);

				int ntouched = 0;
				queryTiles(bmin, bmax, ob->touched, &ntouched, DT_MAX_TOUCHED_TILES);
				ob->ntouched = (unsigned char)ntouched;
				// Add tiles to update list.
				ob->npending = 0;
				for (int j = 0; j < ob->ntouched; ++j)
				{
					if (m_nupdate < MAX_UPDATE)
					{
						if (!contains(m_update, m_nupdate, ob->touched[j]))
							m_update[m_nupdate++] = ob->touched[j];
						ob->pending[ob->npending++] = ob->touched[j];
					}
				}
			}
			else if (req->action == REQUEST_REMOVE)
			{
				// Prepare to remove obstacle.
				ob->state = DT_OBSTACLE_REMOVING;
				// Add tiles to update list.
				ob->npending = 0;
				for (int j = 0; j < ob->ntouched; ++j)
				{
					if (m_nupdate < MAX_UPDATE)
					{
						if (!contains(m_update, m_nupdate, ob->touched[j]))
							m_update[m_nupdate++] = ob->touched[j];
						ob->pending[ob->npending++] = ob->touched[j];
					}
				}
			}
		}
		
		m_nreqs = 0;
	}
	
	dtStatus status = DT_SUCCESS;
	// Process updates
	if (m_nupdate)
	{
		// Build mesh
		const dtCompressedTileRef ref = m_update[0];
		status = buildNavMeshTile(ref, navmesh);
		m_nupdate--;
		if (m_nupdate > 0)
			memmove(m_update, m_update+1, m_nupdate*sizeof(dtCompressedTileRef));

		// Update obstacle states.
		for (int i = 0; i < m_params.maxObstacles; ++i)
		{
			dtTileCacheObstacle* ob = &m_obstacles[i];
			if (ob->state == DT_OBSTACLE_PROCESSING || ob->state == DT_OBSTACLE_REMOVING)
			{
				// Remove handled tile from pending list.
				for (int j = 0; j < (int)ob->npending; j++)
				{
					if (ob->pending[j] == ref)
					{
						ob->pending[j] = ob->pending[(int)ob->npending-1];
						ob->npending--;
						break;
					}
				}
				
				// If all pending tiles processed, change state.
				if (ob->npending == 0)
				{
					if (ob->state == DT_OBSTACLE_PROCESSING)
					{
						ob->state = DT_OBSTACLE_PROCESSED;
					}
					else if (ob->state == DT_OBSTACLE_REMOVING)
					{
						ob->state = DT_OBSTACLE_EMPTY;
						// Update salt, salt should never be zero.
						ob->salt = (ob->salt+1) & ((1<<16)-1);
						if (ob->salt == 0)
							ob->salt++;
						// Return obstacle to free list.
						ob->next = m_nextFreeObstacle;
						m_nextFreeObstacle = ob;
					}
				}
			}
		}
		m_t64LastUpdateTime = TimeTools::getOperatingTime();
	}
	
	if (upToDate)
		*upToDate = m_nupdate == 0 && m_nreqs == 0;

	return status;
}


dtStatus dtTileCache::buildNavMeshTilesAt(const int tx, const int ty, dtNavMesh* navmesh)
{
	const int MAX_TILES = 32;
	dtCompressedTileRef tiles[MAX_TILES];
	const int ntiles = getTilesAt(tx,ty,tiles,MAX_TILES);
	
	for (int i = 0; i < ntiles; ++i)
	{
		dtStatus status = buildNavMeshTile(tiles[i], navmesh);
		if (dtStatusFailed(status))
			return status;
	}
	
	return DT_SUCCESS;
}

dtStatus dtTileCache::buildNavMeshTile(const dtCompressedTileRef ref, dtNavMesh* navmesh)
{	
	dtAssert(m_talloc);
	dtAssert(m_tcomp);
	
	unsigned int idx = decodeTileIdTile(ref);
	if (idx > (unsigned int)m_params.maxTiles)
		return DT_FAILURE | DT_INVALID_PARAM;
	const dtCompressedTile* tile = &m_tiles[idx];
	unsigned int salt = decodeTileIdSalt(ref);
	if (tile->salt != salt)
		return DT_FAILURE | DT_INVALID_PARAM;
	
	m_talloc->reset();
	
	NavMeshTileBuildContext bc(m_talloc);
	const int walkableClimbVx = (int)(m_params.walkableClimb / m_params.ch);
	dtStatus status;
	
	// Decompress tile layer data. 
	status = dtDecompressTileCacheLayer(m_talloc, m_tcomp, tile->data, tile->dataSize, &bc.layer);
	if (dtStatusFailed(status))
		return status;
	
	// Rasterize obstacles.
	for (int i = 0; i < m_params.maxObstacles; ++i)
	{
		const dtTileCacheObstacle* ob = &m_obstacles[i];
		if (ob->state == DT_OBSTACLE_EMPTY || ob->state == DT_OBSTACLE_REMOVING)
			continue;
		if (contains(ob->touched, ob->ntouched, ref))
		{
			if (ob->type == DT_OBSTACLE_CYLINDER)
			{
				dtMarkCylinderArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch,
							    ob->cylinder.pos, ob->cylinder.radius, ob->cylinder.height, ob->areaId);
			}
			else if (ob->type == DT_OBSTACLE_BOX)
			{
				dtMarkBoxArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch,
					ob->box.bmin, ob->box.bmax, ob->areaId);
			}
			else if (ob->type == DT_OBSTACLE_ORIENTED_BOX)
			{
				dtMarkBoxArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch,
					ob->orientedBox.center, ob->orientedBox.halfExtents, ob->orientedBox.rotAux, ob->areaId);
			}
		}
	}
	
	// Build navmesh
	status = dtBuildTileCacheRegions(m_talloc, *bc.layer, walkableClimbVx);
	if (dtStatusFailed(status))
		return status;
	
	bc.lcset = dtAllocTileCacheContourSet(m_talloc);
	if (!bc.lcset)
		return DT_FAILURE | DT_OUT_OF_MEMORY;
	status = dtBuildTileCacheContours(m_talloc, *bc.layer, walkableClimbVx,
									  m_params.maxSimplificationError, *bc.lcset);
	if (dtStatusFailed(status))
		return status;
	
	bc.lmesh = dtAllocTileCachePolyMesh(m_talloc);
	if (!bc.lmesh)
		return DT_FAILURE | DT_OUT_OF_MEMORY;
	status = dtBuildTileCachePolyMesh(m_talloc, *bc.lcset, *bc.lmesh);
	if (dtStatusFailed(status))
		return status;
	
	// Early out if the mesh tile is empty.
	if (!bc.lmesh->npolys)
	{
		// Remove existing tile.
		navmesh->removeTile(navmesh->getTileRefAt(tile->header->tx,tile->header->ty,tile->header->tlayer),0,0);
		return DT_SUCCESS;
	}
	
	dtNavMeshCreateParams params;
	memset(&params, 0, sizeof(params));
	params.verts = bc.lmesh->verts;
	params.vertCount = bc.lmesh->nverts;
	params.polys = bc.lmesh->polys;
	params.polyAreas = bc.lmesh->areas;
	params.polyFlags = bc.lmesh->flags;
	params.polyCount = bc.lmesh->npolys;
	params.nvp = DT_VERTS_PER_POLYGON;
	params.walkableHeight = m_params.walkableHeight;
	params.walkableRadius = m_params.walkableRadius;
	params.walkableClimb = m_params.walkableClimb;
	params.tileX = tile->header->tx;
	params.tileY = tile->header->ty;
	params.tileLayer = tile->header->tlayer;
	params.cs = m_params.cs;
	params.ch = m_params.ch;
	params.buildBvTree = false;
	dtVcopy(params.bmin, tile->header->bmin);
	dtVcopy(params.bmax, tile->header->bmax);
	
	if (m_tmproc)
	{
		m_tmproc->process(&params, bc.lmesh->areas, bc.lmesh->flags);
	}
	
	unsigned char* navData = 0;
	int navDataSize = 0;
	if (!dtCreateNavMeshData(&params, &navData, &navDataSize))
		return DT_FAILURE;

	// Remove existing tile.
	navmesh->removeTile(navmesh->getTileRefAt(tile->header->tx,tile->header->ty,tile->header->tlayer),0,0);

	// Add new tile, or leave the location empty.
	if (navData)
	{
		// Let the navmesh own the data.
		status = navmesh->addTile(navData,navDataSize,DT_TILE_FREE_DATA,0,0);
		if (dtStatusFailed(status))
		{
			dtFree(navData);
			return status;
		}
	}
	
	return DT_SUCCESS;
}

enum SamplePolyFlags
{
    SAMPLE_POLYFLAGS_WALK = 0x01,      // Ability to walk (ground, grass, road)
    SAMPLE_POLYFLAGS_SWIM = 0x02,      // Ability to swim (water).
    SAMPLE_POLYFLAGS_DOOR = 0x04,      // Ability to move through doors.
    SAMPLE_POLYFLAGS_JUMP = 0x08,      // Ability to jump.
    SAMPLE_POLYFLAGS_DISABLED = 0x10,  // Disabled polygon
    SAMPLE_POLYFLAGS_ALL = 0xffff      // All abilities.
};

void dtTileCache::setPolyState(dtNavMesh* navmesh, dtPolyRef ref, bool bEnable) {
    unsigned short flags = 0;
    if (dtStatusSucceed(navmesh->getPolyFlags(ref, &flags)))
    {
        if (bEnable) {
            if ((flags & SAMPLE_POLYFLAGS_DISABLED) != 0) {
                flags ^= SAMPLE_POLYFLAGS_DISABLED;
            }
        }
        else {
            if ((flags & SAMPLE_POLYFLAGS_DISABLED) == 0) {
                flags ^= SAMPLE_POLYFLAGS_DISABLED;
            }
        }
        navmesh->setPolyFlags(ref, flags);
    }
}

void dtTileCache::updateDoorObstaclePolys(dtNavMeshQuery* navQuery)
{
#define MAX_POLYS           1024
	m_mapDoorPoly2Obstacles.clear();
	for (auto& [obRef, stObParam] : m_mapObstacleParams) {
		// 缓存超时一下,重新获取一下 相关polys
		auto& queryPoly = stObParam.queryPoly;
		static dtPolyRef s_polys1[MAX_POLYS];
		int npolys1 = 0;

		static dtPolyRef s_parent[MAX_POLYS];
		static float s_polyPickExt[3];
		s_polyPickExt[0] = 100.0f;
		s_polyPickExt[1] = 2;
		s_polyPickExt[2] = 100.0f;

		dtQueryFilter s_filter;
		/*m_filter.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
		m_filter.setExcludeFlags(0);*/
		s_filter.setIncludeFlags(SAMPLE_POLYFLAGS_DOOR);		// SAMPLE_POLYFLAGS_DOOR = 0x04,      // Ability to move through doors.
		// 条件迷雾坐标是固定的矩形,旋转45度  LU  LD RD UR 分别对应 [0,1,2  3,4,5  6,7,8  9,10,11]
		float fMinX = queryPoly[9];
		float fMaxX = queryPoly[3];
		float fMinY = queryPoly[8];
		float fMaxY = queryPoly[2];
		//printf("(%f, %f)   -  (%f,%f) \n", fMinX, fMinY, fMaxX, fMaxY);
		static float s_posCenter[3];
		s_posCenter[0] = (fMinX + fMaxX) * 0.5f;
		s_posCenter[1] = 0;
		s_posCenter[2] = (fMinY + fMaxY) * 0.5f;
		s_polyPickExt[0] = (fMaxX - fMinX) * 0.5f;
		s_polyPickExt[1] = 2;
		s_polyPickExt[2] = (fMaxY - fMinY) * 0.5f;

		stObParam.setPoly.clear();
		//printf("center:%f,%f,  pickExt:%f,%f,%f \n", s_posCenter[0], s_posCenter[2], s_polyPickExt[0], s_polyPickExt[1], s_polyPickExt[2]);

		navQuery->queryPolygons(s_posCenter, s_polyPickExt, &s_filter, s_polys1, &npolys1, MAX_POLYS);

		std::set<dtPolyRef> setUsed;
		static dtPolyRef s_polys2[MAX_POLYS];
		int npolys = 0;
		for (int i = 0; i < npolys1; ++i) {
			auto startRef = s_polys1[i];
			//printf("test start:%u\n", startRef);
			if (setUsed.count(startRef) == 0) {
				setUsed.emplace(startRef);
				//printf("test start:%u  to found \n", startRef);

				navQuery->findPolysAroundShape(startRef, queryPoly, 4, &s_filter,
					s_polys2, s_parent, 0, &npolys, MAX_POLYS);
				//printf("test start:%u  found poly:%d \n", startRef, npolys);
				for (int j = 0; j < npolys; ++j) {
					auto _polyRef = s_polys2[j];
					setUsed.emplace(_polyRef);
					stObParam.setPoly.emplace(_polyRef);
					m_mapDoorPoly2Obstacles[_polyRef].emplace(obRef);
				}
			}
		}
	}
}

void dtTileCache::setObstaclePolysState(dtNavMeshQuery* navQuery, dtNavMesh* navmesh, dtObstacleRef obRef, bool bOpen, time_t tNow) {

	auto it = m_mapObstacleParams.find(obRef);
	if (it == m_mapObstacleParams.end()) {
		return;
	}
	if(m_t64LastUpdateTime > 0 && m_t64LastUpdateTime < tNow + 240) {
		this->updateDoorObstaclePolys(navQuery);
		m_t64LastUpdateTime = 0;
	}

	auto& stObParam = it->second;
	stObParam.bOpen = bOpen;
	for (auto polyRef: stObParam.setPoly) {
		auto itPoly = m_mapDoorPoly2Obstacles.find(polyRef);
		if (itPoly != m_mapDoorPoly2Obstacles.end()) {
			bool bEnablePoly = false;			// poly块是否有效,有效就是可通过, false则表示不可走
			for (auto& _obRef : itPoly->second) {
				auto itOb = m_mapObstacleParams.find(_obRef);
				if (itOb != m_mapObstacleParams.end()) {
					if (!itOb->second.bOpen) {			// 障碍区域没有开,即可通行,则相关poly就可以通过。
						bEnablePoly = true;
						break;
					}
				}
            }
            navmesh->setPolyEnable(polyRef, bEnablePoly);
		}
	}
}

void dtTileCache::setObstacleRange(dtObstacleRef obRef, const float rectPos[]) {
    auto& stParam = m_mapObstacleParams[obRef];
    stParam.queryPoly[0] = rectPos[0];
    stParam.queryPoly[1] = 0;
    stParam.queryPoly[2] = rectPos[1];

    stParam.queryPoly[3] = rectPos[2];
    stParam.queryPoly[4] = 0;
    stParam.queryPoly[5] = rectPos[3];

    stParam.queryPoly[6] = rectPos[4];
    stParam.queryPoly[7] = 0;
    stParam.queryPoly[8] = rectPos[5];

    stParam.queryPoly[9] = rectPos[6];
    stParam.queryPoly[10] = 0;
    stParam.queryPoly[11] = rectPos[7];
}

void dtTileCache::calcTightTileBounds(const dtTileCacheLayerHeader* header, float* bmin, float* bmax) const
{
	const float cs = m_params.cs;
	bmin[0] = header->bmin[0] + header->minx*cs;
	bmin[1] = header->bmin[1];
	bmin[2] = header->bmin[2] + header->miny*cs;
	bmax[0] = header->bmin[0] + (header->maxx+1)*cs;
	bmax[1] = header->bmax[1];
	bmax[2] = header->bmin[2] + (header->maxy+1)*cs;
}

void dtTileCache::getObstacleBounds(const struct dtTileCacheObstacle* ob, float* bmin, float* bmax) const
{
	if (ob->type == DT_OBSTACLE_CYLINDER)
	{
		const dtObstacleCylinder &cl = ob->cylinder;

		bmin[0] = cl.pos[0] - cl.radius;
		bmin[1] = cl.pos[1];
		bmin[2] = cl.pos[2] - cl.radius;
		bmax[0] = cl.pos[0] + cl.radius;
		bmax[1] = cl.pos[1] + cl.height;
		bmax[2] = cl.pos[2] + cl.radius;
	}
	else if (ob->type == DT_OBSTACLE_BOX)
	{
		dtVcopy(bmin, ob->box.bmin);
		dtVcopy(bmax, ob->box.bmax);
	}
	else if (ob->type == DT_OBSTACLE_ORIENTED_BOX)
	{
		const dtObstacleOrientedBox &orientedBox = ob->orientedBox;

		float maxr = 1.41f*dtMax(orientedBox.halfExtents[0], orientedBox.halfExtents[2]);
		bmin[0] = orientedBox.center[0] - maxr;
		bmax[0] = orientedBox.center[0] + maxr;
		bmin[1] = orientedBox.center[1] - orientedBox.halfExtents[1];
		bmax[1] = orientedBox.center[1] + orientedBox.halfExtents[1];
		bmin[2] = orientedBox.center[2] - maxr;
		bmax[2] = orientedBox.center[2] + maxr;
	}
}


dtStatus dtTileCache::buildNavMeshTile(const dtCompressedTileRef ref, dtNavMesh* navmesh, unsigned char * &nav_data, int & data_size)
{	
	dtAssert(m_talloc);
	dtAssert(m_tcomp);
	
	unsigned int idx = decodeTileIdTile(ref);
	if (idx > (unsigned int)m_params.maxTiles)
		return DT_FAILURE | DT_INVALID_PARAM;
	const dtCompressedTile* tile = &m_tiles[idx];
	unsigned int salt = decodeTileIdSalt(ref);
	if (tile->salt != salt)
		return DT_FAILURE | DT_INVALID_PARAM;
	
	m_talloc->reset();
	
	NavMeshTileBuildContext bc(m_talloc);
	const int walkableClimbVx = (int)(m_params.walkableClimb / m_params.ch);
	dtStatus status;
	
	// Decompress tile layer data. 
	status = dtDecompressTileCacheLayer(m_talloc, m_tcomp, tile->data, tile->dataSize, &bc.layer);
	if (dtStatusFailed(status))
		return status;
	
	// Rasterize obstacles.
	for (int i = 0; i < m_params.maxObstacles; ++i)
	{
		const dtTileCacheObstacle* ob = &m_obstacles[i];
		if (ob->state == DT_OBSTACLE_EMPTY || ob->state == DT_OBSTACLE_REMOVING)
			continue;
		if (contains(ob->touched, ob->ntouched, ref))
		{
			if (ob->type == DT_OBSTACLE_CYLINDER)
			{
				dtMarkCylinderArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch,
							    ob->cylinder.pos, ob->cylinder.radius, ob->cylinder.height, 0);
			}
			else if (ob->type == DT_OBSTACLE_BOX)
			{
				dtMarkBoxArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch,
							ob->box.bmin, ob->box.bmax, 0);
            }
            else if (ob->type == DT_OBSTACLE_ORIENTED_BOX)
            {
                dtMarkBoxArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch,
                    ob->orientedBox.center, ob->orientedBox.halfExtents, ob->orientedBox.rotAux, 0);
            }
		}
	}
	
	// Build navmesh
	status = dtBuildTileCacheRegions(m_talloc, *bc.layer, walkableClimbVx);
	if (dtStatusFailed(status))
		return status;
	
	bc.lcset = dtAllocTileCacheContourSet(m_talloc);
    if (!bc.lcset)
        return DT_FAILURE | DT_OUT_OF_MEMORY;
	status = dtBuildTileCacheContours(m_talloc, *bc.layer, walkableClimbVx,
									  m_params.maxSimplificationError, *bc.lcset);
	if (dtStatusFailed(status))
		return status;
	
	bc.lmesh = dtAllocTileCachePolyMesh(m_talloc);
    if (!bc.lmesh)
        return DT_FAILURE | DT_OUT_OF_MEMORY;
	status = dtBuildTileCachePolyMesh(m_talloc, *bc.lcset, *bc.lmesh);
	if (dtStatusFailed(status))
		return status;
	
	// Early out if the mesh tile is empty.
	if (!bc.lmesh->npolys)
	{
		// Remove existing tile.
		navmesh->removeTile(navmesh->getTileRefAt(tile->header->tx,tile->header->ty,tile->header->tlayer),0,0);
		return DT_SUCCESS;
	}
	
	dtNavMeshCreateParams params;
	memset(&params, 0, sizeof(params));
	params.verts = bc.lmesh->verts;
	params.vertCount = bc.lmesh->nverts;
	params.polys = bc.lmesh->polys;
	params.polyAreas = bc.lmesh->areas;
	params.polyFlags = bc.lmesh->flags;
	params.polyCount = bc.lmesh->npolys;
	params.nvp = DT_VERTS_PER_POLYGON;
	params.walkableHeight = m_params.walkableHeight;
	params.walkableRadius = m_params.walkableRadius;
	params.walkableClimb = m_params.walkableClimb;
	params.tileX = tile->header->tx;
	params.tileY = tile->header->ty;
	params.tileLayer = tile->header->tlayer;
	params.cs = m_params.cs;
	params.ch = m_params.ch;
	params.buildBvTree = false;
	dtVcopy(params.bmin, tile->header->bmin);
	dtVcopy(params.bmax, tile->header->bmax);
	
	if (m_tmproc)
	{
		m_tmproc->process(&params, bc.lmesh->areas, bc.lmesh->flags);
	}
	
	unsigned char* navData = 0;
	int navDataSize = 0;
	if (!dtCreateNavMeshData(&params, &navData, &navDataSize))
		return DT_FAILURE;
	
	nav_data = navData;
	data_size = navDataSize;
	// Remove existing tile.
	navmesh->removeTile(navmesh->getTileRefAt(tile->header->tx,tile->header->ty,tile->header->tlayer),0,0);

	// Add new tile, or leave the location empty.
	if (navData)
	{
		// Let the navmesh own the data.
		status = navmesh->addTile(navData,navDataSize,DT_TILE_FREE_DATA,0,0);
		if (dtStatusFailed(status))
		{
			dtFree(navData);
			return status;
		}
	}
	
	return DT_SUCCESS;
}

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值