Update Files

This commit is contained in:
2025-01-22 16:18:30 +01:00
parent ed4603cf95
commit a36294b518
16718 changed files with 2960346 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,137 @@
//
// 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 <float.h>
#include <string.h>
#include "DetourLocalBoundary.h"
#include "DetourNavMeshQuery.h"
#include "DetourCommon.h"
#include "DetourAssert.h"
dtLocalBoundary::dtLocalBoundary() :
m_nsegs(0),
m_npolys(0)
{
dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX);
}
dtLocalBoundary::~dtLocalBoundary()
{
}
void dtLocalBoundary::reset()
{
dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX);
m_npolys = 0;
m_nsegs = 0;
}
void dtLocalBoundary::addSegment(const float dist, const float* s)
{
// Insert neighbour based on the distance.
Segment* seg = 0;
if (!m_nsegs)
{
// First, trivial accept.
seg = &m_segs[0];
}
else if (dist >= m_segs[m_nsegs-1].d)
{
// Further than the last segment, skip.
if (m_nsegs >= MAX_LOCAL_SEGS)
return;
// Last, trivial accept.
seg = &m_segs[m_nsegs];
}
else
{
// Insert inbetween.
int i;
for (i = 0; i < m_nsegs; ++i)
if (dist <= m_segs[i].d)
break;
const int tgt = i+1;
const int n = dtMin(m_nsegs-i, MAX_LOCAL_SEGS-tgt);
dtAssert(tgt+n <= MAX_LOCAL_SEGS);
if (n > 0)
memmove(&m_segs[tgt], &m_segs[i], sizeof(Segment)*n);
seg = &m_segs[i];
}
seg->d = dist;
memcpy(seg->s, s, sizeof(float)*6);
if (m_nsegs < MAX_LOCAL_SEGS)
m_nsegs++;
}
void dtLocalBoundary::update(dtPolyRef ref, const float* pos, const float collisionQueryRange,
dtNavMeshQuery* navquery, const dtQueryFilter* filter)
{
static const int MAX_SEGS_PER_POLY = DT_VERTS_PER_POLYGON*3;
if (!ref)
{
dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX);
m_nsegs = 0;
m_npolys = 0;
return;
}
dtVcopy(m_center, pos);
// First query non-overlapping polygons.
navquery->findLocalNeighbourhood(ref, pos, collisionQueryRange,
filter, m_polys, 0, &m_npolys, MAX_LOCAL_POLYS);
// Secondly, store all polygon edges.
m_nsegs = 0;
float segs[MAX_SEGS_PER_POLY*6];
int nsegs = 0;
for (int j = 0; j < m_npolys; ++j)
{
navquery->getPolyWallSegments(m_polys[j], filter, segs, 0, &nsegs, MAX_SEGS_PER_POLY);
for (int k = 0; k < nsegs; ++k)
{
const float* s = &segs[k*6];
// Skip too distant segments.
float tseg;
const float distSqr = dtDistancePtSegSqr2D(pos, s, s+3, tseg);
if (distSqr > dtSqr(collisionQueryRange))
continue;
addSegment(distSqr, s);
}
}
}
bool dtLocalBoundary::isValid(dtNavMeshQuery* navquery, const dtQueryFilter* filter)
{
if (!m_npolys)
return false;
// Check that all polygons still pass query filter.
for (int i = 0; i < m_npolys; ++i)
{
if (!navquery->isValidPolyRef(m_polys[i], filter))
return false;
}
return true;
}

View File

