#include "detour-navmesh.h"
#include <DetourNavMesh.h>
#include <DetourNavMeshBuilder.h>
#include <DetourTileCache.h>
#include <DetourTileCacheBuilder.h>
#include <Recast.h>
#include <fastlz.h>

static const int DEFAULT_TILE_SIZE = 64;
static const float DEFAULT_CELL_SIZE = 0.3f;
static const float DEFAULT_CELL_HEIGHT = 0.2f;
static const float DEFAULT_AGENT_HEIGHT = 2.0f;
static const float DEFAULT_AGENT_RADIUS = 0.6f;
static const float DEFAULT_AGENT_MAX_CLIMB = 0.9f;
static const float DEFAULT_AGENT_MAX_SLOPE = 45.0f;
static const float DEFAULT_REGION_MIN_SIZE = 8.0f;
static const float DEFAULT_REGION_MERGE_SIZE = 20.0f;
static const float DEFAULT_EDGE_MAX_LENGTH = 12.0f;
static const float DEFAULT_EDGE_MAX_ERROR = 1.3f;
static const float DEFAULT_DETAIL_SAMPLE_DISTANCE = 6.0f;
static const float DEFAULT_DETAIL_SAMPLE_MAX_ERROR = 1.0f;

#ifdef TILE_CACHE
static const int DEFAULT_MAX_OBSTACLES = 1024;
static const int DEFAULT_MAX_LAYERS = 16;
#endif

#ifdef TILE_CACHE
struct FastLZCompressor : public dtTileCacheCompressor {
	virtual int maxCompressedSize(const int bufferSize) {
		return (int)(bufferSize * 1.05f);
	}

	virtual dtStatus compress(const unsigned char *buffer, const int bufferSize,
			unsigned char *compressed,
			const int /*maxCompressedSize*/,
			int *compressedSize) {
		*compressedSize =
				fastlz_compress((const void *)buffer, bufferSize, compressed);
		return DT_SUCCESS;
	}

	virtual dtStatus decompress(const unsigned char *compressed,
			const int compressedSize, unsigned char *buffer,
			const int maxBufferSize, int *bufferSize) {
		*bufferSize =
				fastlz_decompress(compressed, compressedSize, buffer, maxBufferSize);
		return *bufferSize < 0 ? DT_FAILURE : DT_SUCCESS;
	}
};
struct LinearAllocator : public dtTileCacheAlloc {
	unsigned char *buffer;
	size_t capacity;
	size_t top;
	size_t high;

	LinearAllocator(const size_t cap) :
			buffer(0),
			capacity(0),
			top(0),
			high(0) {
		resize(cap);
	}

	~LinearAllocator() { dtFree(buffer); }

	void resize(const size_t cap) {
		if (buffer)
			dtFree(buffer);
		buffer = (unsigned char *)dtAlloc(cap, DT_ALLOC_PERM);
		capacity = cap;
	}

	virtual void reset() {
		high = MAX(high, top);
		top = 0;
	}

	virtual void *alloc(const size_t size) {
		if (!buffer)
			return 0;
		if (top + size > capacity)
			return 0;
		unsigned char *mem = &buffer[top];
		top += size;
		return mem;
	}

	virtual void free(void * /*ptr*/) {
		// Empty
	}
};

struct NavMeshProcess : public dtTileCacheMeshProcess {
	DetourNavigationMesh *nav;
	inline explicit NavMeshProcess(DetourNavigationMesh *mesh) :
			nav(mesh) {}
	virtual void process(struct dtNavMeshCreateParams *params,
			unsigned char *polyAreas, unsigned short *polyFlags) {
		/* Add proper flags and offmesh connections here */
		for (int i = 0; i < params->polyCount; i++) {
			if (polyAreas[i] != RC_NULL_AREA)
				polyFlags[i] = RC_WALKABLE_AREA;
		}
		params->offMeshConCount = nav->offmesh_radii.size();
		if (params->offMeshConCount > 0) {
			params->offMeshConVerts =
					reinterpret_cast<const float *>(&nav->offmesh_vertices[0]);
			params->offMeshConRad = &nav->offmesh_radii[0];
			params->offMeshConFlags = &nav->offmesh_flags[0];
			params->offMeshConAreas = &nav->offmesh_areas[0];
			params->offMeshConDir = &nav->offmesh_dir[0];
			print_line("added offmesh connection");
		} else {
			print_line("NO offmesh connection");
			params->offMeshConVerts = NULL;
			params->offMeshConRad = NULL;
			params->offMeshConFlags = NULL;
			params->offMeshConAreas = NULL;
			params->offMeshConDir = NULL;
		}
		nav->clear_debug_mesh();
		nav->get_debug_mesh();
	}
};

