From 1de5e2f1198aace213a57f19736d9867f7c6f217 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 25 Mar 2011 09:16:38 +0000 Subject: [PATCH] DETOUR API CHANGE! - Detour Navmesh supports layers - Allow to disable Navmesh BV-tree - Added DetourTileCache - Cleaned up Recast layer code - Moved portal edge detection to Recast - Removed polymesh border offset - Removed lean heighfield --- DebugUtils/Include/DetourDebugDraw.h | 6 + DebugUtils/Include/RecastDebugDraw.h | 3 +- DebugUtils/Source/DebugDraw.cpp | 2 +- DebugUtils/Source/DetourDebugDraw.cpp | 101 +- DebugUtils/Source/RecastDebugDraw.cpp | 306 +-- DebugUtils/Source/RecastDump.cpp | 17 +- Detour/Include/DetourCommon.h | 1 + Detour/Include/DetourNavMesh.h | 68 +- Detour/Include/DetourNavMeshBuilder.h | 4 +- Detour/Include/DetourNavMeshQuery.h | 1 + Detour/Include/DetourStatus.h | 64 + Detour/Source/DetourNavMesh.cpp | 188 +- Detour/Source/DetourNavMeshBuilder.cpp | 81 +- Detour/Source/DetourNavMeshQuery.cpp | 31 +- DetourTileCache/DetourTileCacheBuilder.cpp | 1973 ++++++++++++++++ DetourTileCache/DetourTileCacheBuilder.h | 119 + Recast/Include/Recast.h | 107 +- Recast/Source/Recast.cpp | 270 --- Recast/Source/RecastContour.cpp | 44 +- Recast/Source/RecastLayers.cpp | 2054 +---------------- Recast/Source/RecastMesh.cpp | 36 +- Recast/Source/RecastMeshDetail.cpp | 14 +- Recast/Source/RecastRegion.cpp | 20 +- .../Xcode/Recast.xcodeproj/project.pbxproj | 19 +- RecastDemo/Include/Sample_TempObstacles.h | 2 +- RecastDemo/Include/Sample_TileMesh.h | 17 +- RecastDemo/Source/Sample_SoloMesh.cpp | 5 +- RecastDemo/Source/Sample_TempObstacles.cpp | 1078 ++++----- RecastDemo/Source/Sample_TileMesh.cpp | 189 +- 29 files changed, 3246 insertions(+), 3574 deletions(-) create mode 100644 Detour/Include/DetourStatus.h create mode 100644 DetourTileCache/DetourTileCacheBuilder.cpp create mode 100644 DetourTileCache/DetourTileCacheBuilder.h diff --git a/DebugUtils/Include/DetourDebugDraw.h b/DebugUtils/Include/DetourDebugDraw.h index d0d1d5d..59f3f9e 100755 --- a/DebugUtils/Include/DetourDebugDraw.h +++ b/DebugUtils/Include/DetourDebugDraw.h @@ -21,11 +21,13 @@ #include "DetourNavMesh.h" #include "DetourNavMeshQuery.h" +#include "DetourTileCacheBuilder.h" enum DrawNavMeshFlags { DU_DRAWNAVMESH_OFFMESHCONS = 0x01, DU_DRAWNAVMESH_CLOSEDLIST = 0x02, + DU_DRAWNAVMESH_COLOR_TILES = 0x04, }; void duDebugDrawNavMesh(struct duDebugDraw* dd, const dtNavMesh& mesh, unsigned char flags); @@ -35,4 +37,8 @@ void duDebugDrawNavMeshBVTree(struct duDebugDraw* dd, const dtNavMesh& mesh); void duDebugDrawNavMeshPortals(struct duDebugDraw* dd, const dtNavMesh& mesh); void duDebugDrawNavMeshPoly(struct duDebugDraw* dd, const dtNavMesh& mesh, dtPolyRef ref, const unsigned int col); +void duDebugDrawTileCacheLayer(struct duDebugDraw* dd, const dtTileCacheLayer& layer, + const float* bmin, const float* bmax, + const float cs, const float ch, const int idx); + #endif // DETOURDEBUGDRAW_H \ No newline at end of file diff --git a/DebugUtils/Include/RecastDebugDraw.h b/DebugUtils/Include/RecastDebugDraw.h index 13126a9..f75802d 100644 --- a/DebugUtils/Include/RecastDebugDraw.h +++ b/DebugUtils/Include/RecastDebugDraw.h @@ -29,8 +29,7 @@ void duDebugDrawCompactHeightfieldSolid(struct duDebugDraw* dd, const struct rcC void duDebugDrawCompactHeightfieldRegions(struct duDebugDraw* dd, const struct rcCompactHeightfield& chf); void duDebugDrawCompactHeightfieldDistance(struct duDebugDraw* dd, const struct rcCompactHeightfield& chf); -void duDebugDrawLeanHeightfieldSolid(duDebugDraw* dd, const struct rcLeanHeightfield& lhf); - +void duDebugDrawHeightfieldLayer(duDebugDraw* dd, const struct rcHeightfieldLayer& layer, const int idx); void duDebugDrawHeightfieldLayers(duDebugDraw* dd, const struct rcHeightfieldLayerSet& lset); void duDebugDrawHeightfieldLayersRegions(duDebugDraw* dd, const struct rcHeightfieldLayerSet& lset); diff --git a/DebugUtils/Source/DebugDraw.cpp b/DebugUtils/Source/DebugDraw.cpp index a9c1ac1..982bdba 100644 --- a/DebugUtils/Source/DebugDraw.cpp +++ b/DebugUtils/Source/DebugDraw.cpp @@ -130,7 +130,7 @@ void duDebugDrawBox(struct duDebugDraw* dd, float minx, float miny, float minz, { if (!dd) return; - dd->begin(DU_DRAW_TRIS); + dd->begin(DU_DRAW_QUADS); duAppendBox(dd, minx,miny,minz, maxx,maxy,maxz, fcol); dd->end(); } diff --git a/DebugUtils/Source/DetourDebugDraw.cpp b/DebugUtils/Source/DetourDebugDraw.cpp index ad758d6..effdd53 100755 --- a/DebugUtils/Source/DetourDebugDraw.cpp +++ b/DebugUtils/Source/DetourDebugDraw.cpp @@ -121,6 +121,8 @@ static void drawMeshTile(duDebugDraw* dd, const dtNavMesh& mesh, const dtNavMesh { dtPolyRef base = mesh.getPolyRefBase(tile); + int tileNum = mesh.decodePolyIdTile(base); + dd->depthMask(false); dd->begin(DU_DRAW_TRIS); @@ -137,10 +139,17 @@ static void drawMeshTile(duDebugDraw* dd, const dtNavMesh& mesh, const dtNavMesh col = duRGBA(255,196,0,64); else { - if (p->getArea() == 0) // Treat zero area type as default. - col = duRGBA(0,192,255,64); + if (flags & DU_DRAWNAVMESH_COLOR_TILES) + { + col = duIntToCol(tileNum, 128); + } else - col = duIntToCol(p->getArea(), 64); + { + if (p->getArea() == 0) // Treat zero area type as default. + col = duRGBA(0,192,255,64); + else + col = duIntToCol(p->getArea(), 64); + } } for (int j = 0; j < pd->triCount; ++j) @@ -334,7 +343,7 @@ void duDebugDrawNavMeshBVTree(duDebugDraw* dd, const dtNavMesh& mesh) static void drawMeshTilePortal(duDebugDraw* dd, const dtMeshTile* tile) { // Draw portals - const float padx = 0.02f; + const float padx = 0.04f; const float pady = tile->header->walkableClimb; dd->begin(DU_DRAW_LINES, 2.0f); @@ -464,3 +473,87 @@ void duDebugDrawNavMeshPoly(duDebugDraw* dd, const dtNavMesh& mesh, dtPolyRef re } +void duDebugDrawTileCacheLayer(struct duDebugDraw* dd, const dtTileCacheLayer& layer, + const float* bmin, const float* bmax, + const float cs, const float ch, const int idx) +{ + const int w = (int)layer.header->width; + const int h = (int)layer.header->height; + + unsigned int color = duIntToCol(idx+1, 255); + + // Layer bounds + float lbmin[3], lbmax[3]; + lbmin[0] = bmin[0] + layer.header->minx*cs; + lbmin[1] = bmin[1]; + lbmin[2] = bmin[2] + layer.header->miny*cs; + lbmax[0] = bmin[0] + (layer.header->maxx+1)*cs; + lbmax[1] = bmax[1]; + lbmax[2] = bmin[2] + (layer.header->maxy+1)*cs; + duDebugDrawBoxWire(dd, lbmin[0],lbmin[1],lbmin[2], lbmax[0],lbmax[1],lbmax[2], duTransCol(color,128), 2.0f); + + // Layer height + dd->begin(DU_DRAW_QUADS); + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const int idx = x+y*w; + const int h = (int)layer.heights[idx]; + if (h == 0xff) continue; + const unsigned char area = layer.areas[idx]; + + unsigned int col; + if (area == 63) + col = duLerpCol(color, duRGBA(0,192,255,64), 32); + else if (area == 0) + col = duLerpCol(color, duRGBA(0,0,0,64), 32); + else + col = duLerpCol(color, duIntToCol(area, 255), 32); + + const float fx = bmin[0] + x*cs; + const float fy = bmin[1] + (h+1)*ch; + const float fz = bmin[2] + y*cs; + + dd->vertex(fx, fy, fz, col); + dd->vertex(fx, fy, fz+cs, col); + dd->vertex(fx+cs, fy, fz+cs, col); + dd->vertex(fx+cs, fy, fz, col); + } + } + dd->end(); + + // Portals + unsigned int pcol = duRGBA(255,255,255,255); + + const int segs[4*4] = {0,0,0,1, 0,1,1,1, 1,1,1,0, 1,0,0,0}; + + // Layer portals + dd->begin(DU_DRAW_LINES, 2.0f); + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const int idx = x+y*w; + const int h = (int)layer.heights[idx]; + if (h == 0xff) continue; + + for (int dir = 0; dir < 4; ++dir) + { + if (layer.cons[idx] & (1<<(dir+4))) + { + const int* seg = &segs[dir*4]; + const float ax = bmin[0] + (x+seg[0])*cs; + const float ay = bmin[1] + (h+2)*ch; + const float az = bmin[2] + (y+seg[1])*cs; + const float bx = bmin[0] + (x+seg[2])*cs; + const float by = bmin[1] + (h+2)*ch; + const float bz = bmin[2] + (y+seg[3])*cs; + dd->vertex(ax, ay, az, pcol); + dd->vertex(bx, by, bz, pcol); + } + } + } + } + dd->end(); +} diff --git a/DebugUtils/Source/RecastDebugDraw.cpp b/DebugUtils/Source/RecastDebugDraw.cpp index a3cbe15..e158c35 100644 --- a/DebugUtils/Source/RecastDebugDraw.cpp +++ b/DebugUtils/Source/RecastDebugDraw.cpp @@ -326,61 +326,6 @@ void duDebugDrawCompactHeightfieldDistance(duDebugDraw* dd, const rcCompactHeigh dd->end(); } -void duDebugDrawLeanHeightfieldSolid(duDebugDraw* dd, const rcLeanHeightfield& lhf) -{ - if (!dd) return; - - const float cs = lhf.cs; - const float ch = lhf.ch; - - const int headerSize = rcAlign4(sizeof(rcLeanHeightfield)); - const int countsSize = rcAlign4(sizeof(unsigned char)*lhf.width*lhf.height); - const int floorsSize = rcAlign4(sizeof(unsigned short)*lhf.spanCount); - const unsigned char* data = (const unsigned char*)&lhf; - const unsigned char* counts = (const unsigned char*)&data[headerSize]; - const unsigned short* floors = (const unsigned short*)&data[headerSize+countsSize]; - const unsigned char* areas = (const unsigned char*)&data[headerSize+countsSize+floorsSize]; - - - dd->begin(DU_DRAW_QUADS); - - unsigned short mask = (1<vertex(fx, fy, fz, color); - dd->vertex(fx, fy, fz+cs, color); - dd->vertex(fx+cs, fy, fz+cs, color); - dd->vertex(fx+cs, fy, fz, color); - } - idx += count; - } - } - dd->end(); -} - static void drawLayerPortals(duDebugDraw* dd, const rcHeightfieldLayer* layer, const unsigned int color) { const float cs = layer->cs; @@ -422,116 +367,68 @@ static void drawLayerPortals(duDebugDraw* dd, const rcHeightfieldLayer* layer, c dd->end(); } +void duDebugDrawHeightfieldLayer(duDebugDraw* dd, const struct rcHeightfieldLayer& layer, const int idx) +{ + const float cs = layer.cs; + const float ch = layer.ch; + const int w = layer.width; + const int h = layer.height; + + unsigned int color = duIntToCol(idx+1, 255); + + // Layer bounds + float bmin[3], bmax[3]; + bmin[0] = layer.bmin[0] + layer.minx*cs; + bmin[1] = layer.bmin[1]; + bmin[2] = layer.bmin[2] + layer.miny*cs; + bmax[0] = layer.bmin[0] + (layer.maxx+1)*cs; + bmax[1] = layer.bmax[1]; + bmax[2] = layer.bmin[2] + (layer.maxy+1)*cs; + duDebugDrawBoxWire(dd, bmin[0],bmin[1],bmin[2], bmax[0],bmax[1],bmax[2], duTransCol(color,128), 2.0f); + + // Layer height + dd->begin(DU_DRAW_QUADS); + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const int idx = x+y*w; + const int h = (int)layer.heights[idx]; + if (h == 0xff) continue; + const unsigned char area = layer.areas[idx]; + + unsigned int col; + if (area == RC_WALKABLE_AREA) + col = duLerpCol(color, duRGBA(0,192,255,64), 32); + else if (area == RC_NULL_AREA) + col = duLerpCol(color, duRGBA(0,0,0,64), 32); + else + col = duLerpCol(color, duIntToCol(area, 255), 32); + + const float fx = layer.bmin[0] + x*cs; + const float fy = layer.bmin[1] + (h+1)*ch; + const float fz = layer.bmin[2] + y*cs; + + dd->vertex(fx, fy, fz, col); + dd->vertex(fx, fy, fz+cs, col); + dd->vertex(fx+cs, fy, fz+cs, col); + dd->vertex(fx+cs, fy, fz, col); + } + } + dd->end(); + + // Portals + drawLayerPortals(dd, &layer, color); +} + void duDebugDrawHeightfieldLayers(duDebugDraw* dd, const struct rcHeightfieldLayerSet& lset) { if (!dd) return; - for (int i = 0; i < lset.nlayers; ++i) - { - const rcHeightfieldLayer* layer = &lset.layers[i]; - - const float cs = layer->cs; - const float ch = layer->ch; - const int w = layer->width; - const int h = layer->height; - - unsigned int color = duIntToCol(i+1, 255); - - // Layer bounds - float bmin[3], bmax[3]; - rcVcopy(bmin, layer->bmin); - rcVcopy(bmax, layer->bmax); - duDebugDrawBoxWire(dd, bmin[0],bmin[1],bmin[2], bmax[0],bmax[1],bmax[2], duTransCol(color,128), 2.0f); - - // Layer height - dd->begin(DU_DRAW_QUADS); - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const int idx = x+y*w; - const int h = (int)layer->heights[idx]; - if (h == 0xff) continue; - const unsigned char area = layer->areas[idx]; - - unsigned int col; - if (area == RC_WALKABLE_AREA) - col = duLerpCol(color, duRGBA(0,192,255,64), 32); - else if (area == RC_NULL_AREA) - col = duLerpCol(color, duRGBA(0,0,0,64), 32); - else - col = duLerpCol(color, duIntToCol(area, 255), 32); - - const float fx = layer->bmin[0] + x*cs; - const float fy = layer->bmin[1] + (h+1)*ch; - const float fz = layer->bmin[2] + y*cs; - - dd->vertex(fx, fy, fz, col); - dd->vertex(fx, fy, fz+cs, col); - dd->vertex(fx+cs, fy, fz+cs, col); - dd->vertex(fx+cs, fy, fz, col); - } - } - dd->end(); - - // Portals - drawLayerPortals(dd, layer, color); - } - -} - -void duDebugDrawHeightfieldLayersRegions(duDebugDraw* dd, const struct rcHeightfieldLayerSet& lset) -{ - if (!dd) return; - - for (int i = 0; i < lset.nlayers; ++i) - { - const rcHeightfieldLayer* layer = &lset.layers[i]; - - const float cs = layer->cs; - const float ch = layer->ch; - const int w = layer->width; - const int h = layer->height; - - unsigned int color = duIntToCol(i+1, 255); - - // Layer bounds - float bmin[3], bmax[3]; - rcVcopy(bmin, layer->bmin); - rcVcopy(bmax, layer->bmax); - duDebugDrawBoxWire(dd, bmin[0],bmin[1],bmin[2], bmax[0],bmax[1],bmax[2], duTransCol(color,128), 2.0f); - - // Layer height - dd->begin(DU_DRAW_QUADS); - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const int idx = x+y*w; - const int h = (int)layer->heights[idx]; - if (h == 0xff) continue; - const unsigned char area = layer->regs ? layer->regs[idx]+1 : 1; - - unsigned int col = duLerpCol(color, duIntToCol(area, 255), 32); - - const float fx = layer->bmin[0] + x*cs; - const float fy = layer->bmin[1] + (h+1)*ch; - const float fz = layer->bmin[2] + y*cs; - - dd->vertex(fx, fy, fz, col); - dd->vertex(fx, fy, fz+cs, col); - dd->vertex(fx+cs, fy, fz+cs, col); - dd->vertex(fx+cs, fy, fz, col); - } - } - dd->end(); - - // Portals - drawLayerPortals(dd, layer, color); - } - + duDebugDrawHeightfieldLayer(dd, lset.layers[i], i); } +/* void duDebugDrawLayerContours(duDebugDraw* dd, const struct rcLayerContourSet& lcset) { if (!dd) return; @@ -542,6 +439,8 @@ void duDebugDrawLayerContours(duDebugDraw* dd, const struct rcLayerContourSet& l const unsigned char a = 255;// (unsigned char)(alpha*255.0f); + const int offs[2*4] = {-1,0, 0,1, 1,0, 0,-1}; + dd->begin(DU_DRAW_LINES, 2.0f); for (int i = 0; i < lcset.nconts; ++i) @@ -564,7 +463,21 @@ void duDebugDrawLayerContours(duDebugDraw* dd, const struct rcLayerContourSet& l const float bz = orig[2] + vb[2]*cs; unsigned int col = color; if ((va[3] & 0xf) != 0xf) + { col = duRGBA(255,255,255,128); + int d = va[3] & 0xf; + + const float cx = (ax+bx)*0.5f; + const float cy = (ay+by)*0.5f; + const float cz = (az+bz)*0.5f; + + const float dx = cx + offs[d*2+0]*2*cs; + const float dy = cy; + const float dz = cz + offs[d*2+1]*2*cs; + + dd->vertex(cx,cy,cz,duRGBA(255,0,0,255)); + dd->vertex(dx,dy,dz,duRGBA(255,0,0,255)); + } duAppendArrow(dd, ax,ay,az, bx,by,bz, 0.0f, cs*0.5f, col); } @@ -604,6 +517,8 @@ void duDebugDrawLayerPolyMesh(duDebugDraw* dd, const struct rcLayerPolyMesh& lme const float ch = lmesh.ch; const float* orig = lmesh.bmin; + const int offs[2*4] = {-1,0, 0,1, 1,0, 0,-1}; + dd->begin(DU_DRAW_TRIS); for (int i = 0; i < lmesh.npolys; ++i) @@ -647,13 +562,8 @@ void duDebugDrawLayerPolyMesh(duDebugDraw* dd, const struct rcLayerPolyMesh& lme { if (p[j] == RC_MESH_NULL_IDX) break; if (p[nvp+j] & 0x8000) continue; - - int vi[2]; - vi[0] = p[j]; - if (j+1 >= nvp || p[j+1] == RC_MESH_NULL_IDX) - vi[1] = p[0]; - else - vi[1] = p[j+1]; + const int nj = (j+1 >= nvp || p[j+1] == RC_MESH_NULL_IDX) ? 0 : j+1; + int vi[2] = {p[j], p[nj]}; for (int k = 0; k < 2; ++k) { @@ -677,16 +587,37 @@ void duDebugDrawLayerPolyMesh(duDebugDraw* dd, const struct rcLayerPolyMesh& lme { if (p[j] == RC_MESH_NULL_IDX) break; if ((p[nvp+j] & 0x8000) == 0) continue; - int vi[2]; - vi[0] = p[j]; - if (j+1 >= nvp || p[j+1] == RC_MESH_NULL_IDX) - vi[1] = p[0]; - else - vi[1] = p[j+1]; + const int nj = (j+1 >= nvp || p[j+1] == RC_MESH_NULL_IDX) ? 0 : j+1; + int vi[2] = {p[j], p[nj]}; unsigned int col = colb; - if ((p[nvp+j] & 0xff) != 0xff) + if ((p[nvp+j] & 0xf) != 0xf) + { + const unsigned short* va = &lmesh.verts[vi[0]*3]; + const unsigned short* vb = &lmesh.verts[vi[1]*3]; + + const float ax = orig[0] + va[0]*cs; + const float ay = orig[1] + (va[1]+1+(i&1))*ch; + const float az = orig[2] + va[2]*cs; + const float bx = orig[0] + vb[0]*cs; + const float by = orig[1] + (vb[1]+1+(i&1))*ch; + const float bz = orig[2] + vb[2]*cs; + + const float cx = (ax+bx)*0.5f; + const float cy = (ay+by)*0.5f; + const float cz = (az+bz)*0.5f; + + int d = p[nvp+j] & 0xf; + + const float dx = cx + offs[d*2+0]*2*cs; + const float dy = cy; + const float dz = cz + offs[d*2+1]*2*cs; + + dd->vertex(cx,cy,cz,duRGBA(255,0,0,255)); + dd->vertex(dx,dy,dz,duRGBA(255,0,0,255)); + col = duRGBA(255,255,255,128); + } for (int k = 0; k < 2; ++k) { @@ -712,7 +643,7 @@ void duDebugDrawLayerPolyMesh(duDebugDraw* dd, const struct rcLayerPolyMesh& lme } dd->end(); } - +*/ static void getContourCenter(const rcContour* cont, const float* orig, float cs, float ch, float* center) { @@ -973,13 +904,10 @@ void duDebugDrawPolyMesh(duDebugDraw* dd, const struct rcPolyMesh& mesh) for (int j = 0; j < nvp; ++j) { if (p[j] == RC_MESH_NULL_IDX) break; - if (p[nvp+j] == RC_MESH_NULL_IDX) continue; - int vi[2]; - vi[0] = p[j]; - if (j+1 >= nvp || p[j+1] == RC_MESH_NULL_IDX) - vi[1] = p[0]; - else - vi[1] = p[j+1]; + if (p[nvp+j] & 0x8000) continue; + const int nj = (j+1 >= nvp || p[j+1] == RC_MESH_NULL_IDX) ? 0 : j+1; + const int vi[2] = {p[j], p[nj]}; + for (int k = 0; k < 2; ++k) { const unsigned short* v = &mesh.verts[vi[k]*3]; @@ -1001,20 +929,20 @@ void duDebugDrawPolyMesh(duDebugDraw* dd, const struct rcPolyMesh& mesh) for (int j = 0; j < nvp; ++j) { if (p[j] == RC_MESH_NULL_IDX) break; - if (p[nvp+j] != RC_MESH_NULL_IDX) continue; - int vi[2]; - vi[0] = p[j]; - if (j+1 >= nvp || p[j+1] == RC_MESH_NULL_IDX) - vi[1] = p[0]; - else - vi[1] = p[j+1]; + if ((p[nvp+j] & 0x8000) == 0) continue; + const int nj = (j+1 >= nvp || p[j+1] == RC_MESH_NULL_IDX) ? 0 : j+1; + const int vi[2] = {p[j], p[nj]}; + + unsigned int col = colb; + if ((p[nvp+j] & 0xf) != 0xf) + col = duRGBA(255,255,255,128); for (int k = 0; k < 2; ++k) { const unsigned short* v = &mesh.verts[vi[k]*3]; const float x = orig[0] + v[0]*cs; const float y = orig[1] + (v[1]+1)*ch + 0.1f; const float z = orig[2] + v[2]*cs; - dd->vertex(x, y, z, colb); + dd->vertex(x, y, z, col); } } } diff --git a/DebugUtils/Source/RecastDump.cpp b/DebugUtils/Source/RecastDump.cpp index 4ca3056..7663fc7 100644 --- a/DebugUtils/Source/RecastDump.cpp +++ b/DebugUtils/Source/RecastDump.cpp @@ -135,7 +135,7 @@ bool duDumpPolyMeshDetailToObj(rcPolyMeshDetail& dmesh, duFileIO* io) } static const int CSET_MAGIC = ('c' << 24) | ('s' << 16) | ('e' << 8) | 't'; -static const int CSET_VERSION = 1; +static const int CSET_VERSION = 2; bool duDumpContourSet(struct rcContourSet& cset, duFileIO* io) { @@ -161,6 +161,10 @@ bool duDumpContourSet(struct rcContourSet& cset, duFileIO* io) io->write(&cset.cs, sizeof(cset.cs)); io->write(&cset.ch, sizeof(cset.ch)); + io->write(&cset.width, sizeof(cset.width)); + io->write(&cset.height, sizeof(cset.height)); + io->write(&cset.borderSize, sizeof(cset.borderSize)); + for (int i = 0; i < cset.nconts; ++i) { const rcContour& cont = cset.conts[i]; @@ -221,6 +225,10 @@ bool duReadContourSet(struct rcContourSet& cset, duFileIO* io) io->read(&cset.cs, sizeof(cset.cs)); io->read(&cset.ch, sizeof(cset.ch)); + io->read(&cset.width, sizeof(cset.width)); + io->read(&cset.height, sizeof(cset.height)); + io->read(&cset.borderSize, sizeof(cset.borderSize)); + for (int i = 0; i < cset.nconts; ++i) { rcContour& cont = cset.conts[i]; @@ -251,7 +259,7 @@ bool duReadContourSet(struct rcContourSet& cset, duFileIO* io) static const int CHF_MAGIC = ('r' << 24) | ('c' << 16) | ('h' << 8) | 'f'; -static const int CHF_VERSION = 2; +static const int CHF_VERSION = 3; bool duDumpCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io) { @@ -275,6 +283,7 @@ bool duDumpCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io) io->write(&chf.walkableHeight, sizeof(chf.walkableHeight)); io->write(&chf.walkableClimb, sizeof(chf.walkableClimb)); + io->write(&chf.borderSize, sizeof(chf.borderSize)); io->write(&chf.maxDistance, sizeof(chf.maxDistance)); io->write(&chf.maxRegions, sizeof(chf.maxRegions)); @@ -341,7 +350,8 @@ bool duReadCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io) io->read(&chf.walkableHeight, sizeof(chf.walkableHeight)); io->read(&chf.walkableClimb, sizeof(chf.walkableClimb)); - + io->write(&chf.borderSize, sizeof(chf.borderSize)); + io->read(&chf.maxDistance, sizeof(chf.maxDistance)); io->read(&chf.maxRegions, sizeof(chf.maxRegions)); @@ -412,7 +422,6 @@ void duLogBuildTimes(rcContext& ctx, const int totalTimeUsec) ctx.log(RC_LOG_PROGRESS, "Build Times"); logLine(ctx, RC_TIMER_RASTERIZE_TRIANGLES, "- Rasterize", pc); - logLine(ctx, RC_TIMER_BUILD_LEANHEIGHTFIELD, "- Build Lean", pc); logLine(ctx, RC_TIMER_BUILD_COMPACTHEIGHTFIELD, "- Build Compact", pc); logLine(ctx, RC_TIMER_FILTER_BORDER, "- Filter Border", pc); logLine(ctx, RC_TIMER_FILTER_WALKABLE, "- Filter Walkable", pc); diff --git a/Detour/Include/DetourCommon.h b/Detour/Include/DetourCommon.h index 3cee3f6..b64abf0 100644 --- a/Detour/Include/DetourCommon.h +++ b/Detour/Include/DetourCommon.h @@ -19,6 +19,7 @@ #ifndef DETOURCOMMON_H #define DETOURCOMMON_H + template inline void dtSwap(T& a, T& b) { T t = a; a = b; b = t; } template inline T dtMin(T a, T b) { return a < b ? a : b; } template inline T dtMax(T a, T b) { return a > b ? a : b; } diff --git a/Detour/Include/DetourNavMesh.h b/Detour/Include/DetourNavMesh.h index 3c8df28..7179e0e 100644 --- a/Detour/Include/DetourNavMesh.h +++ b/Detour/Include/DetourNavMesh.h @@ -20,6 +20,8 @@ #define DETOURNAVMESH_H #include "DetourAlloc.h" +#include "DetourStatus.h" + // Note: If you want to use 64-bit refs, change the types of both dtPolyRef & dtTileRef. // It is also recommended to change dtHashRef() to proper 64-bit hash too. @@ -34,7 +36,7 @@ typedef unsigned int dtTileRef; static const int DT_VERTS_PER_POLYGON = 6; static const int DT_NAVMESH_MAGIC = 'D'<<24 | 'N'<<16 | 'A'<<8 | 'V'; //'DNAV'; -static const int DT_NAVMESH_VERSION = 6; +static const int DT_NAVMESH_VERSION = 7; static const int DT_NAVMESH_STATE_MAGIC = 'D'<<24 | 'N'<<16 | 'M'<<8 | 'S'; //'DNMS'; static const int DT_NAVMESH_STATE_VERSION = 1; @@ -67,49 +69,6 @@ enum dtPolyTypes }; -typedef unsigned int dtStatus; - -// High level status. -static const unsigned int DT_FAILURE = 1u << 31; // Operation failed. -static const unsigned int DT_SUCCESS = 1u << 30; // Operation succeed. -static const unsigned int DT_IN_PROGRESS = 1u << 29; // Operation still in progress. - -// Detail information for status. -static const unsigned int DT_STATUS_DETAIL_MASK = 0x0ffffff; -static const unsigned int DT_WRONG_MAGIC = 1 << 0; // Input data is not recognized. -static const unsigned int DT_WRONG_VERSION = 1 << 1; // Input data is in wrong version. -static const unsigned int DT_OUT_OF_MEMORY = 1 << 2; // Operation ran out of memory. -static const unsigned int DT_INVALID_PARAM = 1 << 3; // An input parameter was invalid. -static const unsigned int DT_BUFFER_TOO_SMALL = 1 << 4; // Result buffer for the query was too small to store all results. -static const unsigned int DT_OUT_OF_NODES = 1 << 5; // Query ran out of nodes during search. -static const unsigned int DT_PARTIAL_RESULT = 1 << 6; // Query did not reach the end location, returning best guess. - - -// Returns true of status is success. -inline bool dtStatusSucceed(dtStatus status) -{ - return (status & DT_SUCCESS) != 0; -} - -// Returns true of status is failure. -inline bool dtStatusFailed(dtStatus status) -{ - return (status & DT_FAILURE) != 0; -} - -// Returns true of status is in progress. -inline bool dtStatusInProgress(dtStatus status) -{ - return (status & DT_IN_PROGRESS) != 0; -} - -// Returns true if specific detail is set. -inline bool dtStatusDetail(dtStatus status, unsigned int detail) -{ - return (status & detail) != 0; -} - - // Structure describing the navigation polygon data. struct dtPoly { @@ -164,7 +123,7 @@ struct dtMeshHeader { int magic; // Magic number, used to identify the data. int version; // Data version number. - int x, y; // Location of the time on the grid. + int x, y, layer; // Location of the tile on the grid. unsigned int userId; // User ID of the tile. int polyCount; // Number of polygons in the tile. int vertCount; // Number of vertices in the tile. @@ -265,14 +224,17 @@ public: // Params: // x,y - (in) Location of the tile to get. // Returns: pointer to tile if tile exists or 0 tile does not exists. - const dtMeshTile* getTileAt(int x, int y) const; + const dtMeshTile* getTileAt(const int x, const int y, const int layer) const; + int getTilesAt(const int x, const int y, + dtMeshTile const** tiles, const int maxTiles) const; + // Returns reference to tile at specified location. // Params: // x,y - (in) Location of the tile to get. // Returns: reference to tile if tile exists or 0 tile does not exists. - dtTileRef getTileRefAt(int x, int y) const; - + dtTileRef getTileRefAt(int x, int y, int layer) const; + // Returns tile references of a tile based on tile pointer. dtTileRef getTileRef(const dtMeshTile* tile) const; @@ -388,7 +350,13 @@ private: dtMeshTile* getTile(int i); // Returns neighbour tile based on side. - dtMeshTile* getNeighbourTileAt(int x, int y, int side) const; + int getTilesAt(const int x, const int y, + dtMeshTile** tiles, const int maxTiles) const; + + // Returns neighbour tile based on side. + int getNeighbourTilesAt(const int x, const int y, const int side, + dtMeshTile** tiles, const int maxTiles) const; + // Returns all polygons in neighbour tile based on portal defined by the segment. int findConnectingPolys(const float* va, const float* vb, const dtMeshTile* tile, int side, @@ -405,7 +373,7 @@ private: void connectExtOffMeshLinks(dtMeshTile* tile, dtMeshTile* target, int side); // Removes external links at specified side. - void unconnectExtLinks(dtMeshTile* tile, int side); + void unconnectExtLinks(dtMeshTile* tile, dtMeshTile* target); // TODO: These methods are duplicates from dtNavMeshQuery, but are needed for off-mesh connection finding. diff --git a/Detour/Include/DetourNavMeshBuilder.h b/Detour/Include/DetourNavMeshBuilder.h index 807f05f..e3ad25e 100644 --- a/Detour/Include/DetourNavMeshBuilder.h +++ b/Detour/Include/DetourNavMeshBuilder.h @@ -51,7 +51,7 @@ struct dtNavMeshCreateParams int offMeshConCount; // Number of off-mesh connections // Tile location unsigned int userId; // User ID bound to the tile. - int tileX, tileY; // Tile location (tile coords). + int tileX, tileY, tileLayer; // Tile location (tile coords). float bmin[3], bmax[3]; // Tile bounds (wu). // Settings float walkableHeight; // Agent height (wu). @@ -59,7 +59,7 @@ struct dtNavMeshCreateParams float walkableClimb; // Agent max climb (wu). float cs; // Cell size (xz) (wu). float ch; // Cell height (y) (wu). - int tileSize; // Tile size (width & height) (vx). + bool buildBvTree; // Flag indicating if BVTree for polygon query should be build. }; // Build navmesh data from given input data. diff --git a/Detour/Include/DetourNavMeshQuery.h b/Detour/Include/DetourNavMeshQuery.h index ac48ee7..c3cfe8a 100644 --- a/Detour/Include/DetourNavMeshQuery.h +++ b/Detour/Include/DetourNavMeshQuery.h @@ -20,6 +20,7 @@ #define DETOURNAVMESHQUERY_H #include "DetourNavMesh.h" +#include "DetourStatus.h" // Define DT_VIRTUAL_QUERYFILTER if you wish to derive a custom filter from dtQueryFilter. diff --git a/Detour/Include/DetourStatus.h b/Detour/Include/DetourStatus.h new file mode 100644 index 0000000..af822c4 --- /dev/null +++ b/Detour/Include/DetourStatus.h @@ -0,0 +1,64 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef DETOURSTATUS_H +#define DETOURSTATUS_H + +typedef unsigned int dtStatus; + +// High level status. +static const unsigned int DT_FAILURE = 1u << 31; // Operation failed. +static const unsigned int DT_SUCCESS = 1u << 30; // Operation succeed. +static const unsigned int DT_IN_PROGRESS = 1u << 29; // Operation still in progress. + +// Detail information for status. +static const unsigned int DT_STATUS_DETAIL_MASK = 0x0ffffff; +static const unsigned int DT_WRONG_MAGIC = 1 << 0; // Input data is not recognized. +static const unsigned int DT_WRONG_VERSION = 1 << 1; // Input data is in wrong version. +static const unsigned int DT_OUT_OF_MEMORY = 1 << 2; // Operation ran out of memory. +static const unsigned int DT_INVALID_PARAM = 1 << 3; // An input parameter was invalid. +static const unsigned int DT_BUFFER_TOO_SMALL = 1 << 4; // Result buffer for the query was too small to store all results. +static const unsigned int DT_OUT_OF_NODES = 1 << 5; // Query ran out of nodes during search. +static const unsigned int DT_PARTIAL_RESULT = 1 << 6; // Query did not reach the end location, returning best guess. + + +// Returns true of status is success. +inline bool dtStatusSucceed(dtStatus status) +{ + return (status & DT_SUCCESS) != 0; +} + +// Returns true of status is failure. +inline bool dtStatusFailed(dtStatus status) +{ + return (status & DT_FAILURE) != 0; +} + +// Returns true of status is in progress. +inline bool dtStatusInProgress(dtStatus status) +{ + return (status & DT_IN_PROGRESS) != 0; +} + +// Returns true if specific detail is set. +inline bool dtStatusDetail(dtStatus status, unsigned int detail) +{ + return (status & detail) != 0; +} + +#endif // DETOURSTATUS_H diff --git a/Detour/Source/DetourNavMesh.cpp b/Detour/Source/DetourNavMesh.cpp index 44bbd22..0a84e37 100644 --- a/Detour/Source/DetourNavMesh.cpp +++ b/Detour/Source/DetourNavMesh.cpp @@ -64,6 +64,15 @@ inline bool overlapSlabs(const float* amin, const float* amax, return false; } +static float getSlabCoord(const float* va, const int side) +{ + if (side == 0 || side == 4) + return va[0]; + else if (side == 2 || side == 6) + return va[2]; + return 0; +} + static void calcSlabEndPoints(const float* va, const float* vb, float* bmin, float* bmax, const int side) { if (side == 0 || side == 4) @@ -251,6 +260,7 @@ int dtNavMesh::findConnectingPolys(const float* va, const float* vb, float amin[2], amax[2]; calcSlabEndPoints(va,vb, amin,amax, side); + const float apos = getSlabCoord(va, side); // Remove links pointing to 'side' and compact the links array. float bmin[2], bmax[2]; @@ -267,11 +277,18 @@ int dtNavMesh::findConnectingPolys(const float* va, const float* vb, { // Skip edges which do not point to the right side. if (poly->neis[j] != m) continue; - // Check if the segments touch. + const float* vc = &tile->verts[poly->verts[j]*3]; const float* vd = &tile->verts[poly->verts[(j+1) % nv]*3]; + const float bpos = getSlabCoord(vc, side); + + // Segments are not close enough. + if (dtAbs(apos-bpos) > 0.01f) + continue; + + // Check if the segments touch. calcSlabEndPoints(vc,vd, bmin,bmax, side); - + if (!overlapSlabs(amin,amax, bmin,bmax, 0.01f, tile->header->walkableClimb)) continue; // Add return value. @@ -288,9 +305,11 @@ int dtNavMesh::findConnectingPolys(const float* va, const float* vb, return n; } -void dtNavMesh::unconnectExtLinks(dtMeshTile* tile, int side) +void dtNavMesh::unconnectExtLinks(dtMeshTile* tile, dtMeshTile* target) { - if (!tile) return; + if (!tile || !target) return; + + const unsigned int targetNum = decodePolyIdTile(getTileRef(target)); for (int i = 0; i < tile->header->polyCount; ++i) { @@ -299,7 +318,8 @@ void dtNavMesh::unconnectExtLinks(dtMeshTile* tile, int side) unsigned int pj = DT_NULL_LINK; while (j != DT_NULL_LINK) { - if (tile->links[j].side == side) + if (tile->links[j].side != 0xff && + decodePolyIdTile(tile->links[j].ref) == targetNum) { // Revove link. unsigned int nj = tile->links[j].next; @@ -330,19 +350,25 @@ void dtNavMesh::connectExtLinks(dtMeshTile* tile, dtMeshTile* target, int side) dtPoly* poly = &tile->polys[i]; // Create new links. - unsigned short m = DT_EXT_LINK | (unsigned short)side; +// unsigned short m = DT_EXT_LINK | (unsigned short)side; + const int nv = poly->vertCount; for (int j = 0; j < nv; ++j) { - // Skip edges which do not point to the right side. - if (poly->neis[j] != m) continue; + // Skip non-portal edges. + if ((poly->neis[j] & DT_EXT_LINK) == 0) + continue; + + const int dir = (int)(poly->neis[j] & 0xff); + if (side != -1 && dir != side) + continue; // Create new links const float* va = &tile->verts[poly->verts[j]*3]; const float* vb = &tile->verts[poly->verts[(j+1) % nv]*3]; dtPolyRef nei[4]; float neia[4*2]; - int nnei = findConnectingPolys(va,vb, target, dtOppositeTile(side), nei,neia,4); + int nnei = findConnectingPolys(va,vb, target, dtOppositeTile(dir), nei,neia,4); for (int k = 0; k < nnei; ++k) { unsigned int idx = allocLink(tile); @@ -351,13 +377,13 @@ void dtNavMesh::connectExtLinks(dtMeshTile* tile, dtMeshTile* target, int side) dtLink* link = &tile->links[idx]; link->ref = nei[k]; link->edge = (unsigned char)j; - link->side = (unsigned char)side; + link->side = (unsigned char)dir; link->next = poly->firstLink; poly->firstLink = idx; // Compress portal limits to a byte value. - if (side == 0 || side == 4) + if (dir == 0 || dir == 4) { float tmin = (neia[k*2+0]-va[2]) / (vb[2]-va[2]); float tmax = (neia[k*2+1]-va[2]) / (vb[2]-va[2]); @@ -366,7 +392,7 @@ void dtNavMesh::connectExtLinks(dtMeshTile* tile, dtMeshTile* target, int side) link->bmin = (unsigned char)(dtClamp(tmin, 0.0f, 1.0f)*255.0f); link->bmax = (unsigned char)(dtClamp(tmax, 0.0f, 1.0f)*255.0f); } - else if (side == 2 || side == 6) + else if (dir == 2 || dir == 6) { float tmin = (neia[k*2+0]-va[0]) / (vb[0]-va[0]); float tmax = (neia[k*2+1]-va[0]) / (vb[0]-va[0]); @@ -711,7 +737,7 @@ dtStatus dtNavMesh::addTile(unsigned char* data, int dataSize, int flags, return DT_FAILURE | DT_WRONG_VERSION; // Make sure the location is free. - if (getTileAt(header->x, header->y)) + if (getTileAt(header->x, header->y, header->layer)) return DT_FAILURE; // Allocate a tile. @@ -783,6 +809,10 @@ dtStatus dtNavMesh::addTile(unsigned char* data, int dataSize, int flags, tile->bvTree = (dtBVNode*)d; d += bvtreeSize; tile->offMeshCons = (dtOffMeshConnection*)d; d += offMeshLinksSize; + // If there are no items in the bvtree, reset the tree pointer. + if (!bvtreeSize) + tile->bvTree = 0; + // Build links freelist tile->linksFreeList = 0; tile->links[header->maxLinkCount-1].next = DT_NULL_LINK; @@ -798,16 +828,32 @@ dtStatus dtNavMesh::addTile(unsigned char* data, int dataSize, int flags, connectIntLinks(tile); connectIntOffMeshLinks(tile); - // Create connections connections. + // Create connections with neighbour tiles. + static const int MAX_NEIS = 32; + dtMeshTile* neis[MAX_NEIS]; + int nneis; + + // Connect with layers in current tile. + nneis = getTilesAt(header->x, header->y, neis, MAX_NEIS); + for (int j = 0; j < nneis; ++j) + { + if (neis[j] == tile) continue; + connectExtLinks(tile, neis[j], -1); + connectExtLinks(neis[j], tile, -1); + connectExtOffMeshLinks(tile, neis[j], -1); + connectExtOffMeshLinks(neis[j], tile, -1); + } + + // Connect with neighbour tiles. for (int i = 0; i < 8; ++i) { - dtMeshTile* nei = getNeighbourTileAt(header->x, header->y, i); - if (nei) + nneis = getNeighbourTilesAt(header->x, header->y, i, neis, MAX_NEIS); + for (int j = 0; j < nneis; ++j) { - connectExtLinks(tile, nei, i); - connectExtLinks(nei, tile, dtOppositeTile(i)); - connectExtOffMeshLinks(tile, nei, i); - connectExtOffMeshLinks(nei, tile, dtOppositeTile(i)); + connectExtLinks(tile, neis[j], i); + connectExtLinks(neis[j], tile, dtOppositeTile(i)); + connectExtOffMeshLinks(tile, neis[j], i); + connectExtOffMeshLinks(neis[j], tile, dtOppositeTile(i)); } } @@ -817,55 +863,102 @@ dtStatus dtNavMesh::addTile(unsigned char* data, int dataSize, int flags, return DT_SUCCESS; } -const dtMeshTile* dtNavMesh::getTileAt(int x, int y) const +const dtMeshTile* dtNavMesh::getTileAt(const int x, const int y, const int layer) const { // Find tile based on hash. int h = computeTileHash(x,y,m_tileLutMask); dtMeshTile* tile = m_posLookup[h]; while (tile) { - if (tile->header && tile->header->x == x && tile->header->y == y) + if (tile->header && + tile->header->x == x && + tile->header->y == y && + tile->header->layer == layer) + { return tile; + } tile = tile->next; } return 0; } -dtMeshTile* dtNavMesh::getNeighbourTileAt(int x, int y, int side) const +int dtNavMesh::getNeighbourTilesAt(const int x, const int y, const int side, dtMeshTile** tiles, const int maxTiles) const { + int nx = x, ny = y; switch (side) { - case 0: x++; break; - case 1: x++; y++; break; - case 2: y++; break; - case 3: x--; y++; break; - case 4: x--; break; - case 5: x--; y--; break; - case 6: y--; break; - case 7: x++; y--; break; + case 0: nx++; break; + case 1: nx++; ny++; break; + case 2: ny++; break; + case 3: nx--; ny++; break; + case 4: nx--; break; + case 5: nx--; ny--; break; + case 6: ny--; break; + case 7: nx++; ny--; break; }; + return getTilesAt(nx, ny, tiles, maxTiles); +} + +int dtNavMesh::getTilesAt(const int x, const int y, dtMeshTile** tiles, const int maxTiles) const +{ + int n = 0; + // Find tile based on hash. int h = computeTileHash(x,y,m_tileLutMask); dtMeshTile* tile = m_posLookup[h]; while (tile) { - if (tile->header && tile->header->x == x && tile->header->y == y) - return tile; + if (tile->header && + tile->header->x == x && + tile->header->y == y) + { + if (n < maxTiles) + tiles[n++] = tile; + } tile = tile->next; } - return 0; + + return n; } -dtTileRef dtNavMesh::getTileRefAt(int x, int y) const +int dtNavMesh::getTilesAt(const int x, const int y, dtMeshTile const** tiles, const int maxTiles) const +{ + int n = 0; + + // Find tile based on hash. + int h = computeTileHash(x,y,m_tileLutMask); + dtMeshTile* tile = m_posLookup[h]; + while (tile) + { + if (tile->header && + tile->header->x == x && + tile->header->y == y) + { + if (n < maxTiles) + tiles[n++] = tile; + } + tile = tile->next; + } + + return n; +} + + +dtTileRef dtNavMesh::getTileRefAt(const int x, const int y, const int layer) const { // Find tile based on hash. int h = computeTileHash(x,y,m_tileLutMask); dtMeshTile* tile = m_posLookup[h]; while (tile) { - if (tile->header && tile->header->x == x && tile->header->y == y) + if (tile->header && + tile->header->x == x && + tile->header->y == y && + tile->header->layer == layer) + { return getTileRef(tile); + } tile = tile->next; } return 0; @@ -969,14 +1062,27 @@ dtStatus dtNavMesh::removeTile(dtTileRef ref, unsigned char** data, int* dataSiz } // Remove connections to neighbour tiles. - for (int i = 0; i < 8; ++i) + // Create connections with neighbour tiles. + static const int MAX_NEIS = 32; + dtMeshTile* neis[MAX_NEIS]; + int nneis; + + // Connect with layers in current tile. + nneis = getTilesAt(tile->header->x, tile->header->y, neis, MAX_NEIS); + for (int j = 0; j < nneis; ++j) { - dtMeshTile* nei = getNeighbourTileAt(tile->header->x,tile->header->y,i); - if (!nei) continue; - unconnectExtLinks(nei, dtOppositeTile(i)); + if (neis[j] == tile) continue; + unconnectExtLinks(neis[j], tile); } - + // Connect with neighbour tiles. + for (int i = 0; i < 8; ++i) + { + nneis = getNeighbourTilesAt(tile->header->x, tile->header->y, i, neis, MAX_NEIS); + for (int j = 0; j < nneis; ++j) + unconnectExtLinks(neis[j], tile); + } + // Reset tile. if (tile->flags & DT_TILE_FREE_DATA) { diff --git a/Detour/Source/DetourNavMeshBuilder.cpp b/Detour/Source/DetourNavMeshBuilder.cpp index ddb8372..7216443 100644 --- a/Detour/Source/DetourNavMeshBuilder.cpp +++ b/Detour/Source/DetourNavMeshBuilder.cpp @@ -252,8 +252,6 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, return false; if (!params->polyCount || !params->polys) return false; -// if (!params->detailMeshes || !params->detailVerts || !params->detailTris) -// return false; const int nvp = params->nvp; @@ -298,23 +296,13 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, for (int j = 0; j < nvp; ++j) { if (p[j] == MESH_NULL_IDX) break; - int nj = j+1; - if (nj >= nvp || p[nj] == MESH_NULL_IDX) nj = 0; - const unsigned short* va = ¶ms->verts[p[j]*3]; - const unsigned short* vb = ¶ms->verts[p[nj]*3]; - edgeCount++; - if (params->tileSize > 0) + if (p[nvp+j] & 0x8000) { - if (va[0] == params->tileSize && vb[0] == params->tileSize) - portalCount++; // x+ - else if (va[2] == params->tileSize && vb[2] == params->tileSize) - portalCount++; // z+ - else if (va[0] == 0 && vb[0] == 0) - portalCount++; // x- - else if (va[2] == 0 && vb[2] == 0) - portalCount++; // z- + unsigned short dir = p[nvp+j] & 0xf; + if (dir != 0xf) + portalCount++; } } } @@ -368,7 +356,7 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, const int detailMeshesSize = dtAlign4(sizeof(dtPolyDetail)*params->polyCount); const int detailVertsSize = dtAlign4(sizeof(float)*3*uniqueDetailVertCount); const int detailTrisSize = dtAlign4(sizeof(unsigned char)*4*detailTriCount); - const int bvTreeSize = dtAlign4(sizeof(dtBVNode)*params->polyCount*2); + const int bvTreeSize = params->buildBvTree ? dtAlign4(sizeof(dtBVNode)*params->polyCount*2) : 0; const int offMeshConsSize = dtAlign4(sizeof(dtOffMeshConnection)*storedOffMeshConCount); const int dataSize = headerSize + vertsSize + polysSize + linksSize + @@ -400,6 +388,7 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, header->version = DT_NAVMESH_VERSION; header->x = params->tileX; header->y = params->tileY; + header->layer = params->tileLayer; header->userId = params->userId; header->polyCount = totPolyCount; header->vertCount = totVertCount; @@ -415,7 +404,7 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, header->walkableRadius = params->walkableRadius; header->walkableClimb = params->walkableClimb; header->offMeshConCount = storedOffMeshConCount; - header->bvNodeCount = params->polyCount*2; + header->bvNodeCount = params->buildBvTree ? params->polyCount*2 : 0; const int offMeshVertsBase = params->vertCount; const int offMeshPolyBase = params->polyCount; @@ -459,7 +448,27 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, { if (src[j] == MESH_NULL_IDX) break; p->verts[j] = src[j]; - p->neis[j] = (src[nvp+j]+1) & 0xffff; + if (src[nvp+j] & 0x8000) + { + // Border or portal edge. + unsigned short dir = src[nvp+j] & 0xf; + if (dir == 0xf) // Border + p->neis[j] = 0; + else if (dir == 0) // Portal x- + p->neis[j] = DT_EXT_LINK | 4; + else if (dir == 1) // Portal z+ + p->neis[j] = DT_EXT_LINK | 2; + else if (dir == 2) // Portal x+ + p->neis[j] = DT_EXT_LINK | 0; + else if (dir == 3) // Portal z- + p->neis[j] = DT_EXT_LINK | 6; + } + else + { + // Normal connection + p->neis[j] = src[nvp+j]+1; + } + p->vertCount++; } src += nvp*2; @@ -481,32 +490,6 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, n++; } } - - // Store portal edges. - if (params->tileSize > 0) - { - for (int i = 0; i < params->polyCount; ++i) - { - dtPoly* poly = &navPolys[i]; - for (int j = 0; j < poly->vertCount; ++j) - { - int nj = j+1; - if (nj >= poly->vertCount) nj = 0; - - const unsigned short* va = ¶ms->verts[poly->verts[j]*3]; - const unsigned short* vb = ¶ms->verts[poly->verts[nj]*3]; - - if (va[0] == params->tileSize && vb[0] == params->tileSize) // x+ - poly->neis[j] = DT_EXT_LINK | 0; - else if (va[2] == params->tileSize && vb[2] == params->tileSize) // z+ - poly->neis[j] = DT_EXT_LINK | 2; - else if (va[0] == 0 && vb[0] == 0) // x- - poly->neis[j] = DT_EXT_LINK | 4; - else if (va[2] == 0 && vb[2] == 0) // z- - poly->neis[j] = DT_EXT_LINK | 6; - } - } - } // Store detail meshes and vertices. // The nav polygon vertices are stored as the first vertices on each mesh. @@ -564,8 +547,11 @@ bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, // Store and create BVtree. // TODO: take detail mesh into account! use byte per bbox extent? - createBVTree(params->verts, params->vertCount, params->polys, params->polyCount, - nvp, params->cs, params->ch, params->polyCount*2, navBvtree); + if (params->buildBvTree) + { + createBVTree(params->verts, params->vertCount, params->polys, params->polyCount, + nvp, params->cs, params->ch, params->polyCount*2, navBvtree); + } // Store Off-Mesh connections. n = 0; @@ -653,6 +639,7 @@ bool dtNavMeshHeaderSwapEndian(unsigned char* data, const int /*dataSize*/) swapEndian(&header->version); swapEndian(&header->x); swapEndian(&header->y); + swapEndian(&header->layer); swapEndian(&header->userId); swapEndian(&header->polyCount); swapEndian(&header->vertCount); diff --git a/Detour/Source/DetourNavMeshQuery.cpp b/Detour/Source/DetourNavMeshQuery.cpp index 949e205..7f2b1f7 100644 --- a/Detour/Source/DetourNavMeshQuery.cpp +++ b/Detour/Source/DetourNavMeshQuery.cpp @@ -494,8 +494,12 @@ int dtNavMeshQuery::queryPolygonsInTile(const dtMeshTile* tile, const float* qmi const dtPolyRef base = m_nav->getPolyRefBase(tile); for (int i = 0; i < tile->header->polyCount; ++i) { + const dtPoly* p = &tile->polys[i]; + const dtPolyRef ref = base | (dtPolyRef)i; + if (!filter->passFilter(ref, tile, p)) + continue; + // Calc polygon bounds. - dtPoly* p = &tile->polys[i]; const float* v = &tile->verts[p->verts[0]*3]; dtVcopy(bmin, v); dtVcopy(bmax, v); @@ -507,12 +511,8 @@ int dtNavMeshQuery::queryPolygonsInTile(const dtMeshTile* tile, const float* qmi } if (dtOverlapBounds(qmin,qmax, bmin,bmax)) { - const dtPolyRef ref = base | (dtPolyRef)i; - if (filter->passFilter(ref, tile, p)) - { - if (n < maxPolys) - polys[n++] = ref; - } + if (n < maxPolys) + polys[n++] = ref; } } return n; @@ -534,18 +534,23 @@ dtStatus dtNavMeshQuery::queryPolygons(const float* center, const float* extents m_nav->calcTileLoc(bmin, &minx, &miny); m_nav->calcTileLoc(bmax, &maxx, &maxy); + static const int MAX_NEIS = 32; + const dtMeshTile* neis[MAX_NEIS]; + int n = 0; for (int y = miny; y <= maxy; ++y) { for (int x = minx; x <= maxx; ++x) { - const dtMeshTile* tile = m_nav->getTileAt(x,y); - if (!tile) continue; - n += queryPolygonsInTile(tile, bmin, bmax, filter, polys+n, maxPolys-n); - if (n >= maxPolys) + const int nneis = m_nav->getTilesAt(x,y,neis,MAX_NEIS); + for (int j = 0; j < nneis; ++j) { - *polyCount = n; - return DT_SUCCESS | DT_BUFFER_TOO_SMALL; + n += queryPolygonsInTile(neis[j], bmin, bmax, filter, polys+n, maxPolys-n); + if (n >= maxPolys) + { + *polyCount = n; + return DT_SUCCESS | DT_BUFFER_TOO_SMALL; + } } } } diff --git a/DetourTileCache/DetourTileCacheBuilder.cpp b/DetourTileCache/DetourTileCacheBuilder.cpp new file mode 100644 index 0000000..c380f74 --- /dev/null +++ b/DetourTileCache/DetourTileCacheBuilder.cpp @@ -0,0 +1,1973 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include "DetourCommon.h" +#include "DetourStatus.h" +#include "DetourAssert.h" +#include "DetourTileCacheBuilder.h" +#include + + +template class dtFixedArray +{ + dtTileCacheAlloc* m_alloc; + T* m_ptr; + const int m_size; + inline T* operator=(T* p); + inline dtFixedArray(); +public: + inline dtFixedArray(dtTileCacheAlloc* a, const int s) : m_alloc(a), m_ptr((T*)a->alloc(sizeof(T)*s)), m_size(s) {} + inline ~dtFixedArray() { if (m_alloc) m_alloc->free(m_ptr); } + inline operator T*() { return m_ptr; } + inline int size() const { return m_size; } +}; + +static const unsigned short DT_TILECACHE_NULL_IDX = 0xffff; +static const unsigned char DT_TILECACHE_NULL_AREA = 0; + +inline int getDirOffsetX(int dir) +{ + const int offset[4] = { -1, 0, 1, 0, }; + return offset[dir&0x03]; +} + +inline int getDirOffsetY(int dir) +{ + const int offset[4] = { 0, 1, 0, -1 }; + return offset[dir&0x03]; +} + +static const int MAX_VERTS_PER_POLY = 6; // TODO: use the DT_VERTS_PER_POLYGON +static const int MAX_REM_EDGES = 48; // TODO: make this an expression. + + + +int dtCalcTileCacheLayerBufferSize(const int gridWidth, const int gridHeight) +{ + const int headerSize = dtAlign4(sizeof(dtTileCacheLayerHeader)); + const int gridSize = gridWidth * gridHeight; + return headerSize + gridSize*4; +} + +dtStatus dtCastTileCacheLayer(dtTileCacheLayer& layer, unsigned char* buffer, const int bufferSize) +{ + if (!buffer) + return DT_FAILURE; + + dtTileCacheLayerHeader* header = (dtTileCacheLayerHeader*)buffer; + if (header->magic != DT_TILECACHE_MAGIC) + return DT_FAILURE | DT_WRONG_MAGIC; + if (header->version != DT_TILECACHE_VERSION) + return DT_FAILURE | DT_WRONG_VERSION; + + const int headerSize = dtAlign4(sizeof(dtTileCacheLayerHeader)); + const int gridSize = (int)header->width * (int)header->height; + const int memUsage = headerSize + gridSize*4; + if (bufferSize < memUsage) + return DT_FAILURE | DT_BUFFER_TOO_SMALL; + + memset(&layer, 0, sizeof(layer)); + + layer.header = header; + layer.heights = buffer + headerSize; + layer.areas = buffer + headerSize + gridSize; + layer.cons = buffer + headerSize + gridSize*2; + layer.regs = buffer + headerSize + gridSize*3; + + return DT_SUCCESS; +} + +dtTileCacheContourSet* dtAllocTileCacheContourSet(dtTileCacheAlloc* alloc) +{ + dtAssert(alloc); + + dtTileCacheContourSet* cset = (dtTileCacheContourSet*)alloc->alloc(sizeof(dtTileCacheContourSet)); + memset(cset, 0, sizeof(dtTileCacheContourSet)); + return cset; +} + +void dtFreeTileCacheContourSet(dtTileCacheAlloc* alloc, dtTileCacheContourSet* cset) +{ + dtAssert(alloc); + + if (!cset) return; + for (int i = 0; i < cset->nconts; ++i) + alloc->free(cset->conts[i].verts); + alloc->free(cset->conts); + alloc->free(cset); +} + +dtTileCachePolyMesh* dtAllocTileCachePolyMesh(dtTileCacheAlloc* alloc) +{ + dtAssert(alloc); + + dtTileCachePolyMesh* lmesh = (dtTileCachePolyMesh*)alloc->alloc(sizeof(dtTileCachePolyMesh)); + memset(lmesh, 0, sizeof(dtTileCachePolyMesh)); + return lmesh; +} + +void dtFreeTileCachePolyMesh(dtTileCacheAlloc* alloc, dtTileCachePolyMesh* lmesh) +{ + dtAssert(alloc); + + if (!lmesh) return; + alloc->free(lmesh->verts); + alloc->free(lmesh->polys); + alloc->free(lmesh->flags); + alloc->free(lmesh->areas); + alloc->free(lmesh); +} + + + +struct dtLayerSweepSpan +{ + unsigned short ns; // number samples + unsigned char id; // region id + unsigned char nei; // neighbour id +}; + +static const int DT_LAYER_MAX_NEIS = 16; + +struct dtLayerMonotoneRegion +{ + int area; + unsigned char neis[DT_LAYER_MAX_NEIS]; + unsigned char nneis; + unsigned char regId; +}; + +struct dtTempContour +{ + inline dtTempContour(unsigned char* vbuf, const int nvbuf, + unsigned short* pbuf, const int npbuf) : + verts(vbuf), nverts(0), cverts(nvbuf), + poly(pbuf), npoly(0), cpoly(npbuf) + { + } + unsigned char* verts; + int nverts; + int cverts; + unsigned short* poly; + int npoly; + int cpoly; +}; + + + + +inline bool overlapRangeExl(const unsigned short amin, const unsigned short amax, + const unsigned short bmin, const unsigned short bmax) +{ + return (amin >= bmax || amax <= bmin) ? false : true; +} + +static void addUniqueLast(unsigned char* a, unsigned char& an, unsigned char v) +{ + const int n = (int)an; + if (n > 0 && a[n-1] == v) return; + a[an] = v; + an++; +} + +inline bool isConnected(const dtTileCacheLayer& layer, + const int ia, const int ib, const int walkableClimb) +{ + if (layer.areas[ia] != layer.areas[ib]) return false; + if (dtAbs((int)layer.heights[ia] - (int)layer.heights[ib]) > walkableClimb) return false; + return true; +} + +static bool canMerge(unsigned char oldRegId, unsigned char newRegId, const dtLayerMonotoneRegion* regs, const int nregs) +{ + int count = 0; + for (int i = 0; i < nregs; ++i) + { + const dtLayerMonotoneRegion& reg = regs[i]; + if (reg.regId != oldRegId) continue; + const int nnei = (int)reg.nneis; + for (int j = 0; j < nnei; ++j) + { + if (regs[reg.neis[j]].regId == newRegId) + count++; + } + } + return count == 1; +} + + +dtStatus dtBuildTileCacheRegions(dtTileCacheAlloc* alloc, + dtTileCacheLayer& layer, + const int walkableClimb) +{ + dtAssert(alloc); + + const int w = (int)layer.header->width; + const int h = (int)layer.header->height; + + memset(layer.regs,0xff,sizeof(unsigned char)*w*h); + + const int nsweeps = w; + dtFixedArray sweeps(alloc, nsweeps); + if (!sweeps) + return DT_FAILURE | DT_OUT_OF_MEMORY; + memset(sweeps,0,sizeof(dtLayerSweepSpan)*nsweeps); + + // Partition walkable area into monotone regions. + unsigned char prevCount[256]; + unsigned char regId = 0; + + for (int y = 0; y < h; ++y) + { + if (regId > 0) + memset(prevCount,0,sizeof(unsigned char)*regId); + unsigned char sweepId = 0; + + for (int x = 0; x < w; ++x) + { + const int idx = x + y*w; + if (layer.areas[idx] == DT_TILECACHE_NULL_AREA) continue; + + unsigned char sid = 0xff; + + // -x + const int xidx = (x-1)+y*w; + if (x > 0 && isConnected(layer, idx, xidx, walkableClimb)) + { + if (layer.regs[xidx] != 0xff) + sid = layer.regs[xidx]; + } + + if (sid == 0xff) + { + sid = sweepId++; + sweeps[sid].nei = 0xff; + sweeps[sid].ns = 0; + } + + // -y + const int yidx = x+(y-1)*w; + if (y > 0 && isConnected(layer, idx, yidx, walkableClimb)) + { + const unsigned char nr = layer.regs[yidx]; + if (nr != 0xff) + { + // Set neighbour when first valid neighbour is encoutered. + if (sweeps[sid].ns == 0) + sweeps[sid].nei = nr; + + if (sweeps[sid].nei == nr) + { + // Update existing neighbour + sweeps[sid].ns++; + prevCount[nr]++; + } + else + { + // This is hit if there is nore than one neighbour. + // Invalidate the neighbour. + sweeps[sid].nei = 0xff; + } + } + } + + layer.regs[idx] = sid; + } + + // Create unique ID. + for (int i = 0; i < sweepId; ++i) + { + // If the neighbour is set and there is only one continuous connection to it, + // the sweep will be merged with the previous one, else new region is created. + if (sweeps[i].nei != 0xff && (unsigned short)prevCount[sweeps[i].nei] == sweeps[i].ns) + { + sweeps[i].id = sweeps[i].nei; + } + else + { + if (regId == 255) + { + // Region ID's overflow. + return DT_FAILURE | DT_BUFFER_TOO_SMALL; + } + sweeps[i].id = regId++; + } + } + + // Remap local sweep ids to region ids. + for (int x = 0; x < w; ++x) + { + const int idx = x+y*w; + if (layer.regs[idx] != 0xff) + layer.regs[idx] = sweeps[layer.regs[idx]].id; + } + } + + // Allocate and init layer regions. + const int nregs = (int)regId; + dtFixedArray regs(alloc, nregs); + if (!regs) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + memset(regs, 0, sizeof(dtLayerMonotoneRegion)*nregs); + for (int i = 0; i < nregs; ++i) + regs[i].regId = 0xff; + + // Find region neighbours. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const int idx = x+y*w; + const unsigned char ri = layer.regs[idx]; + if (ri == 0xff) + continue; + + // Update area. + regs[ri].area++; + + // Update neighbours + const int ymi = x+(y-1)*w; + if (y > 0 && isConnected(layer, idx, ymi, walkableClimb)) + { + const unsigned char rai = layer.regs[ymi]; + if (rai != 0xff && rai != ri) + { + addUniqueLast(regs[ri].neis, regs[ri].nneis, rai); + addUniqueLast(regs[rai].neis, regs[rai].nneis, ri); + } + } + } + } + + for (int i = 0; i < nregs; ++i) + regs[i].regId = (unsigned char)i; + + for (int i = 0; i < nregs; ++i) + { + dtLayerMonotoneRegion& reg = regs[i]; + + int merge = -1; + int mergea = 0; + for (int j = 0; j < (int)reg.nneis; ++j) + { + const unsigned char nei = reg.neis[j]; + dtLayerMonotoneRegion& regn = regs[nei]; + if (reg.regId == regn.regId) + continue; + if (regn.area > mergea) + { + if (canMerge(reg.regId, regn.regId, regs, nregs)) + { + mergea = regn.area; + merge = (int)nei; + } + } + } + if (merge != -1) + { + const unsigned char oldId = reg.regId; + const unsigned char newId = regs[merge].regId; + for (int j = 0; j < nregs; ++j) + if (regs[j].regId == oldId) + regs[j].regId = newId; + } + } + + // Compact ids. + unsigned char remap[256]; + memset(remap, 0, 256); + // Find number of unique regions. + regId = 0; + for (int i = 0; i < nregs; ++i) + remap[regs[i].regId] = 1; + for (int i = 0; i < 256; ++i) + if (remap[i]) + remap[i] = regId++; + // Remap ids. + for (int i = 0; i < nregs; ++i) + regs[i].regId = remap[regs[i].regId]; + + layer.regCount = regId; + + for (int i = 0; i < w*h; ++i) + { + if (layer.regs[i] != 0xff) + layer.regs[i] = regs[layer.regs[i]].regId; + } + + return DT_SUCCESS; +} + + + +static bool appendVertex(dtTempContour& cont, const int x, const int y, const int z, const int r) +{ + // Try to merge with existing segments. + if (cont.nverts > 1) + { + unsigned char* pa = &cont.verts[(cont.nverts-2)*4]; + unsigned char* pb = &cont.verts[(cont.nverts-1)*4]; + if ((int)pb[3] == r) + { + if (pa[0] == pb[0] && (int)pb[0] == x) + { + // The verts are aligned aling x-axis, update z. + pb[1] = (unsigned char)y; + pb[2] = (unsigned char)z; + return true; + } + else if (pa[2] == pb[2] && (int)pb[2] == z) + { + // The verts are aligned aling z-axis, update x. + pb[0] = (unsigned char)x; + pb[1] = (unsigned char)y; + return true; + } + } + } + + // Add new point. + if (cont.nverts+1 > cont.cverts) + return false; + + unsigned char* v = &cont.verts[cont.nverts*4]; + v[0] = (unsigned char)x; + v[1] = (unsigned char)y; + v[2] = (unsigned char)z; + v[3] = (unsigned char)r; + cont.nverts++; + + return true; +} + + +static unsigned char getNeighbourReg(dtTileCacheLayer& layer, + const int ax, const int ay, const int dir) +{ + const int w = (int)layer.header->width; + const int ia = ax + ay*w; + + const unsigned char con = layer.cons[ia] & 0xf; + const unsigned char portal = layer.cons[ia] >> 4; + const unsigned char mask = (unsigned char)(1<width; + const int h = (int)layer.header->height; + + cont.nverts = 0; + + int startX = x; + int startY = y; + int startDir = -1; + + for (int i = 0; i < 4; ++i) + { + const int dir = (i+3)&3; + unsigned char rn = getNeighbourReg(layer, x, y, dir); + if (rn != layer.regs[x+y*w]) + { + startDir = dir; + break; + } + } + if (startDir == -1) + return true; + + int dir = startDir; + const int maxIter = w*h; + + int iter = 0; + while (iter < maxIter) + { + unsigned char rn = getNeighbourReg(layer, x, y, dir); + + int nx = x; + int ny = y; + int ndir = dir; + + if (rn != layer.regs[x+y*w]) + { + // Solid edge. + int px = x; + int pz = y; + switch(dir) + { + case 0: pz++; break; + case 1: px++; pz++; break; + case 2: px++; break; + } + + // Try to merge with previous vertex. + if (!appendVertex(cont, px, (int)layer.heights[x+y*w], pz,rn)) + return false; + + ndir = (dir+1) & 0x3; // Rotate CW + } + else + { + // Move to next. + nx = x + getDirOffsetX(dir); + ny = y + getDirOffsetY(dir); + ndir = (dir+3) & 0x3; // Rotate CCW + } + + if (iter > 0 && x == startX && y == startY && dir == startDir) + break; + + x = nx; + y = ny; + dir = ndir; + + iter++; + } + + // Remove last vertex if it is duplicate of the first one. + unsigned char* pa = &cont.verts[(cont.nverts-1)*4]; + unsigned char* pb = &cont.verts[0]; + if (pa[0] == pb[0] && pa[2] == pb[2]) + cont.nverts--; + + return true; +} + + +static float distancePtSeg(const int x, const int z, + const int px, const int pz, + const int qx, const int qz) +{ + float pqx = (float)(qx - px); + float pqz = (float)(qz - pz); + float dx = (float)(x - px); + float dz = (float)(z - pz); + float d = pqx*pqx + pqz*pqz; + float t = pqx*dx + pqz*dz; + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + + dx = px + t*pqx - x; + dz = pz + t*pqz - z; + + return dx*dx + dz*dz; +} + +static void simplifyContour(dtTempContour& cont, const float maxError) +{ + cont.npoly = 0; + + for (int i = 0; i < cont.nverts; ++i) + { + int j = (i+1) % cont.nverts; + // Check for start of a wall segment. + unsigned char ra = cont.verts[j*4+3]; + unsigned char rb = cont.verts[i*4+3]; + if (ra != rb) + cont.poly[cont.npoly++] = i; + } + if (cont.npoly < 2) + { + // If there is no transitions at all, + // create some initial points for the simplification process. + // Find lower-left and upper-right vertices of the contour. + int llx = cont.verts[0]; + int llz = cont.verts[2]; + int lli = 0; + int urx = cont.verts[0]; + int urz = cont.verts[2]; + int uri = 0; + for (int i = 1; i < cont.nverts; ++i) + { + int x = cont.verts[i*4+0]; + int z = cont.verts[i*4+2]; + if (x < llx || (x == llx && z < llz)) + { + llx = x; + llz = z; + lli = i; + } + if (x > urx || (x == urx && z > urz)) + { + urx = x; + urz = z; + uri = i; + } + } + cont.npoly = 0; + cont.poly[cont.npoly++] = lli; + cont.poly[cont.npoly++] = uri; + } + + // Add points until all raw points are within + // error tolerance to the simplified shape. + for (int i = 0; i < cont.npoly; ) + { + int ii = (i+1) % cont.npoly; + + const int ai = (int)cont.poly[i]; + const int ax = (int)cont.verts[ai*4+0]; + const int az = (int)cont.verts[ai*4+2]; + + const int bi = (int)cont.poly[ii]; + const int bx = (int)cont.verts[bi*4+0]; + const int bz = (int)cont.verts[bi*4+2]; + + // Find maximum deviation from the segment. + float maxd = 0; + int maxi = -1; + int ci, cinc, endi; + + // Traverse the segment in lexilogical order so that the + // max deviation is calculated similarly when traversing + // opposite segments. + if (bx > ax || (bx == ax && bz > az)) + { + cinc = 1; + ci = (ai+cinc) % cont.nverts; + endi = bi; + } + else + { + cinc = cont.nverts-1; + ci = (bi+cinc) % cont.nverts; + endi = ai; + } + + // Tessellate only outer edges or edges between areas. + while (ci != endi) + { + float d = distancePtSeg(cont.verts[ci*4+0], cont.verts[ci*4+2], ax, az, bx, bz); + if (d > maxd) + { + maxd = d; + maxi = ci; + } + ci = (ci+cinc) % cont.nverts; + } + + + // If the max deviation is larger than accepted error, + // add new point, else continue to next segment. + if (maxi != -1 && maxd > (maxError*maxError)) + { + cont.npoly++; + for (int j = cont.npoly-1; j > i; --j) + cont.poly[j] = cont.poly[j-1]; + cont.poly[i+1] = (unsigned short)maxi; + } + else + { + ++i; + } + } + + // Remap vertices + int start = 0; + for (int i = 1; i < cont.npoly; ++i) + if (cont.poly[i] < cont.poly[start]) + start = i; + + cont.nverts = 0; + for (int i = 0; i < cont.npoly; ++i) + { + const int j = (start+i) % cont.npoly; + unsigned char* src = &cont.verts[cont.poly[j]*4]; + unsigned char* dst = &cont.verts[cont.nverts*4]; + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + cont.nverts++; + } +} + +static int getCornerHeight(dtTileCacheLayer& layer, + const int x, const int y, const int z, + const int walkableClimb, + bool& shouldRemove) +{ + const int w = (int)layer.header->width; + const int h = (int)layer.header->height; + + int n = 0; + + unsigned char portal = 0xf; + int height = 0; + + for (int dz = -1; dz <= 0; ++dz) + { + for (int dx = -1; dx <= 0; ++dx) + { + const int px = x+dx; + const int pz = z+dz; + if (px >= 0 && pz >= 0 && px < w && pz < h) + { + const int idx = px + pz*w; + const int h = (int)layer.heights[idx]; + if (dtAbs(h-y) <= walkableClimb) + { + height = dtMax(height, h); + portal &= (layer.cons[idx] >> 4); + n++; + } + } + } + } + + int portalCount = 0; + for (int dir = 0; dir < 4; ++dir) + if (portal & (1< 1 && portalCount == 1) + { + shouldRemove = true; + } + + return height; +} + + +// TODO: move this somewhere else, once the layer meshing is done. +dtStatus dtBuildTileCacheContours(dtTileCacheAlloc* alloc, + dtTileCacheLayer& layer, + const int walkableClimb, const float maxError, + dtTileCacheContourSet& lcset) +{ + dtAssert(alloc); + + const int w = (int)layer.header->width; + const int h = (int)layer.header->height; + + lcset.nconts = layer.regCount; + lcset.conts = (dtTileCacheContour*)alloc->alloc(sizeof(dtTileCacheContour)*lcset.nconts); + if (!lcset.conts) + return DT_FAILURE | DT_OUT_OF_MEMORY; + memset(lcset.conts, 0, sizeof(dtTileCacheContour)*lcset.nconts); + + // Allocate temp buffer for contour tracing. + const int maxTempVerts = (w*h*2) * 2; // Twice around the layer. + + dtFixedArray tempVerts(alloc, maxTempVerts*4); + if (!tempVerts) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + dtFixedArray tempPoly(alloc, maxTempVerts); + if (!tempPoly) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + dtTempContour temp(tempVerts, maxTempVerts, tempPoly, maxTempVerts); + + // Find contours. + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const int idx = x+y*w; + const unsigned char ri = layer.regs[idx]; + if (ri == 0xff) + continue; + + dtTileCacheContour& cont = lcset.conts[ri]; + + if (cont.nverts > 0) + continue; + + cont.reg = ri; + cont.area = layer.areas[idx]; + + if (!walkContour(layer, x, y, temp)) + { + // Too complex contour. + // Note: If you hit here ofte, try increasing 'maxTempVerts'. + return DT_FAILURE | DT_BUFFER_TOO_SMALL; + } + + simplifyContour(temp, maxError); + + // Store contour. + cont.nverts = temp.nverts; + if (cont.nverts > 0) + { + cont.verts = (unsigned char*)alloc->alloc(sizeof(unsigned char)*4*temp.nverts); + if (!cont.verts) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + for (int i = 0, j = temp.nverts-1; i < temp.nverts; j=i++) + { + unsigned char* dst = &cont.verts[j*4]; + unsigned char* v = &temp.verts[j*4]; + unsigned char* vn = &temp.verts[i*4]; + unsigned char nei = vn[3]; // The neighbour reg is stored at segment vertex of a segment. + bool shouldRemove = false; + unsigned char h = getCornerHeight(layer, (int)v[0], (int)v[1], (int)v[2], + walkableClimb, shouldRemove); + + dst[0] = v[0]; + dst[1] = h; + dst[2] = v[2]; + + // Store portal direction and remove status to the fourth component. + dst[3] = 0x0f; + if (nei != 0xff && nei >= 0xf8) + dst[3] = nei - 0xf8; + if (shouldRemove) + dst[3] |= 0x80; + } + } + } + } + + return DT_SUCCESS; +} + + + +static const int VERTEX_BUCKET_COUNT2 = (1<<8); + +inline int computeVertexHash2(int x, int y, int z) +{ + const unsigned int h1 = 0x8da6b343; // Large multiplicative constants; + const unsigned int h2 = 0xd8163841; // here arbitrarily chosen primes + const unsigned int h3 = 0xcb1ab31f; + unsigned int n = h1 * x + h2 * y + h3 * z; + return (int)(n & (VERTEX_BUCKET_COUNT2-1)); +} + +static unsigned short addVertex(unsigned short x, unsigned short y, unsigned short z, + unsigned short* verts, unsigned short* firstVert, unsigned short* nextVert, int& nv) +{ + int bucket = computeVertexHash2(x, 0, z); + unsigned short i = firstVert[bucket]; + + while (i != DT_TILECACHE_NULL_IDX) + { + const unsigned short* v = &verts[i*3]; + if (v[0] == x && v[2] == z && (dtAbs(v[1] - y) <= 2)) + return i; + i = nextVert[i]; // next + } + + // Could not find, create new. + i = nv; nv++; + unsigned short* v = &verts[i*3]; + v[0] = x; + v[1] = y; + v[2] = z; + nextVert[i] = firstVert[bucket]; + firstVert[bucket] = i; + + return (unsigned short)i; +} + + +struct rcEdge +{ + unsigned short vert[2]; + unsigned short polyEdge[2]; + unsigned short poly[2]; +}; + +static bool buildMeshAdjacency(dtTileCacheAlloc* alloc, + unsigned short* polys, const int npolys, + const unsigned short* verts, const int nverts, + const dtTileCacheContourSet& lcset) +{ + // Based on code by Eric Lengyel from: + // http://www.terathon.com/code/edges.php + + const int maxEdgeCount = npolys*MAX_VERTS_PER_POLY; + dtFixedArray firstEdge(alloc, nverts + maxEdgeCount); + if (!firstEdge) + return false; + unsigned short* nextEdge = firstEdge + nverts; + int edgeCount = 0; + + dtFixedArray edges(alloc, maxEdgeCount); + if (!edges) + return false; + + for (int i = 0; i < nverts; i++) + firstEdge[i] = DT_TILECACHE_NULL_IDX; + + for (int i = 0; i < npolys; ++i) + { + unsigned short* t = &polys[i*MAX_VERTS_PER_POLY*2]; + for (int j = 0; j < MAX_VERTS_PER_POLY; ++j) + { + if (t[j] == DT_TILECACHE_NULL_IDX) break; + unsigned short v0 = t[j]; + unsigned short v1 = (j+1 >= MAX_VERTS_PER_POLY || t[j+1] == DT_TILECACHE_NULL_IDX) ? t[0] : t[j+1]; + if (v0 < v1) + { + rcEdge& edge = edges[edgeCount]; + edge.vert[0] = v0; + edge.vert[1] = v1; + edge.poly[0] = (unsigned short)i; + edge.polyEdge[0] = (unsigned short)j; + edge.poly[1] = (unsigned short)i; + edge.polyEdge[1] = 0xff; + // Insert edge + nextEdge[edgeCount] = firstEdge[v0]; + firstEdge[v0] = (unsigned short)edgeCount; + edgeCount++; + } + } + } + + for (int i = 0; i < npolys; ++i) + { + unsigned short* t = &polys[i*MAX_VERTS_PER_POLY*2]; + for (int j = 0; j < MAX_VERTS_PER_POLY; ++j) + { + if (t[j] == DT_TILECACHE_NULL_IDX) break; + unsigned short v0 = t[j]; + unsigned short v1 = (j+1 >= MAX_VERTS_PER_POLY || t[j+1] == DT_TILECACHE_NULL_IDX) ? t[0] : t[j+1]; + if (v0 > v1) + { + bool found = false; + for (unsigned short e = firstEdge[v1]; e != DT_TILECACHE_NULL_IDX; e = nextEdge[e]) + { + rcEdge& edge = edges[e]; + if (edge.vert[1] == v0 && edge.poly[0] == edge.poly[1]) + { + edge.poly[1] = (unsigned short)i; + edge.polyEdge[1] = (unsigned short)j; + found = true; + break; + } + } + if (!found) + { + // Matching edge not found, it is an open edge, add it. + rcEdge& edge = edges[edgeCount]; + edge.vert[0] = v1; + edge.vert[1] = v0; + edge.poly[0] = (unsigned short)i; + edge.polyEdge[0] = (unsigned short)j; + edge.poly[1] = (unsigned short)i; + edge.polyEdge[1] = 0xff; + // Insert edge + nextEdge[edgeCount] = firstEdge[v1]; + firstEdge[v1] = (unsigned short)edgeCount; + edgeCount++; + } + } + } + } + + // Mark portal edges. + for (int i = 0; i < lcset.nconts; ++i) + { + dtTileCacheContour& cont = lcset.conts[i]; + if (cont.nverts < 3) + continue; + + for (int j = 0, k = cont.nverts-1; j < cont.nverts; k=j++) + { + const unsigned char* va = &cont.verts[k*4]; + const unsigned char* vb = &cont.verts[j*4]; + const unsigned char dir = va[3] & 0xf; + if (dir == 0xf) + continue; + + if (dir == 0 || dir == 2) + { + // Find matching vertical edge + const unsigned short x = (unsigned short)va[0]; + unsigned short zmin = (unsigned short)va[2]; + unsigned short zmax = (unsigned short)vb[2]; + if (zmin > zmax) + dtSwap(zmin, zmax); + + for (int i = 0; i < edgeCount; ++i) + { + rcEdge& e = edges[i]; + // Skip connected edges. + if (e.poly[0] != e.poly[1]) + continue; + const unsigned short* eva = &verts[e.vert[0]*3]; + const unsigned short* evb = &verts[e.vert[1]*3]; + if (eva[0] == x && evb[0] == x) + { + unsigned short ezmin = eva[2]; + unsigned short ezmax = evb[2]; + if (ezmin > ezmax) + dtSwap(ezmin, ezmax); + if (overlapRangeExl(zmin,zmax, ezmin, ezmax)) + { + // Reuse the other polyedge to store dir. + e.polyEdge[1] = dir; + } + } + } + } + else + { + // Find matching vertical edge + const unsigned short z = (unsigned short)va[2]; + unsigned short xmin = (unsigned short)va[0]; + unsigned short xmax = (unsigned short)vb[0]; + if (xmin > xmax) + dtSwap(xmin, xmax); + for (int i = 0; i < edgeCount; ++i) + { + rcEdge& e = edges[i]; + // Skip connected edges. + if (e.poly[0] != e.poly[1]) + continue; + const unsigned short* eva = &verts[e.vert[0]*3]; + const unsigned short* evb = &verts[e.vert[1]*3]; + if (eva[2] == z && evb[2] == z) + { + unsigned short exmin = eva[0]; + unsigned short exmax = evb[0]; + if (exmin > exmax) + dtSwap(exmin, exmax); + if (overlapRangeExl(xmin,xmax, exmin, exmax)) + { + // Reuse the other polyedge to store dir. + e.polyEdge[1] = dir; + } + } + } + } + } + } + + + // Store adjacency + for (int i = 0; i < edgeCount; ++i) + { + const rcEdge& e = edges[i]; + if (e.poly[0] != e.poly[1]) + { + unsigned short* p0 = &polys[e.poly[0]*MAX_VERTS_PER_POLY*2]; + unsigned short* p1 = &polys[e.poly[1]*MAX_VERTS_PER_POLY*2]; + p0[MAX_VERTS_PER_POLY + e.polyEdge[0]] = e.poly[1]; + p1[MAX_VERTS_PER_POLY + e.polyEdge[1]] = e.poly[0]; + } + else if (e.polyEdge[1] != 0xff) + { + unsigned short* p0 = &polys[e.poly[0]*MAX_VERTS_PER_POLY*2]; + p0[MAX_VERTS_PER_POLY + e.polyEdge[0]] = 0x8000 | (unsigned short)e.polyEdge[1]; + } + + } + + return true; +} + + +inline int prev(int i, int n) { return i-1 >= 0 ? i-1 : n-1; } +inline int next(int i, int n) { return i+1 < n ? i+1 : 0; } + +inline int area2(const unsigned char* a, const unsigned char* b, const unsigned char* c) +{ + return ((int)b[0] - (int)a[0]) * ((int)c[2] - (int)a[2]) - ((int)c[0] - (int)a[0]) * ((int)b[2] - (int)a[2]); +} + +// Exclusive or: true iff exactly one argument is true. +// The arguments are negated to ensure that they are 0/1 +// values. Then the bitwise Xor operator may apply. +// (This idea is due to Michael Baldwin.) +inline bool xorb(bool x, bool y) +{ + return !x ^ !y; +} + +// Returns true iff c is strictly to the left of the directed +// line through a to b. +inline bool left(const unsigned char* a, const unsigned char* b, const unsigned char* c) +{ + return area2(a, b, c) < 0; +} + +inline bool leftOn(const unsigned char* a, const unsigned char* b, const unsigned char* c) +{ + return area2(a, b, c) <= 0; +} + +inline bool collinear(const unsigned char* a, const unsigned char* b, const unsigned char* c) +{ + return area2(a, b, c) == 0; +} + +// Returns true iff ab properly intersects cd: they share +// a point interior to both segments. The properness of the +// intersection is ensured by using strict leftness. +static bool intersectProp(const unsigned char* a, const unsigned char* b, + const unsigned char* c, const unsigned char* d) +{ + // Eliminate improper cases. + if (collinear(a,b,c) || collinear(a,b,d) || + collinear(c,d,a) || collinear(c,d,b)) + return false; + + return xorb(left(a,b,c), left(a,b,d)) && xorb(left(c,d,a), left(c,d,b)); +} + +// Returns T iff (a,b,c) are collinear and point c lies +// on the closed segement ab. +static bool between(const unsigned char* a, const unsigned char* b, const unsigned char* c) +{ + if (!collinear(a, b, c)) + return false; + // If ab not vertical, check betweenness on x; else on y. + if (a[0] != b[0]) + return ((a[0] <= c[0]) && (c[0] <= b[0])) || ((a[0] >= c[0]) && (c[0] >= b[0])); + else + return ((a[2] <= c[2]) && (c[2] <= b[2])) || ((a[2] >= c[2]) && (c[2] >= b[2])); +} + +// Returns true iff segments ab and cd intersect, properly or improperly. +static bool intersect(const unsigned char* a, const unsigned char* b, + const unsigned char* c, const unsigned char* d) +{ + if (intersectProp(a, b, c, d)) + return true; + else if (between(a, b, c) || between(a, b, d) || + between(c, d, a) || between(c, d, b)) + return true; + else + return false; +} + +static bool vequal(const unsigned char* a, const unsigned char* b) +{ + return a[0] == b[0] && a[2] == b[2]; +} + +// Returns T iff (v_i, v_j) is a proper internal *or* external +// diagonal of P, *ignoring edges incident to v_i and v_j*. +static bool diagonalie(int i, int j, int n, const unsigned char* verts, const unsigned short* indices) +{ + const unsigned char* d0 = &verts[(indices[i] & 0x7fff) * 4]; + const unsigned char* d1 = &verts[(indices[j] & 0x7fff) * 4]; + + // For each edge (k,k+1) of P + for (int k = 0; k < n; k++) + { + int k1 = next(k, n); + // Skip edges incident to i or j + if (!((k == i) || (k1 == i) || (k == j) || (k1 == j))) + { + const unsigned char* p0 = &verts[(indices[k] & 0x7fff) * 4]; + const unsigned char* p1 = &verts[(indices[k1] & 0x7fff) * 4]; + + if (vequal(d0, p0) || vequal(d1, p0) || vequal(d0, p1) || vequal(d1, p1)) + continue; + + if (intersect(d0, d1, p0, p1)) + return false; + } + } + return true; +} + +// Returns true iff the diagonal (i,j) is strictly internal to the +// polygon P in the neighborhood of the i endpoint. +static bool inCone(int i, int j, int n, const unsigned char* verts, const unsigned short* indices) +{ + const unsigned char* pi = &verts[(indices[i] & 0x7fff) * 4]; + const unsigned char* pj = &verts[(indices[j] & 0x7fff) * 4]; + const unsigned char* pi1 = &verts[(indices[next(i, n)] & 0x7fff) * 4]; + const unsigned char* pin1 = &verts[(indices[prev(i, n)] & 0x7fff) * 4]; + + // If P[i] is a convex vertex [ i+1 left or on (i-1,i) ]. + if (leftOn(pin1, pi, pi1)) + return left(pi, pj, pin1) && left(pj, pi, pi1); + // Assume (i-1,i,i+1) not collinear. + // else P[i] is reflex. + return !(leftOn(pi, pj, pi1) && leftOn(pj, pi, pin1)); +} + +// Returns T iff (v_i, v_j) is a proper internal +// diagonal of P. +static bool diagonal(int i, int j, int n, const unsigned char* verts, const unsigned short* indices) +{ + return inCone(i, j, n, verts, indices) && diagonalie(i, j, n, verts, indices); +} + +static int triangulate(int n, const unsigned char* verts, unsigned short* indices, unsigned short* tris) +{ + int ntris = 0; + unsigned short* dst = tris; + + // The last bit of the index is used to indicate if the vertex can be removed. + for (int i = 0; i < n; i++) + { + int i1 = next(i, n); + int i2 = next(i1, n); + if (diagonal(i, i2, n, verts, indices)) + indices[i1] |= 0x8000; + } + + while (n > 3) + { + int minLen = -1; + int mini = -1; + for (int i = 0; i < n; i++) + { + int i1 = next(i, n); + if (indices[i1] & 0x8000) + { + const unsigned char* p0 = &verts[(indices[i] & 0x7fff) * 4]; + const unsigned char* p2 = &verts[(indices[next(i1, n)] & 0x7fff) * 4]; + + const int dx = (int)p2[0] - (int)p0[0]; + const int dz = (int)p2[2] - (int)p0[2]; + const int len = dx*dx + dz*dz; + if (minLen < 0 || len < minLen) + { + minLen = len; + mini = i; + } + } + } + + if (mini == -1) + { + // Should not happen. + /* printf("mini == -1 ntris=%d n=%d\n", ntris, n); + for (int i = 0; i < n; i++) + { + printf("%d ", indices[i] & 0x0fffffff); + } + printf("\n");*/ + return -ntris; + } + + int i = mini; + int i1 = next(i, n); + int i2 = next(i1, n); + + *dst++ = indices[i] & 0x7fff; + *dst++ = indices[i1] & 0x7fff; + *dst++ = indices[i2] & 0x7fff; + ntris++; + + // Removes P[i1] by copying P[i+1]...P[n-1] left one index. + n--; + for (int k = i1; k < n; k++) + indices[k] = indices[k+1]; + + if (i1 >= n) i1 = 0; + i = prev(i1,n); + // Update diagonal flags. + if (diagonal(prev(i, n), i1, n, verts, indices)) + indices[i] |= 0x8000; + else + indices[i] &= 0x7fff; + + if (diagonal(i, next(i1, n), n, verts, indices)) + indices[i1] |= 0x8000; + else + indices[i1] &= 0x7fff; + } + + // Append the remaining triangle. + *dst++ = indices[0] & 0x7fff; + *dst++ = indices[1] & 0x7fff; + *dst++ = indices[2] & 0x7fff; + ntris++; + + return ntris; +} + + +static int countPolyVerts(const unsigned short* p) +{ + for (int i = 0; i < MAX_VERTS_PER_POLY; ++i) + if (p[i] == DT_TILECACHE_NULL_IDX) + return i; + return MAX_VERTS_PER_POLY; +} + +inline bool uleft(const unsigned short* a, const unsigned short* b, const unsigned short* c) +{ + return ((int)b[0] - (int)a[0]) * ((int)c[2] - (int)a[2]) - + ((int)c[0] - (int)a[0]) * ((int)b[2] - (int)a[2]) < 0; +} + +static int getPolyMergeValue(unsigned short* pa, unsigned short* pb, + const unsigned short* verts, int& ea, int& eb) +{ + const int na = countPolyVerts(pa); + const int nb = countPolyVerts(pb); + + // If the merged polygon would be too big, do not merge. + if (na+nb-2 > MAX_VERTS_PER_POLY) + return -1; + + // Check if the polygons share an edge. + ea = -1; + eb = -1; + + for (int i = 0; i < na; ++i) + { + unsigned short va0 = pa[i]; + unsigned short va1 = pa[(i+1) % na]; + if (va0 > va1) + dtSwap(va0, va1); + for (int j = 0; j < nb; ++j) + { + unsigned short vb0 = pb[j]; + unsigned short vb1 = pb[(j+1) % nb]; + if (vb0 > vb1) + dtSwap(vb0, vb1); + if (va0 == vb0 && va1 == vb1) + { + ea = i; + eb = j; + break; + } + } + } + + // No common edge, cannot merge. + if (ea == -1 || eb == -1) + return -1; + + // Check to see if the merged polygon would be convex. + unsigned short va, vb, vc; + + va = pa[(ea+na-1) % na]; + vb = pa[ea]; + vc = pb[(eb+2) % nb]; + if (!uleft(&verts[va*3], &verts[vb*3], &verts[vc*3])) + return -1; + + va = pb[(eb+nb-1) % nb]; + vb = pb[eb]; + vc = pa[(ea+2) % na]; + if (!uleft(&verts[va*3], &verts[vb*3], &verts[vc*3])) + return -1; + + va = pa[ea]; + vb = pa[(ea+1)%na]; + + int dx = (int)verts[va*3+0] - (int)verts[vb*3+0]; + int dy = (int)verts[va*3+2] - (int)verts[vb*3+2]; + + return dx*dx + dy*dy; +} + +static void mergePolys(unsigned short* pa, unsigned short* pb, int ea, int eb) +{ + unsigned short tmp[MAX_VERTS_PER_POLY*2]; + + const int na = countPolyVerts(pa); + const int nb = countPolyVerts(pb); + + // Merge polygons. + memset(tmp, 0xff, sizeof(unsigned short)*MAX_VERTS_PER_POLY); + int n = 0; + // Add pa + for (int i = 0; i < na-1; ++i) + tmp[n++] = pa[(ea+1+i) % na]; + // Add pb + for (int i = 0; i < nb-1; ++i) + tmp[n++] = pb[(eb+1+i) % nb]; + + memcpy(pa, tmp, sizeof(unsigned short)*MAX_VERTS_PER_POLY); +} + + +static void pushFront(unsigned short v, unsigned short* arr, int& an) +{ + an++; + for (int i = an-1; i > 0; --i) + arr[i] = arr[i-1]; + arr[0] = v; +} + +static void pushBack(unsigned short v, unsigned short* arr, int& an) +{ + arr[an] = v; + an++; +} + +static bool canRemoveVertex(dtTileCacheAlloc* alloc, dtTileCachePolyMesh& mesh, const unsigned short rem) +{ + // Count number of polygons to remove. + int numRemovedVerts = 0; + int numTouchedVerts = 0; + int numRemainingEdges = 0; + for (int i = 0; i < mesh.npolys; ++i) + { + unsigned short* p = &mesh.polys[i*MAX_VERTS_PER_POLY*2]; + const int nv = countPolyVerts(p); + int numRemoved = 0; + int numVerts = 0; + for (int j = 0; j < nv; ++j) + { + if (p[j] == rem) + { + numTouchedVerts++; + numRemoved++; + } + numVerts++; + } + if (numRemoved) + { + numRemovedVerts += numRemoved; + numRemainingEdges += numVerts-(numRemoved+1); + } + } + + // There would be too few edges remaining to create a polygon. + // This can happen for example when a tip of a triangle is marked + // as deletion, but there are no other polys that share the vertex. + // In this case, the vertex should not be removed. + if (numRemainingEdges <= 2) + return false; + + // Check that there is enough memory for the test. + const int maxEdges = numTouchedVerts*2; + if (maxEdges > MAX_REM_EDGES) + return false; + + // Find edges which share the removed vertex. + unsigned short edges[MAX_REM_EDGES]; + int nedges = 0; + + for (int i = 0; i < mesh.npolys; ++i) + { + unsigned short* p = &mesh.polys[i*MAX_VERTS_PER_POLY*2]; + const int nv = countPolyVerts(p); + + // Collect edges which touches the removed vertex. + for (int j = 0, k = nv-1; j < nv; k = j++) + { + if (p[j] == rem || p[k] == rem) + { + // Arrange edge so that a=rem. + int a = p[j], b = p[k]; + if (b == rem) + dtSwap(a,b); + + // Check if the edge exists + bool exists = false; + for (int k = 0; k < nedges; ++k) + { + unsigned short* e = &edges[k*3]; + if (e[1] == b) + { + // Exists, increment vertex share count. + e[2]++; + exists = true; + } + } + // Add new edge. + if (!exists) + { + unsigned short* e = &edges[nedges*3]; + e[0] = a; + e[1] = b; + e[2] = 1; + nedges++; + } + } + } + } + + // There should be no more than 2 open edges. + // This catches the case that two non-adjacent polygons + // share the removed vertex. In that case, do not remove the vertex. + int numOpenEdges = 0; + for (int i = 0; i < nedges; ++i) + { + if (edges[i*3+2] < 2) + numOpenEdges++; + } + if (numOpenEdges > 2) + return false; + + return true; +} + +static dtStatus removeVertex(dtTileCacheAlloc* alloc, dtTileCachePolyMesh& mesh, const unsigned short rem, const int maxTris) +{ + // Count number of polygons to remove. + int numRemovedVerts = 0; + for (int i = 0; i < mesh.npolys; ++i) + { + unsigned short* p = &mesh.polys[i*MAX_VERTS_PER_POLY*2]; + const int nv = countPolyVerts(p); + for (int j = 0; j < nv; ++j) + { + if (p[j] == rem) + numRemovedVerts++; + } + } + + int nedges = 0; + unsigned short edges[MAX_REM_EDGES*3]; + int nhole = 0; + unsigned short hole[MAX_REM_EDGES]; + int nharea = 0; + unsigned short harea[MAX_REM_EDGES]; + + for (int i = 0; i < mesh.npolys; ++i) + { + unsigned short* p = &mesh.polys[i*MAX_VERTS_PER_POLY*2]; + const int nv = countPolyVerts(p); + bool hasRem = false; + for (int j = 0; j < nv; ++j) + if (p[j] == rem) hasRem = true; + if (hasRem) + { + // Collect edges which does not touch the removed vertex. + for (int j = 0, k = nv-1; j < nv; k = j++) + { + if (p[j] != rem && p[k] != rem) + { + if (nedges >= MAX_REM_EDGES) + return DT_FAILURE | DT_BUFFER_TOO_SMALL; + unsigned short* e = &edges[nedges*3]; + e[0] = p[k]; + e[1] = p[j]; + e[2] = mesh.areas[i]; + nedges++; + } + } + // Remove the polygon. + unsigned short* p2 = &mesh.polys[(mesh.npolys-1)*MAX_VERTS_PER_POLY*2]; + memcpy(p,p2,sizeof(unsigned short)*MAX_VERTS_PER_POLY); + memset(p+MAX_VERTS_PER_POLY,0xff,sizeof(unsigned short)*MAX_VERTS_PER_POLY); + mesh.areas[i] = mesh.areas[mesh.npolys-1]; + mesh.npolys--; + --i; + } + } + + // Remove vertex. + for (int i = (int)rem; i < mesh.nverts; ++i) + { + mesh.verts[i*3+0] = mesh.verts[(i+1)*3+0]; + mesh.verts[i*3+1] = mesh.verts[(i+1)*3+1]; + mesh.verts[i*3+2] = mesh.verts[(i+1)*3+2]; + } + mesh.nverts--; + + // Adjust indices to match the removed vertex layout. + for (int i = 0; i < mesh.npolys; ++i) + { + unsigned short* p = &mesh.polys[i*MAX_VERTS_PER_POLY*2]; + const int nv = countPolyVerts(p); + for (int j = 0; j < nv; ++j) + if (p[j] > rem) p[j]--; + } + for (int i = 0; i < nedges; ++i) + { + if (edges[i*3+0] > rem) edges[i*3+0]--; + if (edges[i*3+1] > rem) edges[i*3+1]--; + } + + if (nedges == 0) + return DT_SUCCESS; + + // Start with one vertex, keep appending connected + // segments to the start and end of the hole. + pushBack(edges[0], hole, nhole); + pushBack(edges[2], harea, nharea); + + while (nedges) + { + bool match = false; + + for (int i = 0; i < nedges; ++i) + { + const unsigned short ea = edges[i*3+0]; + const unsigned short eb = edges[i*3+1]; + const unsigned short a = edges[i*3+2]; + bool add = false; + if (hole[0] == eb) + { + // The segment matches the beginning of the hole boundary. + if (nhole >= MAX_REM_EDGES) + return DT_FAILURE | DT_BUFFER_TOO_SMALL; + pushFront(ea, hole, nhole); + pushFront(a, harea, nharea); + add = true; + } + else if (hole[nhole-1] == ea) + { + // The segment matches the end of the hole boundary. + if (nhole >= MAX_REM_EDGES) + return DT_FAILURE | DT_BUFFER_TOO_SMALL; + pushBack(eb, hole, nhole); + pushBack(a, harea, nharea); + add = true; + } + if (add) + { + // The edge segment was added, remove it. + edges[i*3+0] = edges[(nedges-1)*3+0]; + edges[i*3+1] = edges[(nedges-1)*3+1]; + edges[i*3+2] = edges[(nedges-1)*3+2]; + --nedges; + match = true; + --i; + } + } + + if (!match) + break; + } + + + unsigned short tris[MAX_REM_EDGES*3]; + unsigned char tverts[MAX_REM_EDGES*3]; + unsigned short tpoly[MAX_REM_EDGES*3]; + + // Generate temp vertex array for triangulation. + for (int i = 0; i < nhole; ++i) + { + const unsigned short pi = hole[i]; + tverts[i*4+0] = (unsigned char)mesh.verts[pi*3+0]; + tverts[i*4+1] = (unsigned char)mesh.verts[pi*3+1]; + tverts[i*4+2] = (unsigned char)mesh.verts[pi*3+2]; + tverts[i*4+3] = 0; + tpoly[i] = (unsigned short)i; + } + + // Triangulate the hole. + int ntris = triangulate(nhole, tverts, tpoly, tris); + if (ntris < 0) + { + // TODO: issue warning! + ntris = -ntris; + } + + if (ntris > MAX_REM_EDGES) + return DT_FAILURE | DT_BUFFER_TOO_SMALL; + + unsigned short polys[MAX_REM_EDGES*MAX_VERTS_PER_POLY]; + unsigned char pareas[MAX_REM_EDGES]; + + // Build initial polygons. + int npolys = 0; + memset(polys, 0xff, ntris*MAX_VERTS_PER_POLY*sizeof(unsigned short)); + for (int j = 0; j < ntris; ++j) + { + unsigned short* t = &tris[j*3]; + if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2]) + { + polys[npolys*MAX_VERTS_PER_POLY+0] = hole[t[0]]; + polys[npolys*MAX_VERTS_PER_POLY+1] = hole[t[1]]; + polys[npolys*MAX_VERTS_PER_POLY+2] = hole[t[2]]; + pareas[npolys] = (unsigned char)harea[t[0]]; + npolys++; + } + } + if (!npolys) + return DT_SUCCESS; + + // Merge polygons. + if (MAX_VERTS_PER_POLY > 3) + { + for (;;) + { + // Find best polygons to merge. + int bestMergeVal = 0; + int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0; + + for (int j = 0; j < npolys-1; ++j) + { + unsigned short* pj = &polys[j*MAX_VERTS_PER_POLY]; + for (int k = j+1; k < npolys; ++k) + { + unsigned short* pk = &polys[k*MAX_VERTS_PER_POLY]; + int ea, eb; + int v = getPolyMergeValue(pj, pk, mesh.verts, ea, eb); + if (v > bestMergeVal) + { + bestMergeVal = v; + bestPa = j; + bestPb = k; + bestEa = ea; + bestEb = eb; + } + } + } + + if (bestMergeVal > 0) + { + // Found best, merge. + unsigned short* pa = &polys[bestPa*MAX_VERTS_PER_POLY]; + unsigned short* pb = &polys[bestPb*MAX_VERTS_PER_POLY]; + mergePolys(pa, pb, bestEa, bestEb); + memcpy(pb, &polys[(npolys-1)*MAX_VERTS_PER_POLY], sizeof(unsigned short)*MAX_VERTS_PER_POLY); + pareas[bestPb] = pareas[npolys-1]; + npolys--; + } + else + { + // Could not merge any polygons, stop. + break; + } + } + } + + // Store polygons. + for (int i = 0; i < npolys; ++i) + { + if (mesh.npolys >= maxTris) break; + unsigned short* p = &mesh.polys[mesh.npolys*MAX_VERTS_PER_POLY*2]; + memset(p,0xff,sizeof(unsigned short)*MAX_VERTS_PER_POLY*2); + for (int j = 0; j < MAX_VERTS_PER_POLY; ++j) + p[j] = polys[i*MAX_VERTS_PER_POLY+j]; + mesh.areas[mesh.npolys] = pareas[i]; + mesh.npolys++; + if (mesh.npolys > maxTris) + return DT_FAILURE | DT_BUFFER_TOO_SMALL; + } + + return DT_SUCCESS; +} + + +dtStatus dtBuildTileCachePolyMesh(dtTileCacheAlloc* alloc, + dtTileCacheContourSet& lcset, + dtTileCachePolyMesh& mesh) +{ + dtAssert(alloc); + + int maxVertices = 0; + int maxTris = 0; + int maxVertsPerCont = 0; + for (int i = 0; i < lcset.nconts; ++i) + { + // Skip null contours. + if (lcset.conts[i].nverts < 3) continue; + maxVertices += lcset.conts[i].nverts; + maxTris += lcset.conts[i].nverts - 2; + maxVertsPerCont = dtMax(maxVertsPerCont, lcset.conts[i].nverts); + } + + // TODO: warn about too many vertices? + + dtFixedArray vflags(alloc, maxVertices); + if (!vflags) + return DT_FAILURE | DT_OUT_OF_MEMORY; + memset(vflags, 0, maxVertices); + + mesh.verts = (unsigned short*)alloc->alloc(sizeof(unsigned short)*maxVertices*3); + if (!mesh.verts) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + mesh.polys = (unsigned short*)alloc->alloc(sizeof(unsigned short)*maxTris*MAX_VERTS_PER_POLY*2); + if (!mesh.polys) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + mesh.areas = (unsigned char*)alloc->alloc(sizeof(unsigned char)*maxTris); + if (!mesh.areas) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + mesh.flags = (unsigned short*)alloc->alloc(sizeof(unsigned short)*maxTris); + if (!mesh.flags) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + // Just allocate and clean the mesh flags array. The user is resposible for filling it. + memset(mesh.flags, 0, sizeof(unsigned short) * maxTris); + + mesh.nverts = 0; + mesh.npolys = 0; + + memset(mesh.verts, 0, sizeof(unsigned short)*maxVertices*3); + memset(mesh.polys, 0xff, sizeof(unsigned short)*maxTris*MAX_VERTS_PER_POLY*2); + memset(mesh.areas, 0, sizeof(unsigned char)*maxTris); + + unsigned short firstVert[VERTEX_BUCKET_COUNT2]; + for (int i = 0; i < VERTEX_BUCKET_COUNT2; ++i) + firstVert[i] = DT_TILECACHE_NULL_IDX; + + dtFixedArray nextVert(alloc, maxVertices); + if (!nextVert) + return DT_FAILURE | DT_OUT_OF_MEMORY; + memset(nextVert, 0, sizeof(unsigned short)*maxVertices); + + dtFixedArray indices(alloc, maxVertsPerCont); + if (!indices) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + dtFixedArray tris(alloc, maxVertsPerCont*3); + if (!tris) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + dtFixedArray polys(alloc, maxVertsPerCont*MAX_VERTS_PER_POLY); + if (!polys) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + for (int i = 0; i < lcset.nconts; ++i) + { + dtTileCacheContour& cont = lcset.conts[i]; + + // Skip null contours. + if (cont.nverts < 3) + continue; + + // Triangulate contour + for (int j = 0; j < cont.nverts; ++j) + indices[j] = (unsigned short)j; + + int ntris = triangulate(cont.nverts, cont.verts, &indices[0], &tris[0]); + if (ntris <= 0) + { + // TODO: issue warning! + ntris = -ntris; + } + + // Add and merge vertices. + for (int j = 0; j < cont.nverts; ++j) + { + const unsigned char* v = &cont.verts[j*4]; + indices[j] = addVertex((unsigned short)v[0], (unsigned short)v[1], (unsigned short)v[2], + mesh.verts, firstVert, nextVert, mesh.nverts); + if (v[3] & 0x80) + { + // This vertex should be removed. + vflags[indices[j]] = 1; + } + } + + // Build initial polygons. + int npolys = 0; + memset(polys, 0xff, sizeof(unsigned short) * maxVertsPerCont * MAX_VERTS_PER_POLY); + for (int j = 0; j < ntris; ++j) + { + const unsigned short* t = &tris[j*3]; + if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2]) + { + polys[npolys*MAX_VERTS_PER_POLY+0] = indices[t[0]]; + polys[npolys*MAX_VERTS_PER_POLY+1] = indices[t[1]]; + polys[npolys*MAX_VERTS_PER_POLY+2] = indices[t[2]]; + npolys++; + } + } + if (!npolys) + continue; + + // Merge polygons. + if (MAX_VERTS_PER_POLY > 3) + { + for(;;) + { + // Find best polygons to merge. + int bestMergeVal = 0; + int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0; + + for (int j = 0; j < npolys-1; ++j) + { + unsigned short* pj = &polys[j*MAX_VERTS_PER_POLY]; + for (int k = j+1; k < npolys; ++k) + { + unsigned short* pk = &polys[k*MAX_VERTS_PER_POLY]; + int ea, eb; + int v = getPolyMergeValue(pj, pk, mesh.verts, ea, eb); + if (v > bestMergeVal) + { + bestMergeVal = v; + bestPa = j; + bestPb = k; + bestEa = ea; + bestEb = eb; + } + } + } + + if (bestMergeVal > 0) + { + // Found best, merge. + unsigned short* pa = &polys[bestPa*MAX_VERTS_PER_POLY]; + unsigned short* pb = &polys[bestPb*MAX_VERTS_PER_POLY]; + mergePolys(pa, pb, bestEa, bestEb); + memcpy(pb, &polys[(npolys-1)*MAX_VERTS_PER_POLY], sizeof(unsigned short)*MAX_VERTS_PER_POLY); + npolys--; + } + else + { + // Could not merge any polygons, stop. + break; + } + } + } + + // Store polygons. + for (int j = 0; j < npolys; ++j) + { + unsigned short* p = &mesh.polys[mesh.npolys*MAX_VERTS_PER_POLY*2]; + unsigned short* q = &polys[j*MAX_VERTS_PER_POLY]; + for (int k = 0; k < MAX_VERTS_PER_POLY; ++k) + p[k] = q[k]; + mesh.areas[mesh.npolys] = cont.area; + mesh.npolys++; + if (mesh.npolys > maxTris) + return DT_FAILURE | DT_BUFFER_TOO_SMALL; + } + } + + + // Remove edge vertices. + for (int i = 0; i < mesh.nverts; ++i) + { + if (vflags[i]) + { + if (!canRemoveVertex(alloc, mesh, (unsigned short)i)) + continue; + dtStatus status = removeVertex(alloc, mesh, (unsigned short)i, maxTris); + if (dtStatusFailed(status)) + return status; + // Remove vertex + // Note: mesh.nverts is already decremented inside removeVertex()! + for (int j = i; j < mesh.nverts; ++j) + vflags[j] = vflags[j+1]; + --i; + } + } + + // Calculate adjacency. + if (!buildMeshAdjacency(alloc, mesh.polys, mesh.npolys, mesh.verts, mesh.nverts, lcset)) + return DT_FAILURE | DT_OUT_OF_MEMORY; + + return DT_SUCCESS; +} + diff --git a/DetourTileCache/DetourTileCacheBuilder.h b/DetourTileCache/DetourTileCacheBuilder.h new file mode 100644 index 0000000..c187fd0 --- /dev/null +++ b/DetourTileCache/DetourTileCacheBuilder.h @@ -0,0 +1,119 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef DETOURTILECACHEBUILDER_H +#define DETOURTILECACHEBUILDER_H + +#include "DetourAlloc.h" +#include "DetourStatus.h" + +static const int DT_TILECACHE_MAGIC = 'D'<<24 | 'T'<<16 | 'I'<<8 | 'C'; //'DTIC'; +static const int DT_TILECACHE_VERSION = 1; + +struct dtTileCacheLayerParams +{ + float bmin[3], bmax[3]; // Bounding box of the heightfield. + float cs, ch; // Cell size and height. +}; + +struct dtTileCacheLayerHeader +{ + int magic; // Data magic + int version; // Data version + unsigned short hmin, hmax; // Height min/max range + unsigned char width, height; // Dimension of the layer. + unsigned char minx, maxx, miny, maxy; // Usable sub-region. +}; + +struct dtTileCacheLayer +{ + dtTileCacheLayerHeader* header; + unsigned char regCount; // Region count. + unsigned char* heights; + unsigned char* areas; + unsigned char* cons; + unsigned char* regs; +}; + +struct dtTileCacheContour +{ + int nverts; + unsigned char* verts; + unsigned char reg; + unsigned char area; +}; + +struct dtTileCacheContourSet +{ + int nconts; + dtTileCacheContour* conts; +}; + +struct dtTileCachePolyMesh +{ + int nverts; // Number of vertices. + int npolys; // Number of polygons. + unsigned short* verts; // Vertices of the mesh, 3 elements per vertex. + unsigned short* polys; // Polygons of the mesh, nvp*2 elements per polygon. + unsigned short* flags; // Per polygon flags. + unsigned char* areas; // Area ID of polygons. +}; + + +struct dtTileCacheAlloc +{ + virtual void* alloc(const int size) + { + return dtAlloc(size, DT_ALLOC_TEMP); + } + + virtual void free(void* ptr) + { + dtFree(ptr); + } +}; + + +dtStatus dtCastTileCacheLayer(dtTileCacheLayer& layer, unsigned char* buffer, const int bufferSize); + +// Returns the amount of memory requires for specific grid size. +int dtCalcTileCacheLayerBufferSize(const int gridWidth, const int gridHeight); + + +dtTileCacheContourSet* dtAllocTileCacheContourSet(dtTileCacheAlloc* alloc); +void dtFreeTileCacheContourSet(dtTileCacheAlloc* alloc, dtTileCacheContourSet* cset); + +dtTileCachePolyMesh* dtAllocTileCachePolyMesh(dtTileCacheAlloc* alloc); +void dtFreeTileCachePolyMesh(dtTileCacheAlloc* alloc, dtTileCachePolyMesh* lmesh); + + +dtStatus dtBuildTileCacheRegions(dtTileCacheAlloc* alloc, + dtTileCacheLayer& layer, + const int walkableClimb); + +dtStatus dtBuildTileCacheContours(dtTileCacheAlloc* alloc, + dtTileCacheLayer& layer, + const int walkableClimb, const float maxError, + dtTileCacheContourSet& lcset); + +dtStatus dtBuildTileCachePolyMesh(dtTileCacheAlloc* alloc, + dtTileCacheContourSet& lcset, + dtTileCachePolyMesh& mesh); + + +#endif // DETOURTILECACHEBUILDER_H diff --git a/Recast/Include/Recast.h b/Recast/Include/Recast.h index 648da81..53eb640 100644 --- a/Recast/Include/Recast.h +++ b/Recast/Include/Recast.h @@ -34,7 +34,6 @@ enum rcTimerLabel RC_TIMER_TOTAL, RC_TIMER_TEMP, RC_TIMER_RASTERIZE_TRIANGLES, - RC_TIMER_BUILD_LEANHEIGHTFIELD, RC_TIMER_BUILD_COMPACTHEIGHTFIELD, RC_TIMER_BUILD_CONTOURS, RC_TIMER_BUILD_CONTOURS_TRACE, @@ -184,6 +183,7 @@ struct rcCompactHeightfield int width, height; // Width and height of the heightfield. int spanCount; // Number of spans in the heightfield. int walkableHeight, walkableClimb; // Agent properties. + int borderSize; // Border size of the heighfield. unsigned short maxDistance; // Maximum distance value stored in heightfield. unsigned short maxRegions; // Maximum Region Id stored in heightfield. float bmin[3], bmax[3]; // Bounding box of the heightfield. @@ -198,41 +198,16 @@ rcCompactHeightfield* rcAllocCompactHeightfield(); void rcFreeCompactHeightfield(rcCompactHeightfield* chf); -// Lean heightfield stores minimal information to create rcCompactNeighfield -// in one continuous chunk of memory. The header and data are both laid out -// in the memory one after each other. The data is accessed as follows: -// const int headerSize = rcAlign4(sizeof(rcLeanHeightfield)); -// const int countsSize = rcAlign4(sizeof(unsigned char)*lhf.width*lhf.height); -// const int floorsSize = rcAlign4(sizeof(unsigned short)*lhf.spanCount); -// const unsigned char* data = (const unsigned char*)&lhf; -// const unsigned char* counts = (const unsigned char*)&data[headerSize]; -// const unsigned short* floors = (const unsigned short*)&data[headerSize+countsSize]; -// const unsigned char* areas = (const unsigned char*)&data[headerSize+countsSize+floorsSize]; -// This allows the heighfield to be read and written or compressed as one chunk, i.e.: -// fwrite(lhf, lhf->size, 1, fp); -// Use rcFree() to free the memory occupied by rcLeanHeightfield. -struct rcLeanHeightfield -{ - int width, height; // Width and height of the heightfield. - int spanCount; // Number of spans in the heightfield. - float bmin[3], bmax[3]; // Bounding box of the heightfield. - float cs, ch; // Cell size and height. - int size; // Memory required by the heighfield. -}; - - struct rcHeightfieldLayer { float bmin[3], bmax[3]; // Bounding box of the heightfield. float cs, ch; // Cell size and height. int width, height; // Width and height of the layer. - int nportals; // Number of portals. - unsigned char regCount; - unsigned char maxHeight; // Height min/max range. + int minx,maxx,miny,maxy; // Bounding box of usable data. + int hmin, hmax; // Height min/max unsigned char* heights; // Heighfield. unsigned char* areas; // Area types. - unsigned char* regs; // Regions. unsigned char* cons; // Connections. }; @@ -263,6 +238,8 @@ struct rcContourSet int nconts; // Number of contours. float bmin[3], bmax[3]; // Bounding box of the heightfield. float cs, ch; // Cell size and height. + int width, height; // Region where the contours were build. + int borderSize; // Border size of the heighfield where the contours were build from. }; rcContourSet* rcAllocContourSet(); @@ -293,6 +270,7 @@ struct rcPolyMesh int nvp; // Max number of vertices per polygon. float bmin[3], bmax[3]; // Bounding box of the mesh. float cs, ch; // Cell size and height. + int borderSize; // Border size of the heighfield where the mesh was build from. }; rcPolyMesh* rcAllocPolyMesh(); @@ -620,13 +598,6 @@ void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeight // Returns number of spans. int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf); -// Builds minimal representation of the heighfield. -// Params: -// hf - (in) heightfield to be compacted -// chf - (out) lean heightfield representing the open space. -// Returns pointer to the created lean heighfield. -rcLeanHeightfield* rcBuildLeanHeightfield(rcContext* ctx, rcHeightfield& hf, const int walkableHeight); - // Builds compact representation of the heightfield. // Params: // walkableHeight - (in) minimum height where the agent can still walk @@ -637,16 +608,6 @@ rcLeanHeightfield* rcBuildLeanHeightfield(rcContext* ctx, rcHeightfield& hf, con bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, rcHeightfield& hf, rcCompactHeightfield& chf); -// Builds compact representation of the heightfield from lean data. -// Params: -// walkableHeight - (in) minimum height where the agent can still walk -// walkableClimb - (in) maximum height between grid cells the agent can climb -// lhf - (in) lean heightfield to be used as input -// chf - (out) compact heightfield representing the open space. -// Returns false if operation ran out of memory. -bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcLeanHeightfield& lhf, rcCompactHeightfield& chf); - // Erodes walkable area. // Params: // radius - (in) radius of erosion (max 255). @@ -741,60 +702,6 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, const int walkableHeight, rcHeightfieldLayerSet& lset); -// TODO: move this somewhere else, once the layer meshing is done. -bool rcBuildLayerRegions(rcContext* ctx, rcHeightfieldLayer& layer, const int walkableClimb); - - - -struct rcLayerContour -{ - int nverts; - unsigned char* verts; - unsigned char reg, area; -}; - -struct rcLayerContourSet -{ - float bmin[3], bmax[3]; // Bounding box of the heightfield. - float cs, ch; // Cell size and height. - int nconts; - rcLayerContour* conts; -}; - -rcLayerContourSet* rcAllocLayerContourSet(); -void rcFreeLayerContourSet(rcLayerContourSet* lset); - -// TODO: move this somewhere else, once the layer meshing is done. -bool rcBuildLayerContours(rcContext* ctx, - rcHeightfieldLayer& layer, - const int walkableClimb, const float maxError, - rcLayerContourSet& lcset); - - -struct rcLayerPolyMesh -{ - unsigned short* verts; // Vertices of the mesh, 3 elements per vertex. - unsigned short* polys; // Polygons of the mesh, nvp*2 elements per polygon. - unsigned short* flags; // Per polygon flags. - unsigned char* areas; // Area ID of polygons. - int nverts; // Number of vertices. - int npolys; // Number of polygons. - int maxpolys; // Number of allocated polygons. - int nvp; // Max number of vertices per polygon. - float bmin[3], bmax[3]; // Bounding box of the mesh. - float cs, ch; // Cell size and height. -}; - -rcLayerPolyMesh* rcAllocLayerPolyMesh(); -void rcFreeLayerPolyMesh(rcLayerPolyMesh* lmesh); - -bool rcBuildLayerPolyMesh(rcContext* ctx, - rcLayerContourSet& lcset, - const int maxVertsPerPoly, - rcLayerPolyMesh& lmesh); - - - // Builds simplified contours from the regions outlines. // Params: // chf - (in) compact heightfield which has regions set. @@ -813,7 +720,7 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, // nvp - (in) maximum number of vertices per polygon. // mesh - (out) poly mesh. // Returns false if operation ran out of memory. -bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, int nvp, rcPolyMesh& mesh); +bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh); bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh); diff --git a/Recast/Source/Recast.cpp b/Recast/Source/Recast.cpp index 3a953b4..4f7cf68 100644 --- a/Recast/Source/Recast.cpp +++ b/Recast/Source/Recast.cpp @@ -105,7 +105,6 @@ void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset) { rcFree(lset->layers[i].heights); rcFree(lset->layers[i].areas); - rcFree(lset->layers[i].regs); rcFree(lset->layers[i].cons); } rcFree(lset->layers); @@ -113,44 +112,6 @@ void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset) } -rcLayerContourSet* rcAllocLayerContourSet() -{ - rcLayerContourSet* cset = (rcLayerContourSet*)rcAlloc(sizeof(rcLayerContourSet), RC_ALLOC_PERM); - memset(cset, 0, sizeof(rcLayerContourSet)); - return cset; -} - -void rcFreeLayerContourSet(rcLayerContourSet* cset) -{ - if (!cset) return; - for (int i = 0; i < cset->nconts; ++i) - { - rcFree(cset->conts[i].verts); -// rcFree(cset->conts[i].rverts); - } - rcFree(cset->conts); - rcFree(cset); -} - -rcLayerPolyMesh* rcAllocLayerPolyMesh() -{ - rcLayerPolyMesh* lmesh = (rcLayerPolyMesh*)rcAlloc(sizeof(rcLayerPolyMesh), RC_ALLOC_PERM); - memset(lmesh, 0, sizeof(rcLayerPolyMesh)); - return lmesh; -} - -void rcFreeLayerPolyMesh(rcLayerPolyMesh* lmesh) -{ - if (!lmesh) return; - rcFree(lmesh->verts); - rcFree(lmesh->polys); - rcFree(lmesh->flags); - rcFree(lmesh->areas); - rcFree(lmesh); -} - - - rcContourSet* rcAllocContourSet() { rcContourSet* cset = (rcContourSet*)rcAlloc(sizeof(rcContourSet), RC_ALLOC_PERM); @@ -457,237 +418,6 @@ bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const i return true; } - -rcLeanHeightfield* rcBuildLeanHeightfield(rcContext* ctx, rcHeightfield& hf, const int walkableHeight) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_BUILD_LEANHEIGHTFIELD); - - const int w = hf.width; - const int h = hf.height; - const int spanCount = rcGetHeightFieldSpanCount(ctx, hf); - - const int headerSize = rcAlign4(sizeof(rcLeanHeightfield)); - const int countsSize = rcAlign4(sizeof(unsigned char)*w*h); - const int floorsSize = rcAlign4(sizeof(unsigned short)*spanCount); - const int areasSize = rcAlign4(sizeof(unsigned char)*spanCount); - const int dataSize = headerSize + countsSize + floorsSize + areasSize; - - unsigned char* data = (unsigned char*)rcAlloc(dataSize, RC_ALLOC_PERM); - if (!data) - { - ctx->log(RC_LOG_ERROR, "rcBuildLeanHeightfield: Out of memory (%d)", dataSize); - return 0; - } - - rcLeanHeightfield* lhf = (rcLeanHeightfield*)&data[0]; - unsigned char* counts = (unsigned char*)&data[headerSize]; - unsigned short* floors = (unsigned short*)&data[headerSize+countsSize]; - unsigned char* areas = (unsigned char*)&data[headerSize+countsSize+floorsSize]; - - // Fill in header. - lhf->width = w; - lhf->height = h; - lhf->spanCount = spanCount; - rcVcopy(lhf->bmin, hf.bmin); - rcVcopy(lhf->bmax, hf.bmax); - lhf->cs = hf.cs; - lhf->ch = hf.ch; - lhf->size = dataSize; - - memset(counts, 0, w*h); - memset(floors, 0, sizeof(unsigned short)*spanCount); - memset(areas, RC_NULL_AREA, sizeof(unsigned char)*spanCount); - - const int MAX_HEIGHT = 0xffff; - const int MAX_Y = (1<area != RC_NULL_AREA) - { - const int bot = (int)s->smax; - const int top = s->next ? (int)s->next->smin : MAX_HEIGHT; - const int space = top - bot; - unsigned short y = (unsigned short)rcClamp(bot, 0, MAX_Y); - // TODO: Make sure 'h' can encode at least walkableClimb worth of values. - unsigned short h = 0; // Height stores the space above the walkable height. - if (space >= walkableHeight) - h = (unsigned short)rcClamp(1+(space - walkableHeight)/2, 0, MAX_H); - - floors[idx] = y | (h << RC_SPAN_HEIGHT_BITS); - areas[idx] = s->area; - idx++; - count++; - } - s = s->next; - } - } - } - - ctx->stopTimer(RC_TIMER_BUILD_LEANHEIGHTFIELD); - - return lhf; -} - -bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, - rcLeanHeightfield& lhf, rcCompactHeightfield& chf) -{ - rcAssert(ctx); - - ctx->startTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD); - - const int w = lhf.width; - const int h = lhf.height; - const int spanCount = lhf.spanCount; - - // Fill in header. - chf.width = w; - chf.height = h; - chf.spanCount = spanCount; - chf.walkableHeight = walkableHeight; - chf.walkableClimb = walkableClimb; - chf.maxRegions = 0; - rcVcopy(chf.bmin, lhf.bmin); - rcVcopy(chf.bmax, lhf.bmax); - chf.bmax[1] += walkableHeight*lhf.ch; - chf.cs = lhf.cs; - chf.ch = lhf.ch; - chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*w*h, RC_ALLOC_PERM); - if (!chf.cells) - { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", w*h); - return false; - } - memset(chf.cells, 0, sizeof(rcCompactCell)*w*h); - chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*spanCount, RC_ALLOC_PERM); - if (!chf.spans) - { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount); - return false; - } - memset(chf.spans, 0, sizeof(rcCompactSpan)*spanCount); - chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*spanCount, RC_ALLOC_PERM); - if (!chf.areas) - { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount); - return false; - } - memset(chf.areas, RC_NULL_AREA, sizeof(unsigned char)*spanCount); - - const int headerSize = rcAlign4(sizeof(rcLeanHeightfield)); - const int countsSize = rcAlign4(sizeof(unsigned char)*lhf.width*lhf.height); - const int floorsSize = rcAlign4(sizeof(unsigned short)*lhf.spanCount); - const unsigned char* data = (const unsigned char*)&lhf; - const unsigned char* counts = (const unsigned char*)&data[headerSize]; - const unsigned short* floors = (const unsigned short*)&data[headerSize+countsSize]; - const unsigned char* areas = (const unsigned char*)&data[headerSize+countsSize+floorsSize]; - - const unsigned short MASK_Y = (1<>RC_SPAN_HEIGHT_BITS) & MASK_H); - chf.spans[i].y = y; - chf.spans[i].h = h == 0 ? 0 : (unsigned char)(walkableHeight + (h-1)*2); - chf.areas[i] = areas[i]; - } - - idx += count; - } - } - - // Find neighbour connections. - const int MAX_LAYERS = RC_NOT_CONNECTED-1; - int tooHighNeighbour = 0; - - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) - { - rcCompactSpan& s = chf.spans[i]; - - for (int dir = 0; dir < 4; ++dir) - { - rcSetCon(s, dir, RC_NOT_CONNECTED); - const int nx = x + rcGetDirOffsetX(dir); - const int ny = y + rcGetDirOffsetY(dir); - // First check that the neighbour cell is in bounds. - if (nx < 0 || ny < 0 || nx >= w || ny >= h) - continue; - - // Iterate over all neighbour spans and check if any of the is - // accessible from current cell. - const rcCompactCell& nc = chf.cells[nx+ny*w]; - for (int k = (int)nc.index, nk = (int)(nc.index+nc.count); k < nk; ++k) - { - const rcCompactSpan& ns = chf.spans[k]; - const int bot = rcMax(s.y, ns.y); - const int top = rcMin(s.y+s.h, ns.y+ns.h); - - // Check that the gap between the spans is walkable, - // and that the climb height between the gaps is not too high. - if ((top - bot) >= walkableHeight && rcAbs((int)ns.y - (int)s.y) <= walkableClimb) - { - // Mark direction as walkable. - const int idx = k - (int)nc.index; - if (idx < 0 || idx > MAX_LAYERS) - { - tooHighNeighbour = rcMax(tooHighNeighbour, idx); - continue; - } - rcSetCon(s, dir, idx); - break; - } - } - - } - } - } - } - - if (tooHighNeighbour > MAX_LAYERS) - { - ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)", - tooHighNeighbour, MAX_LAYERS); - } - - ctx->stopTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD); - - return true; -} - - /* static int getHeightfieldMemoryUsage(const rcHeightfield& hf) { diff --git a/Recast/Source/RecastContour.cpp b/Recast/Source/RecastContour.cpp index 630784a..9386a4b 100644 --- a/Recast/Source/RecastContour.cpp +++ b/Recast/Source/RecastContour.cpp @@ -600,13 +600,26 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, const int w = chf.width; const int h = chf.height; + const int borderSize = chf.borderSize; ctx->startTimer(RC_TIMER_BUILD_CONTOURS); rcVcopy(cset.bmin, chf.bmin); rcVcopy(cset.bmax, chf.bmax); + if (borderSize > 0) + { + // If the heightfield was build with bordersize, remove the offset. + const float pad = borderSize*chf.cs; + cset.bmin[0] += pad; + cset.bmin[2] += pad; + cset.bmax[0] -= pad; + cset.bmax[2] -= pad; + } cset.cs = chf.cs; cset.ch = chf.ch; + cset.width = chf.width - chf.borderSize*2; + cset.height = chf.height - chf.borderSize*2; + cset.borderSize = chf.borderSize; int maxContours = rcMax((int)chf.maxRegions, 8); cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM); @@ -658,8 +671,6 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE); - ctx->startTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); - rcIntArray verts(256); rcIntArray simplified(64); @@ -682,10 +693,17 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, verts.resize(0); simplified.resize(0); + + ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE); walkContour(x, y, i, chf, flags, verts); + ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE); + + ctx->startTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags); removeDegenerateSegments(simplified); + ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); + // Store region->contour remap info. // Create contour. if (simplified.size()/4 >= 3) @@ -720,6 +738,16 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, return false; } memcpy(cont->verts, &simplified[0], sizeof(int)*cont->nverts*4); + if (borderSize > 0) + { + // If the heightfield was build with bordersize, remove the offset. + for (int i = 0; i < cont->nverts; ++i) + { + int* v = &cont->verts[i*4]; + v[0] -= borderSize; + v[2] -= borderSize; + } + } cont->nrverts = verts.size()/4; cont->rverts = (int*)rcAlloc(sizeof(int)*cont->nrverts*4, RC_ALLOC_PERM); @@ -729,6 +757,16 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, return false; } memcpy(cont->rverts, &verts[0], sizeof(int)*cont->nrverts*4); + if (borderSize > 0) + { + // If the heightfield was build with bordersize, remove the offset. + for (int i = 0; i < cont->nrverts; ++i) + { + int* v = &cont->rverts[i*4]; + v[0] -= borderSize; + v[2] -= borderSize; + } + } /* cont->cx = cont->cy = cont->cz = 0; for (int i = 0; i < cont->nverts; ++i) @@ -796,8 +834,6 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, } } - ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); - ctx->stopTimer(RC_TIMER_BUILD_CONTOURS); return true; diff --git a/Recast/Source/RecastLayers.cpp b/Recast/Source/RecastLayers.cpp index cd0901b..f2d60d2 100644 --- a/Recast/Source/RecastLayers.cpp +++ b/Recast/Source/RecastLayers.cpp @@ -34,16 +34,11 @@ struct rcLayerRegion { unsigned char layers[RC_MAX_LAYERS]; unsigned char neis[RC_MAX_NEIS]; - unsigned short ymin, ymax; - - unsigned short count; - - unsigned char layerId; - unsigned char nlayers; - unsigned char nneis; - - unsigned char start; + unsigned char layerId; // Layer ID + unsigned char nlayers; // Layer count + unsigned char nneis; // Neighbour count + unsigned char base; // Flag indicating if the region is hte base of merged regions. }; @@ -57,14 +52,6 @@ static void addUnique(unsigned char* a, unsigned char& an, unsigned char v) an++; } -static void addUniqueLast(unsigned char* a, unsigned char& an, unsigned char v) -{ - const int n = (int)an; - if (n > 0 && a[n-1] == v) return; - a[an] = v; - an++; -} - static bool contains(const unsigned char* a, const unsigned char an, const unsigned char v) { const int n = (int)an; @@ -81,6 +68,7 @@ inline bool overlapRange(const unsigned short amin, const unsigned short amax, } + struct rcLayerSweepSpan { unsigned short ns; // number samples @@ -121,13 +109,11 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, int prevCount[256]; unsigned char regId = 0; -// for (int y = 0; y < h; ++y) for (int y = borderSize; y < h-borderSize; ++y) { memset(prevCount,0,sizeof(int)*regId); unsigned char sweepId = 0; -// for (int x = 0; x < w; ++x) for (int x = borderSize; x < w-borderSize; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; @@ -209,7 +195,6 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, } // Remap local sweep ids to region ids. -// for (int x = 0; x < w; ++x) for (int x = borderSize; x < w-borderSize; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; @@ -310,7 +295,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, // Start search. root.layerId = layerId; - root.start = 1; + root.base = 1; nstack = 0; stack[nstack++] = (unsigned char)i; @@ -365,7 +350,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, for (int i = 0; i < nregs; ++i) { rcLayerRegion& ri = regs[i]; - if (!ri.start) continue; + if (!ri.base) continue; unsigned char newId = ri.layerId; @@ -377,7 +362,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, { if (i == j) continue; rcLayerRegion& rj = regs[j]; - if (!rj.start) continue; + if (!rj.base) continue; // Skip if teh regions are not close to each other. if (!overlapRange(ri.ymin,ri.ymax+mergeHeight, rj.ymin,rj.ymax+mergeHeight)) @@ -422,7 +407,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, rcLayerRegion& rj = regs[j]; if (rj.layerId == oldId) { - rj.start = 0; + rj.base = 0; // Remap layerIds. rj.layerId = newId; // Add overlaid layers from 'rj' to 'ri'. @@ -458,7 +443,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, // No layers, return empty. if (layerId == 0) { - ctx->stopTimer(RC_TIMER_BUILD_REGIONS); + ctx->stopTimer(RC_TIMER_BUILD_LAYERS); return true; } @@ -495,52 +480,63 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, // Allocate memory for the current layer. rcHeightfieldLayer* layer = &lset.layers[i]; + memset(layer, 0, sizeof(rcHeightfieldLayer)); + + const int gridSize = sizeof(unsigned char)*lw*lh; + + layer->heights = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); + if (!layer->heights) + { + ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' (%d).", gridSize); + return false; + } + memset(layer->heights, 0xff, gridSize); + + layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); + if (!layer->areas) + { + ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' (%d).", gridSize); + return false; + } + memset(layer->areas, 0, gridSize); + + layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); + if (!layer->cons) + { + ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' (%d).", gridSize); + return false; + } + memset(layer->cons, 0, gridSize); + // Find layer height bounds. + int hmin = 0, hmax = 0; + for (int j = 0; j < nregs; ++j) + { + if (regs[j].base && regs[j].layerId == curId) + { + hmin = (int)regs[j].ymin; + hmax = (int)regs[j].ymax; + } + } + layer->width = lw; layer->height = lh; layer->cs = chf.cs; layer->ch = chf.ch; - - layer->heights = (unsigned char*)rcAlloc(sizeof(unsigned char)*lw*lh, RC_ALLOC_PERM); - if (!layer->heights) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' (%d).", w*h); - return false; - } - memset(layer->heights, 0xff, sizeof(unsigned char)*lw*lh); - layer->areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*lw*lh, RC_ALLOC_PERM); - if (!layer->areas) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' (%d).", w*h); - return false; - } - memset(layer->areas, RC_NULL_AREA, sizeof(unsigned char)*lw*lh); - - layer->cons = (unsigned char*)rcAlloc(sizeof(unsigned char)*lw*lh, RC_ALLOC_PERM); - if (!layer->cons) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' (%d).", w*h); - return false; - } - memset(layer->cons, 0, sizeof(unsigned char)*lw*lh); - - // Find layer height bounds. - int ymin = 0, ymax = 0; - for (int j = 0; j < nregs; ++j) - { - if (regs[j].start && regs[j].layerId == curId) - { - ymin = (int)regs[j].ymin; - ymax = (int)regs[j].ymax; - } - } - // Adjust the bbox to fit the heighfield. rcVcopy(layer->bmin, bmin); rcVcopy(layer->bmax, bmax); - layer->bmin[1] = bmin[1] + ymin*chf.ch; - layer->bmax[1] = bmin[1] + ymax*chf.ch; + layer->bmin[1] = bmin[1] + hmin*chf.ch; + layer->bmax[1] = bmin[1] + hmax*chf.ch; + layer->hmin = hmin; + layer->hmax = hmax; + + // Update usable data region. + layer->minx = layer->width; + layer->maxx = 0; + layer->miny = layer->height; + layer->maxy = 0; // Copy height and area from compact heighfield. for (int y = 0; y < lh; ++y) @@ -560,9 +556,16 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, unsigned char lid = regs[srcReg[i]].layerId; if (lid != curId) continue; + + // Update data bounds. + layer->minx = rcMin(layer->minx, x); + layer->maxx = rcMax(layer->maxx, x); + layer->miny = rcMin(layer->miny, y); + layer->maxy = rcMax(layer->maxy, y); + // Store height and area type. const int idx = x+y*lw; - layer->heights[idx] = (unsigned char)(s.y - ymin); + layer->heights[idx] = (unsigned char)(s.y - hmin); layer->areas[idx] = chf.areas[i]; // Check connection. @@ -594,1930 +597,13 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, } } } - + if (layer->minx > layer->maxx) + layer->minx = layer->maxx = 0; + if (layer->miny > layer->maxy) + layer->miny = layer->maxy = 0; } ctx->stopTimer(RC_TIMER_BUILD_LAYERS); return true; } - - - - -// Runtime stuff.... - -struct rcMonotoneRegion -{ - int area; - unsigned char neis[RC_MAX_NEIS]; - unsigned char nneis; - unsigned char regId; -}; - -inline bool isConnected(rcHeightfieldLayer& layer, const int ia, const int ib, const int walkableClimb) -{ - if (layer.areas[ia] != layer.areas[ib]) return false; - if (rcAbs((int)layer.heights[ia] - (int)layer.heights[ib]) > walkableClimb) return false; - return true; -} - -static bool canMerge(unsigned char oldRegId, unsigned char newRegId, const rcMonotoneRegion* regs, const int nregs) -{ - int count = 0; - for (int i = 0; i < nregs; ++i) - { - const rcMonotoneRegion& reg = regs[i]; - if (reg.regId != oldRegId) continue; - const int nnei = (int)reg.nneis; - for (int j = 0; j < nnei; ++j) - { - if (regs[reg.neis[j]].regId == newRegId) - count++; - } - } - return count == 1; -} - - -// TODO: move this somewhere else, once the layer meshing is done. -bool rcBuildLayerRegions(rcContext* ctx, rcHeightfieldLayer& layer, const int walkableClimb) -{ - rcAssert(ctx); - -// ctx->startTimer(RC_TIMER_BUILD_LAYERS); - - const int w = layer.width; - const int h = layer.height; - - rcAssert(layer.regs == 0); - - layer.regs = (unsigned char*)rcAlloc(sizeof(unsigned char)*w*h, RC_ALLOC_TEMP); - if (!layer.regs) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regs' (%d).", w*h); - return false; - } - memset(layer.regs,0xff,sizeof(unsigned char)*w*h); - - const int nsweeps = w; - rcScopedDelete sweeps = (rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan)*nsweeps, RC_ALLOC_TEMP); - if (!sweeps) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'sweeps' (%d).", nsweeps); - return false; - } - memset(sweeps,0,sizeof(rcLayerSweepSpan)*nsweeps); - - // Partition walkable area into monotone regions. - unsigned char prevCount[256]; - unsigned char regId = 0; - - for (int y = 0; y < h; ++y) - { - if (regId > 0) - memset(prevCount,0,sizeof(unsigned char)*regId); - unsigned char sweepId = 0; - - for (int x = 0; x < w; ++x) - { - const int idx = x + y*w; - if (layer.areas[idx] == RC_NULL_AREA) continue; - - unsigned char sid = 0xff; - - // -x - const int xidx = (x-1)+y*w; - if (x > 0 && isConnected(layer, idx, xidx, walkableClimb)) - { - if (layer.regs[xidx] != 0xff) - sid = layer.regs[xidx]; - } - - if (sid == 0xff) - { - sid = sweepId++; - sweeps[sid].nei = 0xff; - sweeps[sid].ns = 0; - } - - // -y - const int yidx = x+(y-1)*w; - if (y > 0 && isConnected(layer, idx, yidx, walkableClimb)) - { - const unsigned char nr = layer.regs[yidx]; - if (nr != 0xff) - { - // Set neighbour when first valid neighbour is encoutered. - if (sweeps[sid].ns == 0) - sweeps[sid].nei = nr; - - if (sweeps[sid].nei == nr) - { - // Update existing neighbour - sweeps[sid].ns++; - prevCount[nr]++; - } - else - { - // This is hit if there is nore than one neighbour. - // Invalidate the neighbour. - sweeps[sid].nei = 0xff; - } - } - } - - layer.regs[idx] = sid; - } - - // Create unique ID. - for (int i = 0; i < sweepId; ++i) - { - // If the neighbour is set and there is only one continuous connection to it, - // the sweep will be merged with the previous one, else new region is created. - if (sweeps[i].nei != 0xff && (unsigned short)prevCount[sweeps[i].nei] == sweeps[i].ns) - { - sweeps[i].id = sweeps[i].nei; - } - else - { - if (regId == 255) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Region ID overflow."); - return false; - } - sweeps[i].id = regId++; - } - } - - // Remap local sweep ids to region ids. - for (int x = 0; x < w; ++x) - { - const int idx = x+y*w; - if (layer.regs[idx] != 0xff) - layer.regs[idx] = sweeps[layer.regs[idx]].id; - } - } - - // Allocate and init layer regions. - const int nregs = (int)regId; - rcScopedDelete regs = (rcMonotoneRegion*)rcAlloc(sizeof(rcMonotoneRegion)*nregs, RC_ALLOC_TEMP); - if (!regs) - { - ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regs' (%d).", nregs); - return false; - } - memset(regs, 0, sizeof(rcMonotoneRegion)*nregs); - for (int i = 0; i < nregs; ++i) - regs[i].regId = 0xff; - - // Find region neighbours. - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const int idx = x+y*w; - const unsigned char ri = layer.regs[idx]; - if (ri == 0xff) - continue; - - // Update area. - regs[ri].area++; - - // Update neighbours - const int ymi = x+(y-1)*w; - if (y > 0 && isConnected(layer, idx, ymi, walkableClimb)) - { - const unsigned char rai = layer.regs[ymi]; - if (rai != 0xff && rai != ri) - { - addUniqueLast(regs[ri].neis, regs[ri].nneis, rai); - addUniqueLast(regs[rai].neis, regs[rai].nneis, ri); - } - } - } - } - - for (int i = 0; i < nregs; ++i) - regs[i].regId = (unsigned char)i; - - for (int i = 0; i < nregs; ++i) - { - rcMonotoneRegion& reg = regs[i]; - - int merge = -1; - int mergea = 0; - for (int j = 0; j < (int)reg.nneis; ++j) - { - const unsigned char nei = reg.neis[j]; - rcMonotoneRegion& regn = regs[nei]; - if (reg.regId == regn.regId) - continue; - if (regn.area > mergea) - { - if (canMerge(reg.regId, regn.regId, regs, nregs)) - { - mergea = regn.area; - merge = (int)nei; - } - } - } - if (merge != -1) - { - const unsigned char oldId = reg.regId; - const unsigned char newId = regs[merge].regId; - for (int j = 0; j < nregs; ++j) - if (regs[j].regId == oldId) - regs[j].regId = newId; - } - } - - // Compact ids. - unsigned char remap[256]; - memset(remap, 0, 256); - // Find number of unique regions. - regId = 0; - for (int i = 0; i < nregs; ++i) - remap[regs[i].regId] = 1; - for (int i = 0; i < 256; ++i) - if (remap[i]) - remap[i] = regId++; - // Remap ids. - for (int i = 0; i < nregs; ++i) - regs[i].regId = remap[regs[i].regId]; - - layer.regCount = regId; - - for (int i = 0; i < w*h; ++i) - { - if (layer.regs[i] != 0xff) - layer.regs[i] = regs[layer.regs[i]].regId; - } - - return true; -} - - - -struct rcTempContour -{ - inline rcTempContour() : verts(0), poly(0) {} - inline ~rcTempContour() { rcFree(verts); rcFree(poly); } - unsigned char* verts; - int nverts; - int cverts; - unsigned short* poly; - int npoly; - int cpoly; -}; - -static bool appendVertex(rcTempContour& cont, const int x, const int y, const int z, const int r) -{ - // Try to merge with existing segments. - if (cont.nverts > 1) - { - unsigned char* pa = &cont.verts[(cont.nverts-2)*4]; - unsigned char* pb = &cont.verts[(cont.nverts-1)*4]; - if ((int)pb[3] == r) - { - if (pa[0] == pb[0] && (int)pb[0] == x) - { - // The verts are aligned aling x-axis, update z. - pb[1] = (unsigned char)y; - pb[2] = (unsigned char)z; - return true; - } - else if (pa[2] == pb[2] && (int)pb[2] == z) - { - // The verts are aligned aling z-axis, update x. - pb[0] = (unsigned char)x; - pb[1] = (unsigned char)y; - return true; - } - } - } - - // Add new point. - if (cont.nverts+1 > cont.cverts) - return false; - - unsigned char* v = &cont.verts[cont.nverts*4]; - v[0] = (unsigned char)x; - v[1] = (unsigned char)y; - v[2] = (unsigned char)z; - v[3] = (unsigned char)r; - cont.nverts++; - - return true; -} - - -static unsigned char getNeighbourReg(rcHeightfieldLayer& layer, - const int ax, const int ay, const int dir) -{ - const int ia = ax+ay*layer.width; - - const unsigned char con = layer.cons[ia] & 0xf; - const unsigned char portal = layer.cons[ia] >> 4; - const unsigned char mask = (unsigned char)(1< 0 && x == startX && y == startY && dir == startDir) - break; - - x = nx; - y = ny; - dir = ndir; - - iter++; - } - - // Remove last vertex if it is duplicate of the first one. - unsigned char* pa = &cont.verts[(cont.nverts-1)*4]; - unsigned char* pb = &cont.verts[0]; - if (pa[0] == pb[0] && pa[2] == pb[2]) - cont.nverts--; - - return true; -} - - -static float distancePtSeg(const int x, const int z, - const int px, const int pz, - const int qx, const int qz) -{ - float pqx = (float)(qx - px); - float pqz = (float)(qz - pz); - float dx = (float)(x - px); - float dz = (float)(z - pz); - float d = pqx*pqx + pqz*pqz; - float t = pqx*dx + pqz*dz; - if (d > 0) - t /= d; - if (t < 0) - t = 0; - else if (t > 1) - t = 1; - - dx = px + t*pqx - x; - dz = pz + t*pqz - z; - - return dx*dx + dz*dz; -} - -static void simplifyContour(rcTempContour& cont, const float maxError) -{ - cont.npoly = 0; - - for (int i = 0; i < cont.nverts; ++i) - { - int j = (i+1) % cont.nverts; - // Check for start of a wall segment. - unsigned char ra = cont.verts[j*4+3]; - unsigned char rb = cont.verts[i*4+3]; - if (ra != rb) - cont.poly[cont.npoly++] = i; - } - if (cont.npoly < 2) - { - // If there is no transitions at all, - // create some initial points for the simplification process. - // Find lower-left and upper-right vertices of the contour. - int llx = cont.verts[0]; - int llz = cont.verts[2]; - int lli = 0; - int urx = cont.verts[0]; - int urz = cont.verts[2]; - int uri = 0; - for (int i = 1; i < cont.nverts; ++i) - { - int x = cont.verts[i*4+0]; - int z = cont.verts[i*4+2]; - if (x < llx || (x == llx && z < llz)) - { - llx = x; - llz = z; - lli = i; - } - if (x > urx || (x == urx && z > urz)) - { - urx = x; - urz = z; - uri = i; - } - } - cont.npoly = 0; - cont.poly[cont.npoly++] = lli; - cont.poly[cont.npoly++] = uri; - } - - // Add points until all raw points are within - // error tolerance to the simplified shape. - for (int i = 0; i < cont.npoly; ) - { - int ii = (i+1) % cont.npoly; - - const int ai = (int)cont.poly[i]; - const int ax = (int)cont.verts[ai*4+0]; - const int az = (int)cont.verts[ai*4+2]; - - const int bi = (int)cont.poly[ii]; - const int bx = (int)cont.verts[bi*4+0]; - const int bz = (int)cont.verts[bi*4+2]; - - // Find maximum deviation from the segment. - float maxd = 0; - int maxi = -1; - int ci, cinc, endi; - - // Traverse the segment in lexilogical order so that the - // max deviation is calculated similarly when traversing - // opposite segments. - if (bx > ax || (bx == ax && bz > az)) - { - cinc = 1; - ci = (ai+cinc) % cont.nverts; - endi = bi; - } - else - { - cinc = cont.nverts-1; - ci = (bi+cinc) % cont.nverts; - endi = ai; - } - - // Tessellate only outer edges or edges between areas. - while (ci != endi) - { - float d = distancePtSeg(cont.verts[ci*4+0], cont.verts[ci*4+2], ax, az, bx, bz); - if (d > maxd) - { - maxd = d; - maxi = ci; - } - ci = (ci+cinc) % cont.nverts; - } - - - // If the max deviation is larger than accepted error, - // add new point, else continue to next segment. - if (maxi != -1 && maxd > (maxError*maxError)) - { - cont.npoly++; - for (int j = cont.npoly-1; j > i; --j) - cont.poly[j] = cont.poly[j-1]; - cont.poly[i+1] = (unsigned short)maxi; - } - else - { - ++i; - } - } - - // Remap vertices - int start = 0; - for (int i = 1; i < cont.npoly; ++i) - if (cont.poly[i] < cont.poly[start]) - start = i; - - cont.nverts = 0; - for (int i = 0; i < cont.npoly; ++i) - { - const int j = (start+i) % cont.npoly; - unsigned char* src = &cont.verts[cont.poly[j]*4]; - unsigned char* dst = &cont.verts[cont.nverts*4]; - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - dst[3] = src[3]; - cont.nverts++; - } -} - -static int getCornerHeight(rcHeightfieldLayer& layer, - const int x, const int y, const int z, - const int walkableClimb, - bool& shouldRemove) -{ - const int w = layer.width; - const int h = layer.height; - - int n = 0; - - unsigned char portal = 0xf; - int height = 0; - - for (int dz = -1; dz <= 0; ++dz) - { - for (int dx = -1; dx <= 0; ++dx) - { - const int px = x+dx; - const int pz = z+dz; - if (px >= 0 && pz >= 0 && px < w && pz < h) - { - const int idx = px + pz*w; - const int h = (int)layer.heights[idx]; - if (rcAbs(h-y) <= walkableClimb) - { - height = rcMax(height, h); - portal &= (layer.cons[idx] >> 4); - n++; - } - } - } - } - - int portalCount = 0; - for (int dir = 0; dir < 4; ++dir) - if (portal & (1< 1 && portalCount == 1) - { - shouldRemove = true; - } - - return height; -} - - -// TODO: move this somewhere else, once the layer meshing is done. -bool rcBuildLayerContours(rcContext* ctx, - rcHeightfieldLayer& layer, - const int walkableClimb, const float maxError, - rcLayerContourSet& lcset) -{ - rcAssert(ctx); - - const int w = layer.width; - const int h = layer.height; - - rcAssert(lcset.conts == 0); - - rcVcopy(lcset.bmin, layer.bmin); - rcVcopy(lcset.bmax, layer.bmax); - lcset.cs = layer.cs; - lcset.ch = layer.ch; - lcset.nconts = layer.regCount; - lcset.conts = (rcLayerContour*)rcAlloc(sizeof(rcLayerContour)*lcset.nconts, RC_ALLOC_TEMP); - if (!lcset.conts) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerContours: Out of memory 'conts' (%d).", lcset.nconts); - return false; - } - memset(lcset.conts, 0, sizeof(rcLayerContour)*lcset.nconts); - - // Allocate temp buffer for contour tracing. - const int maxTempVerts = (w*h*2)*2; // Twice around the layer. - rcTempContour temp; - temp.nverts = 0; - temp.cverts = maxTempVerts; - temp.npoly = 0; - temp.cpoly = maxTempVerts; - - temp.verts = (unsigned char*)rcAlloc(sizeof(unsigned char)*temp.cverts, RC_ALLOC_TEMP); - if (!temp.verts) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerContours: Out of memory 'temp.verts' (%d).", temp.cverts); - return false; - } - - temp.poly = (unsigned short*)rcAlloc(sizeof(unsigned short)*temp.cpoly, RC_ALLOC_TEMP); - if (!temp.poly) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerContours: Out of memory 'temp.poly' (%d).", temp.cpoly); - return false; - } - - // Find contours. - for (int y = 0; y < h; ++y) - { - for (int x = 0; x < w; ++x) - { - const int idx = x+y*w; - const unsigned char ri = layer.regs[idx]; - if (ri == 0xff) - continue; - - rcLayerContour& cont = lcset.conts[ri]; - - if (cont.nverts > 0) - continue; - - cont.reg = ri; - cont.area = layer.areas[idx]; - - if (!walkContour(layer, x, y, temp)) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerContours: Failed to walk contour (nverts=%d cverts=%d).", temp.nverts, temp.cverts); - return false; - } - - simplifyContour(temp, maxError); - - // Store contour. - cont.nverts = temp.nverts; - if (cont.nverts > 0) - { - cont.verts = (unsigned char*)rcAlloc(sizeof(unsigned char)*4*temp.nverts, RC_ALLOC_PERM); - if (!cont.verts) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerContours: Out of memory 'cont.verts' (%d).", temp.nverts); - return false; - } - - for (int i = 0, j = temp.nverts-1; i < temp.nverts; j=i++) - { - unsigned char* dst = &cont.verts[j*4]; - unsigned char* v = &temp.verts[j*4]; - unsigned char* vn = &temp.verts[i*4]; - unsigned char nei = vn[3]; // The neighbour reg is stored at segment vertex of a segment. - bool shouldRemove = false; - unsigned char h = getCornerHeight(layer, (int)v[0], (int)v[1], (int)v[2], walkableClimb, shouldRemove); - - dst[0] = v[0]; - dst[1] = h; - dst[2] = v[2]; - - // Store portal direction and remove status to the fourth component. - dst[3] = 0x0f; - const int dir = 0xfe - (int)nei; - if (dir >= 0 && dir <= 3) - dst[3] = (unsigned char)dir; - if (shouldRemove) - dst[3] |= 0x80; - } - } - } - } - - return true; -} - - - - - - - -static const int VERTEX_BUCKET_COUNT2 = (1<<8); - -inline int computeVertexHash2(int x, int y, int z) -{ - const unsigned int h1 = 0x8da6b343; // Large multiplicative constants; - const unsigned int h2 = 0xd8163841; // here arbitrarily chosen primes - const unsigned int h3 = 0xcb1ab31f; - unsigned int n = h1 * x + h2 * y + h3 * z; - return (int)(n & (VERTEX_BUCKET_COUNT2-1)); -} - -static unsigned short addVertex(unsigned short x, unsigned short y, unsigned short z, - unsigned short* verts, unsigned short* firstVert, unsigned short* nextVert, int& nv) -{ - int bucket = computeVertexHash2(x, 0, z); - unsigned short i = firstVert[bucket]; - - while (i != RC_MESH_NULL_IDX) - { - const unsigned short* v = &verts[i*3]; - if (v[0] == x && v[2] == z && (rcAbs(v[1] - y) <= 2)) - return i; - i = nextVert[i]; // next - } - - // Could not find, create new. - i = nv; nv++; - unsigned short* v = &verts[i*3]; - v[0] = x; - v[1] = y; - v[2] = z; - nextVert[i] = firstVert[bucket]; - firstVert[bucket] = i; - - return (unsigned short)i; -} - - -struct rcEdge -{ - unsigned short vert[2]; - unsigned short polyEdge[2]; - unsigned short poly[2]; -}; - -static bool buildMeshAdjacency(unsigned short* polys, const int npolys, - const unsigned short* verts, const int nverts, - const int vertsPerPoly, const rcLayerContourSet& lcset) -{ - // Based on code by Eric Lengyel from: - // http://www.terathon.com/code/edges.php - - int maxEdgeCount = npolys*vertsPerPoly; - unsigned short* firstEdge = (unsigned short*)rcAlloc(sizeof(unsigned short)*(nverts + maxEdgeCount), RC_ALLOC_TEMP); - if (!firstEdge) - return false; - unsigned short* nextEdge = firstEdge + nverts; - int edgeCount = 0; - - rcEdge* edges = (rcEdge*)rcAlloc(sizeof(rcEdge)*maxEdgeCount, RC_ALLOC_TEMP); - if (!edges) - { - rcFree(firstEdge); - return false; - } - - for (int i = 0; i < nverts; i++) - firstEdge[i] = RC_MESH_NULL_IDX; - - for (int i = 0; i < npolys; ++i) - { - unsigned short* t = &polys[i*vertsPerPoly*2]; - for (int j = 0; j < vertsPerPoly; ++j) - { - if (t[j] == RC_MESH_NULL_IDX) break; - unsigned short v0 = t[j]; - unsigned short v1 = (j+1 >= vertsPerPoly || t[j+1] == RC_MESH_NULL_IDX) ? t[0] : t[j+1]; - if (v0 < v1) - { - rcEdge& edge = edges[edgeCount]; - edge.vert[0] = v0; - edge.vert[1] = v1; - edge.poly[0] = (unsigned short)i; - edge.polyEdge[0] = (unsigned short)j; - edge.poly[1] = (unsigned short)i; - edge.polyEdge[1] = 0xff; - // Insert edge - nextEdge[edgeCount] = firstEdge[v0]; - firstEdge[v0] = (unsigned short)edgeCount; - edgeCount++; - } - } - } - - for (int i = 0; i < npolys; ++i) - { - unsigned short* t = &polys[i*vertsPerPoly*2]; - for (int j = 0; j < vertsPerPoly; ++j) - { - if (t[j] == RC_MESH_NULL_IDX) break; - unsigned short v0 = t[j]; - unsigned short v1 = (j+1 >= vertsPerPoly || t[j+1] == RC_MESH_NULL_IDX) ? t[0] : t[j+1]; - if (v0 > v1) - { - bool found = false; - for (unsigned short e = firstEdge[v1]; e != RC_MESH_NULL_IDX; e = nextEdge[e]) - { - rcEdge& edge = edges[e]; - if (edge.vert[1] == v0 && edge.poly[0] == edge.poly[1]) - { - edge.poly[1] = (unsigned short)i; - edge.polyEdge[1] = (unsigned short)j; - found = true; - break; - } - } - if (!found) - { - // Matching edge not found, it is an open edge, add it. - rcEdge& edge = edges[edgeCount]; - edge.vert[0] = v1; - edge.vert[1] = v0; - edge.poly[0] = (unsigned short)i; - edge.polyEdge[0] = (unsigned short)j; - edge.poly[1] = (unsigned short)i; - edge.polyEdge[1] = 0xff; - // Insert edge - nextEdge[edgeCount] = firstEdge[v1]; - firstEdge[v1] = (unsigned short)edgeCount; - edgeCount++; - } - } - } - } - - // Mark portal edges. - for (int i = 0; i < lcset.nconts; ++i) - { - rcLayerContour& cont = lcset.conts[i]; - if (cont.nverts < 3) - continue; - - for (int j = 0, k = cont.nverts-1; j < cont.nverts; k=j++) - { - const unsigned char* va = &cont.verts[k*4]; - const unsigned char* vb = &cont.verts[j*4]; - const unsigned char dir = va[3] & 0xf; - if (dir == 0xf) - continue; - - if (dir == 0 || dir == 2) - { - // Find matching vertical edge - const unsigned short x = (unsigned short)va[0]; - unsigned short zmin = (unsigned short)va[2]; - unsigned short zmax = (unsigned short)vb[2]; - if (zmin > zmax) - rcSwap(zmin, zmax); - for (int i = 0; i < edgeCount; ++i) - { - rcEdge& e = edges[i]; - // Skip connected edges. - if (e.poly[0] != e.poly[1]) - continue; - const unsigned short* eva = &verts[e.vert[0]*3]; - const unsigned short* evb = &verts[e.vert[1]*3]; - if (eva[0] == x && evb[0] == x) - { - unsigned short ezmin = eva[2]; - unsigned short ezmax = evb[2]; - if (ezmin > ezmax) - rcSwap(ezmin, ezmax); - if (overlapRange(zmin,zmax, ezmin, ezmax)) - { - // Reuse the other polyedge to store dir. - e.polyEdge[1] = dir; - } - } - } - } - else - { - // Find matching vertical edge - const unsigned short z = (unsigned short)va[2]; - unsigned short xmin = (unsigned short)va[0]; - unsigned short xmax = (unsigned short)vb[0]; - if (xmin > xmax) - rcSwap(xmin, xmax); - for (int i = 0; i < edgeCount; ++i) - { - rcEdge& e = edges[i]; - // Skip connected edges. - if (e.poly[0] != e.poly[1]) - continue; - const unsigned short* eva = &verts[e.vert[0]*3]; - const unsigned short* evb = &verts[e.vert[1]*3]; - if (eva[2] == z && evb[2] == z) - { - unsigned short exmin = eva[0]; - unsigned short exmax = evb[0]; - if (exmin > exmax) - rcSwap(exmin, exmax); - if (overlapRange(xmin,xmax, exmin, exmax)) - { - // Reuse the other polyedge to store dir. - e.polyEdge[1] = dir; - } - } - } - } - } - } - - - // Store adjacency - for (int i = 0; i < edgeCount; ++i) - { - const rcEdge& e = edges[i]; - if (e.poly[0] != e.poly[1]) - { - unsigned short* p0 = &polys[e.poly[0]*vertsPerPoly*2]; - unsigned short* p1 = &polys[e.poly[1]*vertsPerPoly*2]; - p0[vertsPerPoly + e.polyEdge[0]] = e.poly[1]; - p1[vertsPerPoly + e.polyEdge[1]] = e.poly[0]; - } - else if (e.polyEdge[1] != 0xff) - { - unsigned short* p0 = &polys[e.poly[0]*vertsPerPoly*2]; - p0[vertsPerPoly + e.polyEdge[0]] = 0x8000 | (unsigned short)e.poly[1]; - } - - } - - rcFree(firstEdge); - rcFree(edges); - - return true; -} - - -inline int prev(int i, int n) { return i-1 >= 0 ? i-1 : n-1; } -inline int next(int i, int n) { return i+1 < n ? i+1 : 0; } - -inline int area2(const unsigned char* a, const unsigned char* b, const unsigned char* c) -{ - return ((int)b[0] - (int)a[0]) * ((int)c[2] - (int)a[2]) - ((int)c[0] - (int)a[0]) * ((int)b[2] - (int)a[2]); -} - -// Exclusive or: true iff exactly one argument is true. -// The arguments are negated to ensure that they are 0/1 -// values. Then the bitwise Xor operator may apply. -// (This idea is due to Michael Baldwin.) -inline bool xorb(bool x, bool y) -{ - return !x ^ !y; -} - -// Returns true iff c is strictly to the left of the directed -// line through a to b. -inline bool left(const unsigned char* a, const unsigned char* b, const unsigned char* c) -{ - return area2(a, b, c) < 0; -} - -inline bool leftOn(const unsigned char* a, const unsigned char* b, const unsigned char* c) -{ - return area2(a, b, c) <= 0; -} - -inline bool collinear(const unsigned char* a, const unsigned char* b, const unsigned char* c) -{ - return area2(a, b, c) == 0; -} - -// Returns true iff ab properly intersects cd: they share -// a point interior to both segments. The properness of the -// intersection is ensured by using strict leftness. -static bool intersectProp(const unsigned char* a, const unsigned char* b, - const unsigned char* c, const unsigned char* d) -{ - // Eliminate improper cases. - if (collinear(a,b,c) || collinear(a,b,d) || - collinear(c,d,a) || collinear(c,d,b)) - return false; - - return xorb(left(a,b,c), left(a,b,d)) && xorb(left(c,d,a), left(c,d,b)); -} - -// Returns T iff (a,b,c) are collinear and point c lies -// on the closed segement ab. -static bool between(const unsigned char* a, const unsigned char* b, const unsigned char* c) -{ - if (!collinear(a, b, c)) - return false; - // If ab not vertical, check betweenness on x; else on y. - if (a[0] != b[0]) - return ((a[0] <= c[0]) && (c[0] <= b[0])) || ((a[0] >= c[0]) && (c[0] >= b[0])); - else - return ((a[2] <= c[2]) && (c[2] <= b[2])) || ((a[2] >= c[2]) && (c[2] >= b[2])); -} - -// Returns true iff segments ab and cd intersect, properly or improperly. -static bool intersect(const unsigned char* a, const unsigned char* b, - const unsigned char* c, const unsigned char* d) -{ - if (intersectProp(a, b, c, d)) - return true; - else if (between(a, b, c) || between(a, b, d) || - between(c, d, a) || between(c, d, b)) - return true; - else - return false; -} - -static bool vequal(const unsigned char* a, const unsigned char* b) -{ - return a[0] == b[0] && a[2] == b[2]; -} - -// Returns T iff (v_i, v_j) is a proper internal *or* external -// diagonal of P, *ignoring edges incident to v_i and v_j*. -static bool diagonalie(int i, int j, int n, const unsigned char* verts, const unsigned short* indices) -{ - const unsigned char* d0 = &verts[(indices[i] & 0x7fff) * 4]; - const unsigned char* d1 = &verts[(indices[j] & 0x7fff) * 4]; - - // For each edge (k,k+1) of P - for (int k = 0; k < n; k++) - { - int k1 = next(k, n); - // Skip edges incident to i or j - if (!((k == i) || (k1 == i) || (k == j) || (k1 == j))) - { - const unsigned char* p0 = &verts[(indices[k] & 0x7fff) * 4]; - const unsigned char* p1 = &verts[(indices[k1] & 0x7fff) * 4]; - - if (vequal(d0, p0) || vequal(d1, p0) || vequal(d0, p1) || vequal(d1, p1)) - continue; - - if (intersect(d0, d1, p0, p1)) - return false; - } - } - return true; -} - -// Returns true iff the diagonal (i,j) is strictly internal to the -// polygon P in the neighborhood of the i endpoint. -static bool inCone(int i, int j, int n, const unsigned char* verts, const unsigned short* indices) -{ - const unsigned char* pi = &verts[(indices[i] & 0x7fff) * 4]; - const unsigned char* pj = &verts[(indices[j] & 0x7fff) * 4]; - const unsigned char* pi1 = &verts[(indices[next(i, n)] & 0x7fff) * 4]; - const unsigned char* pin1 = &verts[(indices[prev(i, n)] & 0x7fff) * 4]; - - // If P[i] is a convex vertex [ i+1 left or on (i-1,i) ]. - if (leftOn(pin1, pi, pi1)) - return left(pi, pj, pin1) && left(pj, pi, pi1); - // Assume (i-1,i,i+1) not collinear. - // else P[i] is reflex. - return !(leftOn(pi, pj, pi1) && leftOn(pj, pi, pin1)); -} - -// Returns T iff (v_i, v_j) is a proper internal -// diagonal of P. -static bool diagonal(int i, int j, int n, const unsigned char* verts, const unsigned short* indices) -{ - return inCone(i, j, n, verts, indices) && diagonalie(i, j, n, verts, indices); -} - -static int triangulate(int n, const unsigned char* verts, unsigned short* indices, unsigned short* tris) -{ - int ntris = 0; - unsigned short* dst = tris; - - // The last bit of the index is used to indicate if the vertex can be removed. - for (int i = 0; i < n; i++) - { - int i1 = next(i, n); - int i2 = next(i1, n); - if (diagonal(i, i2, n, verts, indices)) - indices[i1] |= 0x8000; - } - - while (n > 3) - { - int minLen = -1; - int mini = -1; - for (int i = 0; i < n; i++) - { - int i1 = next(i, n); - if (indices[i1] & 0x8000) - { - const unsigned char* p0 = &verts[(indices[i] & 0x7fff) * 4]; - const unsigned char* p2 = &verts[(indices[next(i1, n)] & 0x7fff) * 4]; - - const int dx = (int)p2[0] - (int)p0[0]; - const int dz = (int)p2[2] - (int)p0[2]; - const int len = dx*dx + dz*dz; - if (minLen < 0 || len < minLen) - { - minLen = len; - mini = i; - } - } - } - - if (mini == -1) - { - // Should not happen. - /* printf("mini == -1 ntris=%d n=%d\n", ntris, n); - for (int i = 0; i < n; i++) - { - printf("%d ", indices[i] & 0x0fffffff); - } - printf("\n");*/ - return -ntris; - } - - int i = mini; - int i1 = next(i, n); - int i2 = next(i1, n); - - *dst++ = indices[i] & 0x7fff; - *dst++ = indices[i1] & 0x7fff; - *dst++ = indices[i2] & 0x7fff; - ntris++; - - // Removes P[i1] by copying P[i+1]...P[n-1] left one index. - n--; - for (int k = i1; k < n; k++) - indices[k] = indices[k+1]; - - if (i1 >= n) i1 = 0; - i = prev(i1,n); - // Update diagonal flags. - if (diagonal(prev(i, n), i1, n, verts, indices)) - indices[i] |= 0x8000; - else - indices[i] &= 0x7fff; - - if (diagonal(i, next(i1, n), n, verts, indices)) - indices[i1] |= 0x8000; - else - indices[i1] &= 0x7fff; - } - - // Append the remaining triangle. - *dst++ = indices[0] & 0x7fff; - *dst++ = indices[1] & 0x7fff; - *dst++ = indices[2] & 0x7fff; - ntris++; - - return ntris; -} - -static const int MAX_VERTS_PER_POLY = 6; -static const int MAX_REM_EDGES = 48; - - -static int countPolyVerts(const unsigned short* p, const int nvp) -{ - for (int i = 0; i < nvp; ++i) - if (p[i] == RC_MESH_NULL_IDX) - return i; - return nvp; -} - -inline bool uleft(const unsigned short* a, const unsigned short* b, const unsigned short* c) -{ - return ((int)b[0] - (int)a[0]) * ((int)c[2] - (int)a[2]) - - ((int)c[0] - (int)a[0]) * ((int)b[2] - (int)a[2]) < 0; -} - -static int getPolyMergeValue(unsigned short* pa, unsigned short* pb, - const unsigned short* verts, int& ea, int& eb, - const int nvp) -{ - const int na = countPolyVerts(pa, nvp); - const int nb = countPolyVerts(pb, nvp); - - // If the merged polygon would be too big, do not merge. - if (na+nb-2 > nvp) - return -1; - - // Check if the polygons share an edge. - ea = -1; - eb = -1; - - for (int i = 0; i < na; ++i) - { - unsigned short va0 = pa[i]; - unsigned short va1 = pa[(i+1) % na]; - if (va0 > va1) - rcSwap(va0, va1); - for (int j = 0; j < nb; ++j) - { - unsigned short vb0 = pb[j]; - unsigned short vb1 = pb[(j+1) % nb]; - if (vb0 > vb1) - rcSwap(vb0, vb1); - if (va0 == vb0 && va1 == vb1) - { - ea = i; - eb = j; - break; - } - } - } - - // No common edge, cannot merge. - if (ea == -1 || eb == -1) - return -1; - - // Check to see if the merged polygon would be convex. - unsigned short va, vb, vc; - - va = pa[(ea+na-1) % na]; - vb = pa[ea]; - vc = pb[(eb+2) % nb]; - if (!uleft(&verts[va*3], &verts[vb*3], &verts[vc*3])) - return -1; - - va = pb[(eb+nb-1) % nb]; - vb = pb[eb]; - vc = pa[(ea+2) % na]; - if (!uleft(&verts[va*3], &verts[vb*3], &verts[vc*3])) - return -1; - - va = pa[ea]; - vb = pa[(ea+1)%na]; - - int dx = (int)verts[va*3+0] - (int)verts[vb*3+0]; - int dy = (int)verts[va*3+2] - (int)verts[vb*3+2]; - - return dx*dx + dy*dy; -} - -static void mergePolys(unsigned short* pa, unsigned short* pb, int ea, int eb, const int nvp) -{ - unsigned short tmp[MAX_VERTS_PER_POLY*2]; - - const int na = countPolyVerts(pa, nvp); - const int nb = countPolyVerts(pb, nvp); - - // Merge polygons. - memset(tmp, 0xff, sizeof(unsigned short)*nvp); - int n = 0; - // Add pa - for (int i = 0; i < na-1; ++i) - tmp[n++] = pa[(ea+1+i) % na]; - // Add pb - for (int i = 0; i < nb-1; ++i) - tmp[n++] = pb[(eb+1+i) % nb]; - - memcpy(pa, tmp, sizeof(unsigned short)*nvp); -} - - -static void pushFront(unsigned short v, unsigned short* arr, int& an) -{ - an++; - for (int i = an-1; i > 0; --i) - arr[i] = arr[i-1]; - arr[0] = v; -} - -static void pushBack(unsigned short v, unsigned short* arr, int& an) -{ - arr[an] = v; - an++; -} - -static bool canRemoveVertex(rcContext* ctx, rcLayerPolyMesh& mesh, const unsigned short rem) -{ - const int nvp = mesh.nvp; - - // Count number of polygons to remove. - int numRemovedVerts = 0; - int numTouchedVerts = 0; - int numRemainingEdges = 0; - for (int i = 0; i < mesh.npolys; ++i) - { - unsigned short* p = &mesh.polys[i*nvp*2]; - const int nv = countPolyVerts(p, nvp); - int numRemoved = 0; - int numVerts = 0; - for (int j = 0; j < nv; ++j) - { - if (p[j] == rem) - { - numTouchedVerts++; - numRemoved++; - } - numVerts++; - } - if (numRemoved) - { - numRemovedVerts += numRemoved; - numRemainingEdges += numVerts-(numRemoved+1); - } - } - - // There would be too few edges remaining to create a polygon. - // This can happen for example when a tip of a triangle is marked - // as deletion, but there are no other polys that share the vertex. - // In this case, the vertex should not be removed. - if (numRemainingEdges <= 2) - return false; - - // Check that there is enough memory for the test. - const int maxEdges = numTouchedVerts*2; - if (maxEdges > MAX_REM_EDGES) - return false; - - // Find edges which share the removed vertex. - unsigned short edges[MAX_REM_EDGES]; - int nedges = 0; - - for (int i = 0; i < mesh.npolys; ++i) - { - unsigned short* p = &mesh.polys[i*nvp*2]; - const int nv = countPolyVerts(p, nvp); - - // Collect edges which touches the removed vertex. - for (int j = 0, k = nv-1; j < nv; k = j++) - { - if (p[j] == rem || p[k] == rem) - { - // Arrange edge so that a=rem. - int a = p[j], b = p[k]; - if (b == rem) - rcSwap(a,b); - - // Check if the edge exists - bool exists = false; - for (int k = 0; k < nedges; ++k) - { - unsigned short* e = &edges[k*3]; - if (e[1] == b) - { - // Exists, increment vertex share count. - e[2]++; - exists = true; - } - } - // Add new edge. - if (!exists) - { - unsigned short* e = &edges[nedges*3]; - e[0] = a; - e[1] = b; - e[2] = 1; - nedges++; - } - } - } - } - - // There should be no more than 2 open edges. - // This catches the case that two non-adjacent polygons - // share the removed vertex. In that case, do not remove the vertex. - int numOpenEdges = 0; - for (int i = 0; i < nedges; ++i) - { - if (edges[i*3+2] < 2) - numOpenEdges++; - } - if (numOpenEdges > 2) - return false; - - return true; -} - -static bool removeVertex(rcContext* ctx, rcLayerPolyMesh& mesh, const unsigned short rem, const int maxTris) -{ - const int nvp = mesh.nvp; - - // Count number of polygons to remove. - int numRemovedVerts = 0; - for (int i = 0; i < mesh.npolys; ++i) - { - unsigned short* p = &mesh.polys[i*nvp*2]; - const int nv = countPolyVerts(p, nvp); - for (int j = 0; j < nv; ++j) - { - if (p[j] == rem) - numRemovedVerts++; - } - } - - int nedges = 0; - unsigned short edges[MAX_REM_EDGES*3]; - int nhole = 0; - unsigned short hole[MAX_REM_EDGES]; - int nharea = 0; - unsigned short harea[MAX_REM_EDGES]; - - for (int i = 0; i < mesh.npolys; ++i) - { - unsigned short* p = &mesh.polys[i*nvp*2]; - const int nv = countPolyVerts(p, nvp); - bool hasRem = false; - for (int j = 0; j < nv; ++j) - if (p[j] == rem) hasRem = true; - if (hasRem) - { - // Collect edges which does not touch the removed vertex. - for (int j = 0, k = nv-1; j < nv; k = j++) - { - if (p[j] != rem && p[k] != rem) - { - if (nedges >= MAX_REM_EDGES) - return false; - unsigned short* e = &edges[nedges*3]; - e[0] = p[k]; - e[1] = p[j]; - e[2] = mesh.areas[i]; - nedges++; - } - } - // Remove the polygon. - unsigned short* p2 = &mesh.polys[(mesh.npolys-1)*nvp*2]; - memcpy(p,p2,sizeof(unsigned short)*nvp); - memset(p+nvp,0xff,sizeof(unsigned short)*nvp); - mesh.areas[i] = mesh.areas[mesh.npolys-1]; - mesh.npolys--; - --i; - } - } - - // Remove vertex. - for (int i = (int)rem; i < mesh.nverts; ++i) - { - mesh.verts[i*3+0] = mesh.verts[(i+1)*3+0]; - mesh.verts[i*3+1] = mesh.verts[(i+1)*3+1]; - mesh.verts[i*3+2] = mesh.verts[(i+1)*3+2]; - } - mesh.nverts--; - - // Adjust indices to match the removed vertex layout. - for (int i = 0; i < mesh.npolys; ++i) - { - unsigned short* p = &mesh.polys[i*nvp*2]; - const int nv = countPolyVerts(p, nvp); - for (int j = 0; j < nv; ++j) - if (p[j] > rem) p[j]--; - } - for (int i = 0; i < nedges; ++i) - { - if (edges[i*3+0] > rem) edges[i*3+0]--; - if (edges[i*3+1] > rem) edges[i*3+1]--; - } - - if (nedges == 0) - return true; - - // Start with one vertex, keep appending connected - // segments to the start and end of the hole. - pushBack(edges[0], hole, nhole); - pushBack(edges[2], harea, nharea); - - while (nedges) - { - bool match = false; - - for (int i = 0; i < nedges; ++i) - { - const unsigned short ea = edges[i*3+0]; - const unsigned short eb = edges[i*3+1]; - const unsigned short a = edges[i*3+2]; - bool add = false; - if (hole[0] == eb) - { - // The segment matches the beginning of the hole boundary. - if (nhole >= MAX_REM_EDGES) - return false; - pushFront(ea, hole, nhole); - pushFront(a, harea, nharea); - add = true; - } - else if (hole[nhole-1] == ea) - { - // The segment matches the end of the hole boundary. - if (nhole >= MAX_REM_EDGES) - return false; - pushBack(eb, hole, nhole); - pushBack(a, harea, nharea); - add = true; - } - if (add) - { - // The edge segment was added, remove it. - edges[i*3+0] = edges[(nedges-1)*3+0]; - edges[i*3+1] = edges[(nedges-1)*3+1]; - edges[i*3+2] = edges[(nedges-1)*3+2]; - --nedges; - match = true; - --i; - } - } - - if (!match) - break; - } - - - unsigned short tris[MAX_REM_EDGES*3]; - unsigned char tverts[MAX_REM_EDGES*3]; - unsigned short tpoly[MAX_REM_EDGES*3]; - - // Generate temp vertex array for triangulation. - for (int i = 0; i < nhole; ++i) - { - const unsigned short pi = hole[i]; - tverts[i*4+0] = (unsigned char)mesh.verts[pi*3+0]; - tverts[i*4+1] = (unsigned char)mesh.verts[pi*3+1]; - tverts[i*4+2] = (unsigned char)mesh.verts[pi*3+2]; - tverts[i*4+3] = 0; - tpoly[i] = (unsigned short)i; - } - - // Triangulate the hole. - int ntris = triangulate(nhole, tverts, tpoly, tris); - if (ntris < 0) - { - ntris = -ntris; - ctx->log(RC_LOG_WARNING, "removeVertex: triangulate() returned bad results."); - } - - if (ntris > MAX_REM_EDGES) - return false; - - unsigned short polys[MAX_REM_EDGES*MAX_VERTS_PER_POLY]; - unsigned char pareas[MAX_REM_EDGES]; - - // Build initial polygons. - int npolys = 0; - memset(polys, 0xff, ntris*nvp*sizeof(unsigned short)); - for (int j = 0; j < ntris; ++j) - { - unsigned short* t = &tris[j*3]; - if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2]) - { - polys[npolys*nvp+0] = hole[t[0]]; - polys[npolys*nvp+1] = hole[t[1]]; - polys[npolys*nvp+2] = hole[t[2]]; - pareas[npolys] = (unsigned char)harea[t[0]]; - npolys++; - } - } - if (!npolys) - return true; - - // Merge polygons. - if (nvp > 3) - { - for (;;) - { - // Find best polygons to merge. - int bestMergeVal = 0; - int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0; - - for (int j = 0; j < npolys-1; ++j) - { - unsigned short* pj = &polys[j*nvp]; - for (int k = j+1; k < npolys; ++k) - { - unsigned short* pk = &polys[k*nvp]; - int ea, eb; - int v = getPolyMergeValue(pj, pk, mesh.verts, ea, eb, nvp); - if (v > bestMergeVal) - { - bestMergeVal = v; - bestPa = j; - bestPb = k; - bestEa = ea; - bestEb = eb; - } - } - } - - if (bestMergeVal > 0) - { - // Found best, merge. - unsigned short* pa = &polys[bestPa*nvp]; - unsigned short* pb = &polys[bestPb*nvp]; - mergePolys(pa, pb, bestEa, bestEb, nvp); - memcpy(pb, &polys[(npolys-1)*nvp], sizeof(unsigned short)*nvp); - pareas[bestPb] = pareas[npolys-1]; - npolys--; - } - else - { - // Could not merge any polygons, stop. - break; - } - } - } - - // Store polygons. - for (int i = 0; i < npolys; ++i) - { - if (mesh.npolys >= maxTris) break; - unsigned short* p = &mesh.polys[mesh.npolys*nvp*2]; - memset(p,0xff,sizeof(unsigned short)*nvp*2); - for (int j = 0; j < nvp; ++j) - p[j] = polys[i*nvp+j]; - mesh.areas[mesh.npolys] = pareas[i]; - mesh.npolys++; - if (mesh.npolys > maxTris) - { - ctx->log(RC_LOG_ERROR, "removeVertex: Too many polygons %d (max:%d).", mesh.npolys, maxTris); - return false; - } - } - - return true; -} - - -bool rcBuildLayerPolyMesh(rcContext* ctx, - rcLayerContourSet& lcset, - const int maxVertsPerPoly, - rcLayerPolyMesh& mesh) -{ - rcAssert(ctx); - - const int nvp = rcMin(maxVertsPerPoly, MAX_VERTS_PER_POLY); - -// ctx->startTimer(RC_TIMER_BUILD_POLYMESH); - - rcVcopy(mesh.bmin, lcset.bmin); - rcVcopy(mesh.bmax, lcset.bmax); - mesh.cs = lcset.cs; - mesh.ch = lcset.ch; - - int maxVertices = 0; - int maxTris = 0; - int maxVertsPerCont = 0; - for (int i = 0; i < lcset.nconts; ++i) - { - // Skip null contours. - if (lcset.conts[i].nverts < 3) continue; - maxVertices += lcset.conts[i].nverts; - maxTris += lcset.conts[i].nverts - 2; - maxVertsPerCont = rcMax(maxVertsPerCont, lcset.conts[i].nverts); - } - - if (maxVertices >= 0xfffe) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: Too many vertices %d.", maxVertices); - return false; - } - - rcScopedDelete vflags = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxVertices, RC_ALLOC_TEMP); - if (!vflags) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: Out of memory 'vflags' (%d).", maxVertices); - return false; - } - memset(vflags, 0, maxVertices); - - mesh.verts = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertices*3, RC_ALLOC_PERM); - if (!mesh.verts) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: Out of memory 'mesh.verts' (%d).", maxVertices); - return false; - } - mesh.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxTris*nvp*2*2, RC_ALLOC_PERM); - if (!mesh.polys) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: Out of memory 'mesh.polys' (%d).", maxTris*nvp*2); - return false; - } - mesh.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxTris, RC_ALLOC_PERM); - if (!mesh.areas) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: Out of memory 'mesh.areas' (%d).", maxTris); - return false; - } - mesh.flags = (unsigned short*)rcAlloc(sizeof(unsigned short)*mesh.npolys, RC_ALLOC_PERM); - if (!mesh.flags) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: Out of memory 'mesh.flags' (%d).", mesh.npolys); - return false; - } - // Just allocate and clean the mesh flags array. The user is resposible for filling it. - memset(mesh.flags, 0, sizeof(unsigned short) * mesh.npolys); - - - mesh.nverts = 0; - mesh.npolys = 0; - mesh.nvp = nvp; - mesh.maxpolys = maxTris; - - memset(mesh.verts, 0, sizeof(unsigned short)*maxVertices*3); - memset(mesh.polys, 0xff, sizeof(unsigned short)*maxTris*nvp*2); - memset(mesh.areas, 0, sizeof(unsigned char)*maxTris); - - unsigned short firstVert[VERTEX_BUCKET_COUNT2]; - for (int i = 0; i < VERTEX_BUCKET_COUNT2; ++i) - firstVert[i] = RC_MESH_NULL_IDX; - - rcScopedDelete nextVert = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertices, RC_ALLOC_TEMP); - if (!nextVert) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: Out of memory 'nextVert' (%d).", maxVertices); - return false; - } - memset(nextVert, 0, sizeof(unsigned short)*maxVertices); - - rcScopedDelete indices = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertsPerCont, RC_ALLOC_TEMP); - if (!indices) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: Out of memory 'indices' (%d).", maxVertsPerCont); - return false; - } - rcScopedDelete tris = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertsPerCont*3, RC_ALLOC_TEMP); - if (!tris) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: Out of memory 'tris' (%d).", maxVertsPerCont*3); - return false; - } - rcScopedDelete polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertsPerCont*nvp, RC_ALLOC_TEMP); - if (!polys) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: Out of memory 'polys' (%d).", maxVertsPerCont*nvp); - return false; - } - - for (int i = 0; i < lcset.nconts; ++i) - { - rcLayerContour& cont = lcset.conts[i]; - - // Skip null contours. - if (cont.nverts < 3) - continue; - - // Triangulate contour - for (int j = 0; j < cont.nverts; ++j) - indices[j] = (unsigned short)j; - - int ntris = triangulate(cont.nverts, cont.verts, &indices[0], &tris[0]); - if (ntris <= 0) - { - ctx->log(RC_LOG_WARNING, "rcBuildLayerPolyMesh: Bad triangulation Contour %d.", i); - ntris = -ntris; - } - - // Add and merge vertices. - for (int j = 0; j < cont.nverts; ++j) - { - const unsigned char* v = &cont.verts[j*4]; - indices[j] = addVertex((unsigned short)v[0], (unsigned short)v[1], (unsigned short)v[2], - mesh.verts, firstVert, nextVert, mesh.nverts); - if (v[3] & 0x80) - { - // This vertex should be removed. - vflags[indices[j]] = 1; - } - } - - // Build initial polygons. - int npolys = 0; - memset(polys, 0xff, sizeof(unsigned short) * maxVertsPerCont * nvp); - for (int j = 0; j < ntris; ++j) - { - const unsigned short* t = &tris[j*3]; - if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2]) - { - polys[npolys*nvp+0] = indices[t[0]]; - polys[npolys*nvp+1] = indices[t[1]]; - polys[npolys*nvp+2] = indices[t[2]]; - npolys++; - } - } - if (!npolys) - continue; - - // Merge polygons. - if (nvp > 3) - { - for(;;) - { - // Find best polygons to merge. - int bestMergeVal = 0; - int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0; - - for (int j = 0; j < npolys-1; ++j) - { - unsigned short* pj = &polys[j*nvp]; - for (int k = j+1; k < npolys; ++k) - { - unsigned short* pk = &polys[k*nvp]; - int ea, eb; - int v = getPolyMergeValue(pj, pk, mesh.verts, ea, eb, nvp); - if (v > bestMergeVal) - { - bestMergeVal = v; - bestPa = j; - bestPb = k; - bestEa = ea; - bestEb = eb; - } - } - } - - if (bestMergeVal > 0) - { - // Found best, merge. - unsigned short* pa = &polys[bestPa*nvp]; - unsigned short* pb = &polys[bestPb*nvp]; - mergePolys(pa, pb, bestEa, bestEb, nvp); - memcpy(pb, &polys[(npolys-1)*nvp], sizeof(unsigned short)*nvp); - npolys--; - } - else - { - // Could not merge any polygons, stop. - break; - } - } - } - - // Store polygons. - for (int j = 0; j < npolys; ++j) - { - unsigned short* p = &mesh.polys[mesh.npolys*nvp*2]; - unsigned short* q = &polys[j*nvp]; - for (int k = 0; k < nvp; ++k) - p[k] = q[k]; - mesh.areas[mesh.npolys] = cont.area; - mesh.npolys++; - if (mesh.npolys > maxTris) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: Too many polygons %d (max:%d).", mesh.npolys, maxTris); - return false; - } - } - } - - - // Remove edge vertices. - for (int i = 0; i < mesh.nverts; ++i) - { - if (vflags[i]) - { - if (!canRemoveVertex(ctx, mesh, (unsigned short)i)) - continue; - if (!removeVertex(ctx, mesh, (unsigned short)i, maxTris)) - { - // Failed to remove vertex - ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Failed to remove edge vertex %d.", i); - return false; - } - // Remove vertex - // Note: mesh.nverts is already decremented inside removeVertex()! - for (int j = i; j < mesh.nverts; ++j) - vflags[j] = vflags[j+1]; - --i; - } - } - - // Calculate adjacency. - if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.verts, mesh.nverts, nvp, lcset)) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: Adjacency failed."); - return false; - } - - if (mesh.nverts > 0xffff) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: The resulting mesh has too many vertices %d (max %d). Data can be corrupted.", mesh.nverts, 0xffff); - } - if (mesh.npolys > 0xffff) - { - ctx->log(RC_LOG_ERROR, "rcBuildLayerPolyMesh: The resulting mesh has too many polygons %d (max %d). Data can be corrupted.", mesh.npolys, 0xffff); - } - -// ctx->stopTimer(RC_TIMER_BUILD_POLYMESH); - - return true; -} diff --git a/Recast/Source/RecastMesh.cpp b/Recast/Source/RecastMesh.cpp index 030efed..c39d210 100644 --- a/Recast/Source/RecastMesh.cpp +++ b/Recast/Source/RecastMesh.cpp @@ -896,7 +896,7 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short } -bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, int nvp, rcPolyMesh& mesh) +bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh) { rcAssert(ctx); @@ -906,6 +906,7 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, int nvp, rcPolyMesh& me rcVcopy(mesh.bmax, cset.bmax); mesh.cs = cset.cs; mesh.ch = cset.ch; + mesh.borderSize = cset.borderSize; int maxVertices = 0; int maxTris = 0; @@ -939,7 +940,7 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, int nvp, rcPolyMesh& me ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.verts' (%d).", maxVertices); return false; } - mesh.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxTris*nvp*2*2, RC_ALLOC_PERM); + mesh.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxTris*nvp*2, RC_ALLOC_PERM); if (!mesh.polys) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.polys' (%d).", maxTris*nvp*2); @@ -1156,6 +1157,37 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, int nvp, rcPolyMesh& me ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Adjacency failed."); return false; } + + // Find portal edges + if (mesh.borderSize > 0) + { + const int w = cset.width; + const int h = cset.height; + for (int i = 0; i < mesh.npolys; ++i) + { + unsigned short* p = &mesh.polys[i*2*nvp]; + for (int j = 0; j < nvp; ++j) + { + if (p[j] == RC_MESH_NULL_IDX) break; + // Skip connected edges. + if (p[nvp+j] != RC_MESH_NULL_IDX) + continue; + int nj = j+1; + if (nj >= nvp || p[nj] == RC_MESH_NULL_IDX) nj = 0; + const unsigned short* va = &mesh.verts[p[j]*3]; + const unsigned short* vb = &mesh.verts[p[nj]*3]; + + if ((int)va[0] == 0 && (int)vb[0] == 0) + p[nvp+j] = 0x8000 | 0; + else if ((int)va[2] == h && (int)vb[2] == h) + p[nvp+j] = 0x8000 | 1; + else if ((int)va[0] == w && (int)vb[0] == w) + p[nvp+j] = 0x8000 | 2; + else if ((int)va[2] == 0 && (int)vb[2] == 0) + p[nvp+j] = 0x8000 | 3; + } + } + } // Just allocate the mesh flags array. The user is resposible to fill it. mesh.flags = (unsigned short*)rcAlloc(sizeof(unsigned short)*mesh.npolys, RC_ALLOC_PERM); diff --git a/Recast/Source/RecastMeshDetail.cpp b/Recast/Source/RecastMeshDetail.cpp index ffb4b58..b14c45a 100644 --- a/Recast/Source/RecastMeshDetail.cpp +++ b/Recast/Source/RecastMeshDetail.cpp @@ -743,12 +743,15 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, static void getHeightData(const rcCompactHeightfield& chf, const unsigned short* poly, const int npoly, - const unsigned short* verts, + const unsigned short* verts, const int bs, rcHeightPatch& hp, rcIntArray& stack) { // Floodfill the heightfield to get 2D height data, // starting at vertex locations as seeds. + // Note: Reads to the compact heightfield are offset by border size (bs) + // since border size offset is already removed from the polymesh vertices. + memset(hp.data, 0, sizeof(unsigned short)*hp.width*hp.height); stack.resize(0); @@ -772,7 +775,7 @@ static void getHeightData(const rcCompactHeightfield& chf, az < hp.ymin || az >= hp.ymin+hp.height) continue; - const rcCompactCell& c = chf.cells[ax+az*chf.width]; + const rcCompactCell& c = chf.cells[(ax+bs)+(az+bs)*chf.width]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; @@ -844,7 +847,7 @@ static void getHeightData(const rcCompactHeightfield& chf, if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0) continue; - const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(cs, dir); + const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir); int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; hp.data[idx] = 1; @@ -900,7 +903,7 @@ static void getHeightData(const rcCompactHeightfield& chf, if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != RC_UNSET_HEIGHT) continue; - const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(cs, dir); + const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir); const rcCompactSpan& as = chf.spans[ai]; int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; @@ -955,6 +958,7 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa const float cs = mesh.cs; const float ch = mesh.ch; const float* orig = mesh.bmin; + const int borderSize = mesh.borderSize; rcIntArray edges(64); rcIntArray tris(512); @@ -1065,7 +1069,7 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa hp.ymin = bounds[i*4+2]; hp.width = bounds[i*4+1]-bounds[i*4+0]; hp.height = bounds[i*4+3]-bounds[i*4+2]; - getHeightData(chf, p, npoly, mesh.verts, hp, stack); + getHeightData(chf, p, npoly, mesh.verts, borderSize, hp, stack); // Build detail mesh. int nverts = 0; diff --git a/Recast/Source/RecastRegion.cpp b/Recast/Source/RecastRegion.cpp index 131a5b8..b59e67c 100644 --- a/Recast/Source/RecastRegion.cpp +++ b/Recast/Source/RecastRegion.cpp @@ -1058,6 +1058,8 @@ bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, paintRectRegion(w-bw, w, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; paintRectRegion(0, w, 0, bh, id|RC_BORDER_REG, chf, srcReg); id++; paintRectRegion(0, w, h-bh, h, id|RC_BORDER_REG, chf, srcReg); id++; + + chf.borderSize = borderSize; } rcIntArray prev(256); @@ -1208,11 +1210,19 @@ bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, // const int expandIters = 4 + walkableRadius * 2; const int expandIters = 8; - // Mark border regions. - paintRectRegion(0, borderSize, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; - paintRectRegion(w-borderSize, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; - paintRectRegion(0, w, 0, borderSize, regionId|RC_BORDER_REG, chf, srcReg); regionId++; - paintRectRegion(0, w, h-borderSize, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; + if (borderSize > 0) + { + // Make sure border will not overflow. + const int bw = rcMin(w, borderSize); + const int bh = rcMin(h, borderSize); + // Paint regions + paintRectRegion(0, bw, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; + paintRectRegion(w-bw, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; + paintRectRegion(0, w, 0, bh, regionId|RC_BORDER_REG, chf, srcReg); regionId++; + paintRectRegion(0, w, h-bh, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; + + chf.borderSize = borderSize; + } while (level > 0) { diff --git a/RecastDemo/Build/Xcode/Recast.xcodeproj/project.pbxproj b/RecastDemo/Build/Xcode/Recast.xcodeproj/project.pbxproj index e25f48c..2f486d5 100644 --- a/RecastDemo/Build/Xcode/Recast.xcodeproj/project.pbxproj +++ b/RecastDemo/Build/Xcode/Recast.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 6B3F9D6D13179EFC000B33D9 /* RecastLayers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6B3F9D6C13179EFC000B33D9 /* RecastLayers.cpp */; }; 6B555DB1100B212E00247EA3 /* imguiRenderGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6B555DB0100B212E00247EA3 /* imguiRenderGL.cpp */; }; 6B5683B812D9E7D3000B9960 /* Sample_TempObstacles.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6B5683B712D9E7D3000B9960 /* Sample_TempObstacles.cpp */; }; + 6B5DC3B713350E6300D33D05 /* DetourTileCacheBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6B5DC3B513350E6300D33D05 /* DetourTileCacheBuilder.cpp */; }; 6B62416A103434880002E346 /* RecastMeshDetail.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6B624169103434880002E346 /* RecastMeshDetail.cpp */; }; 6B8036AE113BAABE005ED67B /* Sample_Debug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6B8036AD113BAABE005ED67B /* Sample_Debug.cpp */; }; 6B847777122D221D00ADF63D /* ValueHistory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6B847776122D221C00ADF63D /* ValueHistory.cpp */; }; @@ -103,6 +104,8 @@ 6B555DF6100B273500247EA3 /* stb_truetype.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stb_truetype.h; path = ../../Contrib/stb_truetype.h; sourceTree = SOURCE_ROOT; }; 6B5683B612D9E7D3000B9960 /* Sample_TempObstacles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Sample_TempObstacles.h; path = ../../Include/Sample_TempObstacles.h; sourceTree = SOURCE_ROOT; }; 6B5683B712D9E7D3000B9960 /* Sample_TempObstacles.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Sample_TempObstacles.cpp; path = ../../Source/Sample_TempObstacles.cpp; sourceTree = SOURCE_ROOT; }; + 6B5DC3B513350E6300D33D05 /* DetourTileCacheBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DetourTileCacheBuilder.cpp; sourceTree = ""; }; + 6B5DC3B613350E6300D33D05 /* DetourTileCacheBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetourTileCacheBuilder.h; sourceTree = ""; }; 6B624169103434880002E346 /* RecastMeshDetail.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RecastMeshDetail.cpp; path = ../../../Recast/Source/RecastMeshDetail.cpp; sourceTree = SOURCE_ROOT; }; 6B8036AC113BAABE005ED67B /* Sample_Debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Sample_Debug.h; path = ../../Include/Sample_Debug.h; sourceTree = SOURCE_ROOT; }; 6B8036AD113BAABE005ED67B /* Sample_Debug.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Sample_Debug.cpp; path = ../../Source/Sample_Debug.cpp; sourceTree = SOURCE_ROOT; }; @@ -110,6 +113,7 @@ 6B847776122D221C00ADF63D /* ValueHistory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ValueHistory.cpp; path = ../../Source/ValueHistory.cpp; sourceTree = SOURCE_ROOT; }; 6B8632D90F78122C00E2684A /* SDL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL.framework; path = Library/Frameworks/SDL.framework; sourceTree = SDKROOT; }; 6B8632DB0F78123E00E2684A /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; + 6B86B21213387FB200B14842 /* DetourStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DetourStatus.h; path = ../../../Detour/Include/DetourStatus.h; sourceTree = SOURCE_ROOT; }; 6B86C9A812F69DD500C92D2E /* fastlz.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = fastlz.c; path = ../../Contrib/fastlz/fastlz.c; sourceTree = SOURCE_ROOT; }; 6B86C9A912F69DD500C92D2E /* fastlz.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fastlz.h; path = ../../Contrib/fastlz/fastlz.h; sourceTree = SOURCE_ROOT; }; 6B8DE88710B69E3E00DF20FB /* DetourNavMesh.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DetourNavMesh.cpp; path = ../../../Detour/Source/DetourNavMesh.cpp; sourceTree = SOURCE_ROOT; }; @@ -189,10 +193,11 @@ 080E96DDFE201D6D7F000001 /* Classes */ = { isa = PBXGroup; children = ( - 6BB5012B12F458AE001B1957 /* DetourCrowd */, 6BB5012A12F45891001B1957 /* Contrib */, 6BB93C7610CFE1BD00F74F2B /* DebugUtils */, 6BDD9E030F91110C00904EEF /* Detour */, + 6BB5012B12F458AE001B1957 /* DetourCrowd */, + 6B5DC3B413350E6300D33D05 /* DetourTileCache */, 6B137C7D0F7FCBE800459200 /* Recast */, 6B555DF5100B25FC00247EA3 /* Samples */, 6BB7FE8E10F4A175006DA0A6 /* Tools */, @@ -330,6 +335,16 @@ name = Samples; sourceTree = ""; }; + 6B5DC3B413350E6300D33D05 /* DetourTileCache */ = { + isa = PBXGroup; + children = ( + 6B5DC3B613350E6300D33D05 /* DetourTileCacheBuilder.h */, + 6B5DC3B513350E6300D33D05 /* DetourTileCacheBuilder.cpp */, + ); + name = DetourTileCache; + path = ../../../DetourTileCache; + sourceTree = SOURCE_ROOT; + }; 6BB5012A12F45891001B1957 /* Contrib */ = { isa = PBXGroup; children = ( @@ -406,6 +421,7 @@ 6B1185F41006895B0018F96F /* DetourNode.cpp */, 6B1185FC10068B040018F96F /* DetourCommon.h */, 6B1185FD10068B150018F96F /* DetourCommon.cpp */, + 6B86B21213387FB200B14842 /* DetourStatus.h */, ); name = Detour; sourceTree = ""; @@ -515,6 +531,7 @@ 6B86C9AA12F69DD500C92D2E /* fastlz.c in Sources */, 6B3F9D6D13179EFC000B33D9 /* RecastLayers.cpp in Sources */, 6BD1B1D01323E2EC00587F83 /* Sample_SoloMesh.cpp in Sources */, + 6B5DC3B713350E6300D33D05 /* DetourTileCacheBuilder.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/RecastDemo/Include/Sample_TempObstacles.h b/RecastDemo/Include/Sample_TempObstacles.h index 69a9316..f57b29c 100644 --- a/RecastDemo/Include/Sample_TempObstacles.h +++ b/RecastDemo/Include/Sample_TempObstacles.h @@ -30,7 +30,6 @@ class Sample_TempObstacles : public Sample protected: bool m_keepInterResults; float m_cacheBuildTimeMs; - bool m_drawPortals; class TileCache* m_tileCache; class ObstacleSet* m_obs; @@ -44,6 +43,7 @@ protected: DRAWMODE_NAVMESH_PORTALS, DRAWMODE_NAVMESH_INVIS, DRAWMODE_MESH, + DRAWMODE_CACHE_BOUNDS, MAX_DRAWMODE }; diff --git a/RecastDemo/Include/Sample_TileMesh.h b/RecastDemo/Include/Sample_TileMesh.h index 3781654..58f52c1 100644 --- a/RecastDemo/Include/Sample_TileMesh.h +++ b/RecastDemo/Include/Sample_TileMesh.h @@ -30,7 +30,6 @@ protected: bool m_keepInterResults; bool m_buildAll; float m_totalBuildTimeMs; - bool m_drawPortals; unsigned char* m_triareas; rcHeightfield* m_solid; @@ -39,15 +38,6 @@ protected: rcPolyMesh* m_pmesh; rcPolyMeshDetail* m_dmesh; rcConfig m_cfg; - - - static const int MAX_LAYERS = 128; - rcHeightfieldLayerSet* m_lset; - rcLayerContourSet* m_lcsets[MAX_LAYERS]; - int m_nlcsets; - rcLayerPolyMesh* m_lmeshes[MAX_LAYERS]; - int m_nlmeshes; - enum DrawMode { @@ -68,12 +58,7 @@ protected: DRAWMODE_BOTH_CONTOURS, DRAWMODE_CONTOURS, DRAWMODE_POLYMESH, - DRAWMODE_POLYMESH_DETAIL, - - DRAWMODE_HEIGHFIELD_LAYERS, - DRAWMODE_LAYER_CONTOURS, - DRAWMODE_LAYER_MESHES, - + DRAWMODE_POLYMESH_DETAIL, MAX_DRAWMODE }; diff --git a/RecastDemo/Source/Sample_SoloMesh.cpp b/RecastDemo/Source/Sample_SoloMesh.cpp index 322847f..ad42698 100644 --- a/RecastDemo/Source/Sample_SoloMesh.cpp +++ b/RecastDemo/Source/Sample_SoloMesh.cpp @@ -486,7 +486,7 @@ bool Sample_SoloMesh::handleBuild() { // Partition the walkable surface into simple regions without holes. // Monotone partitioning does not need distancefield. - if (!rcBuildRegionsMonotone(m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) + if (!rcBuildRegionsMonotone(m_ctx, *m_chf, 0, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build regions."); return false; @@ -502,7 +502,7 @@ bool Sample_SoloMesh::handleBuild() } // Partition the walkable surface into simple regions without holes. - if (!rcBuildRegions(m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) + if (!rcBuildRegions(m_ctx, *m_chf, 0, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build regions."); return false; @@ -633,6 +633,7 @@ bool Sample_SoloMesh::handleBuild() rcVcopy(params.bmax, m_pmesh->bmax); params.cs = m_cfg.cs; params.ch = m_cfg.ch; + params.buildBvTree = true; if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) { diff --git a/RecastDemo/Source/Sample_TempObstacles.cpp b/RecastDemo/Source/Sample_TempObstacles.cpp index f5d0804..b94716e 100644 --- a/RecastDemo/Source/Sample_TempObstacles.cpp +++ b/RecastDemo/Source/Sample_TempObstacles.cpp @@ -32,6 +32,8 @@ #include "DetourNavMesh.h" #include "DetourNavMeshBuilder.h" #include "DetourDebugDraw.h" +#include "DetourCommon.h" +#include "DetourTileCacheBuilder.h" #include "NavMeshTesterTool.h" #include "OffMeshConnectionTool.h" #include "ConvexVolumeTool.h" @@ -111,41 +113,64 @@ static bool isectSegAABB(const float* sp, const float* sq, struct CompressedTile { - inline CompressedTile() : tx(-1), ty(-1), compressedData(0), compressedSize(0), dataSize(0) {} + inline CompressedTile() : tx(-1), ty(-1), tlayer(-1), compressedData(0), compressedSize(0), dataSize(0) {} inline ~CompressedTile() { purge(); } void purge() { tx = -1; ty = -1; + tlayer = -1; delete [] compressedData; compressedData = 0; compressedSize = 0; dataSize = 0; } - int tx, ty; + int tx, ty, tlayer; float bmin[3], bmax[3]; - int triCount; unsigned char* compressedData; int compressedSize; int dataSize; }; +static const int MAX_TILES = 32; + struct RasterizationContext { - inline RasterizationContext() : tile(0), solid(0), triareas(0), lhf(0) {} - inline ~RasterizationContext() { delete tile; rcFreeHeightField(solid); delete [] triareas; rcFree(lhf); } + RasterizationContext() : + solid(0), + triareas(0), + lset(0), + chf(0), + ntiles(0) + { + memset(tiles, 0, sizeof(CompressedTile*)*MAX_TILES); + } + + ~RasterizationContext() + { + rcFreeHeightField(solid); + delete [] triareas; + rcFreeHeightfieldLayerSet(lset); + rcFreeCompactHeightfield(chf); + for (int i = 0; i < MAX_TILES; ++i) + delete tiles[i]; + } - CompressedTile* tile; rcHeightfield* solid; unsigned char* triareas; - rcLeanHeightfield* lhf; + rcHeightfieldLayerSet* lset; + rcCompactHeightfield* chf; + CompressedTile* tiles[MAX_TILES]; + int ntiles; }; -static CompressedTile* rasterizeTile(BuildContext* ctx, InputGeom* geom, - const int tx, const int ty, - const rcConfig& cfg) +static int rasterizeTileLayers(BuildContext* ctx, InputGeom* geom, + const int tx, const int ty, + const rcConfig& cfg, + CompressedTile** tiles, + const int maxTiles) { if (!geom || !geom->getMesh() || !geom->getChunkyMesh()) { @@ -155,15 +180,6 @@ static CompressedTile* rasterizeTile(BuildContext* ctx, InputGeom* geom, RasterizationContext rc; - rc.tile = new CompressedTile; - if (!rc.tile) - { - return 0; - } - - rc.tile->tx = tx; - rc.tile->ty = ty; - const float* verts = geom->getMesh()->getVerts(); const int nverts = geom->getMesh()->getVertCount(); const rcChunkyTriMesh* chunkyMesh = geom->getChunkyMesh(); @@ -171,18 +187,15 @@ static CompressedTile* rasterizeTile(BuildContext* ctx, InputGeom* geom, // Tile bounds. const float tcs = cfg.tileSize * cfg.cs; - rc.tile->bmin[0] = cfg.bmin[0] + tx*tcs; - rc.tile->bmin[1] = cfg.bmin[1]; - rc.tile->bmin[2] = cfg.bmin[2] + ty*tcs; - rc.tile->bmax[0] = cfg.bmin[0] + (tx+1)*tcs; - rc.tile->bmax[1] = cfg.bmax[1]; - rc.tile->bmax[2] = cfg.bmin[2] + (ty+1)*tcs; - rcConfig tcfg; memcpy(&tcfg, &cfg, sizeof(tcfg)); - - rcVcopy(tcfg.bmin, rc.tile->bmin); - rcVcopy(tcfg.bmax, rc.tile->bmax); + + tcfg.bmin[0] = cfg.bmin[0] + tx*tcs; + tcfg.bmin[1] = cfg.bmin[1]; + tcfg.bmin[2] = cfg.bmin[2] + ty*tcs; + tcfg.bmax[0] = cfg.bmin[0] + (tx+1)*tcs; + tcfg.bmax[1] = cfg.bmax[1]; + tcfg.bmax[2] = cfg.bmin[2] + (ty+1)*tcs; tcfg.bmin[0] -= tcfg.borderSize*tcfg.cs; tcfg.bmin[2] -= tcfg.borderSize*tcfg.cs; tcfg.bmax[0] += tcfg.borderSize*tcfg.cs; @@ -223,16 +236,12 @@ static CompressedTile* rasterizeTile(BuildContext* ctx, InputGeom* geom, return 0; // empty } - rc.tile->triCount = 0; - for (int i = 0; i < ncid; ++i) { const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]]; const int* tris = &chunkyMesh->tris[node.i*3]; const int ntris = node.n; - rc.tile->triCount += ntris; - memset(rc.triareas, 0, ntris*sizeof(unsigned char)); rcMarkWalkableTriangles(ctx, tcfg.walkableSlopeAngle, verts, nverts, tris, ntris, rc.triareas); @@ -248,27 +257,112 @@ static CompressedTile* rasterizeTile(BuildContext* ctx, InputGeom* geom, rcFilterWalkableLowHeightSpans(ctx, tcfg.walkableHeight, *rc.solid); - if (rcGetHeightFieldSpanCount(ctx, *rc.solid) > 0) + rc.chf = rcAllocCompactHeightfield(); + if (!rc.chf) { - rc.lhf = rcBuildLeanHeightfield(ctx, *rc.solid, tcfg.walkableHeight); - - if (rc.lhf) - { - // Compress - const int outSize = (int)(rc.lhf->size*1.05f); - rc.tile->dataSize = rc.lhf->size; - rc.tile->compressedData = new unsigned char[outSize]; - rc.tile->compressedSize = (int)fastlz_compress((const void *const)rc.lhf, rc.lhf->size, rc.tile->compressedData); - } - - // Everything in rc gets deleted when it goes out of scope, save and return tile. - CompressedTile* tile = rc.tile; - rc.tile = 0; - - return tile; + ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'."); + return 0; + } + if (!rcBuildCompactHeightfield(ctx, tcfg.walkableHeight, tcfg.walkableClimb, *rc.solid, *rc.chf)) + { + ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data."); + return 0; } - return 0; + // Erode the walkable area by agent radius. + if (!rcErodeWalkableArea(ctx, tcfg.walkableRadius, *rc.chf)) + { + ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode."); + return 0; + } + + // (Optional) Mark areas. + const ConvexVolume* vols = geom->getConvexVolumes(); + for (int i = 0; i < geom->getConvexVolumeCount(); ++i) + { + rcMarkConvexPolyArea(ctx, vols[i].verts, vols[i].nverts, + vols[i].hmin, vols[i].hmax, + (unsigned char)vols[i].area, *rc.chf); + } + + rc.lset = rcAllocHeightfieldLayerSet(); + if (!rc.lset) + { + ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'lset'."); + return 0; + } + if (!rcBuildHeightfieldLayers(ctx, *rc.chf, tcfg.borderSize, tcfg.walkableHeight, *rc.lset)) + { + ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build heighfield layers."); + return 0; + } + + rc.ntiles = 0; + for (int i = 0; i < rcMin(rc.lset->nlayers, MAX_TILES); ++i) + { + CompressedTile* tile = new CompressedTile; + if (!tile) + return 0; + rc.tiles[rc.ntiles++] = tile; + + const rcHeightfieldLayer* layer = &rc.lset->layers[i]; + + tile->tx = tx; + tile->ty = ty; + tile->tlayer = i; + + // Build tight tile bounds using the are that is represented in the layer. + tile->bmin[0] = layer->bmin[0] + layer->minx*layer->cs; + tile->bmin[1] = layer->bmin[1] + (layer->hmin+1)*layer->ch; + tile->bmin[2] = layer->bmin[2] + layer->miny*layer->cs; + tile->bmax[0] = layer->bmin[0] + (layer->maxx+1)*layer->cs; + tile->bmax[1] = layer->bmin[1] + (layer->hmax+1)*layer->ch; + tile->bmax[2] = layer->bmin[2] + (layer->maxy+1)*layer->cs; + + // Build compressible data chunk. + const int headerSize = dtAlign4(sizeof(dtTileCacheLayerHeader)); + const int gridSize = layer->width * layer->height; + const int bufferSize = headerSize + gridSize*3; + unsigned char* buffer = new unsigned char[bufferSize]; + if (!buffer) + return 0; + + // Store header + dtTileCacheLayerHeader* header = (dtTileCacheLayerHeader*)buffer; + header->magic = DT_TILECACHE_MAGIC; + header->version = DT_TILECACHE_VERSION; + 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; + + // Store layer data + memcpy(buffer+headerSize, layer->heights, gridSize); + memcpy(buffer+headerSize+gridSize, layer->areas, gridSize); + memcpy(buffer+headerSize+gridSize*2, layer->cons, gridSize); + + // Compress + const int outSize = (int)(bufferSize*1.05f); + tile->dataSize = bufferSize; + tile->compressedData = new unsigned char[outSize]; + if (!tile->compressedData) + return 0; + tile->compressedSize = (int)fastlz_compress((const void *const)buffer, bufferSize, tile->compressedData); + } + + // Remove tiles from build context. + int n = 0; + for (int i = 0; i < rcMin(rc.ntiles, maxTiles); ++i) + { + tiles[n++] = rc.tiles[i]; + rc.tiles[i] = 0; + } + + return n; } @@ -380,56 +474,63 @@ public: } }; + +struct TileCacheParams +{ + float orig[3]; + float cs, ch; + int width, height; + float walkableHeight; + float walkableRadius; + float walkableClimb; + float maxSimplificationError; + int maxTiles; +}; + class TileCache { CompressedTile** m_tiles; int m_ntiles; - int m_maxTiles; - CompressedTile* getTile(const int x, const int y) + int getTiles(const int x, const int y, CompressedTile const** tiles, const int maxTiles) { + int n = 0; for (int i = 0; i < m_ntiles; ++i) { CompressedTile* tile = m_tiles[i]; if (tile->tx == x && tile->ty == y) - return tile; + { + if (n < maxTiles) + tiles[n++] = tile; + } } - return 0; + return n; } unsigned char* m_buffer; int m_bufferSize; - rcConfig m_cfg; - float m_agentHeight; - float m_agentRadius; - float m_agentMaxClimb; - - bool m_buildDetail; + TileCacheParams m_params; struct BuildContext { - BuildContext() : chf(0), cset(0), pmesh(0), dmesh(0) {}; - ~BuildContext() { } + inline BuildContext(dtTileCacheAlloc* a) : lcset(0), lmesh(0), alloc(a) {} + inline ~BuildContext() { purge(); } - inline void purge() + void purge() { - rcFreeCompactHeightfield(chf); - rcFreeContourSet(cset); - rcFreePolyMesh(pmesh); - rcFreePolyMeshDetail(dmesh); - chf = 0; - cset = 0; - pmesh = 0; - dmesh = 0; + dtFreeTileCacheContourSet(alloc, lcset); + lcset = 0; + dtFreeTileCachePolyMesh(alloc, lmesh); + lmesh = 0; } - - rcCompactHeightfield* chf; - rcContourSet* cset; - rcPolyMesh* pmesh; - rcPolyMeshDetail* dmesh; + dtTileCacheContourSet* lcset; + dtTileCachePolyMesh* lmesh; + dtTileCacheAlloc* alloc; }; - + + dtTileCacheAlloc m_talloc; + struct Request { int tx,ty; @@ -437,20 +538,14 @@ class TileCache static const int MAX_REQUESTS = 64; Request m_reqs[MAX_REQUESTS]; int m_nreqs; - - int m_buildState; - BuildContext m_bc; public: TileCache() : m_tiles(0), m_ntiles(0), - m_maxTiles(0), m_buffer(0), m_bufferSize(0), - m_buildDetail(false), - m_nreqs(0), - m_buildState(0) + m_nreqs(0) { } @@ -472,50 +567,34 @@ public: m_bufferSize = 0; delete [] m_tiles; m_tiles = 0; - m_maxTiles = 0; m_nreqs = 0; } - bool init(const int maxTiles, const rcConfig& cfg, - const float agentHeight, const float agentRadius, const float agentMaxClimb) + bool init(const TileCacheParams* params) { purge(); - - m_tiles = new CompressedTile*[maxTiles]; + + memcpy(&m_params, params, sizeof(m_params)); + + m_tiles = new CompressedTile*[m_params.maxTiles]; if (!m_tiles) return false; - m_maxTiles = maxTiles; m_ntiles = 0; - memcpy(&m_cfg, &cfg, sizeof(m_cfg)); - - m_agentHeight = agentHeight; - m_agentRadius = agentRadius; - m_agentMaxClimb = agentMaxClimb; - - m_nreqs = 0; - - return true; - } - - const float getBorderSize() const - { - return m_cfg.borderSize*m_cfg.cs; - } - - bool initCompressionBuffer(int size) - { - delete [] m_buffer; - m_bufferSize = (size + 512) & ~511; + // Calculate decompression buffer size. + m_bufferSize = dtCalcTileCacheLayerBufferSize(m_params.width, m_params.height); m_buffer = new unsigned char[m_bufferSize]; if (!m_buffer) return false; + + m_nreqs = 0; + return true; } bool addTile(CompressedTile* tile) { - if (m_ntiles >= m_maxTiles) + if (m_ntiles >= m_params.maxTiles) return false; m_tiles[m_ntiles++] = tile; return true; @@ -536,9 +615,9 @@ public: return true; } - void update(const float /*dt*/, rcContext* ctx, dtNavMesh* navmesh, ObstacleSet* obs) + void update(const float /*dt*/, dtNavMesh* navmesh, ObstacleSet* obs) { - static const int MAX_TIME_USEC = 1000; +/* static const int MAX_TIME_USEC = 1000; if (!m_nreqs) return; @@ -551,563 +630,244 @@ public: while (m_nreqs > 0) { - if (m_buildState == -1) - { - // Pop new request - m_nreqs--; - for (int i = 0; i < m_nreqs; ++i) - m_reqs[i] = m_reqs[i+1]; - m_buildState = 0; - if (!m_nreqs) - return; - } + // Pop new request + m_nreqs--; + for (int i = 0; i < m_nreqs; ++i) + m_reqs[i] = m_reqs[i+1]; + m_buildState = 0; + if (!m_nreqs) + return; const int tx = m_reqs[0].tx; const int ty = m_reqs[0].ty; const CompressedTile* tile = getTile(tx,ty); if (!tile) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could not find tile (%d,%d).", tx, ty); - m_buildState = -1; + continue; + + // Do stuff here + + // Bail out if used too much time. + if (getPerfDeltaTimeUsec(startTime, getPerfTime()) > MAX_TIME_USEC) return; - } - - if (m_buildState == 0) - { - // Decompress tile and build compact heighfield. - int size = fastlz_decompress(tile->compressedData, tile->compressedSize, m_buffer, m_bufferSize); - if (size <= 0) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could not decompress tile."); - m_buildState = -1; - return; - } - rcLeanHeightfield* lhf = (rcLeanHeightfield*)m_buffer; - - m_bc.purge(); - - // Compact the heightfield so that it is faster to handle from now on. - // This will result more cache coherent data as well as the neighbours - // between walkable cells will be calculated. - m_bc.chf = rcAllocCompactHeightfield(); - if (!m_bc.chf) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'."); - m_buildState = -1; - return; - } - - if (!rcBuildCompactHeightfield(ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *lhf, *m_bc.chf)) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data."); - m_buildState = -1; - return; - } - - // Advance state and bail out if used too much time. - m_buildState++; - if (getPerfDeltaTimeUsec(startTime, getPerfTime()) > MAX_TIME_USEC) - return; - } - - if (m_buildState == 1) - { - // Mark obstacles. - for (int i = 0; i < obs->getObstacleCount(); ++i) - { - const TempObstacle* ob = obs->getObstacle(i); - rcMarkCylinderArea(ctx, ob->pos, ob->r, ob->h, RC_NULL_AREA, *m_bc.chf); - } - - // Erode the walkable area by agent radius. - if (!rcErodeWalkableArea(ctx, m_cfg.walkableRadius, *m_bc.chf)) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode."); - m_buildState = -1; - return; - } - - // (Optional) Mark areas. - /* const ConvexVolume* vols = m_geom->getConvexVolumes(); - for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i) - { - rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, - (unsigned char)vols[i].area, *bc.chf); - }*/ - - // Advance state and bail out if used too much time. - m_buildState++; - if (getPerfDeltaTimeUsec(startTime, getPerfTime()) > MAX_TIME_USEC) - return; - } - - if (m_buildState == 2) - { - // Partition the walkable surface into simple regions without holes. - if (!rcBuildRegionsMonotone(ctx, *m_bc.chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build regions."); - m_buildState = -1; - return; - } - - // Advance state and bail out if used too much time. - m_buildState++; - if (getPerfDeltaTimeUsec(startTime, getPerfTime()) > MAX_TIME_USEC) - return; - } - - if (m_buildState == 3) - { - // Create contours. - m_bc.cset = rcAllocContourSet(); - if (!m_bc.cset) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'."); - m_buildState = -1; - return; - } - if (!rcBuildContours(ctx, *m_bc.chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_bc.cset)) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create contours."); - m_buildState = -1; - return; - } - - if (m_bc.cset->nconts == 0) - { - // Done, no mesh. - m_buildState = -1; - return; - } - - // Advance state and bail out if used too much time. - m_buildState++; - if (getPerfDeltaTimeUsec(startTime, getPerfTime()) > MAX_TIME_USEC) - return; - } - - if (m_buildState == 4) - { - // Build polygon navmesh from the contours. - m_bc.pmesh = rcAllocPolyMesh(); - if (!m_bc.pmesh) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'."); - m_buildState = -1; - return; - } - if (!rcBuildPolyMesh(ctx, *m_bc.cset, m_cfg.maxVertsPerPoly, *m_bc.pmesh)) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours."); - m_buildState = -1; - return; - } - - // Advance state and bail out if used too much time. - m_buildState++; - if (getPerfDeltaTimeUsec(startTime, getPerfTime()) > MAX_TIME_USEC) - return; - } - - // Build detail mesh. - if (m_buildState == 5) - { - if (m_buildDetail) - { - m_bc.dmesh = rcAllocPolyMeshDetail(); - if (!m_bc.dmesh) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'dmesh'."); - m_buildState = -1; - return; - } - - if (!rcBuildPolyMeshDetail(ctx, *m_bc.pmesh, *m_bc.chf, - m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, - *m_bc.dmesh)) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could build polymesh detail."); - m_buildState = -1; - return; - } - } - - // Advance state and bail out if used too much time. - m_buildState++; - if (getPerfDeltaTimeUsec(startTime, getPerfTime()) > MAX_TIME_USEC) - return; - } - - if (m_buildState == 6) - { - // Remove padding from the polymesh data. TODO: Remove this odditity. - for (int i = 0; i < m_bc.pmesh->nverts; ++i) - { - unsigned short* v = &m_bc.pmesh->verts[i*3]; - v[0] -= (unsigned short)m_cfg.borderSize; - v[2] -= (unsigned short)m_cfg.borderSize; - } - - if (m_bc.pmesh->nverts >= 0xffff) - { - // The vertex indices are ushorts, and cannot point to more than 0xffff vertices. - ctx->log(RC_LOG_ERROR, "Too many vertices per tile %d (max: %d).", m_bc.pmesh->nverts, 0xffff); - } - - // Update poly flags from areas. - for (int i = 0; i < m_bc.pmesh->npolys; ++i) - { - if (m_bc.pmesh->areas[i] == RC_WALKABLE_AREA) - m_bc.pmesh->areas[i] = SAMPLE_POLYAREA_GROUND; - - if (m_bc.pmesh->areas[i] == SAMPLE_POLYAREA_GROUND || - m_bc.pmesh->areas[i] == SAMPLE_POLYAREA_GRASS || - m_bc.pmesh->areas[i] == SAMPLE_POLYAREA_ROAD) - { - m_bc.pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK; - } - else if (m_bc.pmesh->areas[i] == SAMPLE_POLYAREA_WATER) - { - m_bc.pmesh->flags[i] = SAMPLE_POLYFLAGS_SWIM; - } - else if (m_bc.pmesh->areas[i] == SAMPLE_POLYAREA_DOOR) - { - m_bc.pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR; - } - } - - dtNavMeshCreateParams params; - memset(¶ms, 0, sizeof(params)); - params.verts = m_bc.pmesh->verts; - params.vertCount = m_bc.pmesh->nverts; - params.polys = m_bc.pmesh->polys; - params.polyAreas = m_bc.pmesh->areas; - params.polyFlags = m_bc.pmesh->flags; - params.polyCount = m_bc.pmesh->npolys; - params.nvp = m_bc.pmesh->nvp; - - if (m_bc.dmesh) - { - params.detailMeshes = m_bc.dmesh->meshes; - params.detailVerts = m_bc.dmesh->verts; - params.detailVertsCount = m_bc.dmesh->nverts; - params.detailTris = m_bc.dmesh->tris; - params.detailTriCount = m_bc.dmesh->ntris; - } - - /* params.offMeshConVerts = m_geom->getOffMeshConnectionVerts(); - params.offMeshConRad = m_geom->getOffMeshConnectionRads(); - params.offMeshConDir = m_geom->getOffMeshConnectionDirs(); - params.offMeshConAreas = m_geom->getOffMeshConnectionAreas(); - params.offMeshConFlags = m_geom->getOffMeshConnectionFlags(); - params.offMeshConUserID = m_geom->getOffMeshConnectionId(); - params.offMeshConCount = m_geom->getOffMeshConnectionCount();*/ - - params.walkableHeight = m_agentHeight; - params.walkableRadius = m_agentRadius; - params.walkableClimb = m_agentMaxClimb; - params.tileX = tx; - params.tileY = ty; - - const float tcs = m_cfg.tileSize * m_cfg.cs; - params.bmin[0] = m_cfg.bmin[0] + tx*tcs; - params.bmin[1] = m_cfg.bmin[1]; - params.bmin[2] = m_cfg.bmin[2] + ty*tcs; - params.bmax[0] = m_cfg.bmin[0] + (tx+1)*tcs; - params.bmax[1] = m_cfg.bmax[1]; - params.bmax[2] = m_cfg.bmin[2] + (ty+1)*tcs; - - params.cs = m_cfg.cs; - params.ch = m_cfg.ch; - params.tileSize = m_cfg.tileSize; - - unsigned char* navData = 0; - int navDataSize = 0; - - if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) - { - ctx->log(RC_LOG_ERROR, "Could not build Detour navmesh."); - m_buildState = -1; - return; - } - - navmesh->removeTile(navmesh->getTileRefAt(tx,ty),0,0); - // Let the navmesh own the data. - dtStatus status = navmesh->addTile(navData,navDataSize,DT_TILE_FREE_DATA,0,0); - if (dtStatusFailed(status)) - dtFree(navData); - - // Done! - m_buildState = -1; - // Bail out if used too much time. - if (getPerfDeltaTimeUsec(startTime, getPerfTime()) > MAX_TIME_USEC) - return; - } - } + } +*/ } - unsigned char* buildNavMeshTile(rcContext* ctx, ObstacleSet* obs, const int tx, const int ty, int& dataSize) + void calcTileBounds(float* bmin, float* bmax, + const dtTileCacheLayer& layer, + const int tx, const int ty) + { + const float tw = m_params.width * m_params.cs; + const float th = m_params.height * m_params.cs; + bmin[0] = m_params.orig[0] + tx*tw; + bmin[1] = m_params.orig[1] + layer.header->hmin*m_params.ch; + bmin[2] = m_params.orig[2] + ty*th; + bmax[0] = m_params.orig[0] + (tx+1)*tw; + bmax[1] = m_params.orig[1] + layer.header->hmax*m_params.ch; + bmax[2] = m_params.orig[2] + (ty+1)*th; + } + + bool buildNavMeshTilesAt(ObstacleSet* obs, + const int tx, const int ty, + dtNavMesh* navMesh) { - const CompressedTile* tile = getTile(tx,ty); - if (!tile) - return 0; if (!m_buffer) - return 0; + return false; - int size = fastlz_decompress(tile->compressedData, tile->compressedSize, m_buffer, m_bufferSize); - - if (size <= 0) - return 0; + const int MAX_TILES = 32; + const CompressedTile* tiles[MAX_TILES]; + const int ntiles = getTiles(tx,ty,tiles,MAX_TILES); - BuildContext bc; + BuildContext bc(&m_talloc); + const int walkableClimbVx = (int)(m_params.walkableClimb / m_params.ch); + + for (int i = 0; i < ntiles; ++i) + { + const CompressedTile* tile = tiles[i]; - rcLeanHeightfield* cchf = (rcLeanHeightfield*)m_buffer; + int size = fastlz_decompress(tile->compressedData, tile->compressedSize, m_buffer, m_bufferSize); + if (size <= 0) + return false; + + dtTileCacheLayer layer; + dtStatus status = dtCastTileCacheLayer(layer, m_buffer, m_bufferSize); + if (dtStatusFailed(status)) + return false; - // Compact the heightfield so that it is faster to handle from now on. - // This will result more cache coherent data as well as the neighbours - // between walkable cells will be calculated. - bc.chf = rcAllocCompactHeightfield(); - if (!bc.chf) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'."); - return 0; - } - - if (!rcBuildCompactHeightfield(ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *cchf, *bc.chf)) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data."); - return 0; - } + status = dtBuildTileCacheRegions(&m_talloc, layer, walkableClimbVx); + if (dtStatusFailed(status)) + return false; + + bc.lcset = dtAllocTileCacheContourSet(&m_talloc); + if (!bc.lcset) + return false; + status = dtBuildTileCacheContours(&m_talloc, layer, walkableClimbVx, + m_params.maxSimplificationError, *bc.lcset); + if (dtStatusFailed(status)) + return false; + + bc.lmesh = dtAllocTileCachePolyMesh(&m_talloc); + if (!bc.lmesh) + return false; + status = dtBuildTileCachePolyMesh(&m_talloc, *bc.lcset, *bc.lmesh); + if (dtStatusFailed(status)) + return false; + + // Update poly flags from areas. + for (int i = 0; i < bc.lmesh->npolys; ++i) + { + if (bc.lmesh->areas[i] == RC_WALKABLE_AREA) + bc.lmesh->areas[i] = SAMPLE_POLYAREA_GROUND; - // Mark obstacles. - for (int i = 0; i < obs->getObstacleCount(); ++i) - { - const TempObstacle* ob = obs->getObstacle(i); - rcMarkCylinderArea(ctx, ob->pos, ob->r, ob->h, RC_NULL_AREA, *bc.chf); - } - - // Erode the walkable area by agent radius. - if (!rcErodeWalkableArea(ctx, m_cfg.walkableRadius, *bc.chf)) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode."); - return 0; - } - - // (Optional) Mark areas. -/* const ConvexVolume* vols = m_geom->getConvexVolumes(); - for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i) - { - rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, - (unsigned char)vols[i].area, *bc.chf); - }*/ - - // Partition the walkable surface into simple regions without holes. - if (!rcBuildRegionsMonotone(ctx, *bc.chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build regions."); - return 0; - } - - // Create contours. - bc.cset = rcAllocContourSet(); - if (!bc.cset) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'."); - return 0; - } - if (!rcBuildContours(ctx, *bc.chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *bc.cset)) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create contours."); - return 0; - } - - if (bc.cset->nconts == 0) - { - return 0; - } - - // Build polygon navmesh from the contours. - bc.pmesh = rcAllocPolyMesh(); - if (!bc.pmesh) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'."); - return 0; - } - if (!rcBuildPolyMesh(ctx, *bc.cset, m_cfg.maxVertsPerPoly, *bc.pmesh)) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours."); - return 0; - } - - // Build detail mesh. - if (m_buildDetail) - { - bc.dmesh = rcAllocPolyMeshDetail(); - if (!bc.dmesh) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'dmesh'."); - return 0; + if (bc.lmesh->areas[i] == SAMPLE_POLYAREA_GROUND || + bc.lmesh->areas[i] == SAMPLE_POLYAREA_GRASS || + bc.lmesh->areas[i] == SAMPLE_POLYAREA_ROAD) + { + bc.lmesh->flags[i] = SAMPLE_POLYFLAGS_WALK; + } + else if (bc.lmesh->areas[i] == SAMPLE_POLYAREA_WATER) + { + bc.lmesh->flags[i] = SAMPLE_POLYFLAGS_SWIM; + } + else if (bc.lmesh->areas[i] == SAMPLE_POLYAREA_DOOR) + { + bc.lmesh->flags[i] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR; + } } - if (!rcBuildPolyMeshDetail(ctx, *bc.pmesh, *bc.chf, - m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, - *bc.dmesh)) - { - ctx->log(RC_LOG_ERROR, "buildNavigation: Could build polymesh detail."); - return 0; - } - } - - unsigned char* navData = 0; - int navDataSize = 0; + dtNavMeshCreateParams params; + memset(¶ms, 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.offMeshConVerts = m_geom->getOffMeshConnectionVerts(); + params.offMeshConRad = m_geom->getOffMeshConnectionRads(); + params.offMeshConDir = m_geom->getOffMeshConnectionDirs(); + params.offMeshConAreas = m_geom->getOffMeshConnectionAreas(); + params.offMeshConFlags = m_geom->getOffMeshConnectionFlags(); + params.offMeshConUserID = m_geom->getOffMeshConnectionId(); + params.offMeshConCount = m_geom->getOffMeshConnectionCount();*/ + + params.walkableHeight = m_params.walkableHeight; + params.walkableRadius = m_params.walkableRadius; + params.walkableClimb = m_params.walkableClimb; + params.tileX = tile->tx; + params.tileY = tile->ty; + params.tileLayer = tile->tlayer; + params.cs = m_params.cs; + params.ch = m_params.ch; + params.buildBvTree = false; + calcTileBounds(params.bmin, params.bmax, layer, tx, ty); - // Remove padding from the polymesh data. TODO: Remove this odditity. - for (int i = 0; i < bc.pmesh->nverts; ++i) - { - unsigned short* v = &bc.pmesh->verts[i*3]; - v[0] -= (unsigned short)m_cfg.borderSize; - v[2] -= (unsigned short)m_cfg.borderSize; - } - if (bc.pmesh->nverts >= 0xffff) - { - // The vertex indices are ushorts, and cannot point to more than 0xffff vertices. - ctx->log(RC_LOG_ERROR, "Too many vertices per tile %d (max: %d).", bc.pmesh->nverts, 0xffff); - } + unsigned char* navData = 0; + int navDataSize = 0; + if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) + return false; - // Update poly flags from areas. - for (int i = 0; i < bc.pmesh->npolys; ++i) - { - if (bc.pmesh->areas[i] == RC_WALKABLE_AREA) - bc.pmesh->areas[i] = SAMPLE_POLYAREA_GROUND; - - if (bc.pmesh->areas[i] == SAMPLE_POLYAREA_GROUND || - bc.pmesh->areas[i] == SAMPLE_POLYAREA_GRASS || - bc.pmesh->areas[i] == SAMPLE_POLYAREA_ROAD) + if (navData) { - bc.pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK; + navMesh->removeTile(navMesh->getTileRefAt(tile->tx,tile->ty,tile->tlayer),0,0); + // Let the navmesh own the data. + dtStatus status = navMesh->addTile(navData,navDataSize,DT_TILE_FREE_DATA,0,0); + if (dtStatusFailed(status)) + dtFree(navData); } - else if (bc.pmesh->areas[i] == SAMPLE_POLYAREA_WATER) - { - bc.pmesh->flags[i] = SAMPLE_POLYFLAGS_SWIM; - } - else if (bc.pmesh->areas[i] == SAMPLE_POLYAREA_DOOR) - { - bc.pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR; - } - } - dtNavMeshCreateParams params; - memset(¶ms, 0, sizeof(params)); - params.verts = bc.pmesh->verts; - params.vertCount = bc.pmesh->nverts; - params.polys = bc.pmesh->polys; - params.polyAreas = bc.pmesh->areas; - params.polyFlags = bc.pmesh->flags; - params.polyCount = bc.pmesh->npolys; - params.nvp = bc.pmesh->nvp; - - if (bc.dmesh) - { - params.detailMeshes = bc.dmesh->meshes; - params.detailVerts = bc.dmesh->verts; - params.detailVertsCount = bc.dmesh->nverts; - params.detailTris = bc.dmesh->tris; - params.detailTriCount = bc.dmesh->ntris; + bc.purge(); } -/* params.offMeshConVerts = m_geom->getOffMeshConnectionVerts(); - params.offMeshConRad = m_geom->getOffMeshConnectionRads(); - params.offMeshConDir = m_geom->getOffMeshConnectionDirs(); - params.offMeshConAreas = m_geom->getOffMeshConnectionAreas(); - params.offMeshConFlags = m_geom->getOffMeshConnectionFlags(); - params.offMeshConUserID = m_geom->getOffMeshConnectionId(); - params.offMeshConCount = m_geom->getOffMeshConnectionCount();*/ - - params.walkableHeight = m_agentHeight; - params.walkableRadius = m_agentRadius; - params.walkableClimb = m_agentMaxClimb; - params.tileX = tx; - params.tileY = ty; - - const float tcs = m_cfg.tileSize * m_cfg.cs; - params.bmin[0] = m_cfg.bmin[0] + tx*tcs; - params.bmin[1] = m_cfg.bmin[1]; - params.bmin[2] = m_cfg.bmin[2] + ty*tcs; - params.bmax[0] = m_cfg.bmin[0] + (tx+1)*tcs; - params.bmax[1] = m_cfg.bmax[1]; - params.bmax[2] = m_cfg.bmin[2] + (ty+1)*tcs; - - params.cs = m_cfg.cs; - params.ch = m_cfg.ch; - params.tileSize = m_cfg.tileSize; - - if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) - { - ctx->log(RC_LOG_ERROR, "Could not build Detour navmesh."); - return 0; - } - - dataSize = navDataSize; - - return navData; + return true; } void drawTiles(duDebugDraw* dd) { + unsigned int fcol[6]; + for (int i = 0; i < m_ntiles; ++i) { const CompressedTile* tile = m_tiles[i]; - - duDebugDrawBoxWire(dd, tile->bmin[0],tile->bmin[1],tile->bmin[2], - tile->bmax[0],tile->bmin[1],tile->bmax[2], duRGBA(255,255,255,32), 2.0f); + + const unsigned int col = duIntToCol(i,64); + duCalcBoxColors(fcol, col, col); + duDebugDrawBox(dd, tile->bmin[0],tile->bmin[1],tile->bmin[2], + tile->bmax[0],tile->bmax[1],tile->bmax[2], fcol); } + + for (int i = 0; i < m_ntiles; ++i) + { + const CompressedTile* tile = m_tiles[i]; + const unsigned int col = duIntToCol(i,255); + const float pad = m_params.cs * 0.1f; + duDebugDrawBoxWire(dd, tile->bmin[0]-pad,tile->bmin[1]-pad,tile->bmin[2]-pad, + tile->bmax[0]+pad,tile->bmax[1]+pad,tile->bmax[2]+pad, + col, 2.0f); + } + } void drawDetail(duDebugDraw* dd, const int tx, const int ty) { - const CompressedTile* tile = getTile(tx,ty); - if (!tile) - return; + const int MAX_TILES = 32; + const CompressedTile* tiles[MAX_TILES]; + const int ntiles = getTiles(tx,ty,tiles,MAX_TILES); - int size = fastlz_decompress(tile->compressedData, tile->compressedSize, m_buffer, m_bufferSize); - - if (size > 0) + for (int i = 0; i < ntiles; ++i) { - rcLeanHeightfield* chf = (rcLeanHeightfield*)m_buffer; + const CompressedTile* tile = tiles[i]; - duDebugDrawBoxWire(dd, chf->bmin[0],chf->bmin[1],chf->bmin[2], - chf->bmax[0],chf->bmax[1],chf->bmax[2], duRGBA(0,192,255,128), 1.0f); + const int size = fastlz_decompress(tile->compressedData, tile->compressedSize, m_buffer, m_bufferSize); + if (size <= 0) + continue; - duDebugDrawLeanHeightfieldSolid(dd, *chf); + dtTileCacheLayer layer; + dtStatus status = dtCastTileCacheLayer(layer, m_buffer, m_bufferSize); + if (dtStatusFailed(status)) + return; + + float bmin[3], bmax[3]; + calcTileBounds(bmin,bmax,layer,tile->tx,tile->ty); + + duDebugDrawTileCacheLayer(dd, layer, bmin, bmax, m_params.cs, m_params.ch, tile->tlayer); } } - + void drawDetailOverlay(const int tx, const int ty, double* proj, double* model, int* view) { - const CompressedTile* tile = getTile(tx,ty); - if (!tile) + const int MAX_TILES = 32; + const CompressedTile* tiles[MAX_TILES]; + const int ntiles = getTiles(tx,ty,tiles,MAX_TILES); + if (!ntiles) return; char text[128]; - float pos[3]; - pos[0] = (tile->bmin[0]+tile->bmax[0])/2.0f; - pos[1] = tile->bmin[1]; - pos[2] = (tile->bmin[2]+tile->bmax[2])/2.0f; - - GLdouble x, y, z; - if (gluProject((GLdouble)pos[0], (GLdouble)pos[1], (GLdouble)pos[2], - model, proj, view, &x, &y, &z)) + for (int i = 0; i < ntiles; ++i) { - snprintf(text,128,"(%d,%d)", tile->tx,tile->ty); - imguiDrawText((int)x, (int)y-25, IMGUI_ALIGN_CENTER, text, imguiRGBA(0,0,0,220)); - snprintf(text,128,"Lean Data:%.1fkB", tile->dataSize/1024.0f); - imguiDrawText((int)x, (int)y-45, IMGUI_ALIGN_CENTER, text, imguiRGBA(0,0,0,128)); - snprintf(text,128,"Compressed:%.1fkB", tile->compressedSize/1024.0f); - imguiDrawText((int)x, (int)y-65, IMGUI_ALIGN_CENTER, text, imguiRGBA(0,0,0,128)); - } + const CompressedTile* tile = tiles[i]; + + float pos[3]; + pos[0] = (tile->bmin[0]+tile->bmax[0])/2.0f; + pos[1] = tile->bmin[1]; + pos[2] = (tile->bmin[2]+tile->bmax[2])/2.0f; + + GLdouble x, y, z; + if (gluProject((GLdouble)pos[0], (GLdouble)pos[1], (GLdouble)pos[2], + model, proj, view, &x, &y, &z)) + { + snprintf(text,128,"(%d,%d)/%d", tile->tx,tile->ty,i); + imguiDrawText((int)x, (int)y-25, IMGUI_ALIGN_CENTER, text, imguiRGBA(0,0,0,220)); + snprintf(text,128,"Lean Data:%.1fkB", tile->dataSize/1024.0f); + imguiDrawText((int)x, (int)y-45, IMGUI_ALIGN_CENTER, text, imguiRGBA(0,0,0,128)); + snprintf(text,128,"Compressed:%.1fkB", tile->compressedSize/1024.0f); + imguiDrawText((int)x, (int)y-65, IMGUI_ALIGN_CENTER, text, imguiRGBA(0,0,0,128)); + } + } } int getRawSize() const @@ -1288,7 +1048,6 @@ public: Sample_TempObstacles::Sample_TempObstacles() : m_keepInterResults(false), m_cacheBuildTimeMs(0), - m_drawPortals(false), m_drawMode(DRAWMODE_NAVMESH), m_maxTiles(0), m_maxPolysPerTile(0), @@ -1420,6 +1179,7 @@ void Sample_TempObstacles::handleDebugMode() valid[DRAWMODE_NAVMESH_PORTALS] = m_navMesh != 0; valid[DRAWMODE_NAVMESH_INVIS] = m_navMesh != 0; valid[DRAWMODE_MESH] = true; + valid[DRAWMODE_CACHE_BOUNDS] = true; } int unavail = 0; @@ -1444,6 +1204,8 @@ void Sample_TempObstacles::handleDebugMode() m_drawMode = DRAWMODE_NAVMESH_NODES; if (imguiCheck("Navmesh Portals", m_drawMode == DRAWMODE_NAVMESH_PORTALS, valid[DRAWMODE_NAVMESH_PORTALS])) m_drawMode = DRAWMODE_NAVMESH_PORTALS; + if (imguiCheck("Cache Bounds", m_drawMode == DRAWMODE_CACHE_BOUNDS, valid[DRAWMODE_CACHE_BOUNDS])) + m_drawMode = DRAWMODE_CACHE_BOUNDS; if (unavail) { @@ -1463,7 +1225,7 @@ void Sample_TempObstacles::handleRender() const float texScale = 1.0f / (m_cellSize * 10.0f); // Draw mesh - if (m_drawMode == DRAWMODE_MESH) + if (m_drawMode != DRAWMODE_NAVMESH_TRANS) { // Draw mesh duDebugDrawTriMeshSlope(&dd, m_geom->getMesh()->getVerts(), m_geom->getMesh()->getVertCount(), @@ -1471,16 +1233,12 @@ void Sample_TempObstacles::handleRender() m_agentMaxSlope, texScale); m_geom->drawOffMeshConnections(&dd); } - else if (m_drawMode != DRAWMODE_NAVMESH_TRANS) + + if (m_drawMode == DRAWMODE_CACHE_BOUNDS) { - // Draw mesh - duDebugDrawTriMesh(&dd, m_geom->getMesh()->getVerts(), m_geom->getMesh()->getVertCount(), - m_geom->getMesh()->getTris(), m_geom->getMesh()->getNormals(), m_geom->getMesh()->getTriCount(), 0, - texScale); - m_geom->drawOffMeshConnections(&dd); + m_tileCache->drawTiles(&dd); } - m_tileCache->drawTiles(&dd); m_obs->draw(&dd); glDepthMask(GL_FALSE); @@ -1507,7 +1265,7 @@ void Sample_TempObstacles::handleRender() m_drawMode == DRAWMODE_NAVMESH_INVIS)) { if (m_drawMode != DRAWMODE_NAVMESH_INVIS) - duDebugDrawNavMeshWithClosedList(&dd, *m_navMesh, *m_navQuery, m_navMeshDrawFlags); + duDebugDrawNavMeshWithClosedList(&dd, *m_navMesh, *m_navQuery, m_navMeshDrawFlags|DU_DRAWNAVMESH_COLOR_TILES); if (m_drawMode == DRAWMODE_NAVMESH_BVTREE) duDebugDrawNavMeshBVTree(&dd, *m_navMesh); if (m_drawMode == DRAWMODE_NAVMESH_PORTALS) @@ -1621,8 +1379,7 @@ void Sample_TempObstacles::addTempObstacle(const float* pos) // Update all touched tiles. - const float borderSize = m_tileCache->getBorderSize(); - const float rad2 = orad + borderSize + 0.001f; + const float rad2 = orad + 0.001f; const float minx = pos[0]-rad2; const float minz = pos[2]-rad2; const float maxx = pos[0]+rad2; @@ -1711,6 +1468,7 @@ bool Sample_TempObstacles::handleBuild() const int tw = (gw + ts-1) / ts; const int th = (gh + ts-1) / ts; + // Generation params. rcConfig cfg; memset(&cfg, 0, sizeof(cfg)); cfg.cs = m_cellSize; @@ -1733,7 +1491,20 @@ bool Sample_TempObstacles::handleBuild() rcVcopy(cfg.bmin, bmin); rcVcopy(cfg.bmax, bmax); - m_tileCache->init(tw*th, cfg, m_agentHeight, m_agentRadius, m_agentMaxClimb); + // Tile cache params. + TileCacheParams tcparams; + rcVcopy(tcparams.orig, bmin); + tcparams.cs = m_cellSize; + tcparams.ch = m_cellHeight; + tcparams.width = (int)m_tileSize; + tcparams.height = (int)m_tileSize; + tcparams.walkableHeight = m_agentHeight; + tcparams.walkableRadius = m_agentRadius; + tcparams.walkableClimb = m_agentMaxClimb; + tcparams.maxSimplificationError = m_edgeMaxError; + tcparams.maxTiles = tw*th*2; + + m_tileCache->init(&tcparams); dtFreeNavMesh(m_navMesh); @@ -1779,43 +1550,30 @@ bool Sample_TempObstacles::handleBuild() { for (int x = 0; x < tw; ++x) { - CompressedTile* tile = rasterizeTile(m_ctx, m_geom, x, y, cfg); - if (!tile) - continue; - - maxBufferSize = rcMax(maxBufferSize, tile->dataSize); - - if (!m_tileCache->addTile(tile)) + static const int MAX_TILES = 32; + CompressedTile* tiles[MAX_TILES]; + int ntiles = rasterizeTileLayers(m_ctx, m_geom, x, y, cfg, tiles, MAX_TILES); + + for (int i = 0; i < ntiles; ++i) { - delete tile; - break; + CompressedTile* tile = tiles[i]; + maxBufferSize = rcMax(maxBufferSize, tile->dataSize); + if (!m_tileCache->addTile(tile)) + { + delete tile; + } } } } - m_tileCache->initCompressionBuffer(maxBufferSize); - m_ctx->stopTimer(RC_TIMER_TOTAL); m_cacheBuildTimeMs = m_ctx->getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f; // Build initial meshes for (int y = 0; y < th; ++y) - { for (int x = 0; x < tw; ++x) - { - int dataSize = 0; - unsigned char* data = m_tileCache->buildNavMeshTile(m_ctx, m_obs, x,y, dataSize); - if (data) - { - m_navMesh->removeTile(m_navMesh->getTileRefAt(x,y),0,0); - // Let the navmesh own the data. - dtStatus status = m_navMesh->addTile(data,dataSize,DT_TILE_FREE_DATA,0,0); - if (dtStatusFailed(status)) - dtFree(data); - } - } - } + m_tileCache->buildNavMeshTilesAt(m_obs, x,y, m_navMesh); m_rebuildTileCount = 0; m_rebuildTime = 0; @@ -1834,7 +1592,7 @@ void Sample_TempObstacles::handleUpdate(const float dt) if (!m_navMesh) return; - m_tileCache->update(dt, m_ctx, m_navMesh, m_obs); + m_tileCache->update(dt, m_navMesh, m_obs); } void Sample_TempObstacles::getTilePos(const float* pos, int& tx, int& ty) diff --git a/RecastDemo/Source/Sample_TileMesh.cpp b/RecastDemo/Source/Sample_TileMesh.cpp index bff0950..c6f8518 100644 --- a/RecastDemo/Source/Sample_TileMesh.cpp +++ b/RecastDemo/Source/Sample_TileMesh.cpp @@ -175,16 +175,12 @@ Sample_TileMesh::Sample_TileMesh() : m_keepInterResults(false), m_buildAll(true), m_totalBuildTimeMs(0), - m_drawPortals(false), m_triareas(0), m_solid(0), m_chf(0), m_cset(0), m_pmesh(0), m_dmesh(0), - m_lset(0), - m_nlcsets(0), - m_nlmeshes(0), m_drawMode(DRAWMODE_NAVMESH), m_maxTiles(0), m_maxPolysPerTile(0), @@ -197,9 +193,6 @@ Sample_TileMesh::Sample_TileMesh() : resetCommonSettings(); memset(m_tileBmin, 0, sizeof(m_tileBmin)); memset(m_tileBmax, 0, sizeof(m_tileBmax)); - - memset(m_lcsets, 0, sizeof(m_lcsets)); - memset(m_lmeshes, 0, sizeof(m_lmeshes)); setTool(new NavMeshTileTool); } @@ -225,23 +218,6 @@ void Sample_TileMesh::cleanup() m_pmesh = 0; rcFreePolyMeshDetail(m_dmesh); m_dmesh = 0; - - rcFreeHeightfieldLayerSet(m_lset); - m_lset = 0; - - for (int i = 0; i < MAX_LAYERS; ++i) - { - rcFreeLayerContourSet(m_lcsets[i]); - m_lcsets[i] = 0; - } - m_nlcsets = 0; - - for (int i = 0; i < MAX_LAYERS; ++i) - { - rcFreeLayerPolyMesh(m_lmeshes[i]); - m_lmeshes[i] = 0; - } - m_nlmeshes = 0; } @@ -490,9 +466,6 @@ void Sample_TileMesh::handleDebugMode() valid[DRAWMODE_CONTOURS] = m_cset != 0; valid[DRAWMODE_POLYMESH] = m_pmesh != 0; valid[DRAWMODE_POLYMESH_DETAIL] = m_dmesh != 0; - valid[DRAWMODE_HEIGHFIELD_LAYERS] = m_lset != 0; - valid[DRAWMODE_LAYER_CONTOURS] = m_nlcsets != 0; - valid[DRAWMODE_LAYER_MESHES] = m_nlmeshes != 0; } int unavail = 0; @@ -539,16 +512,7 @@ void Sample_TileMesh::handleDebugMode() m_drawMode = DRAWMODE_POLYMESH; if (imguiCheck("Poly Mesh Detail", m_drawMode == DRAWMODE_POLYMESH_DETAIL, valid[DRAWMODE_POLYMESH_DETAIL])) m_drawMode = DRAWMODE_POLYMESH_DETAIL; - - imguiSeparatorLine(); - - if (imguiCheck("Heighfield Layers", m_drawMode == DRAWMODE_HEIGHFIELD_LAYERS, valid[DRAWMODE_HEIGHFIELD_LAYERS])) - m_drawMode = DRAWMODE_HEIGHFIELD_LAYERS; - if (imguiCheck("Layer Contours", m_drawMode == DRAWMODE_LAYER_CONTOURS, valid[DRAWMODE_LAYER_CONTOURS])) - m_drawMode = DRAWMODE_LAYER_CONTOURS; - if (imguiCheck("Layer Meshes", m_drawMode == DRAWMODE_LAYER_MESHES, valid[DRAWMODE_LAYER_MESHES])) - m_drawMode = DRAWMODE_LAYER_MESHES; - + if (unavail) { imguiValue("Tick 'Keep Itermediate Results'"); @@ -575,13 +539,7 @@ void Sample_TileMesh::handleRender() m_agentMaxSlope, texScale); m_geom->drawOffMeshConnections(&dd); } - - - -/* duDebugDrawTriMesh(&dd, m_geom->getMesh()->getVerts(), m_geom->getMesh()->getVertCount(), - m_geom->getMesh()->getTris(), m_geom->getMesh()->getNormals(), m_geom->getMesh()->getTriCount(), 0); - m_geom->drawOffMeshConnections(&dd);*/ - + glDepthMask(GL_FALSE); // Draw bounds @@ -598,15 +556,9 @@ void Sample_TileMesh::handleRender() duDebugDrawGridXZ(&dd, bmin[0],bmin[1],bmin[2], tw,th, s, duRGBA(0,0,0,64), 1.0f); // Draw active tile - duDebugDrawBoxWire(&dd, m_tileBmin[0],m_tileBmin[1],m_tileBmin[2], m_tileBmax[0],m_tileBmax[1],m_tileBmax[2], m_tileCol, 1.0f); - -/* if (m_navMesh) - { - duDebugDrawNavMeshWithClosedList(&dd, *m_navMesh, *m_navQuery, m_navMeshDrawFlags); - if (m_drawPortals) - duDebugDrawNavMeshPortals(&dd, *m_navMesh); - }*/ - + duDebugDrawBoxWire(&dd, m_tileBmin[0],m_tileBmin[1],m_tileBmin[2], + m_tileBmax[0],m_tileBmax[1],m_tileBmax[2], m_tileCol, 1.0f); + if (m_navMesh && m_navQuery && (m_drawMode == DRAWMODE_NAVMESH || m_drawMode == DRAWMODE_NAVMESH_TRANS || @@ -688,30 +640,7 @@ void Sample_TileMesh::handleRender() duDebugDrawPolyMeshDetail(&dd, *m_dmesh); glDepthMask(GL_TRUE); } - - if (m_lset && m_drawMode == DRAWMODE_HEIGHFIELD_LAYERS) - { - glDepthMask(GL_FALSE); - duDebugDrawHeightfieldLayersRegions(&dd, *m_lset); - glDepthMask(GL_TRUE); - } - - if (m_nlcsets && m_drawMode == DRAWMODE_LAYER_CONTOURS) - { - glDepthMask(GL_FALSE); - for (int i = 0; i < m_nlcsets; ++i) - duDebugDrawLayerContours(&dd, *m_lcsets[i]); - glDepthMask(GL_TRUE); - } - - if (m_nlmeshes && m_drawMode == DRAWMODE_LAYER_MESHES) - { - glDepthMask(GL_FALSE); - for (int i = 0; i < m_nlmeshes; ++i) - duDebugDrawLayerPolyMesh(&dd, *m_lmeshes[i]); - glDepthMask(GL_TRUE); - } - + m_geom->drawConvexVolumes(&dd); if (m_tool) @@ -725,32 +654,12 @@ void Sample_TileMesh::handleRenderOverlay(double* proj, double* model, int* view GLdouble x, y, z; // Draw start and end point labels -/* if (m_tileBuildTime > 0.0f && gluProject((GLdouble)(m_tileBmin[0]+m_tileBmax[0])/2, (GLdouble)(m_tileBmin[1]+m_tileBmax[1])/2, (GLdouble)(m_tileBmin[2]+m_tileBmax[2])/2, + if (m_tileBuildTime > 0.0f && gluProject((GLdouble)(m_tileBmin[0]+m_tileBmax[0])/2, (GLdouble)(m_tileBmin[1]+m_tileBmax[1])/2, (GLdouble)(m_tileBmin[2]+m_tileBmax[2])/2, model, proj, view, &x, &y, &z)) { char text[32]; snprintf(text,32,"%.3fms / %dTris / %.1fkB", m_tileBuildTime, m_tileTriCount, m_tileMemUsage); imguiDrawText((int)x, (int)y-25, IMGUI_ALIGN_CENTER, text, imguiRGBA(0,0,0,220)); - }*/ - - if (m_lset && m_drawMode == DRAWMODE_HEIGHFIELD_LAYERS) - { - for (int i = 0; i < m_lset->nlayers; ++i) - { - const rcHeightfieldLayer* layer = &m_lset->layers[i]; - unsigned int color = duIntToCol(i+1, 255); - float pos[3]; - rcVcopy(pos, layer->bmin); - pos[1] = (layer->bmin[1] + layer->bmax[1])*0.5f; - if (gluProject((GLdouble)pos[0], (GLdouble)pos[1], (GLdouble)pos[2], model, proj, view, &x, &y, &z)) - { - char text[32]; - snprintf(text,32,"Layer %d", i+1); - imguiDrawRoundedRect(x+10-6,y-6,100,20, 4, duTransCol(color,128)); - imguiDrawText((int)x+10, (int)y, IMGUI_ALIGN_LEFT, text, imguiRGBA(255,255,255,255)); - } - - } } if (m_tool) @@ -852,7 +761,7 @@ void Sample_TileMesh::buildTile(const float* pos) if (data) { // Remove any previous data (navmesh owns and deletes the data). - m_navMesh->removeTile(m_navMesh->getTileRefAt(tx,ty),0,0); + m_navMesh->removeTile(m_navMesh->getTileRefAt(tx,ty,0),0,0); // Let the navmesh own the data. dtStatus status = m_navMesh->addTile(data,dataSize,DT_TILE_FREE_DATA,0,0); @@ -896,7 +805,7 @@ void Sample_TileMesh::removeTile(const float* pos) m_tileCol = duRGBA(128,32,16,64); - m_navMesh->removeTile(m_navMesh->getTileRefAt(tx,ty),0,0); + m_navMesh->removeTile(m_navMesh->getTileRefAt(tx,ty,0),0,0); } void Sample_TileMesh::buildAllTiles() @@ -934,7 +843,7 @@ void Sample_TileMesh::buildAllTiles() if (data) { // Remove any previous data (navmesh owns and deletes the data). - m_navMesh->removeTile(m_navMesh->getTileRefAt(x,y),0,0); + m_navMesh->removeTile(m_navMesh->getTileRefAt(x,y,0),0,0); // Let the navmesh own the data. dtStatus status = m_navMesh->addTile(data,dataSize,DT_TILE_FREE_DATA,0,0); if (dtStatusFailed(status)) @@ -962,7 +871,7 @@ void Sample_TileMesh::removeAllTiles() for (int y = 0; y < th; ++y) for (int x = 0; x < tw; ++x) - m_navMesh->removeTile(m_navMesh->getTileRefAt(x,y),0,0); + m_navMesh->removeTile(m_navMesh->getTileRefAt(x,y,0),0,0); } @@ -1109,7 +1018,7 @@ unsigned char* Sample_TileMesh::buildTileMesh(const int tx, const int ty, const if (!rcErodeWalkableArea(m_ctx, m_cfg.walkableRadius, *m_chf)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode."); - return false; + return 0; } // (Optional) Mark areas. @@ -1142,53 +1051,7 @@ unsigned char* Sample_TileMesh::buildTileMesh(const int tx, const int ty, const return 0; } } - - - // TODO: Remove - // NOTE! This is for heighfield layer testing only, not needed for general build process! - { - m_lset = rcAllocHeightfieldLayerSet(); - if (!m_lset) - { - m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'lset'."); - return 0; - } - if (!rcBuildHeightfieldLayers(m_ctx, *m_chf, m_cfg.borderSize, m_cfg.walkableHeight, *m_lset)) - { - m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build heighfield layers."); - return 0; - } - - m_nlcsets = 0; - m_nlmeshes = 0; - for (int i = 0; i < m_lset->nlayers; ++i) - { - rcBuildLayerRegions(m_ctx, m_lset->layers[i], m_cfg.walkableClimb); - - m_lcsets[m_nlcsets] = rcAllocLayerContourSet(); - if (!rcBuildLayerContours(m_ctx, m_lset->layers[i], - m_cfg.walkableClimb, m_cfg.maxSimplificationError, - *m_lcsets[m_nlcsets])) - { - break; - } - - m_lmeshes[m_nlmeshes] = rcAllocLayerPolyMesh(); - if (!rcBuildLayerPolyMesh(m_ctx, *m_lcsets[m_nlcsets], - m_cfg.maxVertsPerPoly, - *m_lmeshes[m_nlmeshes])) - { - break; - } - - - m_nlcsets++; - m_nlmeshes++; - - } - } - - + // Create contours. m_cset = rcAllocContourSet(); if (!m_cset) @@ -1248,14 +1111,6 @@ unsigned char* Sample_TileMesh::buildTileMesh(const int tx, const int ty, const int navDataSize = 0; if (m_cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON) { - // Remove padding from the polymesh data. TODO: Remove this odditity. - for (int i = 0; i < m_pmesh->nverts; ++i) - { - unsigned short* v = &m_pmesh->verts[i*3]; - v[0] -= (unsigned short)m_cfg.borderSize; - v[2] -= (unsigned short)m_cfg.borderSize; - } - if (m_pmesh->nverts >= 0xffff) { // The vertex indices are ushorts, and cannot point to more than 0xffff vertices. @@ -1311,26 +1166,18 @@ unsigned char* Sample_TileMesh::buildTileMesh(const int tx, const int ty, const params.walkableClimb = m_agentMaxClimb; params.tileX = tx; params.tileY = ty; - rcVcopy(params.bmin, bmin); - rcVcopy(params.bmax, bmax); + params.tileLayer = 0; + rcVcopy(params.bmin, m_pmesh->bmin); + rcVcopy(params.bmax, m_pmesh->bmax); params.cs = m_cfg.cs; params.ch = m_cfg.ch; - params.tileSize = m_cfg.tileSize; + params.buildBvTree = true; if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) { m_ctx->log(RC_LOG_ERROR, "Could not build Detour navmesh."); return 0; - } - - // Restore padding so that the debug visualization is correct. - for (int i = 0; i < m_pmesh->nverts; ++i) - { - unsigned short* v = &m_pmesh->verts[i*3]; - v[0] += (unsigned short)m_cfg.borderSize; - v[2] += (unsigned short)m_cfg.borderSize; - } - + } } m_tileMemUsage = navDataSize/1024.0f;