@ -0,0 +1,619 @@
//
// 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 "DetourObstacleAvoidance.h"
#include "DetourCommon.h"
#include "DetourMath.h"
#include "DetourAlloc.h"
#include "DetourAssert.h"
#include <string.h>
#include <float.h>
#include <new>
static const float DT_PI = 3.14159265f;
static int sweepCircleCircle(const float* c0, const float r0, const float* v,
const float* c1, const float r1,
float& tmin, float& tmax)
{
static const float EPS = 0.0001f;
float s[3];
dtVsub(s,c1,c0);
float r = r0+r1;
float c = dtVdot2D(s,s) - r*r;
float a = dtVdot2D(v,v);
if (a < EPS) return 0; // not moving
// Overlap, calc time to exit.
float b = dtVdot2D(v,s);
float d = b*b - a*c;
if (d < 0.0f) return 0; // no intersection.
a = 1.0f / a;
const float rd = dtMathSqrtf(d);
tmin = (b - rd) * a;
tmax = (b + rd) * a;
return 1;
}
static int isectRaySeg(const float* ap, const float* u,
const float* bp, const float* bq,
float& t)
{
float v[3], w[3];
dtVsub(v,bq,bp);
dtVsub(w,ap,bp);
float d = dtVperp2D(u,v);
if (dtMathFabsf(d) < 1e-6f) return 0;
d = 1.0f/d;
t = dtVperp2D(v,w) * d;
if (t < 0 || t > 1) return 0;
float s = dtVperp2D(u,w) * d;
if (s < 0 || s > 1) return 0;
return 1;
}
dtObstacleAvoidanceDebugData* dtAllocObstacleAvoidanceDebugData()
{
void* mem = dtAlloc(sizeof(dtObstacleAvoidanceDebugData), DT_ALLOC_PERM);
if (!mem) return 0;
return new(mem) dtObstacleAvoidanceDebugData;
}
void dtFreeObstacleAvoidanceDebugData(dtObstacleAvoidanceDebugData* ptr)
{
if (!ptr) return;
ptr->~dtObstacleAvoidanceDebugData();
dtFree(ptr);
}
dtObstacleAvoidanceDebugData::dtObstacleAvoidanceDebugData() :
m_nsamples(0),
m_maxSamples(0),
m_vel(0),
m_ssize(0),
m_pen(0),
m_vpen(0),
m_vcpen(0),
m_spen(0),
m_tpen(0)
{
}
dtObstacleAvoidanceDebugData::~dtObstacleAvoidanceDebugData()
{
dtFree(m_vel);
dtFree(m_ssize);
dtFree(m_pen);
dtFree(m_vpen);
dtFree(m_vcpen);
dtFree(m_spen);
dtFree(m_tpen);
}
bool dtObstacleAvoidanceDebugData::init(const int maxSamples)
{
dtAssert(maxSamples);
m_maxSamples = maxSamples;
m_vel = (float*)dtAlloc(sizeof(float)*3*m_maxSamples, DT_ALLOC_PERM);
if (!m_vel)
return false;
m_pen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
if (!m_pen)
return false;
m_ssize = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
if (!m_ssize)
return false;
m_vpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
if (!m_vpen)
return false;
m_vcpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
if (!m_vcpen)
return false;
m_spen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
if (!m_spen)
return false;
m_tpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
if (!m_tpen)
return false;
return true;
}
void dtObstacleAvoidanceDebugData::reset()
{
m_nsamples = 0;
}
void dtObstacleAvoidanceDebugData::addSample(const float* vel, const float ssize, const float pen,
const float vpen, const float vcpen, const float spen, const float tpen)
{
if (m_nsamples >= m_maxSamples)
return;
dtAssert(m_vel);
dtAssert(m_ssize);
dtAssert(m_pen);
dtAssert(m_vpen);
dtAssert(m_vcpen);
dtAssert(m_spen);
dtAssert(m_tpen);
dtVcopy(&m_vel[m_nsamples*3], vel);
m_ssize[m_nsamples] = ssize;
m_pen[m_nsamples] = pen;
m_vpen[m_nsamples] = vpen;
m_vcpen[m_nsamples] = vcpen;
m_spen[m_nsamples] = spen;
m_tpen[m_nsamples] = tpen;
m_nsamples++;
}
static void normalizeArray(float* arr, const int n)
{
// Normalize penaly range.
float minPen = FLT_MAX;
float maxPen = -FLT_MAX;
for (int i = 0; i < n; ++i)
{
minPen = dtMin(minPen, arr[i]);
maxPen = dtMax(maxPen, arr[i]);
}
const float penRange = maxPen-minPen;
const float s = penRange > 0.001f ? (1.0f / penRange) : 1;
for (int i = 0; i < n; ++i)
arr[i] = dtClamp((arr[i]-minPen)*s, 0.0f, 1.0f);
}
void dtObstacleAvoidanceDebugData::normalizeSamples()
{
normalizeArray(m_pen, m_nsamples);
normalizeArray(m_vpen, m_nsamples);
normalizeArray(m_vcpen, m_nsamples);
normalizeArray(m_spen, m_nsamples);
normalizeArray(m_tpen, m_nsamples);
}
dtObstacleAvoidanceQuery* dtAllocObstacleAvoidanceQuery()
{
void* mem = dtAlloc(sizeof(dtObstacleAvoidanceQuery), DT_ALLOC_PERM);
if (!mem) return 0;
return new(mem) dtObstacleAvoidanceQuery;
}
void dtFreeObstacleAvoidanceQuery(dtObstacleAvoidanceQuery* ptr)
{
if (!ptr) return;
ptr->~dtObstacleAvoidanceQuery();
dtFree(ptr);
}
dtObstacleAvoidanceQuery::dtObstacleAvoidanceQuery() :
m_invHorizTime(0),
m_vmax(0),
m_invVmax(0),
m_maxCircles(0),
m_circles(0),
m_ncircles(0),
m_maxSegments(0),
m_segments(0),
m_nsegments(0)
{
}
dtObstacleAvoidanceQuery::~dtObstacleAvoidanceQuery()
{
dtFree(m_circles);
dtFree(m_segments);
}
bool dtObstacleAvoidanceQuery::init(const int maxCircles, const int maxSegments)
{
m_maxCircles = maxCircles;
m_ncircles = 0;
m_circles = (dtObstacleCircle*)dtAlloc(sizeof(dtObstacleCircle)*m_maxCircles, DT_ALLOC_PERM);
if (!m_circles)
return false;
memset(m_circles, 0, sizeof(dtObstacleCircle)*m_maxCircles);
m_maxSegments = maxSegments;
m_nsegments = 0;
m_segments = (dtObstacleSegment*)dtAlloc(sizeof(dtObstacleSegment)*m_maxSegments, DT_ALLOC_PERM);
if (!m_segments)
return false;
memset(m_segments, 0, sizeof(dtObstacleSegment)*m_maxSegments);
return true;
}
void dtObstacleAvoidanceQuery::reset()
{
m_ncircles = 0;
m_nsegments = 0;
}
void dtObstacleAvoidanceQuery::addCircle(const float* pos, const float rad,
const float* vel, const float* dvel)
{
if (m_ncircles >= m_maxCircles)
return;
dtObstacleCircle* cir = &m_circles[m_ncircles++];
dtVcopy(cir->p, pos);
cir->rad = rad;
dtVcopy(cir->vel, vel);
dtVcopy(cir->dvel, dvel);
}
void dtObstacleAvoidanceQuery::addSegment(const float* p, const float* q)
{
if (m_nsegments >= m_maxSegments)
return;
dtObstacleSegment* seg = &m_segments[m_nsegments++];
dtVcopy(seg->p, p);
dtVcopy(seg->q, q);
}
void dtObstacleAvoidanceQuery::prepare(const float* pos, const float* dvel)
{
// Prepare obstacles
for (int i = 0; i < m_ncircles; ++i)
{
dtObstacleCircle* cir = &m_circles[i];
// Side
const float* pa = pos;
const float* pb = cir->p;
const float orig[3] = {0,0,0};
float dv[3];
dtVsub(cir->dp,pb,pa);
dtVnormalize(cir->dp);
dtVsub(dv, cir->dvel, dvel);
const float a = dtTriArea2D(orig, cir->dp,dv);
if (a < 0.01f)
{
cir->np[0] = -cir->dp[2];
cir->np[2] = cir->dp[0];
}
else
{
cir->np[0] = cir->dp[2];
cir->np[2] = -cir->dp[0];
}
}
for (int i = 0; i < m_nsegments; ++i)
{
dtObstacleSegment* seg = &m_segments[i];
// Precalc if the agent is really close to the segment.
const float r = 0.01f;
float t;
seg->touch = dtDistancePtSegSqr2D(pos, seg->p, seg->q, t) < dtSqr(r);
}
}
/* Calculate the collision penalty for a given velocity vector
*
* @param vcand sampled velocity
* @param dvel desired velocity
* @param minPenalty threshold penalty for early out
*/
float dtObstacleAvoidanceQuery::processSample(const float* vcand, const float cs,
const float* pos, const float rad,
const float* vel, const float* dvel,
const float minPenalty,
dtObstacleAvoidanceDebugData* debug)
{
// penalty for straying away from the desired and current velocities
const float vpen = m_params.weightDesVel * (dtVdist2D(vcand, dvel) * m_invVmax);
const float vcpen = m_params.weightCurVel * (dtVdist2D(vcand, vel) * m_invVmax);
// find the threshold hit time to bail out based on the early out penalty
// (see how the penalty is calculated below to understnad)
float minPen = minPenalty - vpen - vcpen;
float tThresold = (m_params.weightToi / minPen - 0.1f) * m_params.horizTime;
if (tThresold - m_params.horizTime > -FLT_EPSILON)
return minPenalty; // already too much
// Find min time of impact and exit amongst all obstacles.
float tmin = m_params.horizTime;
float side = 0;
int nside = 0;
for (int i = 0; i < m_ncircles; ++i)
{
const dtObstacleCircle* cir = &m_circles[i];
// RVO
float vab[3];
dtVscale(vab, vcand, 2);
dtVsub(vab, vab, vel);
dtVsub(vab, vab, cir->vel);
// Side
side += dtClamp(dtMin(dtVdot2D(cir->dp,vab)*0.5f+0.5f, dtVdot2D(cir->np,vab)*2), 0.0f, 1.0f);
nside++;
float htmin = 0, htmax = 0;
if (!sweepCircleCircle(pos,rad, vab, cir->p,cir->rad, htmin, htmax))
continue;
// Handle overlapping obstacles.
if (htmin < 0.0f && htmax > 0.0f)
{
// Avoid more when overlapped.
htmin = -htmin * 0.5f;
}
if (htmin >= 0.0f)
{
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
if (htmin < tmin)
{
tmin = htmin;
if (tmin < tThresold)
return minPenalty;
}
}
}
for (int i = 0; i < m_nsegments; ++i)
{
const dtObstacleSegment* seg = &m_segments[i];
float htmin = 0;
if (seg->touch)
{
// Special case when the agent is very close to the segment.
float sdir[3], snorm[3];
dtVsub(sdir, seg->q, seg->p);
snorm[0] = -sdir[2];
snorm[2] = sdir[0];
// If the velocity is pointing towards the segment, no collision.
if (dtVdot2D(snorm, vcand) < 0.0f)
continue;
// Else immediate collision.
htmin = 0.0f;
}
else
{
if (!isectRaySeg(pos, vcand, seg->p, seg->q, htmin))
continue;
}
// Avoid less when facing walls.
htmin *= 2.0f;
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
if (htmin < tmin)
{
tmin = htmin;
if (tmin < tThresold)
return minPenalty;
}
}
// Normalize side bias, to prevent it dominating too much.
if (nside)
side /= nside;
const float spen = m_params.weightSide * side;
const float tpen = m_params.weightToi * (1.0f/(0.1f+tmin*m_invHorizTime));
const float penalty = vpen + vcpen + spen + tpen;
// Store different penalties for debug viewing
if (debug)
debug->addSample(vcand, cs, penalty, vpen, vcpen, spen, tpen);
return penalty;
}
int dtObstacleAvoidanceQuery::sampleVelocityGrid(const float* pos, const float rad, const float vmax,
const float* vel, const float* dvel, float* nvel,
const dtObstacleAvoidanceParams* params,
dtObstacleAvoidanceDebugData* debug)
{
prepare(pos, dvel);
memcpy(&m_params, params, sizeof(dtObstacleAvoidanceParams));
m_invHorizTime = 1.0f / m_params.horizTime;
m_vmax = vmax;
m_invVmax = vmax > 0 ? 1.0f / vmax : FLT_MAX;
dtVset(nvel, 0,0,0);
if (debug)
debug->reset();
const float cvx = dvel[0] * m_params.velBias;
const float cvz = dvel[2] * m_params.velBias;
const float cs = vmax * 2 * (1 - m_params.velBias) / (float)(m_params.gridSize-1);
const float half = (m_params.gridSize-1)*cs*0.5f;
float minPenalty = FLT_MAX;
int ns = 0;
for (int y = 0; y < m_params.gridSize; ++y)
{
for (int x = 0; x < m_params.gridSize; ++x)
{
float vcand[3];
vcand[0] = cvx + x*cs - half;
vcand[1] = 0;
vcand[2] = cvz + y*cs - half;
if (dtSqr(vcand[0])+dtSqr(vcand[2]) > dtSqr(vmax+cs/2)) continue;
const float penalty = processSample(vcand, cs, pos,rad,vel,dvel, minPenalty, debug);
ns++;
if (penalty < minPenalty)
{
minPenalty = penalty;
dtVcopy(nvel, vcand);
}
}
}
return ns;
}
// vector normalization that ignores the y-component.
inline void dtNormalize2D(float* v)
{
float d = dtMathSqrtf(v[0] * v[0] + v[2] * v[2]);
if (d==0)
return;
d = 1.0f / d;
v[0] *= d;
v[2] *= d;
}
// vector normalization that ignores the y-component.
inline void dtRorate2D(float* dest, const float* v, float ang)
{
float c = cosf(ang);
float s = sinf(ang);
dest[0] = v[0]*c - v[2]*s;
dest[2] = v[0]*s + v[2]*c;
dest[1] = v[1];
}
int dtObstacleAvoidanceQuery::sampleVelocityAdaptive(const float* pos, const float rad, const float vmax,
const float* vel, const float* dvel, float* nvel,
const dtObstacleAvoidanceParams* params,
dtObstacleAvoidanceDebugData* debug)
{
prepare(pos, dvel);
memcpy(&m_params, params, sizeof(dtObstacleAvoidanceParams));
m_invHorizTime = 1.0f / m_params.horizTime;
m_vmax = vmax;
m_invVmax = vmax > 0 ? 1.0f / vmax : FLT_MAX;
dtVset(nvel, 0,0,0);
if (debug)
debug->reset();
// Build sampling pattern aligned to desired velocity.
float pat[(DT_MAX_PATTERN_DIVS*DT_MAX_PATTERN_RINGS+1)*2];
int npat = 0;
const int ndivs = (int)m_params.adaptiveDivs;
const int nrings= (int)m_params.adaptiveRings;
const int depth = (int)m_params.adaptiveDepth;
const int nd = dtClamp(ndivs, 1, DT_MAX_PATTERN_DIVS);
const int nr = dtClamp(nrings, 1, DT_MAX_PATTERN_RINGS);
const float da = (1.0f/nd) * DT_PI*2;
const float ca = cosf(da);
const float sa = sinf(da);
// desired direction
float ddir[6];
dtVcopy(ddir, dvel);
dtNormalize2D(ddir);
dtRorate2D (ddir+3, ddir, da*0.5f); // rotated by da/2
// Always add sample at zero
pat[npat*2+0] = 0;
pat[npat*2+1] = 0;
npat++;
for (int j = 0; j < nr; ++j)
{
const float r = (float)(nr-j)/(float)nr;
pat[npat*2+0] = ddir[(j%2)*3] * r;
pat[npat*2+1] = ddir[(j%2)*3+2] * r;
float* last1 = pat + npat*2;
float* last2 = last1;
npat++;
for (int i = 1; i < nd-1; i+=2)
{
// get next point on the "right" (rotate CW)
pat[npat*2+0] = last1[0]*ca + last1[1]*sa;
pat[npat*2+1] = -last1[0]*sa + last1[1]*ca;
// get next point on the "left" (rotate CCW)
pat[npat*2+2] = last2[0]*ca - last2[1]*sa;
pat[npat*2+3] = last2[0]*sa + last2[1]*ca;
last1 = pat + npat*2;
last2 = last1 + 2;
npat += 2;
}
if ((nd&1) == 0)
{
pat[npat*2+2] = last2[0]*ca - last2[1]*sa;
pat[npat*2+3] = last2[0]*sa + last2[1]*ca;
npat++;
}
}
// Start sampling.
float cr = vmax * (1.0f - m_params.velBias);
float res[3];
dtVset(res, dvel[0] * m_params.velBias, 0, dvel[2] * m_params.velBias);
int ns = 0;
for (int k = 0; k < depth; ++k)
{
float minPenalty = FLT_MAX;
float bvel[3];
dtVset(bvel, 0,0,0);
for (int i = 0; i < npat; ++i)
{
float vcand[3];
vcand[0] = res[0] + pat[i*2+0]*cr;
vcand[1] = 0;
vcand[2] = res[2] + pat[i*2+1]*cr;
if (dtSqr(vcand[0])+dtSqr(vcand[2]) > dtSqr(vmax+0.001f)) continue;
const float penalty = processSample(vcand,cr/10, pos,rad,vel,dvel, minPenalty, debug);
ns++;
if (penalty < minPenalty)
{
minPenalty = penalty;
dtVcopy(bvel, vcand);
}
}
dtVcopy(res, bvel);
cr *= 0.5f;
}
dtVcopy(nvel, res);
return ns;
}