#endif

DetourNavigationMesh::DetourNavigationMesh() :
		Resource(),
		navmesh(NULL),
#ifdef TILE_CACHE
		tile_cache(0),
		tile_cache_alloc(new LinearAllocator(64000)),
		tile_cache_compressor(new FastLZCompressor),
		mesh_process(new NavMeshProcess(this)),
#endif
		group(""),
		initialized(false),
#ifdef TILE_CACHE
		max_obstacles(DEFAULT_MAX_OBSTACLES),
		max_layers(DEFAULT_MAX_LAYERS),
#endif
		tile_size(DEFAULT_TILE_SIZE),
		cell_size(DEFAULT_CELL_SIZE),
		cell_height(DEFAULT_CELL_HEIGHT),
		agent_height(DEFAULT_AGENT_HEIGHT),
		agent_radius(DEFAULT_AGENT_RADIUS),
		agent_max_climb(DEFAULT_AGENT_MAX_CLIMB),
		agent_max_slope(DEFAULT_AGENT_MAX_SLOPE),
		region_min_size(DEFAULT_REGION_MIN_SIZE),
		region_merge_size(DEFAULT_REGION_MERGE_SIZE),
		edge_max_length(DEFAULT_EDGE_MAX_LENGTH),
		edge_max_error(DEFAULT_EDGE_MAX_ERROR),
		detail_sample_distance(DEFAULT_DETAIL_SAMPLE_DISTANCE),
		detail_sample_max_error(DEFAULT_DETAIL_SAMPLE_MAX_ERROR),
		bounding_box(AABB()),
		padding(Vector3(1.0f, 1.0f, 1.0f)) {
}

bool DetourNavigationMesh::alloc_tile_cache() {
	tile_cache = dtAllocTileCache();
	if (!tile_cache) {
		ERR_PRINT("Could not allocate tile cache");
		release_navmesh();
		return false;
	}
	return true;
}

void DetourNavigationMesh::release_navmesh() {
	dtFreeNavMesh((dtNavMesh *)navmesh);
	navmesh = NULL;
	num_tiles_x = 0;
	num_tiles_z = 0;
	bounding_box = AABB();
}

void DetourNavigationMesh::set_group(const String &group) {
	this->group = group;
}

unsigned int DetourNavigationMesh::add_obstacle(const Vector3 &pos,
		real_t radius, real_t height) {
	/* Need to test how this works and why this needed at all */
	/* TODO implement navmesh changes queue */
	//	while (tile_cache->isObstacleQueueFull())
	//		tile_cache->update(1, navMesh_);
	dtObstacleRef ref = 0;
	if (dtStatusFailed(
				tile_cache->addObstacle(&pos.coord[0], radius, height, &ref))) {
		ERR_PRINT("can't add obstacle");
		return 0;
	}
	return (unsigned int)ref;
}
void DetourNavigationMesh::remove_obstacle(unsigned int id) {
	/* Need to test how this works and why this needed at all */
	/* TODO implement navmesh changes queue */
	//	while (tile_cache->isObstacleQueueFull())
	//		tile_cache->update(1, navMesh_);
	if (dtStatusFailed(tile_cache->removeObstacle(id)))
		ERR_PRINT("failed to remove obstacle");
}

