| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- /*
- Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
- recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
- DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
- 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.
- */
- using System;
- using DotRecast.Core;
- using DotRecast.Detour.Crowd.Tracking;
- namespace DotRecast.Detour.Crowd
- {
- using static RcMath;
- public class DtObstacleAvoidanceQuery
- {
- public const int DT_MAX_PATTERN_DIVS = 32;
- /// < Max numver of adaptive divs.
- public const int DT_MAX_PATTERN_RINGS = 4;
- private DtObstacleAvoidanceParams m_params;
- private float m_invHorizTime;
- private float m_vmax;
- private float m_invVmax;
- private readonly int m_maxCircles;
- private readonly DtObstacleCircle[] m_circles;
- private int m_ncircles;
- private readonly int m_maxSegments;
- private readonly DtObstacleSegment[] m_segments;
- private int m_nsegments;
- public DtObstacleAvoidanceQuery(int maxCircles, int maxSegments)
- {
- m_maxCircles = maxCircles;
- m_ncircles = 0;
- m_circles = new DtObstacleCircle[m_maxCircles];
- for (int i = 0; i < m_maxCircles; i++)
- {
- m_circles[i] = new DtObstacleCircle();
- }
- m_maxSegments = maxSegments;
- m_nsegments = 0;
- m_segments = new DtObstacleSegment[m_maxSegments];
- for (int i = 0; i < m_maxSegments; i++)
- {
- m_segments[i] = new DtObstacleSegment();
- }
- }
- public void Reset()
- {
- m_ncircles = 0;
- m_nsegments = 0;
- }
- public void AddCircle(RcVec3f pos, float rad, RcVec3f vel, RcVec3f dvel)
- {
- if (m_ncircles >= m_maxCircles)
- return;
- DtObstacleCircle cir = m_circles[m_ncircles++];
- cir.p = pos;
- cir.rad = rad;
- cir.vel = vel;
- cir.dvel = dvel;
- }
- public void AddSegment(RcVec3f p, RcVec3f q)
- {
- if (m_nsegments >= m_maxSegments)
- return;
- DtObstacleSegment seg = m_segments[m_nsegments++];
- seg.p = p;
- seg.q = q;
- }
- public int GetObstacleCircleCount()
- {
- return m_ncircles;
- }
- public DtObstacleCircle GetObstacleCircle(int i)
- {
- return m_circles[i];
- }
- public int GetObstacleSegmentCount()
- {
- return m_nsegments;
- }
- public DtObstacleSegment GetObstacleSegment(int i)
- {
- return m_segments[i];
- }
- private void Prepare(RcVec3f pos, RcVec3f dvel)
- {
- // Prepare obstacles
- for (int i = 0; i < m_ncircles; ++i)
- {
- DtObstacleCircle cir = m_circles[i];
- // Side
- RcVec3f pa = pos;
- RcVec3f pb = cir.p;
- RcVec3f orig = new RcVec3f();
- RcVec3f dv = new RcVec3f();
- cir.dp = pb.Subtract(pa);
- cir.dp.Normalize();
- dv = cir.dvel.Subtract(dvel);
- float a = DtUtils.TriArea2D(orig, cir.dp, dv);
- if (a < 0.01f)
- {
- cir.np.x = -cir.dp.z;
- cir.np.z = cir.dp.x;
- }
- else
- {
- cir.np.x = cir.dp.z;
- cir.np.z = -cir.dp.x;
- }
- }
- for (int i = 0; i < m_nsegments; ++i)
- {
- DtObstacleSegment seg = m_segments[i];
- // Precalc if the agent is really close to the segment.
- float r = 0.01f;
- var distSqr = DtUtils.DistancePtSegSqr2D(pos, seg.p, seg.q, out var t);
- seg.touch = distSqr < Sqr(r);
- }
- }
- private bool SweepCircleCircle(RcVec3f c0, float r0, RcVec3f v, RcVec3f c1, float r1, out float tmin, out float tmax)
- {
- const float EPS = 0.0001f;
- tmin = 0;
- tmax = 0;
- RcVec3f s = c1.Subtract(c0);
- float r = r0 + r1;
- float c = s.Dot2D(s) - r * r;
- float a = v.Dot2D(v);
- if (a < EPS)
- return false; // not moving
- // Overlap, calc time to exit.
- float b = v.Dot2D(s);
- float d = b * b - a * c;
- if (d < 0.0f)
- return false; // no intersection.
- a = 1.0f / a;
- float rd = (float)Math.Sqrt(d);
- tmin = (b - rd) * a;
- tmax = (b + rd) * a;
- return true;
- }
- private bool IsectRaySeg(RcVec3f ap, RcVec3f u, RcVec3f bp, RcVec3f bq, ref float t)
- {
- RcVec3f v = bq.Subtract(bp);
- RcVec3f w = ap.Subtract(bp);
- float d = RcVec3f.Perp2D(u, v);
- if (Math.Abs(d) < 1e-6f)
- return false;
- d = 1.0f / d;
- t = RcVec3f.Perp2D(v, w) * d;
- if (t < 0 || t > 1)
- return false;
- float s = RcVec3f.Perp2D(u, w) * d;
- if (s < 0 || s > 1)
- return false;
- return true;
- }
- /**
- * Calculate the collision penalty for a given velocity vector
- *
- * @param vcand
- * sampled velocity
- * @param dvel
- * desired velocity
- * @param minPenalty
- * threshold penalty for early out
- */
- private float ProcessSample(RcVec3f vcand, float cs, RcVec3f pos, float rad, RcVec3f vel, RcVec3f dvel,
- float minPenalty, DtObstacleAvoidanceDebugData debug)
- {
- // penalty for straying away from the desired and current velocities
- float vpen = m_params.weightDesVel * (RcVec3f.Dist2D(vcand, dvel) * m_invVmax);
- float vcpen = m_params.weightCurVel * (RcVec3f.Dist2D(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 understand)
- float minPen = minPenalty - vpen - vcpen;
- float tThresold = (m_params.weightToi / minPen - 0.1f) * m_params.horizTime;
- if (tThresold - m_params.horizTime > -float.MinValue)
- 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)
- {
- DtObstacleCircle cir = m_circles[i];
- // RVO
- RcVec3f vab = vcand.Scale(2);
- vab = vab.Subtract(vel);
- vab = vab.Subtract(cir.vel);
- // Side
- side += Clamp(Math.Min(cir.dp.Dot2D(vab) * 0.5f + 0.5f, cir.np.Dot2D(vab) * 2), 0.0f, 1.0f);
- nside++;
- if (!SweepCircleCircle(pos, rad, vab, cir.p, cir.rad, out var htmin, out var 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)
- {
- DtObstacleSegment seg = m_segments[i];
- float htmin = 0;
- if (seg.touch)
- {
- // Special case when the agent is very close to the segment.
- RcVec3f sdir = seg.q.Subtract(seg.p);
- RcVec3f snorm = new RcVec3f();
- snorm.x = -sdir.z;
- snorm.z = sdir.x;
- // If the velocity is pointing towards the segment, no collision.
- if (snorm.Dot2D(vcand) < 0.0f)
- continue;
- // Else immediate collision.
- htmin = 0.0f;
- }
- else
- {
- if (!IsectRaySeg(pos, vcand, seg.p, seg.q, ref 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 != 0)
- side /= nside;
- float spen = m_params.weightSide * side;
- float tpen = m_params.weightToi * (1.0f / (0.1f + tmin * m_invHorizTime));
- float penalty = vpen + vcpen + spen + tpen;
- // Store different penalties for debug viewing
- if (debug != null)
- debug.AddSample(vcand, cs, penalty, vpen, vcpen, spen, tpen);
- return penalty;
- }
- public int SampleVelocityGrid(RcVec3f pos, float rad, float vmax, RcVec3f vel, RcVec3f dvel, out RcVec3f nvel,
- DtObstacleAvoidanceParams option, DtObstacleAvoidanceDebugData debug)
- {
- Prepare(pos, dvel);
- m_params = option;
- m_invHorizTime = 1.0f / m_params.horizTime;
- m_vmax = vmax;
- m_invVmax = vmax > 0 ? 1.0f / vmax : float.MaxValue;
- nvel = RcVec3f.Zero;
- if (debug != null)
- debug.Reset();
- float cvx = dvel.x * m_params.velBias;
- float cvz = dvel.z * m_params.velBias;
- float cs = vmax * 2 * (1 - m_params.velBias) / (m_params.gridSize - 1);
- float half = (m_params.gridSize - 1) * cs * 0.5f;
- float minPenalty = float.MaxValue;
- int ns = 0;
- for (int y = 0; y < m_params.gridSize; ++y)
- {
- for (int x = 0; x < m_params.gridSize; ++x)
- {
- RcVec3f vcand = RcVec3f.Of(cvx + x * cs - half, 0f, cvz + y * cs - half);
- if (Sqr(vcand.x) + Sqr(vcand.z) > Sqr(vmax + cs / 2))
- continue;
- float penalty = ProcessSample(vcand, cs, pos, rad, vel, dvel, minPenalty, debug);
- ns++;
- if (penalty < minPenalty)
- {
- minPenalty = penalty;
- nvel = vcand;
- }
- }
- }
- return ns;
- }
- // vector normalization that ignores the y-component.
- void DtNormalize2D(float[] v)
- {
- float d = (float)Math.Sqrt(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.
- RcVec3f DtRotate2D(float[] v, float ang)
- {
- RcVec3f dest = new RcVec3f();
- float c = (float)Math.Cos(ang);
- float s = (float)Math.Sin(ang);
- dest.x = v[0] * c - v[2] * s;
- dest.z = v[0] * s + v[2] * c;
- dest.y = v[1];
- return dest;
- }
- static readonly float DT_PI = 3.14159265f;
- public int SampleVelocityAdaptive(RcVec3f pos, float rad, float vmax, RcVec3f vel, RcVec3f dvel, out RcVec3f nvel,
- DtObstacleAvoidanceParams option,
- DtObstacleAvoidanceDebugData debug)
- {
- Prepare(pos, dvel);
- m_params = option;
- m_invHorizTime = 1.0f / m_params.horizTime;
- m_vmax = vmax;
- m_invVmax = vmax > 0 ? 1.0f / vmax : float.MaxValue;
- nvel = RcVec3f.Zero;
- if (debug != null)
- debug.Reset();
- // Build sampling pattern aligned to desired velocity.
- float[] pat = new float[(DT_MAX_PATTERN_DIVS * DT_MAX_PATTERN_RINGS + 1) * 2];
- int npat = 0;
- int ndivs = m_params.adaptiveDivs;
- int nrings = m_params.adaptiveRings;
- int depth = m_params.adaptiveDepth;
- int nd = Clamp(ndivs, 1, DT_MAX_PATTERN_DIVS);
- int nr = Clamp(nrings, 1, DT_MAX_PATTERN_RINGS);
- float da = (1.0f / nd) * DT_PI * 2;
- float ca = (float)Math.Cos(da);
- float sa = (float)Math.Sin(da);
- // desired direction
- float[] ddir = new float[6];
- ddir[0] = dvel.x;
- ddir[1] = dvel.y;
- ddir[2] = dvel.z;
- DtNormalize2D(ddir);
- RcVec3f rotated = DtRotate2D(ddir, da * 0.5f); // rotated by da/2
- ddir[3] = rotated.x;
- ddir[4] = rotated.y;
- ddir[5] = rotated.z;
- // Always add sample at zero
- pat[npat * 2 + 0] = 0;
- pat[npat * 2 + 1] = 0;
- npat++;
- for (int j = 0; j < nr; ++j)
- {
- 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;
- int last1 = npat * 2;
- int last2 = last1;
- npat++;
- for (int i = 1; i < nd - 1; i += 2)
- {
- // get next point on the "right" (rotate CW)
- pat[npat * 2 + 0] = pat[last1] * ca + pat[last1 + 1] * sa;
- pat[npat * 2 + 1] = -pat[last1] * sa + pat[last1 + 1] * ca;
- // get next point on the "left" (rotate CCW)
- pat[npat * 2 + 2] = pat[last2] * ca - pat[last2 + 1] * sa;
- pat[npat * 2 + 3] = pat[last2] * sa + pat[last2 + 1] * ca;
- last1 = npat * 2;
- last2 = last1 + 2;
- npat += 2;
- }
- if ((nd & 1) == 0)
- {
- pat[npat * 2 + 2] = pat[last2] * ca - pat[last2 + 1] * sa;
- pat[npat * 2 + 3] = pat[last2] * sa + pat[last2 + 1] * ca;
- npat++;
- }
- }
- // Start sampling.
- float cr = vmax * (1.0f - m_params.velBias);
- RcVec3f res = RcVec3f.Of(dvel.x * m_params.velBias, 0, dvel.z * m_params.velBias);
- int ns = 0;
- for (int k = 0; k < depth; ++k)
- {
- float minPenalty = float.MaxValue;
- RcVec3f bvel = new RcVec3f();
- bvel = RcVec3f.Zero;
- for (int i = 0; i < npat; ++i)
- {
- RcVec3f vcand = RcVec3f.Of(res.x + pat[i * 2 + 0] * cr, 0f, res.z + pat[i * 2 + 1] * cr);
- if (Sqr(vcand.x) + Sqr(vcand.z) > Sqr(vmax + 0.001f))
- continue;
- float penalty = ProcessSample(vcand, cr / 10, pos, rad, vel, dvel, minPenalty, debug);
- ns++;
- if (penalty < minPenalty)
- {
- minPenalty = penalty;
- bvel = vcand;
- }
- }
- res = bvel;
- cr *= 0.5f;
- }
- nvel = res;
- return ns;
- }
- }
- }
|