View File

@ -0,0 +1,597 @@
//
// 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 <string.h>
#include "DetourPathCorridor.h"
#include "DetourNavMeshQuery.h"
#include "DetourCommon.h"
#include "DetourAssert.h"
#include "DetourAlloc.h"
int dtMergeCorridorStartMoved(dtPolyRef* path, const int npath, const int maxPath,
const dtPolyRef* visited, const int nvisited)
{
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
for (int i = npath-1; i >= 0; --i)
{
bool found = false;
for (int j = nvisited-1; j >= 0; --j)
{
if (path[i] == visited[j])
{
furthestPath = i;
furthestVisited = j;
found = true;
}
}
if (found)
break;
}
// If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited == -1)
return npath;
// Concatenate paths.
// Adjust beginning of the buffer to include the visited.
const int req = nvisited - furthestVisited;
const int orig = dtMin(furthestPath+1, npath);
int size = dtMax(0, npath-orig);
if (req+size > maxPath)
size = maxPath-req;
if (size)
memmove(path+req, path+orig, size*sizeof(dtPolyRef));
// Store visited
for (int i = 0; i < req; ++i)
path[i] = visited[(nvisited-1)-i];
return req+size;
}
int dtMergeCorridorEndMoved(dtPolyRef* path, const int npath, const int maxPath,
const dtPolyRef* visited, const int nvisited)
{
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
for (int i = 0; i < npath; ++i)
{
bool found = false;
for (int j = nvisited-1; j >= 0; --j)
{
if (path[i] == visited[j])
{
furthestPath = i;
furthestVisited = j;
found = true;
}
}
if (found)
break;
}
// If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited == -1)
return npath;
// Concatenate paths.
const int ppos = furthestPath+1;
const int vpos = furthestVisited+1;
const int count = dtMin(nvisited-vpos, maxPath-ppos);
dtAssert(ppos+count <= maxPath);
if (count)
memcpy(path+ppos, visited+vpos, sizeof(dtPolyRef)*count);
return ppos+count;
}
int dtMergeCorridorStartShortcut(dtPolyRef* path, const int npath, const int maxPath,
const dtPolyRef* visited, const int nvisited)
{
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
for (int i = npath-1; i >= 0; --i)
{
bool found = false;
for (int j = nvisited-1; j >= 0; --j)
{
if (path[i] == visited[j])
{
furthestPath = i;
furthestVisited = j;
found = true;
}
}
if (found)
break;
}
// If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited == -1)
return npath;
// Concatenate paths.
// Adjust beginning of the buffer to include the visited.
const int req = furthestVisited;
if (req <= 0)
return npath;
const int orig = furthestPath;
int size = dtMax(0, npath-orig);
if (req+size > maxPath)
size = maxPath-req;
if (size)
memmove(path+req, path+orig, size*sizeof(dtPolyRef));
// Store visited
for (int i = 0; i < req; ++i)
path[i] = visited[i];
return req+size;
}
/**
@class dtPathCorridor
@par
The corridor is loaded with a path, usually obtained from a #dtNavMeshQuery::findPath() query. The corridor
is then used to plan local movement, with the corridor automatically updating as needed to deal with inaccurate
agent locomotion.
Example of a common use case:
-# Construct the corridor object and call #init() to allocate its path buffer.
-# Obtain a path from a #dtNavMeshQuery object.
-# Use #reset() to set the agent's current position. (At the beginning of the path.)
-# Use #setCorridor() to load the path and target.
-# Use #findCorners() to plan movement. (This handles dynamic path straightening.)
-# Use #movePosition() to feed agent movement back into the corridor. (The corridor will automatically adjust as needed.)
-# If the target is moving, use #moveTargetPosition() to update the end of the corridor.
(The corridor will automatically adjust as needed.)
-# Repeat the previous 3 steps to continue to move the agent.
The corridor position and target are always constrained to the navigation mesh.
One of the difficulties in maintaining a path is that floating point errors, locomotion inaccuracies, and/or local
steering can result in the agent crossing the boundary of the path corridor, temporarily invalidating the path.
This class uses local mesh queries to detect and update the corridor as needed to handle these types of issues.
The fact that local mesh queries are used to move the position and target locations results in two beahviors that
need to be considered:
Every time a move function is used there is a chance that the path will become non-optimial. Basically, the further
the target is moved from its original location, and the further the position is moved outside the original corridor,
the more likely the path will become non-optimal. This issue can be addressed by periodically running the
#optimizePathTopology() and #optimizePathVisibility() methods.
All local mesh queries have distance limitations. (Review the #dtNavMeshQuery methods for details.) So the most accurate
use case is to move the position and target in small increments. If a large increment is used, then the corridor
may not be able to accurately find the new location. Because of this limiation, if a position is moved in a large
increment, then compare the desired and resulting polygon references. If the two do not match, then path replanning
may be needed. E.g. If you move the target, check #getLastPoly() to see if it is the expected polygon.
*/
dtPathCorridor::dtPathCorridor() :
m_path(0),
m_npath(0),
m_maxPath(0)
{
}
dtPathCorridor::~dtPathCorridor()
{
dtFree(m_path);
}
/// @par
///
/// @warning Cannot be called more than once.
bool dtPathCorridor::init(const int maxPath)
{
dtAssert(!m_path);
m_path = (dtPolyRef*)dtAlloc(sizeof(dtPolyRef)*maxPath, DT_ALLOC_PERM);
if (!m_path)
return false;
m_npath = 0;
m_maxPath = maxPath;
return true;
}
/// @par
///
/// Essentially, the corridor is set of one polygon in size with the target
/// equal to the position.
void dtPathCorridor::reset(dtPolyRef ref, const float* pos)
{
dtAssert(m_path);
dtVcopy(m_pos, pos);
dtVcopy(m_target, pos);
m_path[0] = ref;
m_npath = 1;
}
/**
@par
This is the function used to plan local movement within the corridor. One or more corners can be
detected in order to plan movement. It performs essentially the same function as #dtNavMeshQuery::findStraightPath.
Due to internal optimizations, the maximum number of corners returned will be (@p maxCorners - 1)
For example: If the buffers are sized to hold 10 corners, the function will never return more than 9 corners.
So if 10 corners are needed, the buffers should be sized for 11 corners.
If the target is within range, it will be the last corner and have a polygon reference id of zero.
*/
int dtPathCorridor::findCorners(float* cornerVerts, unsigned char* cornerFlags,
dtPolyRef* cornerPolys, const int maxCorners,
dtNavMeshQuery* navquery, const dtQueryFilter* /*filter*/)
{
dtAssert(m_path);
dtAssert(m_npath);
static const float MIN_TARGET_DIST = 0.01f;
int ncorners = 0;
navquery->findStraightPath(m_pos, m_target, m_path, m_npath,
cornerVerts, cornerFlags, cornerPolys, &ncorners, maxCorners);
// Prune points in the beginning of the path which are too close.
while (ncorners)
{
if ((cornerFlags[0] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) ||
dtVdist2DSqr(&cornerVerts[0], m_pos) > dtSqr(MIN_TARGET_DIST))
break;
ncorners--;
if (ncorners)
{
memmove(cornerFlags, cornerFlags+1, sizeof(unsigned char)*ncorners);
memmove(cornerPolys, cornerPolys+1, sizeof(dtPolyRef)*ncorners);
memmove(cornerVerts, cornerVerts+3, sizeof(float)*3*ncorners);
}
}
// Prune points after an off-mesh connection.
for (int i = 0; i < ncorners; ++i)
{
if (cornerFlags[i] & DT_STRAIGHTPATH_OFFMESH_CONNECTION)
{
ncorners = i+1;
break;
}
}
return ncorners;
}
/**
@par
Inaccurate locomotion or dynamic obstacle avoidance can force the argent position significantly outside the
original corridor. Over time this can result in the formation of a non-optimal corridor. Non-optimal paths can
also form near the corners of tiles.
This function uses an efficient local visibility search to try to optimize the corridor
between the current position and @p next.
The corridor will change only if @p next is visible from the current position and moving directly toward the point
is better than following the existing path.
The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency
of the call to match the needs to the agent.
This function is not suitable for long distance searches.
*/
void dtPathCorridor::optimizePathVisibility(const float* next, const float pathOptimizationRange,
dtNavMeshQuery* navquery, const dtQueryFilter* filter)
{
dtAssert(m_path);
// Clamp the ray to max distance.
float goal[3];
dtVcopy(goal, next);
float dist = dtVdist2D(m_pos, goal);
// If too close to the goal, do not try to optimize.
if (dist < 0.01f)
return;
// Overshoot a little. This helps to optimize open fields in tiled meshes.
dist = dtMin(dist+0.01f, pathOptimizationRange);
// Adjust ray length.
float delta[3];
dtVsub(delta, goal, m_pos);
dtVmad(goal, m_pos, delta, pathOptimizationRange/dist);
static const int MAX_RES = 32;
dtPolyRef res[MAX_RES];
float t, norm[3];
int nres = 0;
navquery->raycast(m_path[0], m_pos, goal, filter, &t, norm, res, &nres, MAX_RES);
if (nres > 1 && t > 0.99f)
{
m_npath = dtMergeCorridorStartShortcut(m_path, m_npath, m_maxPath, res, nres);
}
}
/**
@par
Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the
original corridor. Over time this can result in the formation of a non-optimal corridor. This function will use a
local area path search to try to re-optimize the corridor.
The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency of
the call to match the needs to the agent.
*/
bool dtPathCorridor::optimizePathTopology(dtNavMeshQuery* navquery, const dtQueryFilter* filter)
{
dtAssert(navquery);
dtAssert(filter);
dtAssert(m_path);
if (m_npath < 3)
return false;
static const int MAX_ITER = 32;
static const int MAX_RES = 32;
dtPolyRef res[MAX_RES];
int nres = 0;
navquery->initSlicedFindPath(m_path[0], m_path[m_npath-1], m_pos, m_target, filter);
navquery->updateSlicedFindPath(MAX_ITER, 0);
dtStatus status = navquery->finalizeSlicedFindPathPartial(m_path, m_npath, res, &nres, MAX_RES);
if (dtStatusSucceed(status) && nres > 0)
{
m_npath = dtMergeCorridorStartShortcut(m_path, m_npath, m_maxPath, res, nres);
return true;
}
return false;
}
bool dtPathCorridor::moveOverOffmeshConnection(dtPolyRef offMeshConRef, dtPolyRef* refs,
float* startPos, float* endPos,
dtNavMeshQuery* navquery)
{
dtAssert(navquery);
dtAssert(m_path);
dtAssert(m_npath);
// Advance the path up to and over the off-mesh connection.
dtPolyRef prevRef = 0, polyRef = m_path[0];
int npos = 0;
while (npos < m_npath && polyRef != offMeshConRef)
{
prevRef = polyRef;
polyRef = m_path[npos];
npos++;
}
if (npos == m_npath)
{
// Could not find offMeshConRef
return false;
}
// Prune path
for (int i = npos; i < m_npath; ++i)
m_path[i-npos] = m_path[i];
m_npath -= npos;
refs[0] = prevRef;
refs[1] = polyRef;
const dtNavMesh* nav = navquery->getAttachedNavMesh();
dtAssert(nav);
dtStatus status = nav->getOffMeshConnectionPolyEndPoints(refs[0], refs[1], startPos, endPos);
if (dtStatusSucceed(status))
{
dtVcopy(m_pos, endPos);
return true;
}
return false;
}
/**
@par
Behavior:
- The movement is constrained to the surface of the navigation mesh.
- The corridor is automatically adjusted (shorted or lengthened) in order to remain valid.
- The new position will be located in the adjusted corridor's first polygon.
The expected use case is that the desired position will be 'near' the current corridor. What is considered 'near'
depends on local polygon density, query search half extents, etc.
The resulting position will differ from the desired position if the desired position is not on the navigation mesh,
or it can't be reached using a local search.
*/
bool dtPathCorridor::movePosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter)
{
dtAssert(m_path);
dtAssert(m_npath);
// Move along navmesh and update new position.
float result[3];
static const int MAX_VISITED = 16;
dtPolyRef visited[MAX_VISITED];
int nvisited = 0;
dtStatus status = navquery->moveAlongSurface(m_path[0], m_pos, npos, filter,
result, visited, &nvisited, MAX_VISITED);
if (dtStatusSucceed(status)) {
m_npath = dtMergeCorridorStartMoved(m_path, m_npath, m_maxPath, visited, nvisited);
// Adjust the position to stay on top of the navmesh.
float h = m_pos[1];
navquery->getPolyHeight(m_path[0], result, &h);
result[1] = h;
dtVcopy(m_pos, result);
return true;
}
return false;
}
/**
@par
Behavior:
- The movement is constrained to the surface of the navigation mesh.
- The corridor is automatically adjusted (shorted or lengthened) in order to remain valid.
- The new target will be located in the adjusted corridor's last polygon.
The expected use case is that the desired target will be 'near' the current corridor. What is considered 'near' depends on local polygon density, query search half extents, etc.
The resulting target will differ from the desired target if the desired target is not on the navigation mesh, or it can't be reached using a local search.
*/
bool dtPathCorridor::moveTargetPosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter)
{
dtAssert(m_path);
dtAssert(m_npath);
// Move along navmesh and update new position.
float result[3];
static const int MAX_VISITED = 16;
dtPolyRef visited[MAX_VISITED];
int nvisited = 0;
dtStatus status = navquery->moveAlongSurface(m_path[m_npath-1], m_target, npos, filter,
result, visited, &nvisited, MAX_VISITED);
if (dtStatusSucceed(status))
{
m_npath = dtMergeCorridorEndMoved(m_path, m_npath, m_maxPath, visited, nvisited);
// TODO: should we do that?
// Adjust the position to stay on top of the navmesh.
/* float h = m_target[1];
navquery->getPolyHeight(m_path[m_npath-1], result, &h);
result[1] = h;*/
dtVcopy(m_target, result);
return true;
}
return false;
}
/// @par
///
/// The current corridor position is expected to be within the first polygon in the path. The target
/// is expected to be in the last polygon.
///
/// @warning The size of the path must not exceed the size of corridor's path buffer set during #init().
void dtPathCorridor::setCorridor(const float* target, const dtPolyRef* path, const int npath)
{
dtAssert(m_path);
dtAssert(npath > 0);
dtAssert(npath < m_maxPath);
dtVcopy(m_target, target);
memcpy(m_path, path, sizeof(dtPolyRef)*npath);
m_npath = npath;
}
bool dtPathCorridor::fixPathStart(dtPolyRef safeRef, const float* safePos)
{
dtAssert(m_path);
dtVcopy(m_pos, safePos);
if (m_npath < 3 && m_npath > 0)
{
m_path[2] = m_path[m_npath-1];
m_path[0] = safeRef;
m_path[1] = 0;
m_npath = 3;
}
else
{
m_path[0] = safeRef;
m_path[1] = 0;
}
return true;
}
bool dtPathCorridor::trimInvalidPath(dtPolyRef safeRef, const float* safePos,
dtNavMeshQuery* navquery, const dtQueryFilter* filter)
{
dtAssert(navquery);
dtAssert(filter);
dtAssert(m_path);
// Keep valid path as far as possible.
int n = 0;
while (n < m_npath && navquery->isValidPolyRef(m_path[n], filter)) {
n++;
}
if (n == m_npath)
{
// All valid, no need to fix.
return true;
}
else if (n == 0)
{
// The first polyref is bad, use current safe values.
dtVcopy(m_pos, safePos);
m_path[0] = safeRef;
m_npath = 1;
}
else
{
// The path is partially usable.
m_npath = n;
}
// Clamp target pos to last poly
float tgt[3];
dtVcopy(tgt, m_target);
navquery->closestPointOnPolyBoundary(m_path[m_npath-1], tgt, m_target);
return true;
}
/// @par
///
/// The path can be invalidated if there are structural changes to the underlying navigation mesh, or the state of
/// a polygon within the path changes resulting in it being filtered out. (E.g. An exclusion or inclusion flag changes.)
bool dtPathCorridor::isValid(const int maxLookAhead, dtNavMeshQuery* navquery, const dtQueryFilter* filter)
{
// Check that all polygons still pass query filter.
const int n = dtMin(m_npath, maxLookAhead);
for (int i = 0; i < n; ++i)
{
if (!navquery->isValidPolyRef(m_path[i], filter))
return false;
}
return true;
}