bool DetourNavigationMesh::alloc() {
	navmesh = dtAllocNavMesh();
	return navmesh ? true : false;
}
bool DetourNavigationMesh::init(dtNavMeshParams *params) {
	if (dtStatusFailed((navmesh)->init(params))) {
		release_navmesh();
		return false;
	}
	initialized = true;
	return true;
}
#ifdef TILE_CACHE
bool DetourNavigationMesh::init_tile_cache(dtTileCacheParams *params) {
	if (dtStatusFailed(tile_cache->init(params, tile_cache_alloc,
				tile_cache_compressor, mesh_process))) {
		ERR_PRINT("Could not initialize tile cache");
		release_navmesh();
		return false;
	}
	return true;
}
#endif
Ref<ArrayMesh> DetourNavigationMesh::get_debug_mesh() {
	if (debug_mesh.is_valid())
		return debug_mesh;
	if (!navmesh)
		return debug_mesh;
	print_line("building debug navmesh");
	List<Vector3> lines;
	const dtNavMesh *navm = navmesh;
	for (int i = 0; i < navm->getMaxTiles(); i++) {
		const dtMeshTile *tile = navm->getTile(i);
		if (!tile || !tile->header)
			continue;
		for (int j = 0; j < tile->header->polyCount; j++) {
			dtPoly *poly = tile->polys + j;
			if (poly->getType() != DT_POLYTYPE_OFFMESH_CONNECTION) {
				for (int k = 0; k < poly->vertCount; k++) {
					lines.push_back(*reinterpret_cast<const Vector3 *>(
							&tile->verts[poly->verts[k] * 3]));
					lines.push_back(*reinterpret_cast<const Vector3 *>(
							&tile->verts[poly->verts[(k + 1) % poly->vertCount] * 3]));
				}
			} else if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) {
				const dtOffMeshConnection *con =
						&tile->offMeshCons[j - tile->header->offMeshBase];
				const float *va = &tile->verts[poly->verts[0] * 3];
				const float *vb = &tile->verts[poly->verts[1] * 3];
#if 0
				/* TODO: implement offmesh debug proper */
				bool startSet = false;
				bool endSet = false;
				for (unsigned int k = poly->firstLink; k != DT_NULL_LINK;
						k = tile->links[k].next) {
					if (tile->links[k].edge == 0)
						startSet = true;
					if (tile->links[k].edge == 1)
						endSet = true;
				}
#endif
				Vector3 p0 = *reinterpret_cast<const Vector3 *>(va);
				Vector3 p1 = *reinterpret_cast<const Vector3 *>(&con[0]);
				Vector3 p2 = *reinterpret_cast<const Vector3 *>(&con[3]);
				Vector3 p3 = *reinterpret_cast<const Vector3 *>(vb);
				lines.push_back(p0);
				lines.push_back(p1);
				lines.push_back(p1);
				lines.push_back(p2);
				lines.push_back(p2);
				lines.push_back(p3);
				print_line("debug offmesh connection");
			}
		}
	}
	print_line("debug mesh lines: " + itos(lines.size()));

	PoolVector<Vector3> varr;
	varr.resize(lines.size());
	PoolVector<Vector3>::Write w = varr.write();
	int idx = 0;
	for (List<Vector3>::Element *E = lines.front(); E; E = E->next()) {
		w[idx++] = E->get();
	}

	debug_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));

	Array arr;
	arr.resize(Mesh::ARRAY_MAX);
	arr[Mesh::ARRAY_VERTEX] = varr;

	debug_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, arr);

	return debug_mesh;
}

void DetourNavigationMesh::set_data(const Dictionary &p_value) {
	dtNavMeshParams params;
	Vector3 orig = p_value["orig"];
	rcVcopy(params.orig, &orig.coord[0]);
	params.tileWidth = p_value["tile_edge_length"];
	params.tileHeight = p_value["tile_edge_length"];
	params.maxTiles = p_value["max_tiles"];
	params.maxPolys = p_value["max_polys"];
	if (navmesh) {
		if (initialized)
			dtFreeNavMesh(navmesh);
		else
			dtFree(navmesh);
		navmesh = NULL;
	}
	if (!alloc())
		return;
	if (!init(&params))
		return;
}

