// // 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. // #define _USE_MATH_DEFINES #include #include #include #include #include #include "DetourNavMesh.h" #include "DetourNavMeshQuery.h" #include "DetourObstacleAvoidance.h" #include "DetourCommon.h" #include "CrowdManager.h" #include "SampleInterfaces.h" // For timer #include "DetourAssert.h" #include "DetourAlloc.h" static const int VO_ADAPTIVE_DIVS = 7; static const int VO_ADAPTIVE_RINGS = 2; static const int VO_ADAPTIVE_DEPTH = 5; static const int VO_GRID_SIZE = 33; inline int hashPos2(int x, int y, int n) { return ((x*73856093) ^ (y*19349663)) & (n-1); } ProximityGrid::ProximityGrid() : m_maxItems(0), m_cellSize(0), m_pool(0), m_poolHead(0), m_poolSize(0), m_buckets(0), m_bucketsSize(0) { } ProximityGrid::~ProximityGrid() { dtFree(m_buckets); dtFree(m_pool); } bool ProximityGrid::init(const int maxItems, const float cellSize) { dtAssert(maxItems > 0); dtAssert(cellSize > 0.0f); m_cellSize = cellSize; m_invCellSize = 1.0f / m_cellSize; // Allocate hashs buckets m_bucketsSize = dtNextPow2(maxItems); m_buckets = (unsigned short*)dtAlloc(sizeof(unsigned short)*m_bucketsSize, DT_ALLOC_PERM); if (!m_buckets) return false; // Allocate pool of items. m_poolSize = maxItems*4; m_poolHead = 0; m_pool = (Item*)dtAlloc(sizeof(Item)*m_poolSize, DT_ALLOC_PERM); if (!m_pool) return false; clear(); return true; } void ProximityGrid::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 ProximityGrid::addItem(const unsigned short id, const float minx, const float miny, const float maxx, const float maxy) { const int iminx = (int)floorf(minx * m_invCellSize); const int iminy = (int)floorf(miny * m_invCellSize); const int imaxx = (int)floorf(maxx * m_invCellSize); const int imaxy = (int)floorf(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 ProximityGrid::queryItems(const float minx, const float miny, const float maxx, const float maxy, unsigned short* ids, const int maxIds) const { const int iminx = (int)floorf(minx * m_invCellSize); const int iminy = (int)floorf(miny * m_invCellSize); const int imaxx = (int)floorf(maxx * m_invCellSize); const int imaxy = (int)floorf(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 ProximityGrid::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; } PathQueue::PathQueue() : m_nextHandle(1), m_delay(0) { for (int i = 0; i < MAX_QUEUE; ++i) m_queue[i].ref = PATHQ_INVALID; } PathQueue::~PathQueue() { } void PathQueue::update(dtNavMeshQuery* navquery) { // Artificial delay to test the code better, // update only one request too. // TODO: Use sliced pathfinder. m_delay++; if ((m_delay % 4) == 0) { for (int i = 0; i < MAX_QUEUE; ++i) { PathQuery& q = m_queue[i]; if (q.ref == PATHQ_INVALID) continue; q.npath = navquery->findPath(q.startRef, q.endRef, q.startPos, q.endPos, q.filter, q.path, PQ_MAX_PATH); q.ready = true; break; } } // Kill forgotten request. for (int i = 0; i < MAX_QUEUE; ++i) { PathQuery& q = m_queue[i]; if (q.ref != PATHQ_INVALID && q.ready) { q.keepalive++; if (q.keepalive > 2) q.ref = PATHQ_INVALID; } } } PathQueueRef PathQueue::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 == PATHQ_INVALID) { slot = i; break; } } // Could not find slot. if (slot == -1) return PATHQ_INVALID; PathQueueRef ref = m_nextHandle++; if (m_nextHandle == 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.ready = false; q.npath = 0; q.filter = filter; // TODO: This is potentially dangerous! q.keepalive = 0; return ref; } int PathQueue::getRequestState(PathQueueRef ref) { for (int i = 0; i < MAX_QUEUE; ++i) { if (m_queue[i].ref == ref) return m_queue[i].ready ? PATHQ_STATE_READY : PATHQ_STATE_WORKING; } return PATHQ_STATE_INVALID; } int PathQueue::getPathResult(PathQueueRef ref, dtPolyRef* path, const int maxPath) { for (int i = 0; i < MAX_QUEUE; ++i) { if (m_queue[i].ref == ref) { PathQuery& q = m_queue[i]; // Allow to reuse the request. q.ref = PATHQ_INVALID; int n = 0; for (int j = 0; j < q.npath && j < maxPath; ++j) path[n++] = q.path[j]; return n; } } return 0; } static int fixupCorridor(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; } static int fixupCorridorEnd(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; } static int mergeCorridor(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; } PathCorridor::PathCorridor() : m_path(0), m_npath(0), m_maxPath(0) { } PathCorridor::~PathCorridor() { dtFree(m_path); } bool PathCorridor::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; } void PathCorridor::reset(dtPolyRef ref, const float* pos) { dtAssert(m_path); dtVcopy(m_pos, pos); dtVcopy(m_target, pos); m_path[0] = ref; m_npath = 1; } int PathCorridor::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 = navquery->findStraightPath(m_pos, m_target, m_path, m_npath, cornerVerts, cornerFlags, cornerPolys, 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; } void PathCorridor::optimizePath(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]; const int nres = navquery->raycast(m_path[0], m_pos, goal, filter, t, norm, res, MAX_RES); if (nres > 1 && t > 0.99f) { m_npath = mergeCorridor(m_path, m_npath, m_maxPath, res, nres); } } void PathCorridor::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 = navquery->moveAlongSurface(m_path[0], m_pos, npos, filter, result, visited, MAX_VISITED); m_npath = fixupCorridor(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); } void PathCorridor::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 = navquery->moveAlongSurface(m_path[m_npath-1], m_target, npos, filter, result, visited, MAX_VISITED); m_npath = fixupCorridorEnd(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); } void PathCorridor::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; } void Agent::integrate(const float maxAcc, const float dt) { // Fake dynamic constraint. const float maxDelta = maxAcc * dt; float dv[3]; dtVsub(dv, nvel, vel); float ds = dtVlen(dv); if (ds > maxDelta) dtVscale(dv, dv, maxDelta/ds); dtVadd(vel, vel, dv); // Integrate if (dtVlen(vel) > 0.0001f) dtVmad(npos, npos, vel, dt); else dtVset(vel,0,0,0); } float Agent::getDistanceToGoal(const float range) const { if (!ncorners) return range; const bool endOfPath = (cornerFlags[ncorners-1] & DT_STRAIGHTPATH_END) ? true : false; const bool offMeshConnection = (cornerFlags[ncorners-1] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) ? true : false; if (endOfPath || offMeshConnection) return dtMin(dtVdist2D(npos, &cornerVerts[(ncorners-1)*3]), range); return range; } void Agent::calcSmoothSteerDirection(float* dir) { if (!ncorners) { dtVset(dir, 0,0,0); return; } const int ip0 = 0; const int ip1 = dtMin(1, ncorners-1); const float* p0 = &cornerVerts[ip0*3]; const float* p1 = &cornerVerts[ip1*3]; float dir0[3], dir1[3]; dtVsub(dir0, p0, npos); dtVsub(dir1, p1, npos); dir0[1] = 0; dir1[1] = 0; float len0 = dtVlen(dir0); float len1 = dtVlen(dir1); if (len1 > 0.001f) dtVscale(dir1,dir1,1.0f/len1); dir[0] = dir0[0] - dir1[0]*len0*0.5f; dir[1] = 0; dir[2] = dir0[2] - dir1[2]*len0*0.5f; dtVnormalize(dir); } void Agent::calcStraightSteerDirection(float* dir) { if (!ncorners) { dtVset(dir, 0,0,0); return; } dtVsub(dir, &cornerVerts[0], npos); dir[1] = 0; dtVnormalize(dir); } LocalBoundary::LocalBoundary() : m_nsegs(0) { dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX); } LocalBoundary::~LocalBoundary() { } void LocalBoundary::reset() { dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX); m_nsegs = 0; } void LocalBoundary::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_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_SEGS-tgt); dtAssert(tgt+n <= MAX_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_SEGS) m_nsegs++; } void LocalBoundary::update(dtPolyRef ref, const float* pos, const float collisionQueryRange, dtNavMeshQuery* navquery, const dtQueryFilter* filter) { static const int MAX_LOCAL_POLYS = 16; static const int MAX_SEGS_PER_POLY = DT_VERTS_PER_POLYGON*2; if (!ref) { dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX); m_nsegs = 0; return; } dtVcopy(m_center, pos); // First query non-overlapping polygons. dtPolyRef locals[MAX_LOCAL_POLYS]; const int nlocals = navquery->findLocalNeighbourhood(ref, pos, collisionQueryRange, filter, locals, 0, MAX_LOCAL_POLYS); // Secondly, store all polygon edges. m_nsegs = 0; float segs[MAX_SEGS_PER_POLY*6]; for (int j = 0; j < nlocals; ++j) { const int nsegs = navquery->getPolyWallSegments(locals[j], filter, segs, 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); } } } CrowdManager::CrowdManager() : m_obstacleQuery(0), m_pathResult(0), m_maxPathResult(0), m_totalTime(0), m_rvoTime(0), m_sampleCount(0), m_moveRequestCount(0) { dtVset(m_ext, 2,4,2); m_obstacleQuery = dtAllocObstacleAvoidanceQuery(); m_obstacleQuery->init(6, 8); m_obstacleQuery->setDesiredVelocityWeight(2.0f); m_obstacleQuery->setCurrentVelocityWeight(0.75f); m_obstacleQuery->setPreferredSideWeight(0.75f); m_obstacleQuery->setCollisionTimeWeight(2.5f); m_obstacleQuery->setTimeHorizon(2.5f); m_obstacleQuery->setVelocitySelectionBias(0.4f); memset(m_vodebug, 0, sizeof(m_vodebug)); const int maxAdaptiveSamples = (VO_ADAPTIVE_DIVS*VO_ADAPTIVE_RINGS+1)*VO_ADAPTIVE_DEPTH; const int maxGridSamples = VO_GRID_SIZE*VO_GRID_SIZE; const int sampleCount = dtMax(maxAdaptiveSamples, maxGridSamples); for (int i = 0; i < MAX_AGENTS; ++i) { m_vodebug[i] = dtAllocObstacleAvoidanceDebugData(); m_vodebug[i]->init(sampleCount); } // Allocate temp buffer for merging paths. m_maxPathResult = 256; m_pathResult = (dtPolyRef*)dtAlloc(sizeof(dtPolyRef)*m_maxPathResult, DT_ALLOC_PERM); // Alloca corridors. for (int i = 0; i < MAX_AGENTS; ++i) { m_agents[i].corridor.init(m_maxPathResult); } // TODO: the radius should be related to the agent radius used to create the navmesh! m_grid.init(100, 1.0f); reset(); } CrowdManager::~CrowdManager() { delete [] m_pathResult; for (int i = 0; i < MAX_AGENTS; ++i) dtFreeObstacleAvoidanceDebugData(m_vodebug[i]); dtFreeObstacleAvoidanceQuery(m_obstacleQuery); } void CrowdManager::reset() { for (int i = 0; i < MAX_AGENTS; ++i) m_agents[i].active = 0; } const int CrowdManager::getAgentCount() const { return MAX_AGENTS; } const Agent* CrowdManager::getAgent(const int idx) { return &m_agents[idx]; } int CrowdManager::addAgent(const float* pos, const float radius, const float height, dtNavMeshQuery* navquery) { // Find empty slot. int idx = -1; for (int i = 0; i < MAX_AGENTS; ++i) { if (!m_agents[i].active) { idx = i; break; } } if (idx == -1) return -1; Agent* ag = &m_agents[idx]; // Find nearest position on navmesh and place the agent there. float nearest[3]; dtPolyRef ref = navquery->findNearestPoly(pos, m_ext, &m_filter, nearest); if (!ref) { // Could not find a location on navmesh. return -1; } ag->corridor.reset(ref, nearest); ag->boundary.reset(); ag->radius = radius; ag->height = height; ag->collisionQueryRange = radius * 8; ag->pathOptimizationRange = radius * 30; ag->nneis = 0; dtVset(ag->dvel, 0,0,0); dtVset(ag->nvel, 0,0,0); dtVset(ag->vel, 0,0,0); dtVcopy(ag->npos, nearest); ag->maxspeed = 0; ag->t = 0; dtVset(ag->opts, 0,0,0); dtVset(ag->opte, 0,0,0); ag->active = 1; ag->var = (rand() % 10) / 9.0f; // Init trail for (int i = 0; i < AGENT_MAX_TRAIL; ++i) dtVcopy(&ag->trail[i*3], ag->corridor.getPos()); ag->htrail = 0; return idx; } void CrowdManager::removeAgent(const int idx) { if (idx >= 0 && idx < MAX_AGENTS) memset(&m_agents[idx], 0, sizeof(Agent)); } bool CrowdManager::requestMoveTarget(const int idx, dtPolyRef ref, const float* pos) { if (idx < 0 || idx > MAX_AGENTS) return false; if (!ref) return false; MoveRequest* req = 0; // Check if there is existing request and update that instead. for (int i = 0; i < m_moveRequestCount; ++i) { if (m_moveRequests[i].idx == idx) { req = &m_moveRequests[i]; break; } } if (!req) { if (m_moveRequestCount >= MAX_AGENTS) return false; req = &m_moveRequests[m_moveRequestCount++]; memset(req, 0, sizeof(MoveRequest)); } // Initialize request. req->idx = idx; req->ref = ref; dtVcopy(req->pos, pos); req->pathqRef = PATHQ_INVALID; req->state = MR_TARGET_REQUESTING; req->temp[0] = ref; req->ntemp = 1; return true; } bool CrowdManager::adjustMoveTarget(const int idx, dtPolyRef ref, const float* pos) { if (idx < 0 || idx > MAX_AGENTS) return false; if (!ref) return false; MoveRequest* req = 0; // Check if there is existing request and update that instead. for (int i = 0; i < m_moveRequestCount; ++i) { if (m_moveRequests[i].idx == idx) { req = &m_moveRequests[i]; break; } } if (!req) { if (m_moveRequestCount >= MAX_AGENTS) return false; req = &m_moveRequests[m_moveRequestCount++]; memset(req, 0, sizeof(MoveRequest)); // New adjust request req->state = MR_TARGET_ADJUST; req->idx = idx; } // Set adjustment request. req->aref = ref; dtVcopy(req->apos, pos); return true; } int CrowdManager::getActiveAgents(Agent** agents, const int maxAgents) { int n = 0; for (int i = 0; i < MAX_AGENTS; ++i) { if (!m_agents[i].active) continue; if (n < maxAgents) agents[n++] = &m_agents[i]; } return n; } static int addNeighbour(const int idx, const float dist, Neighbour* neis, const int nneis, const int maxNeis) { // Insert neighbour based on the distance. Neighbour* nei = 0; if (!nneis) { nei = &neis[nneis]; } else if (dist >= neis[nneis-1].dist) { if (nneis >= maxNeis) return nneis; nei = &neis[nneis]; } else { int i; for (i = 0; i < nneis; ++i) if (dist <= neis[i].dist) break; const int tgt = i+1; const int n = dtMin(nneis-i, maxNeis-tgt); dtAssert(tgt+n <= maxNeis); if (n > 0) memmove(&neis[tgt], &neis[i], sizeof(Neighbour)*n); nei = &neis[i]; } memset(nei, 0, sizeof(Neighbour)); nei->idx = idx; nei->dist = dist; return dtMin(nneis+1, maxNeis); } int CrowdManager::getNeighbours(const float* pos, const float height, const float range, const Agent* skip, Neighbour* result, const int maxResult) { int n = 0; unsigned short ids[MAX_AGENTS]; int nids = m_grid.queryItems(pos[0]-range, pos[2]-range, pos[0]+range, pos[2]+range, ids, MAX_AGENTS); for (int i = 0; i < nids; ++i) { Agent* ag = &m_agents[ids[i]]; if (ag == skip) continue; // Check for overlap. float diff[3]; dtVsub(diff, pos, ag->npos); if (fabsf(diff[1]) >= (height+ag->height)/2.0f) continue; diff[1] = 0; const float distSqr = dtVlenSqr(diff); if (distSqr > dtSqr(range)) continue; n = addNeighbour(ids[i], distSqr, result, n, maxResult); } return n; } void CrowdManager::updateMoveRequest(const float dt, dtNavMeshQuery* navquery, const dtQueryFilter* filter) { // Fire off new requests. for (int i = 0; i < m_moveRequestCount; ++i) { MoveRequest* req = &m_moveRequests[i]; Agent* ag = &m_agents[req->idx]; // Agent not active anymore, kill request. if (!ag->active) req->state = MR_TARGET_FAILED; // Adjust target if (req->aref) { if (req->state == MR_TARGET_ADJUST) { // Adjust existing path. ag->corridor.moveTargetPosition(req->apos, navquery, filter); req->state = MR_TARGET_VALID; } else { // Adjust on the flight request. float result[3]; static const int MAX_VISITED = 16; dtPolyRef visited[MAX_VISITED]; int nvisited = navquery->moveAlongSurface(req->temp[req->ntemp-1], req->pos, req->apos, filter, result, visited, MAX_VISITED); req->ntemp = fixupCorridorEnd(req->temp, req->ntemp, MAX_TEMP_PATH, visited, nvisited); dtVcopy(req->pos, result); // Reset adjustment. dtVset(req->apos, 0,0,0); req->aref = 0; } } if (req->state == MR_TARGET_REQUESTING) { // Calculate request position. // If there is a lot of latency between requests, it is possible to // project the current position ahead and use raycast to find the actual // location and path. const dtPolyRef* path = ag->corridor.getPath(); const int npath = ag->corridor.getPathCount(); dtAssert(npath); // Here we take the simple approach and set the path to be just the current location. float reqPos[3]; dtVcopy(reqPos, ag->corridor.getPos()); // The location of the request dtPolyRef reqPath[8]; // The path to the request location reqPath[0] = path[0]; int reqPathCount = 1; req->pathqRef = m_pathq.request(reqPath[reqPathCount-1], req->ref, reqPos, req->pos, &m_filter); if (req->pathqRef != PATHQ_INVALID) { ag->corridor.setCorridor(reqPos, reqPath, reqPathCount); req->state = MR_TARGET_WAITING_FOR_PATH; } } } // Update requests. m_pathq.update(navquery); // Process path results. for (int i = 0; i < m_moveRequestCount; ++i) { MoveRequest* req = &m_moveRequests[i]; Agent* ag = &m_agents[req->idx]; if (req->state == MR_TARGET_WAITING_FOR_PATH) { // Poll path queue. int state = m_pathq.getRequestState(req->pathqRef); if (state == PATHQ_STATE_INVALID) { req->pathqRef = PATHQ_INVALID; req->state = MR_TARGET_FAILED; } else if (state == PATHQ_STATE_READY) { const dtPolyRef* path = ag->corridor.getPath(); const int npath = ag->corridor.getPathCount(); dtAssert(npath); // Apply results. float targetPos[3]; dtVcopy(targetPos, req->pos); dtPolyRef* res = m_pathResult; bool valid = true; int nres = m_pathq.getPathResult(req->pathqRef, res, m_maxPathResult); if (!nres) valid = false; // Merge with any target adjustment that happened during the search. if (req->ntemp > 1) { nres = fixupCorridorEnd(res, nres, m_maxPathResult, req->temp, req->ntemp); } // Merge result and existing path. // The agent might have moved whilst the request is // being processed, so the path may have changed. // We assume that the end of the path is at the same location // where the request was issued. // The last ref in the old path should be the same as // the location where the request was issued.. if (valid && path[npath-1] != res[0]) valid = false; if (valid) { // Put the old path infront of the old path. if (npath > 1) { // Make space for the old path. if ((npath-1)+nres > m_maxPathResult) nres = m_maxPathResult - (npath-1); memmove(res+npath-1, res, sizeof(dtPolyRef)*nres); // Copy old path in the beginning. memcpy(res, path, sizeof(dtPolyRef)*(npath-1)); nres += npath-1; } // Check for partial path. if (res[nres-1] != req->ref) { // Partial path, constrain target position inside the last polygon. float nearest[3]; if (navquery->closestPointOnPoly(res[nres-1], targetPos, nearest)) dtVcopy(targetPos, nearest); else valid = false; } } if (valid) { ag->corridor.setCorridor(targetPos, res, nres); req->state = MR_TARGET_VALID; } else { // Something went wrong. req->state = MR_TARGET_FAILED; } } } // Remove request when done with it. if (req->state == MR_TARGET_VALID || req->state == MR_TARGET_FAILED) { m_moveRequestCount--; if (i != m_moveRequestCount) memcpy(&m_moveRequests[i], &m_moveRequests[m_moveRequestCount], sizeof(MoveRequest)); --i; } } } void CrowdManager::update(const float dt, unsigned int flags, dtNavMeshQuery* navquery) { m_sampleCount = 0; m_totalTime = 0; m_rvoTime = 0; if (!navquery) return; TimeVal startTime = getPerfTime(); Agent* agents[MAX_AGENTS]; int nagents = getActiveAgents(agents, MAX_AGENTS); static const float MAX_ACC = 8.0f; static const float MAX_SPEED = 3.5f; // Update async move request and path finder. updateMoveRequest(dt, navquery, &m_filter); // Register agents to proximity grid. m_grid.clear(); for (int i = 0; i < nagents; ++i) { Agent* ag = agents[i]; const float* p = ag->npos; const float r = ag->radius; m_grid.addItem((unsigned short)i, p[0]-r, p[2]-r, p[0]+r, p[2]+r); } // Get nearby navmesh segments and agents to collide with. for (int i = 0; i < nagents; ++i) { Agent* ag = agents[i]; // Only update the collision boundary after certain distance has been passed. if (dtVdist2DSqr(ag->npos, ag->boundary.getCenter()) > dtSqr(ag->collisionQueryRange*0.25f)) ag->boundary.update(ag->corridor.getFirstPoly(), ag->npos, ag->collisionQueryRange, navquery, &m_filter); // Query neighbour agents ag->nneis = getNeighbours(ag->npos, ag->height, ag->collisionQueryRange, ag, ag->neis, AGENT_MAX_NEIGHBOURS); } // Find next corner to steer to. for (int i = 0; i < nagents; ++i) { Agent* ag = agents[i]; // Find corners for steering ag->ncorners = ag->corridor.findCorners(ag->cornerVerts, ag->cornerFlags, ag->cornerPolys, AGENT_MAX_CORNERS, navquery, &m_filter); // Check to see if the corner after the next corner is directly visible, // and short cut to there. if (ag->ncorners > 0) { const float* target = &ag->cornerVerts[dtMin(1,ag->ncorners-1)*3]; dtVcopy(ag->opts, ag->corridor.getPos()); dtVcopy(ag->opte, target); ag->corridor.optimizePath(target, ag->pathOptimizationRange, navquery, &m_filter); } else { dtVset(ag->opts, 0,0,0); dtVset(ag->opte, 0,0,0); } // Copy data for debug purposes. } // Calculate steering. for (int i = 0; i < nagents; ++i) { Agent* ag = agents[i]; float dvel[3] = {0,0,0}; // Calculate steering direction. if (flags & CROWDMAN_ANTICIPATE_TURNS) ag->calcSmoothSteerDirection(dvel); else ag->calcStraightSteerDirection(dvel); // Calculate speed scale, which tells the agent to slowdown at the end of the path. const float slowDownRadius = ag->radius*2; // TODO: make less hacky. const float speedScale = ag->getDistanceToGoal(slowDownRadius) / slowDownRadius; // Apply style. if (flags & CROWDMAN_DRUNK) { // Drunken steering // Pulsating speed. ag->t += dt * (1.0f - ag->var*0.25f); ag->maxspeed = MAX_SPEED*(1 + dtSqr(cosf(ag->t*2.0f))*0.3f); dtVscale(dvel, dvel, ag->maxspeed * speedScale); // Slightly wandering steering. const float amp = cosf(ag->var*13.69f+ag->t*3.123f) * 0.2f; const float nx = -dvel[2]; const float nz = dvel[0]; dvel[0] += nx*amp; dvel[2] += nz*amp; } else { // Normal steering. ag->maxspeed = MAX_SPEED; dtVscale(dvel, dvel, ag->maxspeed * speedScale); } // Set the desired velocity. dtVcopy(ag->dvel, dvel); } // Velocity planning. TimeVal rvoStartTime = getPerfTime(); for (int i = 0; i < nagents; ++i) { Agent* ag = agents[i]; if (flags & CROWDMAN_USE_VO) { m_obstacleQuery->reset(); // Add neighbours as obstacles. for (int j = 0; j < ag->nneis; ++j) { const Agent* nei = &m_agents[ag->neis[j].idx]; m_obstacleQuery->addCircle(nei->npos, nei->radius, nei->vel, nei->dvel); } // Append neighbour segments as obstacles. for (int j = 0; j < ag->boundary.getSegmentCount(); ++j) { const float* s = ag->boundary.getSegment(j); if (dtTriArea2D(ag->npos, s, s+3) < 0.0f) continue; m_obstacleQuery->addSegment(s, s+3); } // Sample new safe velocity. bool adaptive = true; if (adaptive) { m_obstacleQuery->sampleVelocityAdaptive(ag->npos, ag->radius, ag->maxspeed, ag->vel, ag->dvel, ag->nvel, VO_ADAPTIVE_DIVS, VO_ADAPTIVE_RINGS, VO_ADAPTIVE_DEPTH, m_vodebug[i]); } else { m_obstacleQuery->sampleVelocityGrid(ag->npos, ag->radius, ag->maxspeed, ag->vel, ag->dvel, ag->nvel, VO_GRID_SIZE, m_vodebug[i]); } } else { // If not using velocity planning, new velocity is directly the desired velocity. dtVcopy(ag->nvel, ag->dvel); } } TimeVal rvoEndTime = getPerfTime(); // Integrate. for (int i = 0; i < nagents; ++i) { Agent* ag = agents[i]; ag->integrate(MAX_ACC, dt); } // Handle collisions. for (int iter = 0; iter < 4; ++iter) { for (int i = 0; i < nagents; ++i) { Agent* ag = agents[i]; dtVset(ag->disp, 0,0,0); float w = 0; for (int j = 0; j < ag->nneis; ++j) { const Agent* nei = &m_agents[ag->neis[j].idx]; float diff[3]; dtVsub(diff, ag->npos, nei->npos); if (fabsf(diff[1]) >= (ag->height+ nei->height)/2.0f) continue; diff[1] = 0; float dist = dtVlenSqr(diff); if (dist > dtSqr(ag->radius + nei->radius)) continue; dist = sqrtf(dist); float pen = (ag->radius + nei->radius) - dist; if (dist > 0.0001f) pen = (1.0f/dist) * (pen*0.5f) * 0.7f; dtVmad(ag->disp, ag->disp, diff, pen); w += 1.0f; } if (w > 0.0001f) { const float iw = 1.0f / w; dtVscale(ag->disp, ag->disp, iw); } } for (int i = 0; i < nagents; ++i) { Agent* ag = agents[i]; dtVadd(ag->npos, ag->npos, ag->disp); } } for (int i = 0; i < nagents; ++i) { Agent* ag = agents[i]; // Move along navmesh. ag->corridor.movePosition(ag->npos, navquery, &m_filter); // Get valid constrained position back. dtVcopy(ag->npos, ag->corridor.getPos()); } TimeVal endTime = getPerfTime(); // Debug/demo book keeping int ns = 0; for (int i = 0; i < nagents; ++i) { Agent* ag = agents[i]; if (flags & CROWDMAN_USE_VO) { // Normalize samples for debug draw m_vodebug[i]->normalizeSamples(); ns += m_vodebug[i]->getSampleCount(); } // Update agent movement trail. ag->htrail = (ag->htrail + 1) % AGENT_MAX_TRAIL; dtVcopy(&ag->trail[ag->htrail*3], ag->npos); } m_sampleCount = ns; m_rvoTime = getPerfDeltaTimeUsec(rvoStartTime, rvoEndTime); m_totalTime = getPerfDeltaTimeUsec(startTime, endTime); }