View File

@ -0,0 +1,200 @@
//
// 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 <string.h>
#include "DetourPathQueue.h"
#include "DetourNavMesh.h"
#include "DetourNavMeshQuery.h"
#include "DetourAlloc.h"
#include "DetourCommon.h"
dtPathQueue::dtPathQueue() :
m_nextHandle(1),
m_maxPathSize(0),
m_queueHead(0),
m_navquery(0)
{
for (int i = 0; i < MAX_QUEUE; ++i)
m_queue[i].path = 0;
}
dtPathQueue::~dtPathQueue()
{
purge();
}
void dtPathQueue::purge()
{
dtFreeNavMeshQuery(m_navquery);
m_navquery = 0;
for (int i = 0; i < MAX_QUEUE; ++i)
{
dtFree(m_queue[i].path);
m_queue[i].path = 0;
}
}
bool dtPathQueue::init(const int maxPathSize, const int maxSearchNodeCount, dtNavMesh* nav)
{
purge();
m_navquery = dtAllocNavMeshQuery();
if (!m_navquery)
return false;
if (dtStatusFailed(m_navquery->init(nav, maxSearchNodeCount)))
return false;
m_maxPathSize = maxPathSize;
for (int i = 0; i < MAX_QUEUE; ++i)
{
m_queue[i].ref = DT_PATHQ_INVALID;
m_queue[i].path = (dtPolyRef*)dtAlloc(sizeof(dtPolyRef)*m_maxPathSize, DT_ALLOC_PERM);
if (!m_queue[i].path)
return false;
}
m_queueHead = 0;
return true;
}
void dtPathQueue::update(const int maxIters)
{
static const int MAX_KEEP_ALIVE = 2; // in update ticks.
// Update path request until there is nothing to update
// or upto maxIters pathfinder iterations has been consumed.
int iterCount = maxIters;
for (int i = 0; i < MAX_QUEUE; ++i)
{
PathQuery& q = m_queue[m_queueHead % MAX_QUEUE];
// Skip inactive requests.
if (q.ref == DT_PATHQ_INVALID)
{
m_queueHead++;
continue;
}
// Handle completed request.
if (dtStatusSucceed(q.status) || dtStatusFailed(q.status))
{
// If the path result has not been read in few frames, free the slot.
q.keepAlive++;
if (q.keepAlive > MAX_KEEP_ALIVE)
{
q.ref = DT_PATHQ_INVALID;
q.status = 0;
}
m_queueHead++;
continue;
}
// Handle query start.
if (q.status == 0)
{
q.status = m_navquery->initSlicedFindPath(q.startRef, q.endRef, q.startPos, q.endPos, q.filter);
}
// Handle query in progress.
if (dtStatusInProgress(q.status))
{
int iters = 0;
q.status = m_navquery->updateSlicedFindPath(iterCount, &iters);
iterCount -= iters;
}
if (dtStatusSucceed(q.status))
{
q.status = m_navquery->finalizeSlicedFindPath(q.path, &q.npath, m_maxPathSize);
}
if (iterCount <= 0)
break;
m_queueHead++;
}
}
dtPathQueueRef dtPathQueue::request(dtPolyRef startRef, dtPolyRef endRef,
const float* startPos, const float* endPos,
const dtQueryFilter* filter)
{
// Find empty slot
int slot = -1;
for (int i = 0; i < MAX_QUEUE; ++i)
{
if (m_queue[i].ref == DT_PATHQ_INVALID)
{
slot = i;
break;
}
}
// Could not find slot.
if (slot == -1)
return DT_PATHQ_INVALID;
dtPathQueueRef ref = m_nextHandle++;
if (m_nextHandle == DT_PATHQ_INVALID) m_nextHandle++;
PathQuery& q = m_queue[slot];
q.ref = ref;
dtVcopy(q.startPos, startPos);
q.startRef = startRef;
dtVcopy(q.endPos, endPos);
q.endRef = endRef;
q.status = 0;
q.npath = 0;
q.filter = filter;
q.keepAlive = 0;
return ref;
}
dtStatus dtPathQueue::getRequestStatus(dtPathQueueRef ref) const
{
for (int i = 0; i < MAX_QUEUE; ++i)
{
if (m_queue[i].ref == ref)
return m_queue[i].status;
}
return DT_FAILURE;
}
dtStatus dtPathQueue::getPathResult(dtPathQueueRef ref, dtPolyRef* path, int* pathSize, const int maxPath)
{
for (int i = 0; i < MAX_QUEUE; ++i)
{
if (m_queue[i].ref == ref)
{
PathQuery& q = m_queue[i];
dtStatus details = q.status & DT_STATUS_DETAIL_MASK;
// Free request for reuse.
q.ref = DT_PATHQ_INVALID;
q.status = 0;
// Copy path
int n = dtMin(q.npath, maxPath);
memcpy(path, q.path, sizeof(dtPolyRef)*n);
*pathSize = n;
return details | DT_SUCCESS;
}
}
return DT_FAILURE;
}