Dictionary DetourNavigationMesh::get_data() {
	Dictionary t;
#if 0
	t["initialized"] = initialized;
	if (!initialized) {
		return t;
	}
        const dtNavMeshParams *params = navmesh->getParams();
	Vector3 orig;
	rcVcopy(&orig.coord[0], params->orig);
	t["orig"] = orig;
	t["tile_edge_length"] = params->tileWidth;
	t["max_tiles"] = params->maxTiles;
	t["max_polys"] = params->maxPolys;
	PoolVector<uint8_t> data;
	PoolVector<uint8_t>::Write data_w = data.write();
	const dtNavMesh *nm = navmesh;
	int pos = 0;
	for (int z = 0; z < num_tiles_z; z++)
		for (int x = 0; x < num_tiles_x; x++) {
			const dtMeshTile* tile = nm->getTileAt(x, z, 0);
			if (!tile)
				continue;
			if (pos >= data.size())
				data.resize(data.size() + sizeof(int) * 2 + sizeof(unsigned int) * 2 + tile->dataSize);
			memcpy(&data_w[pos], &x, sizeof(x));
			pos += sizeof(x);
			memcpy(&data_w[pos], &z, sizeof(x));
			pos += sizeof(z);
			uint32_t tile_ref = (uint32_t)nm->getTileRef(tile);
			memcpy(&data_w[pos], &tile_ref, sizeof(tile_ref));
			pos += sizeof(tile_ref);
			uint32_t data_size = (uint32_t)tile->dataSize;
			memcpy(&data_w[pos], &data_size, sizeof(data_size));
			pos += sizeof(data_size);
			memcpy(&data_w[pos], tile->data, data_size);
			pos += data_size;
		}
	print_line("submitted: " + itos(data.size()));
	t["data"] = data;
#endif
	return t;
}

