123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- //
- // 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;
- }
|