View File

@ -0,0 +1,194 @@
//
// 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 <string.h>
#include <new>
#include "DetourProximityGrid.h"
#include "DetourCommon.h"
#include "DetourMath.h"
#include "DetourAlloc.h"
#include "DetourAssert.h"
dtProximityGrid* dtAllocProximityGrid()
{
void* mem = dtAlloc(sizeof(dtProximityGrid), DT_ALLOC_PERM);
if (!mem) return 0;
return new(mem) dtProximityGrid;
}
void dtFreeProximityGrid(dtProximityGrid* ptr)
{
if (!ptr) return;
ptr->~dtProximityGrid();
dtFree(ptr);
}
inline int hashPos2(int x, int y, int n)
{
return ((x*73856093) ^ (y*19349663)) & (n-1);
}
dtProximityGrid::dtProximityGrid() :
m_cellSize(0),
m_invCellSize(0),
m_pool(0),
m_poolHead(0),
m_poolSize(0),
m_buckets(0),
m_bucketsSize(0)
{
}
dtProximityGrid::~dtProximityGrid()
{
dtFree(m_buckets);
dtFree(m_pool);
}
bool dtProximityGrid::init(const int poolSize, const float cellSize)
{
dtAssert(poolSize > 0);
dtAssert(cellSize > 0.0f);
m_cellSize = cellSize;
m_invCellSize = 1.0f / m_cellSize;
// Allocate hashs buckets
m_bucketsSize = dtNextPow2(poolSize);
m_buckets = (unsigned short*)dtAlloc(sizeof(unsigned short)*m_bucketsSize, DT_ALLOC_PERM);
if (!m_buckets)
return false;
// Allocate pool of items.
m_poolSize = poolSize;
m_poolHead = 0;
m_pool = (Item*)dtAlloc(sizeof(Item)*m_poolSize, DT_ALLOC_PERM);
if (!m_pool)
return false;
clear();
return true;
}
void dtProximityGrid::clear()
{
memset(m_buckets, 0xff, sizeof(unsigned short)*m_bucketsSize);
m_poolHead = 0;
m_bounds[0] = 0xffff;
m_bounds[1] = 0xffff;
m_bounds[2] = -0xffff;
m_bounds[3] = -0xffff;
}
void dtProximityGrid::addItem(const unsigned short id,
const float minx, const float miny,
const float maxx, const float maxy)
{
const int iminx = (int)dtMathFloorf(minx * m_invCellSize);
const int iminy = (int)dtMathFloorf(miny * m_invCellSize);
const int imaxx = (int)dtMathFloorf(maxx * m_invCellSize);
const int imaxy = (int)dtMathFloorf(maxy * m_invCellSize);
m_bounds[0] = dtMin(m_bounds[0], iminx);
m_bounds[1] = dtMin(m_bounds[1], iminy);
m_bounds[2] = dtMax(m_bounds[2], imaxx);
m_bounds[3] = dtMax(m_bounds[3], imaxy);
for (int y = iminy; y <= imaxy; ++y)
{
for (int x = iminx; x <= imaxx; ++x)
{
if (m_poolHead < m_poolSize)
{
const int h = hashPos2(x, y, m_bucketsSize);
const unsigned short idx = (unsigned short)m_poolHead;
m_poolHead++;
Item& item = m_pool[idx];
item.x = (short)x;
item.y = (short)y;
item.id = id;
item.next = m_buckets[h];
m_buckets[h] = idx;
}
}
}
}
int dtProximityGrid::queryItems(const float minx, const float miny,
const float maxx, const float maxy,
unsigned short* ids, const int maxIds) const
{
const int iminx = (int)dtMathFloorf(minx * m_invCellSize);
const int iminy = (int)dtMathFloorf(miny * m_invCellSize);
const int imaxx = (int)dtMathFloorf(maxx * m_invCellSize);
const int imaxy = (int)dtMathFloorf(maxy * m_invCellSize);
int n = 0;
for (int y = iminy; y <= imaxy; ++y)
{
for (int x = iminx; x <= imaxx; ++x)
{
const int h = hashPos2(x, y, m_bucketsSize);
unsigned short idx = m_buckets[h];
while (idx != 0xffff)
{
Item& item = m_pool[idx];
if ((int)item.x == x && (int)item.y == y)
{
// Check if the id exists already.
const unsigned short* end = ids + n;
unsigned short* i = ids;
while (i != end && *i != item.id)
++i;
// Item not found, add it.
if (i == end)
{
if (n >= maxIds)
return n;
ids[n++] = item.id;
}
}
idx = item.next;
}
}
}
return n;
}
int dtProximityGrid::getItemCountAt(const int x, const int y) const
{
int n = 0;
const int h = hashPos2(x, y, m_bucketsSize);
unsigned short idx = m_buckets[h];
while (idx != 0xffff)
{
Item& item = m_pool[idx];
if ((int)item.x == x && (int)item.y == y)
n++;
idx = item.next;
}
return n;
}