void dtNavMesh::connectExtLinks(dtMeshTile* tile, dtMeshTile* target, int side) { if (!tile) return; // Connect border links. for (int i = 0; i < tile->header->polyCount; ++i) { dtPoly* poly = &tile->polys[i]; // Create new links. 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; // 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); for (int k = 0; k < nnei; ++k) { unsigned int idx = allocLink(tile); if (idx != DT_NULL_LINK) { dtLink* link = &tile->links[idx]; link->ref = nei[k]; link->edge = (unsigned char)j; link->side = (unsigned char)side; link->next = poly->firstLink; poly->firstLink = idx; // Compress portal limits to a byte value. if (side == 0 || side == 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]); if (tmin > tmax) dtSwap(tmin,tmax); 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) { float tmin = (neia[k*2+0]-va[0]) / (vb[0]-va[0]); float tmax = (neia[k*2+1]-va[0]) / (vb[0]-va[0]); if (tmin > tmax) dtSwap(tmin,tmax); link->bmin = (unsigned char)(dtClamp(tmin, 0.0f, 1.0f)*255.0f); link->bmax = (unsigned char)(dtClamp(tmax, 0.0f, 1.0f)*255.0f); } } } } } }
dtStatus dtBuildTileCacheDistanceField(dtTileCacheAlloc* alloc, dtTileCacheLayer& layer, dtTileCacheDistanceField& dfield) { dtAssert(alloc); const int w = (int)layer.header->width; const int h = (int)layer.header->height; dfield.data = (unsigned short*)alloc->alloc(w*h*sizeof(unsigned short)); if (!dfield.data) { return DT_FAILURE | DT_OUT_OF_MEMORY; } dtTileCacheDistanceField tmpField; tmpField.data = (unsigned short*)alloc->alloc(w*h*sizeof(unsigned short)); if (!tmpField.data) { return DT_FAILURE | DT_OUT_OF_MEMORY; } calculateDistanceField(layer, dfield.data, dfield.maxDist); if (boxBlur(layer, 1, dfield.data, tmpField.data) != dfield.data) { dtSwap(dfield.data, tmpField.data); } alloc->free(tmpField.data); return DT_SUCCESS; }
static bool isectSegAABB(const float* sp, const float* sq, const float* amin, const float* amax, float& tmin, float& tmax) { static const float EPS = 1e-6f; float d[3]; dtVsub(d, sq, sp); tmin = 0; // set to -FLT_MAX to get first hit on line tmax = FLT_MAX; // set to max distance ray can travel (for segment) // For all three slabs for (int i = 0; i < 3; i++) { if (fabsf(d[i]) < EPS) { // Ray is parallel to slab. No hit if origin not within slab if (sp[i] < amin[i] || sp[i] > amax[i]) return false; } else { // Compute intersection t value of ray with near and far plane of slab const float ood = 1.0f / d[i]; float t1 = (amin[i] - sp[i]) * ood; float t2 = (amax[i] - sp[i]) * ood; // Make t1 be intersection with near plane, t2 with far plane if (t1 > t2) dtSwap(t1, t2); // Compute the intersection of slab intersections intervals if (t1 > tmin) tmin = t1; if (t2 < tmax) tmax = t2; // Exit with no collision as soon as slab intersection becomes empty if (tmin > tmax) return false; } } return true; }
dtStatus dtBuildTileCacheRegions(dtTileCacheAlloc* alloc, const int minRegionArea, const int mergeRegionArea, dtTileCacheLayer& layer, dtTileCacheDistanceField dfield) { dtAssert(alloc); const int w = (int)layer.header->width; const int h = (int)layer.header->height; const int size = w*h; dtFixedArray<unsigned short> buf(alloc, size*4); if (!buf) { return DT_FAILURE | DT_OUT_OF_MEMORY; } dtIntArray stack(1024); dtIntArray visited(1024); unsigned short* srcReg = buf; unsigned short* srcDist = buf+size; unsigned short* dstReg = buf+size*2; unsigned short* dstDist = buf+size*3; memset(srcReg, 0, sizeof(unsigned short)*size); memset(srcDist, 0, sizeof(unsigned short)*size); unsigned short regionId = 1; unsigned short level = (dfield.maxDist+1) & ~1; // TODO: Figure better formula, expandIters defines how much the // watershed "overflows" and simplifies the regions. Tying it to // agent radius was usually good indication how greedy it could be. // const int expandIters = 4 + walkableRadius * 2; const int expandIters = 8; while (level > 0) { level = level >= 2 ? level-2 : 0; // Expand current regions until no empty connected cells found. if (expandRegions(expandIters, level, layer, dfield, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) { dtSwap(srcReg, dstReg); dtSwap(srcDist, dstDist); } // Mark new regions with IDs. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const int i=x+y*w; if (dfield.data[i] < level || srcReg[i] != 0 || layer.areas[i] == DT_TILECACHE_NULL_AREA) continue; if (floodRegion(x, y, i, level, regionId, layer, dfield, srcReg, srcDist, stack)) regionId++; } } } // Expand current regions until no empty connected cells found. if (expandRegions(expandIters*8, 0, layer, dfield, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) { dtSwap(srcReg, dstReg); dtSwap(srcDist, dstDist); } dtStatus status = filterSmallRegions(alloc, layer, minRegionArea, mergeRegionArea, regionId, srcReg); if (dtStatusFailed(status)) { return status; } // Write the result out. memcpy(layer.regs, srcReg, sizeof(unsigned short)*size); layer.regCount = regionId; return DT_SUCCESS; }
static unsigned short* expandRegions(int maxIter, unsigned short level, dtTileCacheLayer& layer, dtTileCacheDistanceField& dfield, unsigned short* srcReg, unsigned short* srcDist, unsigned short* dstReg, unsigned short* dstDist, dtIntArray& stack) { const int w = (int)layer.header->width; const int h = (int)layer.header->height; // Find cells revealed by the raised level. stack.resize(0); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const int i = x+y*w; if (dfield.data[i] >= level && srcReg[i] == 0 && layer.areas[i] != DT_TILECACHE_NULL_AREA) { stack.push(x); stack.push(y); stack.push(i); } } } int iter = 0; while (stack.size() > 0) { int failed = 0; memcpy(dstReg, srcReg, sizeof(unsigned short)*w*h); memcpy(dstDist, srcDist, sizeof(unsigned short)*w*h); for (int j = 0; j < stack.size(); j += 3) { int x = stack[j+0]; int y = stack[j+1]; int i = stack[j+2]; if (i < 0) { failed++; continue; } unsigned short r = srcReg[i]; unsigned short d2 = 0xffff; const unsigned char area = layer.areas[i]; for (int dir = 0; dir < 4; ++dir) { const int ax = x + getDirOffsetX(dir); const int ay = y + getDirOffsetY(dir); const int ai = ax+ay*w; if (ax >= 0 && ax < w && ay >= 0 && ay < h && isConnected(layer, i, dir)) { if (layer.areas[ai] != area) continue; if (srcReg[ai] > 0) { if ((int)srcDist[ai]+2 < (int)d2) { r = srcReg[ai]; d2 = srcDist[ai]+2; } } } } if (r) { stack[j+2] = -1; // mark as used dstReg[i] = r; dstDist[i] = d2; } else { failed++; } } // rcSwap source and dest. dtSwap(srcReg, dstReg); dtSwap(srcDist, dstDist); if (failed*3 == stack.size()) break; if (level > 0) { ++iter; if (iter >= maxIter) break; } } return srcReg; }