#define SETGET(v, t)                                                           \
	ClassDB::bind_method(D_METHOD("set_" #v, #v),                              \
			&DetourNavigationMesh::set_##v);                                   \
	ClassDB::bind_method(D_METHOD("get_" #v), &DetourNavigationMesh::get_##v); \
	ADD_PROPERTY(PropertyInfo(Variant::t, #v), "set_" #v, "get_" #v)

void DetourNavigationMesh::_bind_methods() {
	SETGET(cell_size, REAL);
	SETGET(cell_height, REAL);
	SETGET(agent_height, REAL);
	SETGET(agent_radius, REAL);
	SETGET(agent_max_climb, REAL);
	SETGET(agent_max_slope, REAL);
	SETGET(region_min_size, REAL);
	SETGET(region_merge_size, REAL);
	SETGET(edge_max_length, REAL);
	SETGET(edge_max_error, REAL);
	SETGET(detail_sample_distance, REAL);
	SETGET(detail_sample_max_error, REAL);
	SETGET(group, STRING);
	SETGET(padding, VECTOR3);
	SETGET(tile_size, INT);
	BIND_ENUM_CONSTANT(PARTITION_WATERSHED);
	BIND_ENUM_CONSTANT(PARTITION_MONOTONE);
	ClassDB::bind_method(D_METHOD("set_partition_type", "type"),
			&DetourNavigationMesh::set_partition_type);
	ClassDB::bind_method(D_METHOD("get_partition_type"),
			&DetourNavigationMesh::get_partition_type);
	ClassDB::bind_method(D_METHOD("set_data", "data"),
			&DetourNavigationMesh::set_data);
	ClassDB::bind_method(D_METHOD("get_data"), &DetourNavigationMesh::get_data);
	ADD_PROPERTY(PropertyInfo(Variant::INT, "partition_type", PROPERTY_HINT_ENUM,
						 "watershed,monotone"),
			"set_partition_type", "get_partition_type");
	ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "",
						 PROPERTY_USAGE_STORAGE),
			"set_data", "get_data");
}
#undef SETGET

void DetourNavigationMesh::get_tile_bounding_box(int x, int z, Vector3 &bmin,
		Vector3 &bmax) {
	const float tile_edge_length = (float)tile_size * cell_size;
	bmin = bounding_box.position +
		   Vector3(tile_edge_length * (float)x, 0, tile_edge_length * (float)z);
	bmax =
			bmin + Vector3(tile_edge_length, bounding_box.size.y, tile_edge_length);
	// print_line("tile bounding box: " +itos(x) + " " + itos(z) + ": " +
	// String(bmin) + "/" + String(bmax));
	// print_line("mesh bounding box: " + String(mesh->bounding_box));
}
bool DetourNavigationMesh::build_tile(const Transform &xform,
		const Vector<Ref<Mesh> > &geometries,
		const Vector<Transform> &xforms, int x,
		int z) {
	Vector3 bmin, bmax;
	get_tile_bounding_box(x, z, bmin, bmax);
	dtNavMesh *nav = get_navmesh();
#ifdef TILE_CACHE
	dtTileCache *tile_cache = get_tile_cache();
	tile_cache->removeTile(nav->getTileRefAt(x, z, 0), NULL, NULL);
#else
	nav->removeTile(nav->getTileRefAt(x, z, 0), NULL, NULL);
#endif
	rcConfig cfg;
	cfg.cs = cell_size;
	cfg.ch = cell_height;
	cfg.walkableSlopeAngle = agent_max_slope;
	cfg.walkableHeight = (int)ceil(agent_height / cfg.ch);
	cfg.walkableClimb = (int)floor(agent_max_climb / cfg.ch);
	cfg.walkableRadius = (int)ceil(agent_radius / cfg.cs);
	cfg.maxEdgeLen = (int)(edge_max_length / cfg.cs);
	cfg.maxSimplificationError = edge_max_error;
	cfg.minRegionArea = (int)sqrtf(region_min_size);
	cfg.mergeRegionArea = (int)sqrtf(region_merge_size);
	cfg.maxVertsPerPoly = 6;
	cfg.tileSize = tile_size;
	cfg.borderSize = cfg.walkableRadius + 3;
	cfg.width = cfg.tileSize + cfg.borderSize * 2;
	cfg.height = cfg.tileSize + cfg.borderSize * 2;
	cfg.detailSampleDist =
			detail_sample_distance < 0.9f ? 0.0f : cell_size * detail_sample_distance;
	cfg.detailSampleMaxError = cell_height * detail_sample_max_error;
	rcVcopy(cfg.bmin, &bmin.coord[0]);
	rcVcopy(cfg.bmax, &bmax.coord[0]);
	cfg.bmin[0] -= cfg.borderSize * cfg.cs;
	cfg.bmin[2] -= cfg.borderSize * cfg.cs;
	cfg.bmax[0] += cfg.borderSize * cfg.cs;
	cfg.bmax[2] += cfg.borderSize * cfg.cs;

	AABB expbox(bmin, bmax - bmin);
	expbox.position.x -= cfg.borderSize * cfg.cs;
	expbox.position.z -= cfg.borderSize * cfg.cs;
	expbox.size.x += 2.0 * cfg.borderSize * cfg.cs;
	expbox.size.z += 2.0 * cfg.borderSize * cfg.cs;
	Vector<float> points;
	Vector<int> indices;
	Transform base = xform.inverse();
	for (int idx = 0; idx < geometries.size(); idx++) {
		if (!geometries[idx].is_valid())
			continue;
		AABB mesh_aabb = geometries[idx]->get_aabb();
		Transform mxform = base * xforms[idx];
		mesh_aabb = mxform.xform(mesh_aabb);
		if (!mesh_aabb.intersects_inclusive(expbox) &&
				!expbox.encloses(mesh_aabb)) {
			continue;
		}
		// Add offmesh
		// Add NavArea
		// Add PhysicsBodies?
		Ref<Mesh> mdata = geometries[idx];
		// FIXME
		add_meshdata(mdata, mxform, points, indices);
	}
// print_line(String() + "points: " + itos(points.size()) + " indices: " +
// itos(indices.size()) + " tile_size: " + itos(mesh->tile_size));
#if 0
	print_line("mesh points:");
	for (int k = 0; k < points.size(); k += 3)
		print_line("point: " + itos(k) + ": " + rtos(points[k]) + ", " + rtos(points[k + 1]) + ", " + rtos(points[k + 2]));
#endif
	if (points.size() == 0 || indices.size() == 0)
		/* Nothing to do */
		return true;
	rcHeightfield *heightfield = rcAllocHeightfield();
	if (!heightfield) {
		ERR_PRINT("Failed to allocate height field");
		return false;
	}
	rcContext *ctx = new rcContext(true);
	if (!rcCreateHeightfield(ctx, *heightfield, cfg.width, cfg.height, cfg.bmin,
				cfg.bmax, cfg.cs, cfg.ch)) {
		ERR_PRINT("Failed to create height field");
		return false;
	}
	int ntris = indices.size() / 3;
	Vector<unsigned char> tri_areas;
	tri_areas.resize(ntris);
	memset(&tri_areas.write[0], 0, ntris);
	rcMarkWalkableTriangles(ctx, cfg.walkableSlopeAngle, &points[0],
			points.size() / 3, &indices[0], ntris,
			&tri_areas.write[0]);
	rcRasterizeTriangles(ctx, &points[0], points.size() / 3, &indices[0],
			&tri_areas[0], ntris, *heightfield, cfg.walkableClimb);
	rcFilterLowHangingWalkableObstacles(ctx, cfg.walkableClimb, *heightfield);

	rcFilterLedgeSpans(ctx, cfg.walkableHeight, cfg.walkableClimb, *heightfield);
	rcFilterWalkableLowHeightSpans(ctx, cfg.walkableHeight, *heightfield);

	rcCompactHeightfield *compact_heightfield = rcAllocCompactHeightfield();
	if (!compact_heightfield) {
		ERR_PRINT("Failed to allocate compact height field");
		return false;
	}
	if (!rcBuildCompactHeightfield(ctx, cfg.walkableHeight, cfg.walkableClimb,
				*heightfield, *compact_heightfield)) {
		ERR_PRINT("Could not build compact height field");
		return false;
	}
	if (!rcErodeWalkableArea(ctx, cfg.walkableRadius, *compact_heightfield)) {
		ERR_PRINT("Could not erode walkable area");
		return false;
	}

// TODO: Implement area storage in navmesh data and build from that data
// populate at collect_geometry stage
// areas should indicate if walkable
#if 0
	for (unsigned int i = 0; i < nav_areas.size(); i++) {
		Vector3 amin = nav_areas[i].bounds.position;
		Vector3 amax = amin + nav_areas[i].bounds.size;
		int id = nav_areas[i].id;
		rcMarkBoxArea(ctx, &amin.coord[0], &amax.coord[0],
			id, *compact_heightfield);
	}
#endif
	if (partition_type == DetourNavigationMesh::PARTITION_WATERSHED) {
		if (!rcBuildDistanceField(ctx, *compact_heightfield))
			return false;
		if (!rcBuildRegions(ctx, *compact_heightfield, cfg.borderSize,
					cfg.minRegionArea, cfg.mergeRegionArea))
			return false;
	} else if (!rcBuildRegionsMonotone(ctx, *compact_heightfield, cfg.borderSize,
					   cfg.minRegionArea, cfg.mergeRegionArea))
		return false;
#ifdef TILE_CACHE
	rcHeightfieldLayerSet *heightfield_layer_set = rcAllocHeightfieldLayerSet();
	if (!heightfield_layer_set) {
		ERR_PRINT("Could not allocate height field layer set");
		return false;
	}
	if (!rcBuildHeightfieldLayers(ctx, *compact_heightfield, cfg.borderSize,
				cfg.walkableHeight, *heightfield_layer_set)) {
		ERR_PRINT("Could not build heightfield layers");
		return false;
	}
	for (int i = 0; i < heightfield_layer_set->nlayers; i++) {
		dtTileCacheLayerHeader header;
		header.magic = DT_TILECACHE_MAGIC;
		header.version = DT_TILECACHE_VERSION;
		header.tx = x;
		header.ty = z;
		header.tlayer = i;
		rcHeightfieldLayer *layer = &heightfield_layer_set->layers[i];
		rcVcopy(header.bmin, layer->bmin);
		rcVcopy(header.bmax, layer->bmax);
		header.width = (unsigned char)layer->width;
		header.height = (unsigned char)layer->height;
		header.minx = (unsigned char)layer->minx;
		header.maxx = (unsigned char)layer->maxx;
		header.miny = (unsigned char)layer->miny;
		header.maxy = (unsigned char)layer->maxy;
		header.hmin = (unsigned short)layer->hmin;
		header.hmax = (unsigned short)layer->hmax;
		unsigned char *tile_data;
		int tile_data_size;
		if (dtStatusFailed(dtBuildTileCacheLayer(
					get_tile_cache_compressor(), &header, layer->heights, layer->areas,
					layer->cons, &tile_data, &tile_data_size))) {
			ERR_PRINT("Failed to build tile cache layers");
			return false;
		}
		dtCompressedTileRef tileRef;
		int status = tile_cache->addTile(tile_data, tile_data_size,
				DT_COMPRESSEDTILE_FREE_DATA, &tileRef);
		if (dtStatusFailed((dtStatus)status)) {
			dtFree(tile_data);
			tile_data = NULL;
		}
		tile_cache->buildNavMeshTilesAt(x, z, nav);
	}
#else
	rcContourSet *contour_set = rcAllocContourSet();
	if (!contour_set)
		return false;
	print_line("allocated contour set");
	if (!rcBuildContours(ctx, *compact_heightfield, cfg.maxSimplificationError,
				cfg.maxEdgeLen, *contour_set))
		return false;
	print_line("created contour set");
	rcPolyMesh *poly_mesh = rcAllocPolyMesh();
	if (!poly_mesh)
		return false;
	print_line("allocated polymesh");
	if (!rcBuildPolyMesh(ctx, *contour_set, cfg.maxVertsPerPoly, *poly_mesh))
		return false;
	print_line("created polymesh");
	rcPolyMeshDetail *poly_mesh_detail = rcAllocPolyMeshDetail();
	if (!poly_mesh_detail)
		return false;
	print_line("allocated polymesh detail");
	if (!rcBuildPolyMeshDetail(ctx, *poly_mesh, *compact_heightfield,
				cfg.detailSampleDist, cfg.detailSampleMaxError,
				*poly_mesh_detail))
		return false;
	print_line("created polymesh detail");
	/* Assign area flags TODO: use nav area assignment here */
	for (int i = 0; i < poly_mesh->npolys; i++) {
		if (poly_mesh->areas[i] != RC_NULL_AREA)
			poly_mesh->flags[i] = 0x1;
	}
	print_line("created area flags");
	unsigned char *nav_data = NULL;
	int nav_data_size = 0;
	dtNavMeshCreateParams params;
	memset(&params, 0, sizeof params);
	params.verts = poly_mesh->verts;
	params.vertCount = poly_mesh->nverts;
	params.polys = poly_mesh->polys;
	params.polyAreas = poly_mesh->areas;
	params.polyFlags = poly_mesh->flags;
	params.polyCount = poly_mesh->npolys;
	params.nvp = poly_mesh->nvp;
	params.detailMeshes = poly_mesh_detail->meshes;
	params.detailVerts = poly_mesh_detail->verts;
	params.detailVertsCount = poly_mesh_detail->nverts;
	params.detailTris = poly_mesh_detail->tris;
	params.detailTriCount = poly_mesh_detail->ntris;
	params.walkableHeight = mesh->agent_height;
	params.walkableRadius = mesh->agent_radius;
	params.walkableClimb = mesh->agent_max_climb;
	params.tileX = x;
	params.tileY = z;
	rcVcopy(params.bmin, poly_mesh->bmin);
	rcVcopy(params.bmax, poly_mesh->bmax);
	params.cs = cfg.cs;
	params.ch = cfg.ch;
	params.buildBvTree = true;
#if 0
	// building offmesh conections
    if (build.offMeshRadii_.Size())
    {
        params.offMeshConCount = build.offMeshRadii_.Size();
        params.offMeshConVerts = &build.offMeshVertices_[0].x_;
        params.offMeshConRad = &build.offMeshRadii_[0];
        params.offMeshConFlags = &build.offMeshFlags_[0];
        params.offMeshConAreas = &build.offMeshAreas_[0];
        params.offMeshConDir = &build.offMeshDir_[0];
    }
#endif
	print_line("setup offmesh connections");

	if (!dtCreateNavMeshData(&params, &nav_data, &nav_data_size))
		return false;
	if (dtStatusFailed(mesh->get_navmesh()->addTile(
				nav_data, nav_data_size, DT_TILE_FREE_DATA, 0, NULL))) {
		dtFree(nav_data);
		return false;
	}
	print_line("created navmesh data");
#endif
	return true;
}

void DetourNavigationMesh::add_meshdata(const Ref<Mesh> &p_mesh,
		const Transform &p_xform,
		Vector<float> &p_verticies,
		Vector<int> &p_indices) {
	int current_vertex_count = 0;

	for (int i = 0; i < p_mesh->get_surface_count(); i++) {
		current_vertex_count = p_verticies.size() / 3;

		if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES)
			continue;

		int index_count = 0;
		if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
			index_count = p_mesh->surface_get_array_index_len(i);
		} else {
			index_count = p_mesh->surface_get_array_len(i);
		}

		ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0));

		int face_count = index_count / 3;

		Array a = p_mesh->surface_get_arrays(i);

		PoolVector<Vector3> mesh_vertices = a[Mesh::ARRAY_VERTEX];
		PoolVector<Vector3>::Read vr = mesh_vertices.read();

		if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {

			PoolVector<int> mesh_indices = a[Mesh::ARRAY_INDEX];
			PoolVector<int>::Read ir = mesh_indices.read();

			for (int i = 0; i < mesh_vertices.size(); i++) {
				Vector3 p_vec3 = p_xform.xform(vr[i]);
				p_verticies.push_back(p_vec3.x);
				p_verticies.push_back(p_vec3.y);
				p_verticies.push_back(p_vec3.z);
			}

			for (int i = 0; i < face_count; i++) {
				// CCW
				p_indices.push_back(current_vertex_count + (ir[i * 3 + 0]));
				p_indices.push_back(current_vertex_count + (ir[i * 3 + 2]));
				p_indices.push_back(current_vertex_count + (ir[i * 3 + 1]));
			}
		} else {
			face_count = mesh_vertices.size() / 3;
			for (int i = 0; i < face_count; i++) {
				Vector3 p_vec3 = p_xform.xform(vr[i * 3 + 0]);
				p_verticies.push_back(p_vec3.x);
				p_verticies.push_back(p_vec3.y);
				p_verticies.push_back(p_vec3.z);
				p_vec3 = p_xform.xform(vr[i * 3 + 2]);
				p_verticies.push_back(p_vec3.x);
				p_verticies.push_back(p_vec3.y);
				p_verticies.push_back(p_vec3.z);
				p_vec3 = p_xform.xform(vr[i * 3 + 1]);
				p_verticies.push_back(p_vec3.x);
				p_verticies.push_back(p_vec3.y);
				p_verticies.push_back(p_vec3.z);

				p_indices.push_back(current_vertex_count + (i * 3 + 0));
				p_indices.push_back(current_vertex_count + (i * 3 + 1));
				p_indices.push_back(current_vertex_count + (i * 3 + 2));
			}
		}
	}
}

void DetourNavigationMesh::remove_tile(int x, int z) {
#ifdef TILE_CACHE
	dtTileCache *tile_cache = get_tile_cache();
	tile_cache->removeTile(navmesh->getTileRefAt(x, z, 0), NULL, NULL);
#else
	nav->removeTile(navmesh->getTileRefAt(x, z, 0), NULL, NULL);
#endif
}

unsigned int DetourNavigationMesh::build_tiles(
		const Transform &xform, const Vector<Ref<Mesh> > &geometries,
		const Vector<Transform> &xforms, int x1, int z1, int x2, int z2) {
	unsigned ret = 0;
	for (int z = z1; z <= z2; z++) {
		for (int x = x1; x <= x2; x++) {
			if (build_tile(xform, geometries, xforms, x, z))
				ret++;
		}
	}
#ifdef TILE_CACHE
	get_tile_cache()->update(0, get_navmesh());
#endif
	return ret;
}
