2023-03-14 08:02:43 +03:00
|
|
|
/*
|
|
|
|
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
|
|
|
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
|
2023-03-15 17:00:29 +03:00
|
|
|
DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
|
2023-03-14 08:02:43 +03:00
|
|
|
|
|
|
|
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 System.Collections.Generic;
|
|
|
|
using System.Collections.Immutable;
|
2023-03-25 09:43:20 +03:00
|
|
|
using DotRecast.Core;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:09:10 +03:00
|
|
|
namespace DotRecast.Detour
|
|
|
|
{
|
2023-05-18 02:30:38 +03:00
|
|
|
using static RcMath;
|
2023-06-08 15:38:02 +03:00
|
|
|
using static DtNode;
|
2023-03-16 19:09:10 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
public class DtNavMeshQuery
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
/**
|
2023-03-14 08:02:43 +03:00
|
|
|
* Use raycasts during pathfind to "shortcut" (raycast still consider costs) Options for
|
|
|
|
* NavMeshQuery::initSlicedFindPath and updateSlicedFindPath
|
|
|
|
*/
|
2023-03-16 19:48:49 +03:00
|
|
|
public const int DT_FINDPATH_ANY_ANGLE = 0x02;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/** Raycast should calculate movement cost along the ray and fill RaycastHit::cost */
|
|
|
|
public const int DT_RAYCAST_USE_COSTS = 0x01;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/// Vertex flags returned by findStraightPath.
|
|
|
|
/** The vertex is the start position in the path. */
|
|
|
|
public const int DT_STRAIGHTPATH_START = 0x01;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/** The vertex is the end position in the path. */
|
|
|
|
public const int DT_STRAIGHTPATH_END = 0x02;
|
|
|
|
|
|
|
|
/** The vertex is the start of an off-mesh connection. */
|
|
|
|
public const int DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04;
|
|
|
|
|
|
|
|
/// Options for findStraightPath.
|
|
|
|
public const int DT_STRAIGHTPATH_AREA_CROSSINGS = 0x01;
|
|
|
|
|
|
|
|
/// < Add a vertex at every polygon edge crossing
|
|
|
|
/// where area changes.
|
|
|
|
public const int DT_STRAIGHTPATH_ALL_CROSSINGS = 0x02;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/// < Add a vertex at every polygon edge crossing.
|
2023-06-08 15:38:02 +03:00
|
|
|
protected readonly DtNavMesh m_nav;
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
protected readonly DtNodePool m_nodePool;
|
|
|
|
protected readonly DtNodeQueue m_openList;
|
|
|
|
protected DtQueryData m_query;
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
/// < Sliced query state.
|
2023-06-08 15:38:02 +03:00
|
|
|
public DtNavMeshQuery(DtNavMesh nav)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
m_nav = nav;
|
2023-06-08 15:38:02 +03:00
|
|
|
m_nodePool = new DtNodePool();
|
|
|
|
m_openList = new DtNodeQueue();
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-10 15:57:39 +03:00
|
|
|
/// Returns random location on navmesh.
|
|
|
|
/// Polygons are chosen weighted by area. The search runs in linear related to number of polygon.
|
|
|
|
/// @param[in] filter The polygon filter to apply to the query.
|
|
|
|
/// @param[in] frand Function returning a random number [0..1).
|
|
|
|
/// @param[out] randomRef The reference id of the random location.
|
|
|
|
/// @param[out] randomPt The random location.
|
|
|
|
/// @returns The status flags for the query.
|
2023-07-14 16:42:48 +03:00
|
|
|
public DtStatus FindRandomPoint(IDtQueryFilter filter, IRcRand frand, out long randomRef, out RcVec3f randomPt)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-10 15:57:39 +03:00
|
|
|
randomRef = 0;
|
|
|
|
randomPt = RcVec3f.Zero;
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (null == filter || null == frand)
|
|
|
|
{
|
2023-06-10 15:57:39 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-10 15:57:39 +03:00
|
|
|
// Randomly pick one tile. Assume that all tiles cover roughly the same area.
|
2023-06-08 15:38:02 +03:00
|
|
|
DtMeshTile tile = null;
|
2023-03-16 19:48:49 +03:00
|
|
|
float tsum = 0.0f;
|
2023-05-05 02:44:48 +03:00
|
|
|
for (int i = 0; i < m_nav.GetMaxTiles(); i++)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtMeshTile mt = m_nav.GetTile(i);
|
2023-03-16 19:48:49 +03:00
|
|
|
if (mt == null || mt.data == null || mt.data.header == null)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Choose random tile using reservoi sampling.
|
|
|
|
float area = 1.0f; // Could be tile area too.
|
|
|
|
tsum += area;
|
2023-05-06 05:44:57 +03:00
|
|
|
float u = frand.Next();
|
2023-03-16 19:48:49 +03:00
|
|
|
if (u * tsum <= area)
|
|
|
|
{
|
|
|
|
tile = mt;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (tile == null)
|
|
|
|
{
|
2023-06-10 15:57:39 +03:00
|
|
|
return DtStatus.DT_FAILURE;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Randomly pick one polygon weighted by polygon area.
|
2023-06-08 15:38:02 +03:00
|
|
|
DtPoly poly = null;
|
2023-03-16 19:48:49 +03:00
|
|
|
long polyRef = 0;
|
2023-05-05 02:44:48 +03:00
|
|
|
long @base = m_nav.GetPolyRefBase(tile);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
float areaSum = 0.0f;
|
|
|
|
for (int i = 0; i < tile.data.header.polyCount; ++i)
|
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtPoly p = tile.data.polys[i];
|
2023-03-16 19:48:49 +03:00
|
|
|
// Do not return off-mesh connection polygons.
|
2023-06-08 15:38:02 +03:00
|
|
|
if (p.GetPolyType() != DtPoly.DT_POLYTYPE_GROUND)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Must pass filter
|
2023-06-01 18:13:25 +03:00
|
|
|
long refs = @base | (long)i;
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!filter.PassFilter(refs, tile, p))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calc area of the polygon.
|
|
|
|
float polyArea = 0.0f;
|
|
|
|
for (int j = 2; j < p.vertCount; ++j)
|
|
|
|
{
|
|
|
|
int va = p.verts[0] * 3;
|
|
|
|
int vb = p.verts[j - 1] * 3;
|
|
|
|
int vc = p.verts[j] * 3;
|
2023-06-01 18:13:25 +03:00
|
|
|
polyArea += DetourCommon.TriArea2D(tile.data.verts, va, vb, vc);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Choose random polygon weighted by area, using reservoi sampling.
|
|
|
|
areaSum += polyArea;
|
2023-05-06 05:44:57 +03:00
|
|
|
float u = frand.Next();
|
2023-03-16 19:48:49 +03:00
|
|
|
if (u * areaSum <= polyArea)
|
|
|
|
{
|
|
|
|
poly = p;
|
|
|
|
polyRef = refs;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (poly == null)
|
|
|
|
{
|
2023-06-10 15:57:39 +03:00
|
|
|
return DtStatus.DT_FAILURE;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Randomly pick point on polygon.
|
2023-05-05 02:44:48 +03:00
|
|
|
float[] verts = new float[3 * m_nav.GetMaxVertsPerPoly()];
|
|
|
|
float[] areas = new float[m_nav.GetMaxVertsPerPoly()];
|
2023-03-16 19:48:49 +03:00
|
|
|
Array.Copy(tile.data.verts, poly.verts[0] * 3, verts, 0, 3);
|
|
|
|
for (int j = 1; j < poly.vertCount; ++j)
|
|
|
|
{
|
|
|
|
Array.Copy(tile.data.verts, poly.verts[j] * 3, verts, j * 3, 3);
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-05-06 05:44:57 +03:00
|
|
|
float s = frand.Next();
|
|
|
|
float t = frand.Next();
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-01 18:13:25 +03:00
|
|
|
var pt = DetourCommon.RandomPointInConvexPoly(verts, poly.vertCount, areas, s, t);
|
2023-06-10 09:16:52 +03:00
|
|
|
ClosestPointOnPoly(polyRef, pt, out var closest, out var _);
|
2023-06-10 15:57:39 +03:00
|
|
|
|
|
|
|
randomRef = polyRef;
|
|
|
|
randomPt = closest;
|
|
|
|
|
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/**
|
2023-03-14 08:02:43 +03:00
|
|
|
* Returns random location on navmesh within the reach of specified location. Polygons are chosen weighted by area.
|
|
|
|
* The search runs in linear related to number of polygon. The location is not exactly constrained by the circle,
|
|
|
|
* but it limits the visited polygons.
|
|
|
|
*
|
|
|
|
* @param startRef
|
|
|
|
* The reference id of the polygon where the search starts.
|
|
|
|
* @param centerPos
|
|
|
|
* The center of the search circle. [(x, y, z)]
|
|
|
|
* @param maxRadius
|
|
|
|
* @param filter
|
|
|
|
* The polygon filter to apply to the query.
|
|
|
|
* @param frand
|
|
|
|
* Function returning a random number [0..1).
|
|
|
|
* @return Random location
|
|
|
|
*/
|
2023-06-10 16:20:56 +03:00
|
|
|
public DtStatus FindRandomPointAroundCircle(long startRef, RcVec3f centerPos, float maxRadius,
|
2023-07-14 16:42:48 +03:00
|
|
|
IDtQueryFilter filter, IRcRand frand, out long randomRef, out RcVec3f randomPt)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-10 16:34:53 +03:00
|
|
|
return FindRandomPointAroundCircle(startRef, centerPos, maxRadius, filter, frand, NoOpPolygonByCircleConstraint.Noop, out randomRef, out randomPt);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/**
|
2023-03-14 08:02:43 +03:00
|
|
|
* Returns random location on navmesh within the reach of specified location. Polygons are chosen weighted by area.
|
|
|
|
* The search runs in linear related to number of polygon. The location is strictly constrained by the circle.
|
|
|
|
*
|
|
|
|
* @param startRef
|
|
|
|
* The reference id of the polygon where the search starts.
|
|
|
|
* @param centerPos
|
|
|
|
* The center of the search circle. [(x, y, z)]
|
|
|
|
* @param maxRadius
|
|
|
|
* @param filter
|
|
|
|
* The polygon filter to apply to the query.
|
|
|
|
* @param frand
|
|
|
|
* Function returning a random number [0..1).
|
|
|
|
* @return Random location
|
|
|
|
*/
|
2023-06-10 16:20:56 +03:00
|
|
|
public DtStatus FindRandomPointWithinCircle(long startRef, RcVec3f centerPos, float maxRadius,
|
2023-07-14 16:42:48 +03:00
|
|
|
IDtQueryFilter filter, IRcRand frand, out long randomRef, out RcVec3f randomPt)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-10 16:34:53 +03:00
|
|
|
return FindRandomPointAroundCircle(startRef, centerPos, maxRadius, filter, frand, StrictPolygonByCircleConstraint.Strict, out randomRef, out randomPt);
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-10 16:34:53 +03:00
|
|
|
public DtStatus FindRandomPointAroundCircle(long startRef, RcVec3f centerPos, float maxRadius,
|
2023-07-14 16:42:48 +03:00
|
|
|
IDtQueryFilter filter, IRcRand frand, IPolygonByCircleConstraint constraint,
|
2023-06-10 16:34:53 +03:00
|
|
|
out long randomRef, out RcVec3f randomPt)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-10 16:34:53 +03:00
|
|
|
randomRef = startRef;
|
|
|
|
randomPt = centerPos;
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Validate input
|
2023-06-03 15:47:26 +03:00
|
|
|
if (!m_nav.IsValidPolyRef(startRef) || !RcVec3f.IsFinite(centerPos) || maxRadius < 0
|
2023-03-16 19:48:49 +03:00
|
|
|
|| !float.IsFinite(maxRadius) || null == filter || null == frand)
|
|
|
|
{
|
2023-06-10 16:34:53 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(startRef, out var startTile, out var startPoly);
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!filter.PassFilter(startRef, startTile, startPoly))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-10 16:34:53 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
m_nodePool.Clear();
|
|
|
|
m_openList.Clear();
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode startNode = m_nodePool.GetNode(startRef);
|
2023-04-12 17:53:28 +03:00
|
|
|
startNode.pos = centerPos;
|
2023-03-16 19:48:49 +03:00
|
|
|
startNode.pidx = 0;
|
|
|
|
startNode.cost = 0;
|
|
|
|
startNode.total = 0;
|
|
|
|
startNode.id = startRef;
|
|
|
|
startNode.flags = DT_NODE_OPEN;
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Push(startNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-10 16:34:53 +03:00
|
|
|
DtStatus status = DtStatus.DT_SUCCSESS;
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
float radiusSqr = maxRadius * maxRadius;
|
|
|
|
float areaSum = 0.0f;
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtPoly randomPoly = null;
|
2023-03-16 19:48:49 +03:00
|
|
|
long randomPolyRef = 0;
|
|
|
|
float[] randomPolyVerts = null;
|
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
while (!m_openList.IsEmpty())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode bestNode = m_openList.Pop();
|
2023-03-16 19:48:49 +03:00
|
|
|
bestNode.flags &= ~DT_NODE_OPEN;
|
|
|
|
bestNode.flags |= DT_NODE_CLOSED;
|
|
|
|
// Get poly and tile.
|
|
|
|
// The API input has been cheked already, skip checking internal data.
|
|
|
|
long bestRef = bestNode.id;
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(bestRef, out var bestTile, out var bestPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Place random locations on on ground.
|
2023-06-08 15:38:02 +03:00
|
|
|
if (bestPoly.GetPolyType() == DtPoly.DT_POLYTYPE_GROUND)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Calc area of the polygon.
|
|
|
|
float polyArea = 0.0f;
|
|
|
|
float[] polyVerts = new float[bestPoly.vertCount * 3];
|
|
|
|
for (int j = 0; j < bestPoly.vertCount; ++j)
|
|
|
|
{
|
|
|
|
Array.Copy(bestTile.data.verts, bestPoly.verts[j] * 3, polyVerts, j * 3, 3);
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
float[] constrainedVerts = constraint.Aply(polyVerts, centerPos, maxRadius);
|
2023-03-16 19:48:49 +03:00
|
|
|
if (constrainedVerts != null)
|
|
|
|
{
|
|
|
|
int vertCount = constrainedVerts.Length / 3;
|
|
|
|
for (int j = 2; j < vertCount; ++j)
|
|
|
|
{
|
|
|
|
int va = 0;
|
|
|
|
int vb = (j - 1) * 3;
|
|
|
|
int vc = j * 3;
|
2023-06-01 18:13:25 +03:00
|
|
|
polyArea += DetourCommon.TriArea2D(constrainedVerts, va, vb, vc);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Choose random polygon weighted by area, using reservoi sampling.
|
|
|
|
areaSum += polyArea;
|
2023-05-06 05:44:57 +03:00
|
|
|
float u = frand.Next();
|
2023-03-16 19:48:49 +03:00
|
|
|
if (u * areaSum <= polyArea)
|
|
|
|
{
|
|
|
|
randomPoly = bestPoly;
|
|
|
|
randomPolyRef = bestRef;
|
|
|
|
randomPolyVerts = constrainedVerts;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Get parent poly and tile.
|
|
|
|
long parentRef = 0;
|
|
|
|
if (bestNode.pidx != 0)
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
parentRef = m_nodePool.GetNodeAtIdx(bestNode.pidx).id;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int i = bestTile.polyLinks[bestPoly.index]; i != DtNavMesh.DT_NULL_LINK; i = bestTile.links[i].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtLink link = bestTile.links[i];
|
2023-03-16 19:48:49 +03:00
|
|
|
long neighbourRef = link.refs;
|
|
|
|
// Skip invalid neighbours and do not follow back to parent.
|
|
|
|
if (neighbourRef == 0 || neighbourRef == parentRef)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Expand to neighbour
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(neighbourRef, out var neighbourTile, out var neighbourPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Do not advance if the polygon is excluded by the filter.
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!filter.PassFilter(neighbourRef, neighbourTile, neighbourPoly))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Find edge and calc distance to the edge.
|
2023-06-18 03:54:29 +03:00
|
|
|
var ppStatus = GetPortalPoints(bestRef, bestPoly, bestTile, neighbourRef,
|
|
|
|
neighbourPoly, neighbourTile, out var va, out var vb);
|
|
|
|
if (ppStatus.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// If the circle is not touching the next polygon, skip it.
|
2023-06-01 18:13:25 +03:00
|
|
|
var distSqr = DetourCommon.DistancePtSegSqr2D(centerPos, va, vb, out var tesg);
|
2023-03-16 19:48:49 +03:00
|
|
|
if (distSqr > radiusSqr)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode neighbourNode = m_nodePool.GetNode(neighbourRef);
|
2023-06-10 16:34:53 +03:00
|
|
|
if (null == neighbourNode)
|
|
|
|
{
|
|
|
|
status |= DtStatus.DT_OUT_OF_NODES;
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_CLOSED) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Cost
|
|
|
|
if (neighbourNode.flags == 0)
|
|
|
|
{
|
2023-06-03 15:47:26 +03:00
|
|
|
neighbourNode.pos = RcVec3f.Lerp(va, vb, 0.5f);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-03 15:47:26 +03:00
|
|
|
float total = bestNode.total + RcVec3f.Distance(bestNode.pos, neighbourNode.pos);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// The node is already in open list and the new result is worse, skip.
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_OPEN) != 0 && total >= neighbourNode.total)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
neighbourNode.id = neighbourRef;
|
2023-06-08 15:38:02 +03:00
|
|
|
neighbourNode.flags = (neighbourNode.flags & ~DtNode.DT_NODE_CLOSED);
|
2023-05-05 02:44:48 +03:00
|
|
|
neighbourNode.pidx = m_nodePool.GetNodeIdx(bestNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
neighbourNode.total = total;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_OPEN) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Modify(neighbourNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
neighbourNode.flags = DtNode.DT_NODE_OPEN;
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Push(neighbourNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (randomPoly == null)
|
|
|
|
{
|
2023-06-10 16:34:53 +03:00
|
|
|
return DtStatus.DT_FAILURE;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Randomly pick point on polygon.
|
2023-05-06 05:44:57 +03:00
|
|
|
float s = frand.Next();
|
|
|
|
float t = frand.Next();
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
float[] areas = new float[randomPolyVerts.Length / 3];
|
2023-06-03 15:47:26 +03:00
|
|
|
RcVec3f pt = DetourCommon.RandomPointInConvexPoly(randomPolyVerts, randomPolyVerts.Length / 3, areas, s, t);
|
2023-06-10 09:16:52 +03:00
|
|
|
ClosestPointOnPoly(randomPolyRef, pt, out var closest, out var _);
|
2023-06-10 16:41:50 +03:00
|
|
|
|
|
|
|
randomRef = randomPolyRef;
|
|
|
|
randomPt = closest;
|
2023-06-10 16:34:53 +03:00
|
|
|
|
|
|
|
return status;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @par
|
|
|
|
///
|
|
|
|
/// Uses the detail polygons to find the surface height. (Most accurate.)
|
|
|
|
///
|
|
|
|
/// @p pos does not have to be within the bounds of the polygon or navigation mesh.
|
|
|
|
///
|
2023-05-05 02:44:48 +03:00
|
|
|
/// See ClosestPointOnPolyBoundary() for a limited but faster option.
|
2023-03-16 19:48:49 +03:00
|
|
|
///
|
|
|
|
/// Finds the closest point on the specified polygon.
|
|
|
|
/// @param[in] ref The reference id of the polygon.
|
|
|
|
/// @param[in] pos The position to check. [(x, y, z)]
|
|
|
|
/// @param[out] closest
|
|
|
|
/// @param[out] posOverPoly
|
|
|
|
/// @returns The status flags for the query.
|
2023-06-10 09:16:52 +03:00
|
|
|
public DtStatus ClosestPointOnPoly(long refs, RcVec3f pos, out RcVec3f closest, out bool posOverPoly)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-10 15:57:39 +03:00
|
|
|
closest = pos;
|
2023-06-10 09:16:52 +03:00
|
|
|
posOverPoly = false;
|
2023-06-10 15:57:39 +03:00
|
|
|
|
2023-06-03 15:47:26 +03:00
|
|
|
if (!m_nav.IsValidPolyRef(refs) || !RcVec3f.IsFinite(pos))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-10 09:16:52 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-10 09:16:52 +03:00
|
|
|
m_nav.ClosestPointOnPoly(refs, pos, out closest, out posOverPoly);
|
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// @par
|
|
|
|
///
|
2023-05-05 02:44:48 +03:00
|
|
|
/// Much faster than ClosestPointOnPoly().
|
2023-03-16 19:48:49 +03:00
|
|
|
///
|
2023-06-18 05:43:01 +03:00
|
|
|
/// If the provided position lies within the polygon's xz-bounds (above or below),
|
2023-03-16 19:48:49 +03:00
|
|
|
/// then @p pos and @p closest will be equal.
|
|
|
|
///
|
2023-06-18 05:43:01 +03:00
|
|
|
/// The height of @p closest will be the polygon boundary. The height detail is not used.
|
|
|
|
///
|
2023-03-16 19:48:49 +03:00
|
|
|
/// @p pos does not have to be within the bounds of the polybon or the navigation mesh.
|
2023-06-18 05:43:01 +03:00
|
|
|
///
|
|
|
|
/// Returns a point on the boundary closest to the source point if the source point is outside the
|
2023-03-16 19:48:49 +03:00
|
|
|
/// polygon's xz-bounds.
|
2023-06-18 05:43:01 +03:00
|
|
|
/// @param[in] ref The reference id to the polygon.
|
|
|
|
/// @param[in] pos The position to check. [(x, y, z)]
|
|
|
|
/// @param[out] closest The closest point. [(x, y, z)]
|
2023-03-16 19:48:49 +03:00
|
|
|
/// @returns The status flags for the query.
|
2023-06-18 05:43:01 +03:00
|
|
|
public DtStatus ClosestPointOnPolyBoundary(long refs, RcVec3f pos, out RcVec3f closest)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 05:43:01 +03:00
|
|
|
closest = pos;
|
2023-06-10 19:15:44 +03:00
|
|
|
var status = m_nav.GetTileAndPolyByRef(refs, out var tile, out var poly);
|
|
|
|
if (status.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 05:43:01 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-18 05:43:01 +03:00
|
|
|
if (tile == null || !RcVec3f.IsFinite(pos))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 05:43:01 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Collect vertices.
|
2023-05-05 02:44:48 +03:00
|
|
|
float[] verts = new float[m_nav.GetMaxVertsPerPoly() * 3];
|
|
|
|
float[] edged = new float[m_nav.GetMaxVertsPerPoly()];
|
|
|
|
float[] edget = new float[m_nav.GetMaxVertsPerPoly()];
|
2023-03-16 19:48:49 +03:00
|
|
|
int nv = poly.vertCount;
|
|
|
|
for (int i = 0; i < nv; ++i)
|
|
|
|
{
|
|
|
|
Array.Copy(tile.data.verts, poly.verts[i] * 3, verts, i * 3, 3);
|
|
|
|
}
|
|
|
|
|
2023-06-01 18:13:25 +03:00
|
|
|
if (DetourCommon.DistancePtPolyEdgesSqr(pos, verts, nv, edged, edget))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-03-28 19:52:26 +03:00
|
|
|
closest = pos;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Point is outside the polygon, dtClamp to nearest edge.
|
|
|
|
float dmin = edged[0];
|
|
|
|
int imin = 0;
|
|
|
|
for (int i = 1; i < nv; ++i)
|
|
|
|
{
|
|
|
|
if (edged[i] < dmin)
|
|
|
|
{
|
|
|
|
dmin = edged[i];
|
|
|
|
imin = i;
|
|
|
|
}
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
int va = imin * 3;
|
|
|
|
int vb = ((imin + 1) % nv) * 3;
|
2023-06-03 15:47:26 +03:00
|
|
|
closest = RcVec3f.Lerp(verts, va, vb, edget[imin]);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-18 05:43:01 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/// @par
|
|
|
|
///
|
|
|
|
/// Will return #DT_FAILURE if the provided position is outside the xz-bounds
|
|
|
|
/// of the polygon.
|
|
|
|
///
|
|
|
|
/// Gets the height of the polygon at the provided position using the height detail. (Most accurate.)
|
|
|
|
/// @param[in] ref The reference id of the polygon.
|
|
|
|
/// @param[in] pos A position within the xz-bounds of the polygon. [(x, y, z)]
|
|
|
|
/// @param[out] height The height at the surface of the polygon.
|
|
|
|
/// @returns The status flags for the query.
|
2023-06-12 17:39:41 +03:00
|
|
|
public DtStatus GetPolyHeight(long refs, RcVec3f pos, out float height)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-12 17:39:41 +03:00
|
|
|
height = default;
|
2023-06-17 11:58:58 +03:00
|
|
|
|
2023-06-10 19:15:44 +03:00
|
|
|
var status = m_nav.GetTileAndPolyByRef(refs, out var tile, out var poly);
|
|
|
|
if (status.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-12 17:39:41 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-03 15:47:26 +03:00
|
|
|
if (!RcVec3f.IsFinite2D(pos))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-12 17:39:41 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// We used to return success for offmesh connections, but the
|
|
|
|
// getPolyHeight in DetourNavMesh does not do this, so special
|
|
|
|
// case it here.
|
2023-06-08 15:38:02 +03:00
|
|
|
if (poly.GetPolyType() == DtPoly.DT_POLYTYPE_OFFMESH_CONNECTION)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
int i = poly.verts[0] * 3;
|
2023-06-03 15:47:26 +03:00
|
|
|
var v0 = new RcVec3f { x = tile.data.verts[i], y = tile.data.verts[i + 1], z = tile.data.verts[i + 2] };
|
2023-03-16 19:48:49 +03:00
|
|
|
i = poly.verts[1] * 3;
|
2023-06-03 15:47:26 +03:00
|
|
|
var v1 = new RcVec3f { x = tile.data.verts[i], y = tile.data.verts[i + 1], z = tile.data.verts[i + 2] };
|
2023-06-12 17:39:41 +03:00
|
|
|
DetourCommon.DistancePtSegSqr2D(pos, v0, v1, out var t);
|
|
|
|
height = v0.y + (v1.y - v0.y) * t;
|
2023-06-17 11:58:58 +03:00
|
|
|
|
2023-06-12 17:39:41 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-12 17:39:41 +03:00
|
|
|
float? h = m_nav.GetPolyHeight(tile, poly, pos);
|
|
|
|
if (!h.HasValue)
|
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
|
|
|
|
|
|
|
height = h.Value;
|
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-11 07:28:05 +03:00
|
|
|
/// Finds the polygon nearest to the specified center point.
|
|
|
|
/// [opt] means the specified parameter can be a null pointer, in that case the output parameter will not be set.
|
|
|
|
///
|
|
|
|
/// @param[in] center The center of the search box. [(x, y, z)]
|
|
|
|
/// @param[in] halfExtents The search distance along each axis. [(x, y, z)]
|
|
|
|
/// @param[in] filter The polygon filter to apply to the query.
|
|
|
|
/// @param[out] nearestRef The reference id of the nearest polygon. Will be set to 0 if no polygon is found.
|
|
|
|
/// @param[out] nearestPt The nearest point on the polygon. Unchanged if no polygon is found. [opt] [(x, y, z)]
|
|
|
|
/// @param[out] isOverPoly Set to true if the point's X/Z coordinate lies inside the polygon, false otherwise. Unchanged if no polygon is found. [opt]
|
|
|
|
/// @returns The status flags for the query.
|
|
|
|
public DtStatus FindNearestPoly(RcVec3f center, RcVec3f halfExtents, IDtQueryFilter filter,
|
|
|
|
out long nearestRef, out RcVec3f nearestPt, out bool isOverPoly)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-11 07:28:05 +03:00
|
|
|
nearestRef = 0;
|
|
|
|
nearestPt = center;
|
|
|
|
isOverPoly = false;
|
2023-06-11 08:17:48 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Get nearby polygons from proximity grid.
|
2023-06-08 15:38:02 +03:00
|
|
|
DtFindNearestPolyQuery query = new DtFindNearestPolyQuery(this, center);
|
|
|
|
DtStatus status = QueryPolygons(center, halfExtents, filter, query);
|
2023-06-10 15:57:39 +03:00
|
|
|
if (status.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-11 07:28:05 +03:00
|
|
|
return status;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-11 07:28:05 +03:00
|
|
|
nearestRef = query.NearestRef();
|
|
|
|
nearestPt = query.NearestPt();
|
|
|
|
isOverPoly = query.OverPoly();
|
|
|
|
|
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// FIXME: (PP) duplicate?
|
2023-06-08 15:38:02 +03:00
|
|
|
protected void QueryPolygonsInTile(DtMeshTile tile, RcVec3f qmin, RcVec3f qmax, IDtQueryFilter filter, IDtPolyQuery query)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
if (tile.data.bvTree != null)
|
|
|
|
{
|
|
|
|
int nodeIndex = 0;
|
2023-03-28 19:52:26 +03:00
|
|
|
var tbmin = tile.data.header.bmin;
|
|
|
|
var tbmax = tile.data.header.bmax;
|
2023-03-16 19:48:49 +03:00
|
|
|
float qfac = tile.data.header.bvQuantFactor;
|
|
|
|
// Calculate quantized box
|
|
|
|
int[] bmin = new int[3];
|
|
|
|
int[] bmax = new int[3];
|
|
|
|
// dtClamp query box to world box.
|
2023-05-05 02:44:48 +03:00
|
|
|
float minx = Clamp(qmin.x, tbmin.x, tbmax.x) - tbmin.x;
|
|
|
|
float miny = Clamp(qmin.y, tbmin.y, tbmax.y) - tbmin.y;
|
|
|
|
float minz = Clamp(qmin.z, tbmin.z, tbmax.z) - tbmin.z;
|
|
|
|
float maxx = Clamp(qmax.x, tbmin.x, tbmax.x) - tbmin.x;
|
|
|
|
float maxy = Clamp(qmax.y, tbmin.y, tbmax.y) - tbmin.y;
|
|
|
|
float maxz = Clamp(qmax.z, tbmin.z, tbmax.z) - tbmin.z;
|
2023-03-16 19:48:49 +03:00
|
|
|
// Quantize
|
|
|
|
bmin[0] = (int)(qfac * minx) & 0x7ffffffe;
|
|
|
|
bmin[1] = (int)(qfac * miny) & 0x7ffffffe;
|
|
|
|
bmin[2] = (int)(qfac * minz) & 0x7ffffffe;
|
|
|
|
bmax[0] = (int)(qfac * maxx + 1) | 1;
|
|
|
|
bmax[1] = (int)(qfac * maxy + 1) | 1;
|
|
|
|
bmax[2] = (int)(qfac * maxz + 1) | 1;
|
|
|
|
|
|
|
|
// Traverse tree
|
2023-05-05 02:44:48 +03:00
|
|
|
long @base = m_nav.GetPolyRefBase(tile);
|
2023-03-16 19:48:49 +03:00
|
|
|
int end = tile.data.header.bvNodeCount;
|
|
|
|
while (nodeIndex < end)
|
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtBVNode node = tile.data.bvTree[nodeIndex];
|
2023-06-01 18:13:25 +03:00
|
|
|
bool overlap = DetourCommon.OverlapQuantBounds(bmin, bmax, node.bmin, node.bmax);
|
2023-03-16 19:48:49 +03:00
|
|
|
bool isLeafNode = node.i >= 0;
|
|
|
|
|
|
|
|
if (isLeafNode && overlap)
|
|
|
|
{
|
2023-06-01 18:13:25 +03:00
|
|
|
long refs = @base | (long)node.i;
|
2023-05-05 02:44:48 +03:00
|
|
|
if (filter.PassFilter(refs, tile, tile.data.polys[node.i]))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
query.Process(tile, tile.data.polys[node.i], refs);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (overlap || isLeafNode)
|
|
|
|
{
|
|
|
|
nodeIndex++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int escapeIndex = -node.i;
|
|
|
|
nodeIndex += escapeIndex;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
else
|
|
|
|
{
|
2023-06-03 15:47:26 +03:00
|
|
|
RcVec3f bmin = new RcVec3f();
|
|
|
|
RcVec3f bmax = new RcVec3f();
|
2023-05-05 02:44:48 +03:00
|
|
|
long @base = m_nav.GetPolyRefBase(tile);
|
2023-03-16 19:48:49 +03:00
|
|
|
for (int i = 0; i < tile.data.header.polyCount; ++i)
|
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtPoly p = tile.data.polys[i];
|
2023-03-16 19:48:49 +03:00
|
|
|
// Do not return off-mesh connection polygons.
|
2023-06-08 15:38:02 +03:00
|
|
|
if (p.GetPolyType() == DtPoly.DT_POLYTYPE_OFFMESH_CONNECTION)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-06-01 18:13:25 +03:00
|
|
|
long refs = @base | (long)i;
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!filter.PassFilter(refs, tile, p))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calc polygon bounds.
|
|
|
|
int v = p.verts[0] * 3;
|
2023-05-20 06:33:51 +03:00
|
|
|
bmin.Set(tile.data.verts, v);
|
|
|
|
bmax.Set(tile.data.verts, v);
|
2023-03-16 19:48:49 +03:00
|
|
|
for (int j = 1; j < p.vertCount; ++j)
|
|
|
|
{
|
|
|
|
v = p.verts[j] * 3;
|
2023-05-22 18:07:12 +03:00
|
|
|
bmin.Min(tile.data.verts, v);
|
|
|
|
bmax.Max(tile.data.verts, v);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-01 18:13:25 +03:00
|
|
|
if (DetourCommon.OverlapBounds(qmin, qmax, bmin, bmax))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
query.Process(tile, p, refs);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/**
|
2023-03-14 08:02:43 +03:00
|
|
|
* Finds polygons that overlap the search box.
|
|
|
|
*
|
|
|
|
* If no polygons are found, the function will return with a polyCount of zero.
|
|
|
|
*
|
|
|
|
* @param center
|
|
|
|
* The center of the search box. [(x, y, z)]
|
|
|
|
* @param halfExtents
|
|
|
|
* The search distance along each axis. [(x, y, z)]
|
|
|
|
* @param filter
|
|
|
|
* The polygon filter to apply to the query.
|
|
|
|
* @return The reference ids of the polygons that overlap the query box.
|
|
|
|
*/
|
2023-06-08 15:38:02 +03:00
|
|
|
public DtStatus QueryPolygons(RcVec3f center, RcVec3f halfExtents, IDtQueryFilter filter, IDtPolyQuery query)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-03 15:47:26 +03:00
|
|
|
if (!RcVec3f.IsFinite(center) || !RcVec3f.IsFinite(halfExtents) || null == filter)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-10 06:37:39 +03:00
|
|
|
return DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Find tiles the query touches.
|
2023-06-03 15:47:26 +03:00
|
|
|
RcVec3f bmin = center.Subtract(halfExtents);
|
|
|
|
RcVec3f bmax = center.Add(halfExtents);
|
2023-05-05 02:44:48 +03:00
|
|
|
foreach (var t in QueryTiles(center, halfExtents))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
QueryPolygonsInTile(t, bmin, bmax, filter, query);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-10 06:37:39 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/**
|
2023-03-14 08:02:43 +03:00
|
|
|
* Finds tiles that overlap the search box.
|
|
|
|
*/
|
2023-06-08 15:38:02 +03:00
|
|
|
public IList<DtMeshTile> QueryTiles(RcVec3f center, RcVec3f halfExtents)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-03 15:47:26 +03:00
|
|
|
if (!RcVec3f.IsFinite(center) || !RcVec3f.IsFinite(halfExtents))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
return ImmutableArray<DtMeshTile>.Empty;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-03 15:47:26 +03:00
|
|
|
RcVec3f bmin = center.Subtract(halfExtents);
|
|
|
|
RcVec3f bmax = center.Add(halfExtents);
|
2023-05-31 16:13:47 +03:00
|
|
|
m_nav.CalcTileLoc(bmin, out var minx, out var miny);
|
|
|
|
m_nav.CalcTileLoc(bmax, out var maxx, out var maxy);
|
2023-06-01 18:13:25 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
List<DtMeshTile> tiles = new List<DtMeshTile>();
|
2023-03-16 19:48:49 +03:00
|
|
|
for (int y = miny; y <= maxy; ++y)
|
|
|
|
{
|
|
|
|
for (int x = minx; x <= maxx; ++x)
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
tiles.AddRange(m_nav.GetTilesAt(x, y));
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return tiles;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
/**
|
2023-03-14 08:02:43 +03:00
|
|
|
* Finds a path from the start polygon to the end polygon.
|
|
|
|
*
|
|
|
|
* If the end polygon cannot be reached through the navigation graph, the last polygon in the path will be the
|
|
|
|
* nearest the end polygon.
|
|
|
|
*
|
|
|
|
* The start and end positions are used to calculate traversal costs. (The y-values impact the result.)
|
|
|
|
*
|
|
|
|
* @param startRef
|
|
|
|
* The refrence id of the start polygon.
|
|
|
|
* @param endRef
|
|
|
|
* The reference id of the end polygon.
|
|
|
|
* @param startPos
|
|
|
|
* A position within the start polygon. [(x, y, z)]
|
|
|
|
* @param endPos
|
|
|
|
* A position within the end polygon. [(x, y, z)]
|
|
|
|
* @param filter
|
|
|
|
* The polygon filter to apply to the query.
|
|
|
|
* @return Found path
|
|
|
|
*/
|
2023-06-23 01:33:03 +03:00
|
|
|
public DtStatus FindPath(long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, ref List<long> path, DtFindPathOption fpo)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-23 01:33:03 +03:00
|
|
|
if (null == path)
|
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
|
|
|
|
|
|
|
path.Clear();
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Validate input
|
2023-06-03 15:47:26 +03:00
|
|
|
if (!m_nav.IsValidPolyRef(startRef) || !m_nav.IsValidPolyRef(endRef) || !RcVec3f.IsFinite(startPos) || !RcVec3f.IsFinite(endPos) || null == filter)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-23 01:33:03 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-22 18:46:51 +03:00
|
|
|
var heuristic = fpo.heuristic;
|
|
|
|
var raycastLimit = fpo.raycastLimit;
|
|
|
|
var options = fpo.options;
|
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
float raycastLimitSqr = Sqr(raycastLimit);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// trade quality with performance?
|
|
|
|
if ((options & DT_FINDPATH_ANY_ANGLE) != 0 && raycastLimit < 0f)
|
|
|
|
{
|
|
|
|
// limiting to several times the character radius yields nice results. It is not sensitive
|
|
|
|
// so it is enough to compute it from the first tile.
|
2023-06-08 15:38:02 +03:00
|
|
|
DtMeshTile tile = m_nav.GetTileByRef(startRef);
|
2023-03-16 19:48:49 +03:00
|
|
|
float agentRadius = tile.data.header.walkableRadius;
|
2023-06-08 15:38:02 +03:00
|
|
|
raycastLimitSqr = Sqr(agentRadius * DtNavMesh.DT_RAY_CAST_LIMIT_PROPORTIONS);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (startRef == endRef)
|
|
|
|
{
|
2023-06-23 01:33:03 +03:00
|
|
|
path.Add(startRef);
|
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
m_nodePool.Clear();
|
|
|
|
m_openList.Clear();
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode startNode = m_nodePool.GetNode(startRef);
|
2023-04-12 17:53:28 +03:00
|
|
|
startNode.pos = startPos;
|
2023-03-16 19:48:49 +03:00
|
|
|
startNode.pidx = 0;
|
|
|
|
startNode.cost = 0;
|
2023-05-05 02:44:48 +03:00
|
|
|
startNode.total = heuristic.GetCost(startPos, endPos);
|
2023-03-16 19:48:49 +03:00
|
|
|
startNode.id = startRef;
|
2023-06-08 15:38:02 +03:00
|
|
|
startNode.flags = DtNode.DT_NODE_OPEN;
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Push(startNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode lastBestNode = startNode;
|
2023-03-16 19:48:49 +03:00
|
|
|
float lastBestNodeCost = startNode.total;
|
|
|
|
|
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
while (!m_openList.IsEmpty())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Remove node from open list and put it in closed list.
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode bestNode = m_openList.Pop();
|
|
|
|
bestNode.flags &= ~DtNode.DT_NODE_OPEN;
|
|
|
|
bestNode.flags |= DtNode.DT_NODE_CLOSED;
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Reached the goal, stop searching.
|
|
|
|
if (bestNode.id == endRef)
|
|
|
|
{
|
|
|
|
lastBestNode = bestNode;
|
|
|
|
break;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Get current poly and tile.
|
|
|
|
// The API input has been cheked already, skip checking internal data.
|
|
|
|
long bestRef = bestNode.id;
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(bestRef, out var bestTile, out var bestPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Get parent poly and tile.
|
|
|
|
long parentRef = 0, grandpaRef = 0;
|
2023-06-08 15:38:02 +03:00
|
|
|
DtMeshTile parentTile = null;
|
|
|
|
DtPoly parentPoly = null;
|
|
|
|
DtNode parentNode = null;
|
2023-03-16 19:48:49 +03:00
|
|
|
if (bestNode.pidx != 0)
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
parentNode = m_nodePool.GetNodeAtIdx(bestNode.pidx);
|
2023-03-16 19:48:49 +03:00
|
|
|
parentRef = parentNode.id;
|
|
|
|
if (parentNode.pidx != 0)
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
grandpaRef = m_nodePool.GetNodeAtIdx(parentNode.pidx).id;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (parentRef != 0)
|
|
|
|
{
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(parentRef, out parentTile, out parentPoly);
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// decide whether to test raycast to previous nodes
|
|
|
|
bool tryLOS = false;
|
|
|
|
if ((options & DT_FINDPATH_ANY_ANGLE) != 0)
|
|
|
|
{
|
|
|
|
if ((parentRef != 0) && (raycastLimitSqr >= float.MaxValue
|
2023-06-03 15:47:26 +03:00
|
|
|
|| RcVec3f.DistSqr(parentNode.pos, bestNode.pos) < raycastLimitSqr))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
tryLOS = true;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int i = bestTile.polyLinks[bestPoly.index]; i != DtNavMesh.DT_NULL_LINK; i = bestTile.links[i].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
long neighbourRef = bestTile.links[i].refs;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Skip invalid ids and do not expand back to where we came from.
|
|
|
|
if (neighbourRef == 0 || neighbourRef == parentRef)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Get neighbour poly and tile.
|
|
|
|
// The API input has been cheked already, skip checking internal data.
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(neighbourRef, out var neighbourTile, out var neighbourPoly);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!filter.PassFilter(neighbourRef, neighbourTile, neighbourPoly))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// get the node
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode neighbourNode = m_nodePool.GetNode(neighbourRef, 0);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// do not expand to nodes that were already visited from the
|
|
|
|
// same parent
|
|
|
|
if (neighbourNode.pidx != 0 && neighbourNode.pidx == bestNode.pidx)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// If the node is visited the first time, calculate node position.
|
2023-03-28 19:52:26 +03:00
|
|
|
var neighbourPos = neighbourNode.pos;
|
2023-06-18 08:36:38 +03:00
|
|
|
var empStatus = neighbourRef == endRef
|
|
|
|
? GetEdgeIntersectionPoint(bestNode.pos, bestRef, bestPoly, bestTile,
|
|
|
|
endPos, neighbourRef, neighbourPoly, neighbourTile,
|
|
|
|
ref neighbourPos)
|
|
|
|
: GetEdgeMidPoint(bestRef, bestPoly, bestTile,
|
|
|
|
neighbourRef, neighbourPoly, neighbourTile,
|
|
|
|
ref neighbourPos);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Calculate cost and heuristic.
|
|
|
|
float cost = 0;
|
|
|
|
float heuristicCost = 0;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// raycast parent
|
|
|
|
bool foundShortCut = false;
|
|
|
|
List<long> shortcut = null;
|
|
|
|
if (tryLOS)
|
|
|
|
{
|
2023-06-23 01:33:03 +03:00
|
|
|
var rayStatus = Raycast(parentRef, parentNode.pos, neighbourPos, filter,
|
2023-06-16 13:15:34 +03:00
|
|
|
DT_RAYCAST_USE_COSTS, grandpaRef, out var rayHit);
|
2023-06-23 01:33:03 +03:00
|
|
|
if (rayStatus.Succeeded())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-16 13:15:34 +03:00
|
|
|
foundShortCut = rayHit.t >= 1.0f;
|
2023-03-16 19:48:49 +03:00
|
|
|
if (foundShortCut)
|
|
|
|
{
|
2023-06-16 13:15:34 +03:00
|
|
|
shortcut = rayHit.path;
|
2023-03-16 19:48:49 +03:00
|
|
|
// shortcut found using raycast. Using shorter cost
|
|
|
|
// instead
|
2023-06-16 13:15:34 +03:00
|
|
|
cost = parentNode.cost + rayHit.pathCost;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// update move cost
|
|
|
|
if (!foundShortCut)
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
float curCost = filter.GetCost(bestNode.pos, neighbourPos, parentRef, parentTile,
|
2023-03-14 08:02:43 +03:00
|
|
|
parentPoly, bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
cost = bestNode.cost + curCost;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Special case for last node.
|
|
|
|
if (neighbourRef == endRef)
|
|
|
|
{
|
|
|
|
// Cost
|
2023-05-05 02:44:48 +03:00
|
|
|
float endCost = filter.GetCost(neighbourPos, endPos, bestRef, bestTile, bestPoly, neighbourRef,
|
2023-03-14 08:02:43 +03:00
|
|
|
neighbourTile, neighbourPoly, 0L, null, null);
|
2023-03-16 19:48:49 +03:00
|
|
|
cost = cost + endCost;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Cost
|
2023-05-05 02:44:48 +03:00
|
|
|
heuristicCost = heuristic.GetCost(neighbourPos, endPos);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
float total = cost + heuristicCost;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// The node is already in open list and the new result is worse, skip.
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_OPEN) != 0 && total >= neighbourNode.total)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// The node is already visited and process, and the new result is worse, skip.
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Add or update the node.
|
2023-05-05 02:44:48 +03:00
|
|
|
neighbourNode.pidx = foundShortCut ? bestNode.pidx : m_nodePool.GetNodeIdx(bestNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
neighbourNode.id = neighbourRef;
|
2023-06-08 15:38:02 +03:00
|
|
|
neighbourNode.flags = (neighbourNode.flags & ~DtNode.DT_NODE_CLOSED);
|
2023-03-16 19:48:49 +03:00
|
|
|
neighbourNode.cost = cost;
|
|
|
|
neighbourNode.total = total;
|
|
|
|
neighbourNode.pos = neighbourPos;
|
|
|
|
neighbourNode.shortcut = shortcut;
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_OPEN) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Already in open, update node location.
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Modify(neighbourNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Put the node in open list.
|
2023-06-08 15:38:02 +03:00
|
|
|
neighbourNode.flags |= DtNode.DT_NODE_OPEN;
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Push(neighbourNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Update nearest node to target so far.
|
|
|
|
if (heuristicCost < lastBestNodeCost)
|
|
|
|
{
|
|
|
|
lastBestNodeCost = heuristicCost;
|
|
|
|
lastBestNode = neighbourNode;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-23 01:33:03 +03:00
|
|
|
var status = GetPathToNode(lastBestNode, ref path);
|
2023-03-16 19:48:49 +03:00
|
|
|
if (lastBestNode.id != endRef)
|
|
|
|
{
|
2023-06-23 01:33:03 +03:00
|
|
|
status |= DtStatus.DT_PARTIAL_RESULT;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-23 01:33:03 +03:00
|
|
|
return status;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/**
|
2023-03-14 08:02:43 +03:00
|
|
|
* Intializes a sliced path query.
|
|
|
|
*
|
2023-05-05 02:44:48 +03:00
|
|
|
* Common use case: -# Call InitSlicedFindPath() to initialize the sliced path query. -# Call UpdateSlicedFindPath()
|
|
|
|
* until it returns complete. -# Call FinalizeSlicedFindPath() to get the path.
|
2023-03-14 08:02:43 +03:00
|
|
|
*
|
|
|
|
* @param startRef
|
|
|
|
* The reference id of the start polygon.
|
|
|
|
* @param endRef
|
|
|
|
* The reference id of the end polygon.
|
|
|
|
* @param startPos
|
|
|
|
* A position within the start polygon. [(x, y, z)]
|
|
|
|
* @param endPos
|
|
|
|
* A position within the end polygon. [(x, y, z)]
|
|
|
|
* @param filter
|
|
|
|
* The polygon filter to apply to the query.
|
|
|
|
* @param options
|
|
|
|
* query options (see: #FindPathOptions)
|
|
|
|
* @return
|
|
|
|
*/
|
2023-06-10 09:16:52 +03:00
|
|
|
public DtStatus InitSlicedFindPath(long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, int options)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-21 16:47:47 +03:00
|
|
|
return InitSlicedFindPath(startRef, endRef, startPos, endPos, filter, options, DefaultQueryHeuristic.Default, -1.0f);
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-10 09:16:52 +03:00
|
|
|
public DtStatus InitSlicedFindPath(long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, int options, float raycastLimit)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-21 16:47:47 +03:00
|
|
|
return InitSlicedFindPath(startRef, endRef, startPos, endPos, filter, options, DefaultQueryHeuristic.Default, raycastLimit);
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-10 09:16:52 +03:00
|
|
|
public DtStatus InitSlicedFindPath(long startRef, long endRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, int options, IQueryHeuristic heuristic, float raycastLimit)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Init path state.
|
2023-06-08 15:38:02 +03:00
|
|
|
m_query = new DtQueryData();
|
2023-06-10 06:37:39 +03:00
|
|
|
m_query.status = DtStatus.DT_FAILURE;
|
2023-03-16 19:48:49 +03:00
|
|
|
m_query.startRef = startRef;
|
|
|
|
m_query.endRef = endRef;
|
2023-04-12 17:53:28 +03:00
|
|
|
m_query.startPos = startPos;
|
|
|
|
m_query.endPos = endPos;
|
2023-03-16 19:48:49 +03:00
|
|
|
m_query.filter = filter;
|
|
|
|
m_query.options = options;
|
|
|
|
m_query.heuristic = heuristic;
|
2023-05-05 02:44:48 +03:00
|
|
|
m_query.raycastLimitSqr = Sqr(raycastLimit);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Validate input
|
2023-06-03 15:47:26 +03:00
|
|
|
if (!m_nav.IsValidPolyRef(startRef) || !m_nav.IsValidPolyRef(endRef) || !RcVec3f.IsFinite(startPos) || !RcVec3f.IsFinite(endPos) || null == filter)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-10 06:37:39 +03:00
|
|
|
return DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// trade quality with performance?
|
|
|
|
if ((options & DT_FINDPATH_ANY_ANGLE) != 0 && raycastLimit < 0f)
|
|
|
|
{
|
|
|
|
// limiting to several times the character radius yields nice results. It is not sensitive
|
|
|
|
// so it is enough to compute it from the first tile.
|
2023-06-08 15:38:02 +03:00
|
|
|
DtMeshTile tile = m_nav.GetTileByRef(startRef);
|
2023-03-16 19:48:49 +03:00
|
|
|
float agentRadius = tile.data.header.walkableRadius;
|
2023-06-08 15:38:02 +03:00
|
|
|
m_query.raycastLimitSqr = Sqr(agentRadius * DtNavMesh.DT_RAY_CAST_LIMIT_PROPORTIONS);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (startRef == endRef)
|
|
|
|
{
|
2023-06-10 06:37:39 +03:00
|
|
|
m_query.status = DtStatus.DT_SUCCSESS;
|
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
m_nodePool.Clear();
|
|
|
|
m_openList.Clear();
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode startNode = m_nodePool.GetNode(startRef);
|
2023-04-12 17:53:28 +03:00
|
|
|
startNode.pos = startPos;
|
2023-03-16 19:48:49 +03:00
|
|
|
startNode.pidx = 0;
|
|
|
|
startNode.cost = 0;
|
2023-05-05 02:44:48 +03:00
|
|
|
startNode.total = heuristic.GetCost(startPos, endPos);
|
2023-03-16 19:48:49 +03:00
|
|
|
startNode.id = startRef;
|
2023-06-08 15:38:02 +03:00
|
|
|
startNode.flags = DtNode.DT_NODE_OPEN;
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Push(startNode);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-10 06:37:39 +03:00
|
|
|
m_query.status = DtStatus.DT_IN_PROGRESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
m_query.lastBestNode = startNode;
|
|
|
|
m_query.lastBestNodeCost = startNode.total;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
return m_query.status;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/**
|
2023-03-14 08:02:43 +03:00
|
|
|
* Updates an in-progress sliced path query.
|
|
|
|
*
|
|
|
|
* @param maxIter
|
|
|
|
* The maximum number of iterations to perform.
|
|
|
|
* @return The status flags for the query.
|
|
|
|
*/
|
2023-06-20 16:41:51 +03:00
|
|
|
public virtual DtStatus UpdateSlicedFindPath(int maxIter, out int doneIters)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-20 16:41:51 +03:00
|
|
|
doneIters = 0;
|
2023-06-10 15:57:39 +03:00
|
|
|
if (!m_query.status.InProgress())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-20 16:41:51 +03:00
|
|
|
return m_query.status;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Make sure the request is still valid.
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!m_nav.IsValidPolyRef(m_query.startRef) || !m_nav.IsValidPolyRef(m_query.endRef))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-10 06:37:39 +03:00
|
|
|
m_query.status = DtStatus.DT_FAILURE;
|
2023-06-20 16:41:51 +03:00
|
|
|
return DtStatus.DT_FAILURE;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
int iter = 0;
|
2023-05-05 02:44:48 +03:00
|
|
|
while (iter < maxIter && !m_openList.IsEmpty())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
iter++;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Remove node from open list and put it in closed list.
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode bestNode = m_openList.Pop();
|
|
|
|
bestNode.flags &= ~DtNode.DT_NODE_OPEN;
|
|
|
|
bestNode.flags |= DtNode.DT_NODE_CLOSED;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Reached the goal, stop searching.
|
|
|
|
if (bestNode.id == m_query.endRef)
|
|
|
|
{
|
|
|
|
m_query.lastBestNode = bestNode;
|
2023-06-20 16:41:51 +03:00
|
|
|
var details = m_query.status & DtStatus.DT_STATUS_DETAIL_MASK;
|
|
|
|
m_query.status = DtStatus.DT_SUCCSESS | details;
|
|
|
|
doneIters = iter;
|
|
|
|
return m_query.status;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Get current poly and tile.
|
|
|
|
// The API input has been cheked already, skip checking internal
|
|
|
|
// data.
|
|
|
|
long bestRef = bestNode.id;
|
2023-06-10 19:15:44 +03:00
|
|
|
var status = m_nav.GetTileAndPolyByRef(bestRef, out var bestTile, out var bestPoly);
|
|
|
|
if (status.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// The polygon has disappeared during the sliced query, fail.
|
2023-06-20 16:41:51 +03:00
|
|
|
m_query.status = DtStatus.DT_FAILURE;
|
|
|
|
doneIters = iter;
|
|
|
|
return m_query.status;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Get parent and grand parent poly and tile.
|
|
|
|
long parentRef = 0, grandpaRef = 0;
|
2023-06-08 15:38:02 +03:00
|
|
|
DtMeshTile parentTile = null;
|
|
|
|
DtPoly parentPoly = null;
|
|
|
|
DtNode parentNode = null;
|
2023-03-16 19:48:49 +03:00
|
|
|
if (bestNode.pidx != 0)
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
parentNode = m_nodePool.GetNodeAtIdx(bestNode.pidx);
|
2023-03-16 19:48:49 +03:00
|
|
|
parentRef = parentNode.id;
|
|
|
|
if (parentNode.pidx != 0)
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
grandpaRef = m_nodePool.GetNodeAtIdx(parentNode.pidx).id;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (parentRef != 0)
|
|
|
|
{
|
|
|
|
bool invalidParent = false;
|
2023-06-10 19:15:44 +03:00
|
|
|
status = m_nav.GetTileAndPolyByRef(parentRef, out parentTile, out parentPoly);
|
|
|
|
invalidParent = status.Failed();
|
2023-05-05 02:44:48 +03:00
|
|
|
if (invalidParent || (grandpaRef != 0 && !m_nav.IsValidPolyRef(grandpaRef)))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-20 16:41:51 +03:00
|
|
|
// The polygon has disappeared during the sliced query fail.
|
2023-06-10 06:37:39 +03:00
|
|
|
m_query.status = DtStatus.DT_FAILURE;
|
2023-06-20 16:41:51 +03:00
|
|
|
doneIters = iter;
|
|
|
|
return m_query.status;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// decide whether to test raycast to previous nodes
|
|
|
|
bool tryLOS = false;
|
|
|
|
if ((m_query.options & DT_FINDPATH_ANY_ANGLE) != 0)
|
|
|
|
{
|
|
|
|
if ((parentRef != 0) && (m_query.raycastLimitSqr >= float.MaxValue
|
2023-06-03 15:47:26 +03:00
|
|
|
|| RcVec3f.DistSqr(parentNode.pos, bestNode.pos) < m_query.raycastLimitSqr))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
tryLOS = true;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int i = bestTile.polyLinks[bestPoly.index]; i != DtNavMesh.DT_NULL_LINK; i = bestTile.links[i].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
long neighbourRef = bestTile.links[i].refs;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Skip invalid ids and do not expand back to where we came
|
|
|
|
// from.
|
|
|
|
if (neighbourRef == 0 || neighbourRef == parentRef)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get neighbour poly and tile.
|
|
|
|
// The API input has been cheked already, skip checking internal
|
|
|
|
// data.
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(neighbourRef, out var neighbourTile, out var neighbourPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!m_query.filter.PassFilter(neighbourRef, neighbourTile, neighbourPoly))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// get the neighbor node
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode neighbourNode = m_nodePool.GetNode(neighbourRef, 0);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// do not expand to nodes that were already visited from the
|
|
|
|
// same parent
|
|
|
|
if (neighbourNode.pidx != 0 && neighbourNode.pidx == bestNode.pidx)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the node is visited the first time, calculate node
|
|
|
|
// position.
|
2023-03-28 19:52:26 +03:00
|
|
|
var neighbourPos = neighbourNode.pos;
|
2023-06-18 08:36:38 +03:00
|
|
|
var empStatus = neighbourRef == m_query.endRef
|
2023-06-18 11:29:36 +03:00
|
|
|
? GetEdgeIntersectionPoint(bestNode.pos, bestRef, bestPoly, bestTile,
|
2023-06-18 08:36:38 +03:00
|
|
|
m_query.endPos, neighbourRef, neighbourPoly, neighbourTile,
|
|
|
|
ref neighbourPos)
|
2023-06-18 11:29:36 +03:00
|
|
|
: GetEdgeMidPoint(bestRef, bestPoly, bestTile,
|
|
|
|
neighbourRef, neighbourPoly, neighbourTile,
|
2023-06-18 08:36:38 +03:00
|
|
|
ref neighbourPos);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Calculate cost and heuristic.
|
|
|
|
float cost = 0;
|
|
|
|
float heuristic = 0;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// raycast parent
|
|
|
|
bool foundShortCut = false;
|
|
|
|
List<long> shortcut = null;
|
|
|
|
if (tryLOS)
|
|
|
|
{
|
2023-06-20 16:41:51 +03:00
|
|
|
status = Raycast(parentRef, parentNode.pos, neighbourPos, m_query.filter, DT_RAYCAST_USE_COSTS, grandpaRef, out var rayHit);
|
2023-06-16 13:15:34 +03:00
|
|
|
if (status.Succeeded())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-16 13:15:34 +03:00
|
|
|
foundShortCut = rayHit.t >= 1.0f;
|
2023-03-16 19:48:49 +03:00
|
|
|
if (foundShortCut)
|
|
|
|
{
|
2023-06-16 13:15:34 +03:00
|
|
|
shortcut = rayHit.path;
|
2023-03-16 19:48:49 +03:00
|
|
|
// shortcut found using raycast. Using shorter cost
|
|
|
|
// instead
|
2023-06-16 13:15:34 +03:00
|
|
|
cost = parentNode.cost + rayHit.pathCost;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
2023-06-20 16:41:51 +03:00
|
|
|
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// update move cost
|
|
|
|
if (!foundShortCut)
|
|
|
|
{
|
|
|
|
// No shortcut found.
|
2023-05-05 02:44:48 +03:00
|
|
|
float curCost = m_query.filter.GetCost(bestNode.pos, neighbourPos, parentRef, parentTile,
|
2023-03-14 08:02:43 +03:00
|
|
|
parentPoly, bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
cost = bestNode.cost + curCost;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Special case for last node.
|
|
|
|
if (neighbourRef == m_query.endRef)
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
float endCost = m_query.filter.GetCost(neighbourPos, m_query.endPos, bestRef, bestTile,
|
2023-03-14 08:02:43 +03:00
|
|
|
bestPoly, neighbourRef, neighbourTile, neighbourPoly, 0, null, null);
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
cost = cost + endCost;
|
|
|
|
heuristic = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
heuristic = m_query.heuristic.GetCost(neighbourPos, m_query.endPos);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
float total = cost + heuristic;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// The node is already in open list and the new result is worse,
|
|
|
|
// skip.
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_OPEN) != 0 && total >= neighbourNode.total)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// The node is already visited and process, and the new result
|
|
|
|
// is worse, skip.
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Add or update the node.
|
2023-05-05 02:44:48 +03:00
|
|
|
neighbourNode.pidx = foundShortCut ? bestNode.pidx : m_nodePool.GetNodeIdx(bestNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
neighbourNode.id = neighbourRef;
|
2023-06-20 16:41:51 +03:00
|
|
|
neighbourNode.flags = (neighbourNode.flags & ~DT_NODE_CLOSED);
|
2023-03-16 19:48:49 +03:00
|
|
|
neighbourNode.cost = cost;
|
|
|
|
neighbourNode.total = total;
|
|
|
|
neighbourNode.pos = neighbourPos;
|
|
|
|
neighbourNode.shortcut = shortcut;
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_OPEN) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Already in open, update node location.
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Modify(neighbourNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Put the node in open list.
|
2023-06-08 15:38:02 +03:00
|
|
|
neighbourNode.flags |= DtNode.DT_NODE_OPEN;
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Push(neighbourNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Update nearest node to target so far.
|
|
|
|
if (heuristic < m_query.lastBestNodeCost)
|
|
|
|
{
|
|
|
|
m_query.lastBestNodeCost = heuristic;
|
|
|
|
m_query.lastBestNode = neighbourNode;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Exhausted all nodes, but could not find path.
|
2023-05-05 02:44:48 +03:00
|
|
|
if (m_openList.IsEmpty())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-20 16:41:51 +03:00
|
|
|
var details = m_query.status & DtStatus.DT_STATUS_DETAIL_MASK;
|
|
|
|
m_query.status = DtStatus.DT_SUCCSESS | details;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-20 16:41:51 +03:00
|
|
|
doneIters = iter;
|
|
|
|
return m_query.status;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/// Finalizes and returns the results of a sliced path query.
|
|
|
|
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
|
|
|
|
/// [(polyRef) * @p pathCount]
|
|
|
|
/// @returns The status flags for the query.
|
2023-06-23 01:54:28 +03:00
|
|
|
public virtual DtStatus FinalizeSlicedFindPath(ref List<long> path)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-23 01:54:28 +03:00
|
|
|
if (null == path)
|
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
|
|
|
|
|
|
|
path.Clear();
|
|
|
|
|
2023-06-10 15:57:39 +03:00
|
|
|
if (m_query.status.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Reset query.
|
2023-06-08 15:38:02 +03:00
|
|
|
m_query = new DtQueryData();
|
2023-06-23 01:54:28 +03:00
|
|
|
return DtStatus.DT_FAILURE;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (m_query.startRef == m_query.endRef)
|
|
|
|
{
|
|
|
|
// Special case: the search starts and ends at same poly.
|
|
|
|
path.Add(m_query.startRef);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Reverse the path.
|
|
|
|
if (m_query.lastBestNode.id != m_query.endRef)
|
|
|
|
{
|
2023-06-23 01:33:03 +03:00
|
|
|
m_query.status |= DtStatus.DT_PARTIAL_RESULT;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-23 01:06:01 +03:00
|
|
|
GetPathToNode(m_query.lastBestNode, ref path);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-23 01:33:03 +03:00
|
|
|
var details = m_query.status & DtStatus.DT_STATUS_DETAIL_MASK;
|
|
|
|
|
2023-03-14 08:02:43 +03:00
|
|
|
// Reset query.
|
2023-06-08 15:38:02 +03:00
|
|
|
m_query = new DtQueryData();
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-23 01:54:28 +03:00
|
|
|
return DtStatus.DT_SUCCSESS | details;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/// Finalizes and returns the results of an incomplete sliced path query, returning the path to the furthest
|
|
|
|
/// polygon on the existing path that was visited during the search.
|
|
|
|
/// @param[in] existing An array of polygon references for the existing path.
|
|
|
|
/// @param[in] existingSize The number of polygon in the @p existing array.
|
|
|
|
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
|
|
|
|
/// [(polyRef) * @p pathCount]
|
|
|
|
/// @returns The status flags for the query.
|
2023-06-23 01:54:28 +03:00
|
|
|
public virtual DtStatus FinalizeSlicedFindPathPartial(List<long> existing, ref List<long> path)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-23 01:54:28 +03:00
|
|
|
if (null == path)
|
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
|
|
|
|
|
|
|
path.Clear();
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (null == existing || existing.Count <= 0)
|
|
|
|
{
|
2023-06-23 01:54:28 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-10 15:57:39 +03:00
|
|
|
if (m_query.status.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Reset query.
|
2023-06-08 15:38:02 +03:00
|
|
|
m_query = new DtQueryData();
|
2023-06-23 01:54:28 +03:00
|
|
|
return DtStatus.DT_FAILURE;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (m_query.startRef == m_query.endRef)
|
|
|
|
{
|
|
|
|
// Special case: the search starts and ends at same poly.
|
|
|
|
path.Add(m_query.startRef);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Find furthest existing node that was visited.
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode node = null;
|
2023-03-16 19:48:49 +03:00
|
|
|
for (int i = existing.Count - 1; i >= 0; --i)
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
node = m_nodePool.FindNode(existing[i]);
|
2023-03-16 19:48:49 +03:00
|
|
|
if (node != null)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (node == null)
|
|
|
|
{
|
2023-06-23 01:54:28 +03:00
|
|
|
m_query.status |= DtStatus.DT_PARTIAL_RESULT;
|
2023-03-16 19:48:49 +03:00
|
|
|
node = m_query.lastBestNode;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-23 01:06:01 +03:00
|
|
|
GetPathToNode(node, ref path);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-23 01:54:28 +03:00
|
|
|
var details = m_query.status & DtStatus.DT_STATUS_DETAIL_MASK;
|
|
|
|
|
2023-03-14 08:02:43 +03:00
|
|
|
// Reset query.
|
2023-06-08 15:38:02 +03:00
|
|
|
m_query = new DtQueryData();
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-23 01:54:28 +03:00
|
|
|
return DtStatus.DT_SUCCSESS | details;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-19 19:36:03 +03:00
|
|
|
protected DtStatus AppendVertex(RcVec3f pos, int flags, long refs, ref List<StraightPathItem> straightPath,
|
2023-03-16 19:48:49 +03:00
|
|
|
int maxStraightPath)
|
|
|
|
{
|
2023-06-01 18:13:25 +03:00
|
|
|
if (straightPath.Count > 0 && DetourCommon.VEqual(straightPath[straightPath.Count - 1].pos, pos))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// The vertices are equal, update flags and poly.
|
|
|
|
straightPath[straightPath.Count - 1].flags = flags;
|
|
|
|
straightPath[straightPath.Count - 1].refs = refs;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
else
|
|
|
|
{
|
|
|
|
if (straightPath.Count < maxStraightPath)
|
|
|
|
{
|
|
|
|
// Append new vertex.
|
|
|
|
straightPath.Add(new StraightPathItem(pos, flags, refs));
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// If reached end of path or there is no space to append more vertices, return.
|
|
|
|
if (flags == DT_STRAIGHTPATH_END || straightPath.Count >= maxStraightPath)
|
|
|
|
{
|
2023-06-10 06:37:39 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-10 06:37:39 +03:00
|
|
|
return DtStatus.DT_IN_PROGRESS;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
protected DtStatus AppendPortals(int startIdx, int endIdx, RcVec3f endPos, List<long> path,
|
2023-06-19 19:36:03 +03:00
|
|
|
ref List<StraightPathItem> straightPath, int maxStraightPath, int options)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-03-28 19:52:26 +03:00
|
|
|
var startPos = straightPath[straightPath.Count - 1].pos;
|
2023-03-16 19:48:49 +03:00
|
|
|
// Append or update last vertex
|
2023-06-08 15:38:02 +03:00
|
|
|
DtStatus stat;
|
2023-03-16 19:48:49 +03:00
|
|
|
for (int i = startIdx; i < endIdx; i++)
|
|
|
|
{
|
|
|
|
// Calculate portal
|
|
|
|
long from = path[i];
|
2023-06-10 19:15:44 +03:00
|
|
|
var status = m_nav.GetTileAndPolyByRef(from, out var fromTile, out var fromPoly);
|
|
|
|
if (status.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-10 06:37:39 +03:00
|
|
|
return DtStatus.DT_FAILURE;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
long to = path[i + 1];
|
2023-06-10 19:15:44 +03:00
|
|
|
status = m_nav.GetTileAndPolyByRef(to, out var toTile, out var toPoly);
|
|
|
|
if (status.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-10 06:37:39 +03:00
|
|
|
return DtStatus.DT_FAILURE;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-18 03:54:29 +03:00
|
|
|
var ppStatus = GetPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, out var left, out var right);
|
|
|
|
if (ppStatus.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if ((options & DT_STRAIGHTPATH_AREA_CROSSINGS) != 0)
|
|
|
|
{
|
|
|
|
// Skip intersection if only area crossings are requested.
|
2023-05-05 02:44:48 +03:00
|
|
|
if (fromPoly.GetArea() == toPoly.GetArea())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Append intersection
|
2023-06-01 18:13:25 +03:00
|
|
|
if (DetourCommon.IntersectSegSeg2D(startPos, endPos, left, right, out var _, out var t))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-03 15:47:26 +03:00
|
|
|
var pt = RcVec3f.Lerp(left, right, t);
|
2023-06-19 19:36:03 +03:00
|
|
|
stat = AppendVertex(pt, 0, path[i + 1], ref straightPath, maxStraightPath);
|
2023-06-10 15:57:39 +03:00
|
|
|
if (!stat.InProgress())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
return stat;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-10 06:37:39 +03:00
|
|
|
return DtStatus.DT_IN_PROGRESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// @par
|
2023-06-19 19:36:03 +03:00
|
|
|
///
|
2023-03-16 19:48:49 +03:00
|
|
|
/// This method peforms what is often called 'string pulling'.
|
|
|
|
///
|
2023-06-19 19:36:03 +03:00
|
|
|
/// The start position is clamped to the first polygon in the path, and the
|
|
|
|
/// end position is clamped to the last. So the start and end positions should
|
2023-03-16 19:48:49 +03:00
|
|
|
/// normally be within or very near the first and last polygons respectively.
|
|
|
|
///
|
2023-06-19 19:36:03 +03:00
|
|
|
/// The returned polygon references represent the reference id of the polygon
|
|
|
|
/// that is entered at the associated path position. The reference id associated
|
|
|
|
/// with the end point will always be zero. This allows, for example, matching
|
2023-03-16 19:48:49 +03:00
|
|
|
/// off-mesh link points to their representative polygons.
|
|
|
|
///
|
2023-06-19 19:36:03 +03:00
|
|
|
/// If the provided result buffers are too small for the entire result set,
|
|
|
|
/// they will be filled as far as possible from the start toward the end
|
2023-03-16 19:48:49 +03:00
|
|
|
/// position.
|
|
|
|
///
|
2023-06-19 19:36:03 +03:00
|
|
|
/// Finds the straight path from the start to the end position within the polygon corridor.
|
|
|
|
/// @param[in] startPos Path start position. [(x, y, z)]
|
|
|
|
/// @param[in] endPos Path end position. [(x, y, z)]
|
|
|
|
/// @param[in] path An array of polygon references that represent the path corridor.
|
|
|
|
/// @param[in] pathSize The number of polygons in the @p path array.
|
|
|
|
/// @param[out] straightPath Points describing the straight path. [(x, y, z) * @p straightPathCount].
|
|
|
|
/// @param[in] maxStraightPath The maximum number of points the straight path arrays can hold. [Limit: > 0]
|
|
|
|
/// @param[in] options Query options. (see: #dtStraightPathOptions)
|
2023-03-16 19:48:49 +03:00
|
|
|
/// @returns The status flags for the query.
|
2023-06-20 15:13:25 +03:00
|
|
|
public virtual DtStatus FindStraightPath(RcVec3f startPos, RcVec3f endPos, List<long> path,
|
2023-06-19 19:36:03 +03:00
|
|
|
ref List<StraightPathItem> straightPath,
|
2023-03-16 19:48:49 +03:00
|
|
|
int maxStraightPath, int options)
|
|
|
|
{
|
2023-06-20 15:13:25 +03:00
|
|
|
if (!RcVec3f.IsFinite(startPos) || !RcVec3f.IsFinite(endPos) || null == straightPath
|
|
|
|
|| null == path || 0 == path.Count || path[0] == 0 || maxStraightPath <= 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-19 19:36:03 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-20 15:13:25 +03:00
|
|
|
straightPath.Clear();
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// TODO: Should this be callers responsibility?
|
2023-06-18 05:43:01 +03:00
|
|
|
var closestStartPosRes = ClosestPointOnPolyBoundary(path[0], startPos, out var closestStartPos);
|
2023-04-28 17:22:09 +03:00
|
|
|
if (closestStartPosRes.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-19 19:36:03 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-18 05:43:01 +03:00
|
|
|
var closestEndPosRes = ClosestPointOnPolyBoundary(path[path.Count - 1], endPos, out var closestEndPos);
|
2023-04-28 17:22:09 +03:00
|
|
|
if (closestEndPosRes.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-19 19:36:03 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add start point.
|
2023-06-19 19:36:03 +03:00
|
|
|
DtStatus stat = AppendVertex(closestStartPos, DT_STRAIGHTPATH_START, path[0], ref straightPath, maxStraightPath);
|
2023-06-10 15:57:39 +03:00
|
|
|
if (!stat.InProgress())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-19 19:36:03 +03:00
|
|
|
return stat;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (path.Count > 1)
|
|
|
|
{
|
2023-06-03 15:47:26 +03:00
|
|
|
RcVec3f portalApex = closestStartPos;
|
|
|
|
RcVec3f portalLeft = portalApex;
|
|
|
|
RcVec3f portalRight = portalApex;
|
2023-03-16 19:48:49 +03:00
|
|
|
int apexIndex = 0;
|
|
|
|
int leftIndex = 0;
|
|
|
|
int rightIndex = 0;
|
|
|
|
|
|
|
|
int leftPolyType = 0;
|
|
|
|
int rightPolyType = 0;
|
|
|
|
|
|
|
|
long leftPolyRef = path[0];
|
|
|
|
long rightPolyRef = path[0];
|
|
|
|
|
|
|
|
for (int i = 0; i < path.Count; ++i)
|
|
|
|
{
|
2023-06-03 15:47:26 +03:00
|
|
|
RcVec3f left;
|
|
|
|
RcVec3f right;
|
2023-03-16 19:48:49 +03:00
|
|
|
int toType;
|
|
|
|
|
|
|
|
if (i + 1 < path.Count)
|
|
|
|
{
|
2023-06-18 04:19:40 +03:00
|
|
|
int fromType; // // fromType is ignored.
|
2023-06-18 08:36:38 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Next portal.
|
2023-06-18 05:43:01 +03:00
|
|
|
var ppStatus = GetPortalPoints(path[i], path[i + 1], out left, out right, out fromType, out toType);
|
|
|
|
if (ppStatus.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 04:19:40 +03:00
|
|
|
// Failed to get portal points, in practice this means that path[i+1] is invalid polygon.
|
|
|
|
// Clamp the end point to path[i], and return the path so far.
|
2023-06-18 05:43:01 +03:00
|
|
|
var cpStatus = ClosestPointOnPolyBoundary(path[i], endPos, out closestEndPos);
|
|
|
|
if (cpStatus.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-19 19:36:03 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Append portals along the current straight path segment.
|
|
|
|
if ((options & (DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0)
|
|
|
|
{
|
|
|
|
// Ignore status return value as we're just about to return anyway.
|
2023-06-19 19:36:03 +03:00
|
|
|
AppendPortals(apexIndex, i, closestEndPos, path, ref straightPath, maxStraightPath, options);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
|
|
|
// Ignore status return value as we're just about to return anyway.
|
2023-06-19 19:36:03 +03:00
|
|
|
AppendVertex(closestEndPos, 0, path[i], ref straightPath, maxStraightPath);
|
2023-06-20 15:13:25 +03:00
|
|
|
|
2023-06-19 19:36:03 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM | (straightPath.Count >= maxStraightPath ? DtStatus.DT_BUFFER_TOO_SMALL : DtStatus.DT_STATUS_NOTHING);
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// If starting really close the portal, advance.
|
|
|
|
if (i == 0)
|
|
|
|
{
|
2023-06-01 18:13:25 +03:00
|
|
|
var distSqr = DetourCommon.DistancePtSegSqr2D(portalApex, left, right, out var t);
|
2023-05-30 16:15:44 +03:00
|
|
|
if (distSqr < Sqr(0.001f))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// End of the path.
|
2023-03-28 19:52:26 +03:00
|
|
|
left = closestEndPos;
|
|
|
|
right = closestEndPos;
|
2023-06-08 15:38:02 +03:00
|
|
|
toType = DtPoly.DT_POLYTYPE_GROUND;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Right vertex.
|
2023-06-01 18:13:25 +03:00
|
|
|
if (DetourCommon.TriArea2D(portalApex, portalRight, right) <= 0.0f)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-01 18:13:25 +03:00
|
|
|
if (DetourCommon.VEqual(portalApex, portalRight) || DetourCommon.TriArea2D(portalApex, portalLeft, right) > 0.0f)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-03-28 19:52:26 +03:00
|
|
|
portalRight = right;
|
2023-03-16 19:48:49 +03:00
|
|
|
rightPolyRef = (i + 1 < path.Count) ? path[i + 1] : 0;
|
|
|
|
rightPolyType = toType;
|
|
|
|
rightIndex = i;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Append portals along the current straight path segment.
|
|
|
|
if ((options & (DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0)
|
|
|
|
{
|
2023-06-19 19:36:03 +03:00
|
|
|
stat = AppendPortals(apexIndex, leftIndex, portalLeft, path, ref straightPath, maxStraightPath, options);
|
2023-06-10 15:57:39 +03:00
|
|
|
if (!stat.InProgress())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-19 19:36:03 +03:00
|
|
|
return stat;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-28 19:52:26 +03:00
|
|
|
portalApex = portalLeft;
|
2023-03-16 19:48:49 +03:00
|
|
|
apexIndex = leftIndex;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
int flags = 0;
|
|
|
|
if (leftPolyRef == 0)
|
|
|
|
{
|
|
|
|
flags = DT_STRAIGHTPATH_END;
|
|
|
|
}
|
2023-06-08 15:38:02 +03:00
|
|
|
else if (leftPolyType == DtPoly.DT_POLYTYPE_OFFMESH_CONNECTION)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
flags = DT_STRAIGHTPATH_OFFMESH_CONNECTION;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
long refs = leftPolyRef;
|
|
|
|
|
|
|
|
// Append or update vertex
|
2023-06-19 19:36:03 +03:00
|
|
|
stat = AppendVertex(portalApex, flags, refs, ref straightPath, maxStraightPath);
|
2023-06-10 15:57:39 +03:00
|
|
|
if (!stat.InProgress())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-19 19:36:03 +03:00
|
|
|
return stat;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-28 19:52:26 +03:00
|
|
|
portalLeft = portalApex;
|
|
|
|
portalRight = portalApex;
|
2023-03-16 19:48:49 +03:00
|
|
|
leftIndex = apexIndex;
|
|
|
|
rightIndex = apexIndex;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Restart
|
|
|
|
i = apexIndex;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Left vertex.
|
2023-06-01 18:13:25 +03:00
|
|
|
if (DetourCommon.TriArea2D(portalApex, portalLeft, left) >= 0.0f)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-01 18:13:25 +03:00
|
|
|
if (DetourCommon.VEqual(portalApex, portalLeft) || DetourCommon.TriArea2D(portalApex, portalRight, left) < 0.0f)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-03-28 19:52:26 +03:00
|
|
|
portalLeft = left;
|
2023-03-16 19:48:49 +03:00
|
|
|
leftPolyRef = (i + 1 < path.Count) ? path[i + 1] : 0;
|
|
|
|
leftPolyType = toType;
|
|
|
|
leftIndex = i;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Append portals along the current straight path segment.
|
|
|
|
if ((options & (DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0)
|
|
|
|
{
|
2023-06-19 19:36:03 +03:00
|
|
|
stat = AppendPortals(apexIndex, rightIndex, portalRight, path, ref straightPath, maxStraightPath, options);
|
2023-06-10 15:57:39 +03:00
|
|
|
if (!stat.InProgress())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-19 19:36:03 +03:00
|
|
|
return stat;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-28 19:52:26 +03:00
|
|
|
portalApex = portalRight;
|
2023-03-16 19:48:49 +03:00
|
|
|
apexIndex = rightIndex;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
int flags = 0;
|
|
|
|
if (rightPolyRef == 0)
|
|
|
|
{
|
|
|
|
flags = DT_STRAIGHTPATH_END;
|
|
|
|
}
|
2023-06-08 15:38:02 +03:00
|
|
|
else if (rightPolyType == DtPoly.DT_POLYTYPE_OFFMESH_CONNECTION)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
flags = DT_STRAIGHTPATH_OFFMESH_CONNECTION;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
long refs = rightPolyRef;
|
|
|
|
|
|
|
|
// Append or update vertex
|
2023-06-19 19:36:03 +03:00
|
|
|
stat = AppendVertex(portalApex, flags, refs, ref straightPath, maxStraightPath);
|
2023-06-10 15:57:39 +03:00
|
|
|
if (!stat.InProgress())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-19 19:36:03 +03:00
|
|
|
return stat;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-28 19:52:26 +03:00
|
|
|
portalLeft = portalApex;
|
|
|
|
portalRight = portalApex;
|
2023-03-16 19:48:49 +03:00
|
|
|
leftIndex = apexIndex;
|
|
|
|
rightIndex = apexIndex;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Restart
|
|
|
|
i = apexIndex;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Append portals along the current straight path segment.
|
|
|
|
if ((options & (DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0)
|
|
|
|
{
|
2023-06-19 19:36:03 +03:00
|
|
|
stat = AppendPortals(apexIndex, path.Count - 1, closestEndPos, path, ref straightPath, maxStraightPath, options);
|
2023-06-10 15:57:39 +03:00
|
|
|
if (!stat.InProgress())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-19 19:36:03 +03:00
|
|
|
return stat;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Ignore status return value as we're just about to return anyway.
|
2023-06-19 19:36:03 +03:00
|
|
|
AppendVertex(closestEndPos, DT_STRAIGHTPATH_END, 0, ref straightPath, maxStraightPath);
|
|
|
|
return DtStatus.DT_SUCCSESS | (straightPath.Count >= maxStraightPath ? DtStatus.DT_BUFFER_TOO_SMALL : DtStatus.DT_STATUS_NOTHING);
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/// @par
|
|
|
|
///
|
2023-06-11 18:02:58 +03:00
|
|
|
/// This method is optimized for small delta movement and a small number of
|
|
|
|
/// polygons. If used for too great a distance, the result set will form an
|
2023-03-16 19:48:49 +03:00
|
|
|
/// incomplete path.
|
|
|
|
///
|
2023-06-11 18:02:58 +03:00
|
|
|
/// @p resultPos will equal the @p endPos if the end is reached.
|
2023-03-16 19:48:49 +03:00
|
|
|
/// Otherwise the closest reachable position will be returned.
|
2023-06-11 18:02:58 +03:00
|
|
|
///
|
|
|
|
/// @p resultPos is not projected onto the surface of the navigation
|
2023-03-16 19:48:49 +03:00
|
|
|
/// mesh. Use #getPolyHeight if this is needed.
|
|
|
|
///
|
2023-06-11 18:02:58 +03:00
|
|
|
/// This method treats the end position in the same manner as
|
|
|
|
/// the #raycast method. (As a 2D point.) See that method's documentation
|
2023-03-16 19:48:49 +03:00
|
|
|
/// for details.
|
2023-06-11 18:02:58 +03:00
|
|
|
///
|
|
|
|
/// If the @p visited array is too small to hold the entire result set, it will
|
|
|
|
/// be filled as far as possible from the start position toward the end
|
2023-03-16 19:48:49 +03:00
|
|
|
/// position.
|
|
|
|
///
|
|
|
|
/// Moves from the start to the end position constrained to the navigation mesh.
|
2023-06-11 18:02:58 +03:00
|
|
|
/// @param[in] startRef The reference id of the start polygon.
|
|
|
|
/// @param[in] startPos A position of the mover within the start polygon. [(x, y, x)]
|
|
|
|
/// @param[in] endPos The desired end position of the mover. [(x, y, z)]
|
|
|
|
/// @param[in] filter The polygon filter to apply to the query.
|
|
|
|
/// @param[out] resultPos The result position of the mover. [(x, y, z)]
|
|
|
|
/// @param[out] visited The reference ids of the polygons visited during the move.
|
|
|
|
/// @param[out] visitedCount The number of polygons visited during the move.
|
|
|
|
/// @param[in] maxVisitedSize The maximum number of polygons the @p visited array can hold.
|
|
|
|
/// @returns The status flags for the query.
|
|
|
|
public DtStatus MoveAlongSurface(long startRef, RcVec3f startPos, RcVec3f endPos,
|
|
|
|
IDtQueryFilter filter,
|
2023-07-04 18:03:37 +03:00
|
|
|
out RcVec3f resultPos, ref List<long> visited)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-11 18:02:58 +03:00
|
|
|
resultPos = RcVec3f.Zero;
|
2023-07-08 04:54:07 +03:00
|
|
|
|
2023-07-04 18:03:37 +03:00
|
|
|
if (null != visited)
|
|
|
|
visited.Clear();
|
2023-06-11 18:02:58 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Validate input
|
2023-06-03 15:47:26 +03:00
|
|
|
if (!m_nav.IsValidPolyRef(startRef) || !RcVec3f.IsFinite(startPos)
|
|
|
|
|| !RcVec3f.IsFinite(endPos) || null == filter)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-11 18:02:58 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNodePool tinyNodePool = new DtNodePool();
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode startNode = tinyNodePool.GetNode(startRef);
|
2023-03-16 19:48:49 +03:00
|
|
|
startNode.pidx = 0;
|
|
|
|
startNode.cost = 0;
|
|
|
|
startNode.total = 0;
|
|
|
|
startNode.id = startRef;
|
2023-06-08 15:38:02 +03:00
|
|
|
startNode.flags = DtNode.DT_NODE_CLOSED;
|
|
|
|
LinkedList<DtNode> stack = new LinkedList<DtNode>();
|
2023-03-16 19:48:49 +03:00
|
|
|
stack.AddLast(startNode);
|
|
|
|
|
2023-06-03 15:47:26 +03:00
|
|
|
RcVec3f bestPos = new RcVec3f();
|
2023-03-16 19:48:49 +03:00
|
|
|
float bestDist = float.MaxValue;
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode bestNode = null;
|
2023-04-12 17:53:28 +03:00
|
|
|
bestPos = startPos;
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Search constraints
|
2023-06-03 15:47:26 +03:00
|
|
|
var searchPos = RcVec3f.Lerp(startPos, endPos, 0.5f);
|
|
|
|
float searchRadSqr = Sqr(RcVec3f.Distance(startPos, endPos) / 2.0f + 0.001f);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
float[] verts = new float[m_nav.GetMaxVertsPerPoly() * 3];
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
while (0 < stack.Count)
|
|
|
|
{
|
|
|
|
// Pop front.
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode curNode = stack.First?.Value;
|
2023-03-16 19:48:49 +03:00
|
|
|
stack.RemoveFirst();
|
|
|
|
|
|
|
|
// Get poly and tile.
|
|
|
|
// The API input has been cheked already, skip checking internal data.
|
|
|
|
long curRef = curNode.id;
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(curRef, out var curTile, out var curPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Collect vertices.
|
|
|
|
int nverts = curPoly.vertCount;
|
|
|
|
for (int i = 0; i < nverts; ++i)
|
|
|
|
{
|
|
|
|
Array.Copy(curTile.data.verts, curPoly.verts[i] * 3, verts, i * 3, 3);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If target is inside the poly, stop search.
|
2023-06-01 18:13:25 +03:00
|
|
|
if (DetourCommon.PointInPolygon(endPos, verts, nverts))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
bestNode = curNode;
|
2023-04-12 17:53:28 +03:00
|
|
|
bestPos = endPos;
|
2023-03-16 19:48:49 +03:00
|
|
|
break;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Find wall edges and find nearest point inside the walls.
|
|
|
|
for (int i = 0, j = curPoly.vertCount - 1; i < curPoly.vertCount; j = i++)
|
|
|
|
{
|
|
|
|
// Find links to neighbours.
|
|
|
|
int MAX_NEIS = 8;
|
|
|
|
int nneis = 0;
|
|
|
|
long[] neis = new long[MAX_NEIS];
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((curPoly.neis[j] & DtNavMesh.DT_EXT_LINK) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Tile border.
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int k = curTile.polyLinks[curPoly.index]; k != DtNavMesh.DT_NULL_LINK; k = curTile.links[k].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtLink link = curTile.links[k];
|
2023-03-16 19:48:49 +03:00
|
|
|
if (link.edge == j)
|
|
|
|
{
|
|
|
|
if (link.refs != 0)
|
|
|
|
{
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(link.refs, out var neiTile, out var neiPoly);
|
2023-05-05 02:44:48 +03:00
|
|
|
if (filter.PassFilter(link.refs, neiTile, neiPoly))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
if (nneis < MAX_NEIS)
|
|
|
|
{
|
|
|
|
neis[nneis++] = link.refs;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
else if (curPoly.neis[j] != 0)
|
|
|
|
{
|
|
|
|
int idx = curPoly.neis[j] - 1;
|
2023-06-01 18:13:25 +03:00
|
|
|
long refs = m_nav.GetPolyRefBase(curTile) | (long)idx;
|
2023-05-05 02:44:48 +03:00
|
|
|
if (filter.PassFilter(refs, curTile, curTile.data.polys[idx]))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Internal edge, encode id.
|
|
|
|
neis[nneis++] = refs;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (nneis == 0)
|
|
|
|
{
|
|
|
|
// Wall edge, calc distance.
|
2023-03-14 08:02:43 +03:00
|
|
|
int vj = j * 3;
|
|
|
|
int vi = i * 3;
|
2023-06-01 18:13:25 +03:00
|
|
|
var distSqr = DetourCommon.DistancePtSegSqr2D(endPos, verts, vj, vi, out var tseg);
|
2023-03-16 19:48:49 +03:00
|
|
|
if (distSqr < bestDist)
|
|
|
|
{
|
|
|
|
// Update nearest distance.
|
2023-06-03 15:47:26 +03:00
|
|
|
bestPos = RcVec3f.Lerp(verts, vj, vi, tseg);
|
2023-03-16 19:48:49 +03:00
|
|
|
bestDist = distSqr;
|
|
|
|
bestNode = curNode;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (int k = 0; k < nneis; ++k)
|
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode neighbourNode = tinyNodePool.GetNode(neis[k]);
|
2023-03-16 19:48:49 +03:00
|
|
|
// Skip if already visited.
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_CLOSED) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip the link if it is too far from search constraint.
|
2023-05-05 02:44:48 +03:00
|
|
|
// TODO: Maybe should use GetPortalPoints(), but this one is way faster.
|
2023-03-16 19:48:49 +03:00
|
|
|
int vj = j * 3;
|
|
|
|
int vi = i * 3;
|
2023-06-01 18:13:25 +03:00
|
|
|
var distSqr = DetourCommon.DistancePtSegSqr2D(searchPos, verts, vj, vi, out var _);
|
2023-03-16 19:48:49 +03:00
|
|
|
if (distSqr > searchRadSqr)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Mark as the node as visited and push to queue.
|
2023-05-05 02:44:48 +03:00
|
|
|
neighbourNode.pidx = tinyNodePool.GetNodeIdx(curNode);
|
2023-06-08 15:38:02 +03:00
|
|
|
neighbourNode.flags |= DtNode.DT_NODE_CLOSED;
|
2023-03-16 19:48:49 +03:00
|
|
|
stack.AddLast(neighbourNode);
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (bestNode != null)
|
|
|
|
{
|
|
|
|
// Reverse the path.
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode prev = null;
|
|
|
|
DtNode node = bestNode;
|
2023-03-16 19:48:49 +03:00
|
|
|
do
|
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode next = tinyNodePool.GetNodeAtIdx(node.pidx);
|
2023-05-05 02:44:48 +03:00
|
|
|
node.pidx = tinyNodePool.GetNodeIdx(prev);
|
2023-03-16 19:48:49 +03:00
|
|
|
prev = node;
|
|
|
|
node = next;
|
|
|
|
} while (node != null);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Store result
|
|
|
|
node = prev;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
visited.Add(node.id);
|
2023-05-05 02:44:48 +03:00
|
|
|
node = tinyNodePool.GetNodeAtIdx(node.pidx);
|
2023-03-16 19:48:49 +03:00
|
|
|
} while (node != null);
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-11 18:02:58 +03:00
|
|
|
resultPos = bestPos;
|
|
|
|
|
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-18 04:19:40 +03:00
|
|
|
protected DtStatus GetPortalPoints(long from, long to, out RcVec3f left, out RcVec3f right, out int fromType, out int toType)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 04:19:40 +03:00
|
|
|
left = RcVec3f.Zero;
|
|
|
|
right = RcVec3f.Zero;
|
|
|
|
fromType = 0;
|
|
|
|
toType = 0;
|
|
|
|
|
2023-06-10 19:15:44 +03:00
|
|
|
var status = m_nav.GetTileAndPolyByRef(from, out var fromTile, out var fromPoly);
|
|
|
|
if (status.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 04:19:40 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-18 04:19:40 +03:00
|
|
|
fromType = fromPoly.GetPolyType();
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-10 19:15:44 +03:00
|
|
|
status = m_nav.GetTileAndPolyByRef(to, out var toTile, out var toPoly);
|
|
|
|
if (status.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 04:19:40 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-18 04:19:40 +03:00
|
|
|
toType = toPoly.GetPolyType();
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-18 04:19:40 +03:00
|
|
|
return GetPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, out left, out right);
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Returns portal points between two polygons.
|
2023-06-18 03:54:29 +03:00
|
|
|
protected DtStatus GetPortalPoints(long from, DtPoly fromPoly, DtMeshTile fromTile,
|
|
|
|
long to, DtPoly toPoly, DtMeshTile toTile,
|
|
|
|
out RcVec3f left, out RcVec3f right)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 03:54:29 +03:00
|
|
|
left = RcVec3f.Zero;
|
|
|
|
right = RcVec3f.Zero;
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Find the link that points to the 'to' polygon.
|
2023-06-08 15:38:02 +03:00
|
|
|
DtLink link = null;
|
|
|
|
for (int i = fromTile.polyLinks[fromPoly.index]; i != DtNavMesh.DT_NULL_LINK; i = fromTile.links[i].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
if (fromTile.links[i].refs == to)
|
|
|
|
{
|
|
|
|
link = fromTile.links[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (link == null)
|
|
|
|
{
|
2023-06-18 03:54:29 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Handle off-mesh connections.
|
2023-06-08 15:38:02 +03:00
|
|
|
if (fromPoly.GetPolyType() == DtPoly.DT_POLYTYPE_OFFMESH_CONNECTION)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Find link that points to first vertex.
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int i = fromTile.polyLinks[fromPoly.index]; i != DtNavMesh.DT_NULL_LINK; i = fromTile.links[i].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
if (fromTile.links[i].refs == to)
|
|
|
|
{
|
|
|
|
int v = fromTile.links[i].edge;
|
2023-04-29 06:48:56 +03:00
|
|
|
left.x = fromTile.data.verts[fromPoly.verts[v] * 3];
|
|
|
|
left.y = fromTile.data.verts[fromPoly.verts[v] * 3 + 1];
|
|
|
|
left.z = fromTile.data.verts[fromPoly.verts[v] * 3 + 2];
|
2023-03-31 18:51:17 +03:00
|
|
|
|
2023-04-29 06:48:56 +03:00
|
|
|
right.x = fromTile.data.verts[fromPoly.verts[v] * 3];
|
|
|
|
right.y = fromTile.data.verts[fromPoly.verts[v] * 3 + 1];
|
|
|
|
right.z = fromTile.data.verts[fromPoly.verts[v] * 3 + 2];
|
2023-06-18 03:54:29 +03:00
|
|
|
|
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-18 03:54:29 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
if (toPoly.GetPolyType() == DtPoly.DT_POLYTYPE_OFFMESH_CONNECTION)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int i = toTile.polyLinks[toPoly.index]; i != DtNavMesh.DT_NULL_LINK; i = toTile.links[i].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
if (toTile.links[i].refs == from)
|
|
|
|
{
|
|
|
|
int v = toTile.links[i].edge;
|
2023-04-29 06:48:56 +03:00
|
|
|
left.x = toTile.data.verts[toPoly.verts[v] * 3];
|
|
|
|
left.y = toTile.data.verts[toPoly.verts[v] * 3 + 1];
|
|
|
|
left.z = toTile.data.verts[toPoly.verts[v] * 3 + 2];
|
2023-03-31 18:51:17 +03:00
|
|
|
|
2023-04-29 06:48:56 +03:00
|
|
|
right.x = toTile.data.verts[toPoly.verts[v] * 3];
|
|
|
|
right.y = toTile.data.verts[toPoly.verts[v] * 3 + 1];
|
|
|
|
right.z = toTile.data.verts[toPoly.verts[v] * 3 + 2];
|
2023-03-31 18:51:17 +03:00
|
|
|
|
2023-06-18 03:54:29 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-18 03:54:29 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Find portal vertices.
|
|
|
|
int v0 = fromPoly.verts[link.edge];
|
|
|
|
int v1 = fromPoly.verts[(link.edge + 1) % fromPoly.vertCount];
|
2023-04-29 06:48:56 +03:00
|
|
|
left.x = fromTile.data.verts[v0 * 3];
|
|
|
|
left.y = fromTile.data.verts[v0 * 3 + 1];
|
|
|
|
left.z = fromTile.data.verts[v0 * 3 + 2];
|
2023-03-31 18:51:17 +03:00
|
|
|
|
2023-04-29 06:48:56 +03:00
|
|
|
right.x = fromTile.data.verts[v1 * 3];
|
|
|
|
right.y = fromTile.data.verts[v1 * 3 + 1];
|
|
|
|
right.z = fromTile.data.verts[v1 * 3 + 2];
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// If the link is at tile boundary, dtClamp the vertices to
|
|
|
|
// the link width.
|
|
|
|
if (link.side != 0xff)
|
|
|
|
{
|
|
|
|
// Unpack portal limits.
|
|
|
|
if (link.bmin != 0 || link.bmax != 255)
|
|
|
|
{
|
|
|
|
float s = 1.0f / 255.0f;
|
|
|
|
float tmin = link.bmin * s;
|
|
|
|
float tmax = link.bmax * s;
|
2023-06-03 15:47:26 +03:00
|
|
|
left = RcVec3f.Lerp(fromTile.data.verts, v0 * 3, v1 * 3, tmin);
|
|
|
|
right = RcVec3f.Lerp(fromTile.data.verts, v0 * 3, v1 * 3, tmax);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-18 03:54:29 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-18 08:36:38 +03:00
|
|
|
protected DtStatus GetEdgeMidPoint(long from, DtPoly fromPoly, DtMeshTile fromTile, long to,
|
|
|
|
DtPoly toPoly, DtMeshTile toTile, ref RcVec3f mid)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 03:54:29 +03:00
|
|
|
var ppStatus = GetPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, out var left, out var right);
|
|
|
|
if (ppStatus.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 08:36:38 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-04-29 06:48:56 +03:00
|
|
|
mid.x = (left.x + right.x) * 0.5f;
|
|
|
|
mid.y = (left.y + right.y) * 0.5f;
|
|
|
|
mid.z = (left.z + right.z) * 0.5f;
|
2023-06-18 08:36:38 +03:00
|
|
|
|
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-18 08:36:38 +03:00
|
|
|
protected DtStatus GetEdgeIntersectionPoint(RcVec3f fromPos, long from, DtPoly fromPoly, DtMeshTile fromTile,
|
|
|
|
RcVec3f toPos, long to, DtPoly toPoly, DtMeshTile toTile,
|
|
|
|
ref RcVec3f pt)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 03:54:29 +03:00
|
|
|
var ppStatus = GetPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, out var left, out var right);
|
|
|
|
if (ppStatus.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 08:36:38 +03:00
|
|
|
return DtStatus.DT_FAILURE;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
float t = 0.5f;
|
2023-06-01 18:13:25 +03:00
|
|
|
if (DetourCommon.IntersectSegSeg2D(fromPos, toPos, left, right, out var _, out var t2))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-05-30 16:15:44 +03:00
|
|
|
t = Clamp(t2, 0.1f, 0.9f);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-18 08:36:38 +03:00
|
|
|
pt = RcVec3f.Lerp(left, right, t);
|
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// @par
|
|
|
|
///
|
|
|
|
/// This method is meant to be used for quick, short distance checks.
|
|
|
|
///
|
|
|
|
/// If the path array is too small to hold the result, it will be filled as
|
|
|
|
/// far as possible from the start postion toward the end position.
|
|
|
|
///
|
|
|
|
/// <b>Using the Hit Parameter t of RaycastHit</b>
|
|
|
|
///
|
|
|
|
/// If the hit parameter is a very high value (FLT_MAX), then the ray has hit
|
|
|
|
/// the end position. In this case the path represents a valid corridor to the
|
|
|
|
/// end position and the value of @p hitNormal is undefined.
|
|
|
|
///
|
|
|
|
/// If the hit parameter is zero, then the start position is on the wall that
|
|
|
|
/// was hit and the value of @p hitNormal is undefined.
|
|
|
|
///
|
|
|
|
/// If 0 < t < 1.0 then the following applies:
|
|
|
|
///
|
|
|
|
/// @code
|
|
|
|
/// distanceToHitBorder = distanceToEndPosition * t
|
|
|
|
/// hitPoint = startPos + (endPos - startPos) * t
|
|
|
|
/// @endcode
|
|
|
|
///
|
|
|
|
/// <b>Use Case Restriction</b>
|
|
|
|
///
|
|
|
|
/// The raycast ignores the y-value of the end position. (2D check.) This
|
|
|
|
/// places significant limits on how it can be used. For example:
|
|
|
|
///
|
|
|
|
/// Consider a scene where there is a main floor with a second floor balcony
|
|
|
|
/// that hangs over the main floor. So the first floor mesh extends below the
|
|
|
|
/// balcony mesh. The start position is somewhere on the first floor. The end
|
|
|
|
/// position is on the balcony.
|
|
|
|
///
|
|
|
|
/// The raycast will search toward the end position along the first floor mesh.
|
|
|
|
/// If it reaches the end position's xz-coordinates it will indicate FLT_MAX
|
|
|
|
/// (no wall hit), meaning it reached the end position. This is one example of why
|
|
|
|
/// this method is meant for short distance checks.
|
|
|
|
///
|
|
|
|
/// Casts a 'walkability' ray along the surface of the navigation mesh from
|
|
|
|
/// the start position toward the end position.
|
2023-05-05 02:44:48 +03:00
|
|
|
/// @note A wrapper around Raycast(..., RaycastHit*). Retained for backward compatibility.
|
2023-03-16 19:48:49 +03:00
|
|
|
/// @param[in] startRef The reference id of the start polygon.
|
|
|
|
/// @param[in] startPos A position within the start polygon representing
|
|
|
|
/// the start of the ray. [(x, y, z)]
|
|
|
|
/// @param[in] endPos The position to cast the ray toward. [(x, y, z)]
|
|
|
|
/// @param[out] t The hit parameter. (FLT_MAX if no wall hit.)
|
|
|
|
/// @param[out] hitNormal The normal of the nearest wall hit. [(x, y, z)]
|
|
|
|
/// @param[in] filter The polygon filter to apply to the query.
|
|
|
|
/// @param[out] path The reference ids of the visited polygons. [opt]
|
|
|
|
/// @param[out] pathCount The number of visited polygons. [opt]
|
|
|
|
/// @param[in] maxPath The maximum number of polygons the @p path array can hold.
|
|
|
|
/// @returns The status flags for the query.
|
2023-06-16 13:15:34 +03:00
|
|
|
public DtStatus Raycast(long startRef, RcVec3f startPos, RcVec3f endPos, IDtQueryFilter filter, int options,
|
|
|
|
long prevRef, out DtRaycastHit hit)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-16 13:15:34 +03:00
|
|
|
hit = null;
|
2023-06-17 11:58:58 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Validate input
|
2023-06-17 11:58:58 +03:00
|
|
|
if (!m_nav.IsValidPolyRef(startRef) || !RcVec3f.IsFinite(startPos) || !RcVec3f.IsFinite(endPos)
|
|
|
|
|| null == filter || (prevRef != 0 && !m_nav.IsValidPolyRef(prevRef)))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-16 13:15:34 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-16 13:15:34 +03:00
|
|
|
hit = new DtRaycastHit();
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-29 18:12:36 +03:00
|
|
|
RcVec3f[] verts = new RcVec3f[m_nav.GetMaxVertsPerPoly() + 1];
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-03 15:47:26 +03:00
|
|
|
RcVec3f curPos = RcVec3f.Zero;
|
|
|
|
RcVec3f lastPos = RcVec3f.Zero;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-04-12 17:53:28 +03:00
|
|
|
curPos = startPos;
|
2023-05-14 10:57:57 +03:00
|
|
|
var dir = endPos.Subtract(startPos);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtMeshTile prevTile, tile, nextTile;
|
|
|
|
DtPoly prevPoly, poly, nextPoly;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// The API input has been checked already, skip checking internal data.
|
|
|
|
long curRef = startRef;
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(curRef, out tile, out poly);
|
2023-03-16 19:48:49 +03:00
|
|
|
nextTile = prevTile = tile;
|
|
|
|
nextPoly = prevPoly = poly;
|
|
|
|
if (prevRef != 0)
|
|
|
|
{
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(prevRef, out prevTile, out prevPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
while (curRef != 0)
|
|
|
|
{
|
|
|
|
// Cast ray against current polygon.
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Collect vertices.
|
|
|
|
int nv = 0;
|
|
|
|
for (int i = 0; i < poly.vertCount; ++i)
|
|
|
|
{
|
2023-06-29 18:12:36 +03:00
|
|
|
verts[nv] = RcVec3f.Of(tile.data.verts, poly.verts[i] * 3);
|
2023-03-16 19:48:49 +03:00
|
|
|
nv++;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-01 18:13:25 +03:00
|
|
|
IntersectResult iresult = DetourCommon.IntersectSegmentPoly2D(startPos, endPos, verts, nv);
|
2023-03-16 19:48:49 +03:00
|
|
|
if (!iresult.intersects)
|
|
|
|
{
|
|
|
|
// Could not hit the polygon, keep the old t and report hit.
|
2023-06-16 13:15:34 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
hit.hitEdgeIndex = iresult.segMax;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Keep track of furthest t so far.
|
|
|
|
if (iresult.tmax > hit.t)
|
|
|
|
{
|
|
|
|
hit.t = iresult.tmax;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Store visited polygons.
|
|
|
|
hit.path.Add(curRef);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Ray end is completely inside the polygon.
|
|
|
|
if (iresult.segMax == -1)
|
|
|
|
{
|
|
|
|
hit.t = float.MaxValue;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// add the cost
|
|
|
|
if ((options & DT_RAYCAST_USE_COSTS) != 0)
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
hit.pathCost += filter.GetCost(curPos, endPos, prevRef, prevTile, prevPoly, curRef, tile, poly,
|
2023-03-16 19:48:49 +03:00
|
|
|
curRef, tile, poly);
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-16 13:15:34 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Follow neighbours.
|
|
|
|
long nextRef = 0;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int i = tile.polyLinks[poly.index]; i != DtNavMesh.DT_NULL_LINK; i = tile.links[i].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtLink link = tile.links[i];
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Find link which contains this edge.
|
|
|
|
if (link.edge != iresult.segMax)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Get pointer to the next polygon.
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(link.refs, out nextTile, out nextPoly);
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Skip off-mesh connections.
|
2023-06-08 15:38:02 +03:00
|
|
|
if (nextPoly.GetPolyType() == DtPoly.DT_POLYTYPE_OFFMESH_CONNECTION)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Skip links based on filter.
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!filter.PassFilter(link.refs, nextTile, nextPoly))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// If the link is internal, just return the ref.
|
|
|
|
if (link.side == 0xff)
|
|
|
|
{
|
|
|
|
nextRef = link.refs;
|
|
|
|
break;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// If the link is at tile boundary,
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Check if the link spans the whole edge, and accept.
|
|
|
|
if (link.bmin == 0 && link.bmax == 255)
|
|
|
|
{
|
|
|
|
nextRef = link.refs;
|
|
|
|
break;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Check for partial edge links.
|
|
|
|
int v0 = poly.verts[link.edge];
|
|
|
|
int v1 = poly.verts[(link.edge + 1) % poly.vertCount];
|
|
|
|
int left = v0 * 3;
|
|
|
|
int right = v1 * 3;
|
|
|
|
|
|
|
|
// Check that the intersection lies inside the link portal.
|
|
|
|
if (link.side == 0 || link.side == 4)
|
|
|
|
{
|
|
|
|
// Calculate link size.
|
2023-06-20 17:23:13 +03:00
|
|
|
const float s = 1.0f / 255.0f;
|
2023-03-16 19:48:49 +03:00
|
|
|
float lmin = tile.data.verts[left + 2]
|
|
|
|
+ (tile.data.verts[right + 2] - tile.data.verts[left + 2]) * (link.bmin * s);
|
|
|
|
float lmax = tile.data.verts[left + 2]
|
|
|
|
+ (tile.data.verts[right + 2] - tile.data.verts[left + 2]) * (link.bmax * s);
|
|
|
|
if (lmin > lmax)
|
|
|
|
{
|
2023-04-16 11:34:04 +03:00
|
|
|
(lmin, lmax) = (lmax, lmin);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Find Z intersection.
|
2023-04-29 06:48:56 +03:00
|
|
|
float z = startPos.z + (endPos.z - startPos.z) * iresult.tmax;
|
2023-03-16 19:48:49 +03:00
|
|
|
if (z >= lmin && z <= lmax)
|
|
|
|
{
|
|
|
|
nextRef = link.refs;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (link.side == 2 || link.side == 6)
|
|
|
|
{
|
|
|
|
// Calculate link size.
|
2023-06-20 17:23:13 +03:00
|
|
|
const float s = 1.0f / 255.0f;
|
2023-03-16 19:48:49 +03:00
|
|
|
float lmin = tile.data.verts[left]
|
|
|
|
+ (tile.data.verts[right] - tile.data.verts[left]) * (link.bmin * s);
|
|
|
|
float lmax = tile.data.verts[left]
|
|
|
|
+ (tile.data.verts[right] - tile.data.verts[left]) * (link.bmax * s);
|
|
|
|
if (lmin > lmax)
|
|
|
|
{
|
2023-04-16 11:34:04 +03:00
|
|
|
(lmin, lmax) = (lmax, lmin);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Find X intersection.
|
2023-04-29 06:48:56 +03:00
|
|
|
float x = startPos.x + (endPos.x - startPos.x) * iresult.tmax;
|
2023-03-16 19:48:49 +03:00
|
|
|
if (x >= lmin && x <= lmax)
|
|
|
|
{
|
|
|
|
nextRef = link.refs;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// add the cost
|
|
|
|
if ((options & DT_RAYCAST_USE_COSTS) != 0)
|
|
|
|
{
|
|
|
|
// compute the intersection point at the furthest end of the polygon
|
|
|
|
// and correct the height (since the raycast moves in 2d)
|
2023-04-12 17:53:28 +03:00
|
|
|
lastPos = curPos;
|
2023-06-03 15:47:26 +03:00
|
|
|
curPos = RcVec3f.Mad(startPos, dir, hit.t);
|
2023-06-29 18:12:36 +03:00
|
|
|
var e1 = verts[iresult.segMax];
|
|
|
|
var e2 = verts[(iresult.segMax + 1) % nv];
|
2023-05-14 10:57:57 +03:00
|
|
|
var eDir = e2.Subtract(e1);
|
|
|
|
var diff = curPos.Subtract(e1);
|
2023-05-05 02:44:48 +03:00
|
|
|
float s = Sqr(eDir.x) > Sqr(eDir.z) ? diff.x / eDir.x : diff.z / eDir.z;
|
2023-05-13 06:46:18 +03:00
|
|
|
curPos.y = e1.y + eDir.y * s;
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
hit.pathCost += filter.GetCost(lastPos, curPos, prevRef, prevTile, prevPoly, curRef, tile, poly,
|
2023-03-16 19:48:49 +03:00
|
|
|
nextRef, nextTile, nextPoly);
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (nextRef == 0)
|
|
|
|
{
|
|
|
|
// No neighbour, we hit a wall.
|
|
|
|
|
|
|
|
// Calculate hit normal.
|
|
|
|
int a = iresult.segMax;
|
|
|
|
int b = iresult.segMax + 1 < nv ? iresult.segMax + 1 : 0;
|
2023-06-29 18:12:36 +03:00
|
|
|
// int va = a * 3;
|
|
|
|
// int vb = b * 3;
|
|
|
|
float dx = verts[b].x - verts[a].x;
|
|
|
|
float dz = verts[b].z - verts[a].x;
|
2023-04-29 06:48:56 +03:00
|
|
|
hit.hitNormal.x = dz;
|
|
|
|
hit.hitNormal.y = 0;
|
|
|
|
hit.hitNormal.z = -dx;
|
2023-05-15 17:25:50 +03:00
|
|
|
hit.hitNormal.Normalize();
|
2023-06-16 13:15:34 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// No hit, advance to neighbour polygon.
|
|
|
|
prevRef = curRef;
|
|
|
|
curRef = nextRef;
|
|
|
|
prevTile = tile;
|
|
|
|
tile = nextTile;
|
|
|
|
prevPoly = poly;
|
|
|
|
poly = nextPoly;
|
|
|
|
}
|
|
|
|
|
2023-06-16 13:15:34 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// @par
|
|
|
|
///
|
|
|
|
/// At least one result array must be provided.
|
|
|
|
///
|
|
|
|
/// The order of the result set is from least to highest cost to reach the polygon.
|
|
|
|
///
|
|
|
|
/// A common use case for this method is to perform Dijkstra searches.
|
|
|
|
/// Candidate polygons are found by searching the graph beginning at the start polygon.
|
|
|
|
///
|
|
|
|
/// If a polygon is not found via the graph search, even if it intersects the
|
|
|
|
/// search circle, it will not be included in the result set. For example:
|
|
|
|
///
|
|
|
|
/// polyA is the start polygon.
|
|
|
|
/// polyB shares an edge with polyA. (Is adjacent.)
|
|
|
|
/// polyC shares an edge with polyB, but not with polyA
|
|
|
|
/// Even if the search circle overlaps polyC, it will not be included in the
|
|
|
|
/// result set unless polyB is also in the set.
|
|
|
|
///
|
|
|
|
/// The value of the center point is used as the start position for cost
|
|
|
|
/// calculations. It is not projected onto the surface of the mesh, so its
|
|
|
|
/// y-value will effect the costs.
|
|
|
|
///
|
|
|
|
/// Intersection tests occur in 2D. All polygons and the search circle are
|
|
|
|
/// projected onto the xz-plane. So the y-value of the center point does not
|
|
|
|
/// effect intersection tests.
|
|
|
|
///
|
|
|
|
/// If the result arrays are to small to hold the entire result set, they will be
|
|
|
|
/// filled to capacity.
|
|
|
|
///
|
2023-06-11 08:17:48 +03:00
|
|
|
///@}
|
2023-03-16 19:48:49 +03:00
|
|
|
/// @name Dijkstra Search Functions
|
2023-06-11 08:17:48 +03:00
|
|
|
/// @{
|
2023-03-16 19:48:49 +03:00
|
|
|
/// Finds the polygons along the navigation graph that touch the specified circle.
|
2023-06-11 08:17:48 +03:00
|
|
|
/// @param[in] startRef The reference id of the polygon where the search starts.
|
|
|
|
/// @param[in] centerPos The center of the search circle. [(x, y, z)]
|
|
|
|
/// @param[in] radius The radius of the search circle.
|
|
|
|
/// @param[in] filter The polygon filter to apply to the query.
|
|
|
|
/// @param[out] resultRef The reference ids of the polygons touched by the circle. [opt]
|
|
|
|
/// @param[out] resultParent The reference ids of the parent polygons for each result.
|
|
|
|
/// Zero if a result polygon has no parent. [opt]
|
|
|
|
/// @param[out] resultCost The search cost from @p centerPos to the polygon. [opt]
|
|
|
|
/// @param[out] resultCount The number of polygons found. [opt]
|
|
|
|
/// @param[in] maxResult The maximum number of polygons the result arrays can hold.
|
2023-03-16 19:48:49 +03:00
|
|
|
/// @returns The status flags for the query.
|
2023-06-11 08:17:48 +03:00
|
|
|
public DtStatus FindPolysAroundCircle(long startRef, RcVec3f centerPos, float radius, IDtQueryFilter filter,
|
2023-07-04 18:03:37 +03:00
|
|
|
ref List<long> resultRef, ref List<long> resultParent, ref List<float> resultCost)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-07-04 18:03:37 +03:00
|
|
|
if (null != resultRef)
|
|
|
|
{
|
|
|
|
resultRef.Clear();
|
|
|
|
resultParent.Clear();
|
|
|
|
resultCost.Clear();
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-11 08:17:48 +03:00
|
|
|
// Validate input
|
2023-06-03 15:47:26 +03:00
|
|
|
if (!m_nav.IsValidPolyRef(startRef) || !RcVec3f.IsFinite(centerPos) || radius < 0
|
2023-03-16 19:48:49 +03:00
|
|
|
|| !float.IsFinite(radius) || null == filter)
|
|
|
|
{
|
2023-06-11 08:17:48 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
m_nodePool.Clear();
|
|
|
|
m_openList.Clear();
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode startNode = m_nodePool.GetNode(startRef);
|
2023-04-12 17:53:28 +03:00
|
|
|
startNode.pos = centerPos;
|
2023-03-16 19:48:49 +03:00
|
|
|
startNode.pidx = 0;
|
|
|
|
startNode.cost = 0;
|
|
|
|
startNode.total = 0;
|
|
|
|
startNode.id = startRef;
|
2023-06-08 15:38:02 +03:00
|
|
|
startNode.flags = DtNode.DT_NODE_OPEN;
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Push(startNode);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
float radiusSqr = Sqr(radius);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
while (!m_openList.IsEmpty())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode bestNode = m_openList.Pop();
|
|
|
|
bestNode.flags &= ~DtNode.DT_NODE_OPEN;
|
|
|
|
bestNode.flags |= DtNode.DT_NODE_CLOSED;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Get poly and tile.
|
|
|
|
// The API input has been cheked already, skip checking internal data.
|
|
|
|
long bestRef = bestNode.id;
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(bestRef, out var bestTile, out var bestPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Get parent poly and tile.
|
|
|
|
long parentRef = 0;
|
2023-06-08 15:38:02 +03:00
|
|
|
DtMeshTile parentTile = null;
|
|
|
|
DtPoly parentPoly = null;
|
2023-03-16 19:48:49 +03:00
|
|
|
if (bestNode.pidx != 0)
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
parentRef = m_nodePool.GetNodeAtIdx(bestNode.pidx).id;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (parentRef != 0)
|
|
|
|
{
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(parentRef, out parentTile, out parentPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
resultRef.Add(bestRef);
|
|
|
|
resultParent.Add(parentRef);
|
|
|
|
resultCost.Add(bestNode.total);
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int i = bestTile.polyLinks[bestPoly.index]; i != DtNavMesh.DT_NULL_LINK; i = bestTile.links[i].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtLink link = bestTile.links[i];
|
2023-03-16 19:48:49 +03:00
|
|
|
long neighbourRef = link.refs;
|
|
|
|
// Skip invalid neighbours and do not follow back to parent.
|
|
|
|
if (neighbourRef == 0 || neighbourRef == parentRef)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Expand to neighbour
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(neighbourRef, out var neighbourTile, out var neighbourPoly);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Do not advance if the polygon is excluded by the filter.
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!filter.PassFilter(neighbourRef, neighbourTile, neighbourPoly))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Find edge and calc distance to the edge.
|
2023-06-18 03:54:29 +03:00
|
|
|
var ppStatus = GetPortalPoints(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly,
|
|
|
|
neighbourTile, out var va, out var vb);
|
|
|
|
if (ppStatus.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// If the circle is not touching the next polygon, skip it.
|
2023-06-01 18:13:25 +03:00
|
|
|
var distSqr = DetourCommon.DistancePtSegSqr2D(centerPos, va, vb, out var _);
|
2023-03-16 19:48:49 +03:00
|
|
|
if (distSqr > radiusSqr)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode neighbourNode = m_nodePool.GetNode(neighbourRef);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_CLOSED) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cost
|
|
|
|
if (neighbourNode.flags == 0)
|
|
|
|
{
|
2023-06-03 15:47:26 +03:00
|
|
|
neighbourNode.pos = RcVec3f.Lerp(va, vb, 0.5f);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
float cost = filter.GetCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile, parentPoly, bestRef,
|
2023-03-16 19:48:49 +03:00
|
|
|
bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly);
|
|
|
|
|
|
|
|
float total = bestNode.total + cost;
|
|
|
|
// The node is already in open list and the new result is worse, skip.
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_OPEN) != 0 && total >= neighbourNode.total)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
neighbourNode.id = neighbourRef;
|
2023-05-05 02:44:48 +03:00
|
|
|
neighbourNode.pidx = m_nodePool.GetNodeIdx(bestNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
neighbourNode.total = total;
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_OPEN) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Modify(neighbourNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
neighbourNode.flags = DtNode.DT_NODE_OPEN;
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Push(neighbourNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-11 08:17:48 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// @par
|
|
|
|
///
|
|
|
|
/// The order of the result set is from least to highest cost.
|
2023-06-11 08:17:48 +03:00
|
|
|
///
|
2023-03-16 19:48:49 +03:00
|
|
|
/// At least one result array must be provided.
|
|
|
|
///
|
2023-06-11 08:17:48 +03:00
|
|
|
/// A common use case for this method is to perform Dijkstra searches.
|
|
|
|
/// Candidate polygons are found by searching the graph beginning at the start
|
2023-03-16 19:48:49 +03:00
|
|
|
/// polygon.
|
2023-06-11 08:17:48 +03:00
|
|
|
///
|
|
|
|
/// The same intersection test restrictions that apply to findPolysAroundCircle()
|
2023-03-16 19:48:49 +03:00
|
|
|
/// method apply to this method.
|
2023-06-11 08:17:48 +03:00
|
|
|
///
|
|
|
|
/// The 3D centroid of the search polygon is used as the start position for cost
|
2023-03-16 19:48:49 +03:00
|
|
|
/// calculations.
|
2023-06-11 08:17:48 +03:00
|
|
|
///
|
|
|
|
/// Intersection tests occur in 2D. All polygons are projected onto the
|
2023-03-16 19:48:49 +03:00
|
|
|
/// xz-plane. So the y-values of the vertices do not effect intersection tests.
|
2023-06-11 08:17:48 +03:00
|
|
|
///
|
|
|
|
/// If the result arrays are is too small to hold the entire result set, they will
|
2023-03-16 19:48:49 +03:00
|
|
|
/// be filled to capacity.
|
|
|
|
///
|
|
|
|
/// Finds the polygons along the naviation graph that touch the specified convex polygon.
|
2023-06-11 08:17:48 +03:00
|
|
|
/// @param[in] startRef The reference id of the polygon where the search starts.
|
|
|
|
/// @param[in] verts The vertices describing the convex polygon. (CCW)
|
|
|
|
/// [(x, y, z) * @p nverts]
|
|
|
|
/// @param[in] nverts The number of vertices in the polygon.
|
|
|
|
/// @param[in] filter The polygon filter to apply to the query.
|
|
|
|
/// @param[out] resultRef The reference ids of the polygons touched by the search polygon. [opt]
|
|
|
|
/// @param[out] resultParent The reference ids of the parent polygons for each result. Zero if a
|
|
|
|
/// result polygon has no parent. [opt]
|
|
|
|
/// @param[out] resultCost The search cost from the centroid point to the polygon. [opt]
|
|
|
|
/// @param[out] resultCount The number of polygons found.
|
|
|
|
/// @param[in] maxResult The maximum number of polygons the result arrays can hold.
|
2023-03-16 19:48:49 +03:00
|
|
|
/// @returns The status flags for the query.
|
2023-06-29 18:12:36 +03:00
|
|
|
public DtStatus FindPolysAroundShape(long startRef, RcVec3f[] verts, IDtQueryFilter filter,
|
2023-06-28 17:36:58 +03:00
|
|
|
ref List<long> resultRef, ref List<long> resultParent, ref List<float> resultCost)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-28 17:36:58 +03:00
|
|
|
resultRef.Clear();
|
|
|
|
resultParent.Clear();
|
|
|
|
resultCost.Clear();
|
2023-07-08 04:54:07 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Validate input
|
2023-06-29 18:12:36 +03:00
|
|
|
int nverts = verts.Length;
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!m_nav.IsValidPolyRef(startRef) || null == verts || nverts < 3 || null == filter)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-11 08:17:48 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
m_nodePool.Clear();
|
|
|
|
m_openList.Clear();
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-03 15:47:26 +03:00
|
|
|
RcVec3f centerPos = RcVec3f.Zero;
|
2023-03-16 19:48:49 +03:00
|
|
|
for (int i = 0; i < nverts; ++i)
|
|
|
|
{
|
2023-06-29 18:12:36 +03:00
|
|
|
centerPos += verts[i];
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
float scale = 1.0f / nverts;
|
2023-04-29 06:48:56 +03:00
|
|
|
centerPos.x *= scale;
|
|
|
|
centerPos.y *= scale;
|
|
|
|
centerPos.z *= scale;
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode startNode = m_nodePool.GetNode(startRef);
|
2023-04-16 11:34:04 +03:00
|
|
|
startNode.pos = centerPos;
|
2023-03-16 19:48:49 +03:00
|
|
|
startNode.pidx = 0;
|
|
|
|
startNode.cost = 0;
|
|
|
|
startNode.total = 0;
|
|
|
|
startNode.id = startRef;
|
2023-06-08 15:38:02 +03:00
|
|
|
startNode.flags = DtNode.DT_NODE_OPEN;
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Push(startNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
while (!m_openList.IsEmpty())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode bestNode = m_openList.Pop();
|
|
|
|
bestNode.flags &= ~DtNode.DT_NODE_OPEN;
|
|
|
|
bestNode.flags |= DtNode.DT_NODE_CLOSED;
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Get poly and tile.
|
|
|
|
// The API input has been cheked already, skip checking internal data.
|
|
|
|
long bestRef = bestNode.id;
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(bestRef, out var bestTile, out var bestPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Get parent poly and tile.
|
|
|
|
long parentRef = 0;
|
2023-06-08 15:38:02 +03:00
|
|
|
DtMeshTile parentTile = null;
|
|
|
|
DtPoly parentPoly = null;
|
2023-03-16 19:48:49 +03:00
|
|
|
if (bestNode.pidx != 0)
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
parentRef = m_nodePool.GetNodeAtIdx(bestNode.pidx).id;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (parentRef != 0)
|
|
|
|
{
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(parentRef, out parentTile, out parentPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
resultRef.Add(bestRef);
|
|
|
|
resultParent.Add(parentRef);
|
|
|
|
resultCost.Add(bestNode.total);
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int i = bestTile.polyLinks[bestPoly.index]; i != DtNavMesh.DT_NULL_LINK; i = bestTile.links[i].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtLink link = bestTile.links[i];
|
2023-03-16 19:48:49 +03:00
|
|
|
long neighbourRef = link.refs;
|
|
|
|
// Skip invalid neighbours and do not follow back to parent.
|
|
|
|
if (neighbourRef == 0 || neighbourRef == parentRef)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expand to neighbour
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(neighbourRef, out var neighbourTile, out var neighbourPoly);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Do not advance if the polygon is excluded by the filter.
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!filter.PassFilter(neighbourRef, neighbourTile, neighbourPoly))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find edge and calc distance to the edge.
|
2023-06-18 03:54:29 +03:00
|
|
|
var ppStatus = GetPortalPoints(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly,
|
|
|
|
neighbourTile, out var va, out var vb);
|
|
|
|
if (ppStatus.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// If the poly is not touching the edge to the next polygon, skip the connection it.
|
2023-06-01 18:13:25 +03:00
|
|
|
IntersectResult ir = DetourCommon.IntersectSegmentPoly2D(va, vb, verts, nverts);
|
2023-03-16 19:48:49 +03:00
|
|
|
if (!ir.intersects)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (ir.tmin > 1.0f || ir.tmax < 0.0f)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode neighbourNode = m_nodePool.GetNode(neighbourRef);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_CLOSED) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Cost
|
|
|
|
if (neighbourNode.flags == 0)
|
|
|
|
{
|
2023-06-03 15:47:26 +03:00
|
|
|
neighbourNode.pos = RcVec3f.Lerp(va, vb, 0.5f);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
float cost = filter.GetCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile, parentPoly, bestRef,
|
2023-03-14 08:02:43 +03:00
|
|
|
bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly);
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
float total = bestNode.total + cost;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// The node is already in open list and the new result is worse, skip.
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_OPEN) != 0 && total >= neighbourNode.total)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
neighbourNode.id = neighbourRef;
|
2023-05-05 02:44:48 +03:00
|
|
|
neighbourNode.pidx = m_nodePool.GetNodeIdx(bestNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
neighbourNode.total = total;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_OPEN) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Modify(neighbourNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
neighbourNode.flags = DtNode.DT_NODE_OPEN;
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Push(neighbourNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-11 08:17:48 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// @par
|
|
|
|
///
|
2023-06-18 11:57:00 +03:00
|
|
|
/// This method is optimized for a small search radius and small number of result
|
2023-03-16 19:48:49 +03:00
|
|
|
/// polygons.
|
|
|
|
///
|
2023-06-18 11:57:00 +03:00
|
|
|
/// Candidate polygons are found by searching the navigation graph beginning at
|
2023-03-16 19:48:49 +03:00
|
|
|
/// the start polygon.
|
|
|
|
///
|
2023-06-18 11:57:00 +03:00
|
|
|
/// The same intersection test restrictions that apply to the findPolysAroundCircle
|
2023-03-16 19:48:49 +03:00
|
|
|
/// mehtod applies to this method.
|
|
|
|
///
|
2023-06-18 11:57:00 +03:00
|
|
|
/// The value of the center point is used as the start point for cost calculations.
|
|
|
|
/// It is not projected onto the surface of the mesh, so its y-value will effect
|
2023-03-16 19:48:49 +03:00
|
|
|
/// the costs.
|
2023-06-18 11:57:00 +03:00
|
|
|
///
|
|
|
|
/// Intersection tests occur in 2D. All polygons and the search circle are
|
|
|
|
/// projected onto the xz-plane. So the y-value of the center point does not
|
2023-03-16 19:48:49 +03:00
|
|
|
/// effect intersection tests.
|
2023-06-18 11:57:00 +03:00
|
|
|
///
|
|
|
|
/// If the result arrays are is too small to hold the entire result set, they will
|
2023-03-16 19:48:49 +03:00
|
|
|
/// be filled to capacity.
|
2023-06-18 11:57:00 +03:00
|
|
|
///
|
2023-03-16 19:48:49 +03:00
|
|
|
/// Finds the non-overlapping navigation polygons in the local neighbourhood around the center position.
|
2023-06-18 11:57:00 +03:00
|
|
|
/// @param[in] startRef The reference id of the polygon where the search starts.
|
|
|
|
/// @param[in] centerPos The center of the query circle. [(x, y, z)]
|
|
|
|
/// @param[in] radius The radius of the query circle.
|
|
|
|
/// @param[in] filter The polygon filter to apply to the query.
|
|
|
|
/// @param[out] resultRef The reference ids of the polygons touched by the circle.
|
|
|
|
/// @param[out] resultParent The reference ids of the parent polygons for each result.
|
2023-03-16 19:48:49 +03:00
|
|
|
/// @returns The status flags for the query.
|
2023-06-18 11:57:00 +03:00
|
|
|
public DtStatus FindLocalNeighbourhood(long startRef, RcVec3f centerPos, float radius,
|
|
|
|
IDtQueryFilter filter,
|
|
|
|
ref List<long> resultRef, ref List<long> resultParent)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Validate input
|
2023-06-03 15:47:26 +03:00
|
|
|
if (!m_nav.IsValidPolyRef(startRef) || !RcVec3f.IsFinite(centerPos) || radius < 0
|
2023-07-16 10:14:44 +03:00
|
|
|
|| !float.IsFinite(radius) || null == filter
|
|
|
|
|| null == resultRef || null == resultParent)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 11:57:00 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-18 11:57:00 +03:00
|
|
|
resultRef.Clear();
|
|
|
|
resultParent.Clear();
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNodePool tinyNodePool = new DtNodePool();
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode startNode = tinyNodePool.GetNode(startRef);
|
2023-03-16 19:48:49 +03:00
|
|
|
startNode.pidx = 0;
|
|
|
|
startNode.id = startRef;
|
2023-06-08 15:38:02 +03:00
|
|
|
startNode.flags = DtNode.DT_NODE_CLOSED;
|
|
|
|
LinkedList<DtNode> stack = new LinkedList<DtNode>();
|
2023-03-16 19:48:49 +03:00
|
|
|
stack.AddLast(startNode);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
resultRef.Add(startNode.id);
|
|
|
|
resultParent.Add(0L);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
float radiusSqr = Sqr(radius);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
float[] pa = new float[m_nav.GetMaxVertsPerPoly() * 3];
|
|
|
|
float[] pb = new float[m_nav.GetMaxVertsPerPoly() * 3];
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
while (0 < stack.Count)
|
|
|
|
{
|
|
|
|
// Pop front.
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode curNode = stack.First?.Value;
|
2023-03-16 19:48:49 +03:00
|
|
|
stack.RemoveFirst();
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Get poly and tile.
|
|
|
|
// The API input has been cheked already, skip checking internal data.
|
|
|
|
long curRef = curNode.id;
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(curRef, out var curTile, out var curPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int i = curTile.polyLinks[curPoly.index]; i != DtNavMesh.DT_NULL_LINK; i = curTile.links[i].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtLink link = curTile.links[i];
|
2023-03-16 19:48:49 +03:00
|
|
|
long neighbourRef = link.refs;
|
|
|
|
// Skip invalid neighbours.
|
|
|
|
if (neighbourRef == 0)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode neighbourNode = tinyNodePool.GetNode(neighbourRef);
|
2023-03-16 19:48:49 +03:00
|
|
|
// Skip visited.
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_CLOSED) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Expand to neighbour
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(neighbourRef, out var neighbourTile, out var neighbourPoly);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Skip off-mesh connections.
|
2023-06-08 15:38:02 +03:00
|
|
|
if (neighbourPoly.GetPolyType() == DtPoly.DT_POLYTYPE_OFFMESH_CONNECTION)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Do not advance if the polygon is excluded by the filter.
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!filter.PassFilter(neighbourRef, neighbourTile, neighbourPoly))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Find edge and calc distance to the edge.
|
2023-06-18 03:54:29 +03:00
|
|
|
var ppStatus = GetPortalPoints(curRef, curPoly, curTile, neighbourRef, neighbourPoly,
|
|
|
|
neighbourTile, out var va, out var vb);
|
|
|
|
if (ppStatus.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// If the circle is not touching the next polygon, skip it.
|
2023-06-01 18:13:25 +03:00
|
|
|
var distSqr = DetourCommon.DistancePtSegSqr2D(centerPos, va, vb, out var _);
|
2023-03-16 19:48:49 +03:00
|
|
|
if (distSqr > radiusSqr)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Mark node visited, this is done before the overlap test so that
|
|
|
|
// we will not visit the poly again if the test fails.
|
2023-06-08 15:38:02 +03:00
|
|
|
neighbourNode.flags |= DtNode.DT_NODE_CLOSED;
|
2023-05-05 02:44:48 +03:00
|
|
|
neighbourNode.pidx = tinyNodePool.GetNodeIdx(curNode);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Check that the polygon does not collide with existing polygons.
|
|
|
|
|
|
|
|
// Collect vertices of the neighbour poly.
|
|
|
|
int npa = neighbourPoly.vertCount;
|
|
|
|
for (int k = 0; k < npa; ++k)
|
|
|
|
{
|
|
|
|
Array.Copy(neighbourTile.data.verts, neighbourPoly.verts[k] * 3, pa, k * 3, 3);
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
bool overlap = false;
|
|
|
|
for (int j = 0; j < resultRef.Count; ++j)
|
|
|
|
{
|
|
|
|
long pastRef = resultRef[j];
|
|
|
|
|
|
|
|
// Connected polys do not overlap.
|
|
|
|
bool connected = false;
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int k = curTile.polyLinks[curPoly.index]; k != DtNavMesh.DT_NULL_LINK; k = curTile.links[k].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
if (curTile.links[k].refs == pastRef)
|
|
|
|
{
|
|
|
|
connected = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (connected)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Potentially overlapping.
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(pastRef, out var pastTile, out var pastPoly);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Get vertices and test overlap
|
|
|
|
int npb = pastPoly.vertCount;
|
|
|
|
for (int k = 0; k < npb; ++k)
|
|
|
|
{
|
|
|
|
Array.Copy(pastTile.data.verts, pastPoly.verts[k] * 3, pb, k * 3, 3);
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-01 18:13:25 +03:00
|
|
|
if (DetourCommon.OverlapPolyPoly2D(pa, npa, pb, npb))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
overlap = true;
|
2023-03-14 08:02:43 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
if (overlap)
|
|
|
|
{
|
2023-03-14 08:02:43 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
resultRef.Add(neighbourRef);
|
|
|
|
resultParent.Add(curRef);
|
|
|
|
stack.AddLast(neighbourNode);
|
|
|
|
}
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-18 11:57:00 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
protected void InsertInterval(List<DtSegInterval> ints, int tmin, int tmax, long refs)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Find insertion point.
|
|
|
|
int idx = 0;
|
|
|
|
while (idx < ints.Count)
|
|
|
|
{
|
|
|
|
if (tmax <= ints[idx].tmin)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
idx++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store
|
2023-06-08 15:38:02 +03:00
|
|
|
ints.Insert(idx, new DtSegInterval(refs, tmin, tmax));
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// @par
|
|
|
|
///
|
|
|
|
/// If the @p segmentRefs parameter is provided, then all polygon segments will be returned.
|
|
|
|
/// Otherwise only the wall segments are returned.
|
|
|
|
///
|
|
|
|
/// A segment that is normally a portal will be included in the result set as a
|
|
|
|
/// wall if the @p filter results in the neighbor polygon becoomming impassable.
|
|
|
|
///
|
|
|
|
/// The @p segmentVerts and @p segmentRefs buffers should normally be sized for the
|
|
|
|
/// maximum segments per polygon of the source navigation mesh.
|
|
|
|
///
|
|
|
|
/// Returns the segments for the specified polygon, optionally including portals.
|
|
|
|
/// @param[in] ref The reference id of the polygon.
|
|
|
|
/// @param[in] filter The polygon filter to apply to the query.
|
|
|
|
/// @param[out] segmentVerts The segments. [(ax, ay, az, bx, by, bz) * segmentCount]
|
|
|
|
/// @param[out] segmentRefs The reference ids of each segment's neighbor polygon.
|
|
|
|
/// Or zero if the segment is a wall. [opt] [(parentRef) * @p segmentCount]
|
|
|
|
/// @param[out] segmentCount The number of segments returned.
|
|
|
|
/// @param[in] maxSegments The maximum number of segments the result arrays can hold.
|
|
|
|
/// @returns The status flags for the query.
|
2023-06-19 17:10:48 +03:00
|
|
|
public DtStatus GetPolyWallSegments(long refs, bool storePortals, IDtQueryFilter filter,
|
2023-07-17 17:24:28 +03:00
|
|
|
ref List<RcSegmentVert> segmentVerts, ref List<long> segmentRefs)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-19 17:10:48 +03:00
|
|
|
segmentVerts.Clear();
|
|
|
|
segmentRefs.Clear();
|
2023-06-20 15:13:25 +03:00
|
|
|
|
2023-06-10 19:15:44 +03:00
|
|
|
var status = m_nav.GetTileAndPolyByRef(refs, out var tile, out var poly);
|
|
|
|
if (status.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-19 17:10:48 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
if (null == filter)
|
|
|
|
{
|
2023-06-19 17:10:48 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
List<DtSegInterval> ints = new List<DtSegInterval>(16);
|
2023-03-16 19:48:49 +03:00
|
|
|
for (int i = 0, j = poly.vertCount - 1; i < poly.vertCount; j = i++)
|
|
|
|
{
|
|
|
|
// Skip non-solid edges.
|
|
|
|
ints.Clear();
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((poly.neis[j] & DtNavMesh.DT_EXT_LINK) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Tile border.
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int k = tile.polyLinks[poly.index]; k != DtNavMesh.DT_NULL_LINK; k = tile.links[k].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtLink link = tile.links[k];
|
2023-03-16 19:48:49 +03:00
|
|
|
if (link.edge == j)
|
|
|
|
{
|
|
|
|
if (link.refs != 0)
|
|
|
|
{
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(link.refs, out var neiTile, out var neiPoly);
|
2023-05-05 02:44:48 +03:00
|
|
|
if (filter.PassFilter(link.refs, neiTile, neiPoly))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
InsertInterval(ints, link.bmin, link.bmax, link.refs);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// Internal edge
|
|
|
|
long neiRef = 0;
|
|
|
|
if (poly.neis[j] != 0)
|
|
|
|
{
|
|
|
|
int idx = (poly.neis[j] - 1);
|
2023-06-01 18:13:25 +03:00
|
|
|
neiRef = m_nav.GetPolyRefBase(tile) | (long)idx;
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!filter.PassFilter(neiRef, tile, tile.data.polys[idx]))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
neiRef = 0;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// If the edge leads to another polygon and portals are not stored, skip.
|
|
|
|
if (neiRef != 0 && !storePortals)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
int ivj = poly.verts[j] * 3;
|
|
|
|
int ivi = poly.verts[i] * 3;
|
2023-07-17 17:24:28 +03:00
|
|
|
var seg = new RcSegmentVert();
|
2023-05-20 06:33:51 +03:00
|
|
|
seg.vmin.Set(tile.data.verts, ivj);
|
|
|
|
seg.vmax.Set(tile.data.verts, ivi);
|
2023-04-22 07:43:24 +03:00
|
|
|
// Array.Copy(tile.data.verts, ivj, seg, 0, 3);
|
|
|
|
// Array.Copy(tile.data.verts, ivi, seg, 3, 3);
|
2023-03-14 08:02:43 +03:00
|
|
|
segmentVerts.Add(seg);
|
2023-03-16 19:48:49 +03:00
|
|
|
segmentRefs.Add(neiRef);
|
|
|
|
continue;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Add sentinels
|
2023-05-05 02:44:48 +03:00
|
|
|
InsertInterval(ints, -1, 0, 0);
|
|
|
|
InsertInterval(ints, 255, 256, 0);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Store segments.
|
|
|
|
int vj = poly.verts[j] * 3;
|
|
|
|
int vi = poly.verts[i] * 3;
|
|
|
|
for (int k = 1; k < ints.Count; ++k)
|
|
|
|
{
|
|
|
|
// Portal segment.
|
|
|
|
if (storePortals && ints[k].refs != 0)
|
|
|
|
{
|
|
|
|
float tmin = ints[k].tmin / 255.0f;
|
|
|
|
float tmax = ints[k].tmax / 255.0f;
|
2023-07-17 17:24:28 +03:00
|
|
|
var seg = new RcSegmentVert();
|
2023-06-03 15:47:26 +03:00
|
|
|
seg.vmin = RcVec3f.Lerp(tile.data.verts, vj, vi, tmin);
|
|
|
|
seg.vmax = RcVec3f.Lerp(tile.data.verts, vj, vi, tmax);
|
2023-03-16 19:48:49 +03:00
|
|
|
segmentVerts.Add(seg);
|
|
|
|
segmentRefs.Add(ints[k].refs);
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Wall segment.
|
|
|
|
int imin = ints[k - 1].tmax;
|
|
|
|
int imax = ints[k].tmin;
|
|
|
|
if (imin != imax)
|
|
|
|
{
|
|
|
|
float tmin = imin / 255.0f;
|
|
|
|
float tmax = imax / 255.0f;
|
2023-07-17 17:24:28 +03:00
|
|
|
var seg = new RcSegmentVert();
|
2023-06-03 15:47:26 +03:00
|
|
|
seg.vmin = RcVec3f.Lerp(tile.data.verts, vj, vi, tmin);
|
|
|
|
seg.vmax = RcVec3f.Lerp(tile.data.verts, vj, vi, tmax);
|
2023-03-16 19:48:49 +03:00
|
|
|
segmentVerts.Add(seg);
|
|
|
|
segmentRefs.Add(0L);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-19 17:10:48 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// @par
|
|
|
|
///
|
|
|
|
/// @p hitPos is not adjusted using the height detail data.
|
|
|
|
///
|
|
|
|
/// @p hitDist will equal the search radius if there is no wall within the
|
|
|
|
/// radius. In this case the values of @p hitPos and @p hitNormal are
|
|
|
|
/// undefined.
|
|
|
|
///
|
|
|
|
/// The normal will become unpredicable if @p hitDist is a very small number.
|
|
|
|
///
|
|
|
|
/// Finds the distance from the specified position to the nearest polygon wall.
|
2023-06-18 11:29:36 +03:00
|
|
|
/// @param[in] startRef The reference id of the polygon containing @p centerPos.
|
|
|
|
/// @param[in] centerPos The center of the search circle. [(x, y, z)]
|
|
|
|
/// @param[in] maxRadius The radius of the search circle.
|
|
|
|
/// @param[in] filter The polygon filter to apply to the query.
|
|
|
|
/// @param[out] hitDist The distance to the nearest wall from @p centerPos.
|
|
|
|
/// @param[out] hitPos The nearest position on the wall that was hit. [(x, y, z)]
|
|
|
|
/// @param[out] hitNormal The normalized ray formed from the wall point to the
|
|
|
|
/// source point. [(x, y, z)]
|
2023-03-16 19:48:49 +03:00
|
|
|
/// @returns The status flags for the query.
|
2023-06-18 11:29:36 +03:00
|
|
|
public virtual DtStatus FindDistanceToWall(long startRef, RcVec3f centerPos, float maxRadius,
|
|
|
|
IDtQueryFilter filter,
|
|
|
|
out float hitDist, out RcVec3f hitPos, out RcVec3f hitNormal)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-18 11:29:36 +03:00
|
|
|
hitDist = 0;
|
|
|
|
hitPos = RcVec3f.Zero;
|
|
|
|
hitNormal = RcVec3f.Zero;
|
2023-06-18 11:57:00 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Validate input
|
2023-06-03 15:47:26 +03:00
|
|
|
if (!m_nav.IsValidPolyRef(startRef) || !RcVec3f.IsFinite(centerPos) || maxRadius < 0
|
2023-03-16 19:48:49 +03:00
|
|
|
|| !float.IsFinite(maxRadius) || null == filter)
|
|
|
|
{
|
2023-06-18 11:29:36 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
m_nodePool.Clear();
|
|
|
|
m_openList.Clear();
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode startNode = m_nodePool.GetNode(startRef);
|
2023-04-12 17:53:28 +03:00
|
|
|
startNode.pos = centerPos;
|
2023-03-16 19:48:49 +03:00
|
|
|
startNode.pidx = 0;
|
|
|
|
startNode.cost = 0;
|
|
|
|
startNode.total = 0;
|
|
|
|
startNode.id = startRef;
|
2023-06-08 15:38:02 +03:00
|
|
|
startNode.flags = DtNode.DT_NODE_OPEN;
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Push(startNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
float radiusSqr = Sqr(maxRadius);
|
2023-07-15 05:11:03 +03:00
|
|
|
|
|
|
|
var hasBestV = false;
|
|
|
|
var bestvj = RcVec3f.Zero;
|
|
|
|
var bestvi = RcVec3f.Zero;
|
2023-06-18 11:29:36 +03:00
|
|
|
|
|
|
|
var status = DtStatus.DT_SUCCSESS;
|
2023-05-05 02:44:48 +03:00
|
|
|
while (!m_openList.IsEmpty())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode bestNode = m_openList.Pop();
|
|
|
|
bestNode.flags &= ~DtNode.DT_NODE_OPEN;
|
|
|
|
bestNode.flags |= DtNode.DT_NODE_CLOSED;
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Get poly and tile.
|
|
|
|
// The API input has been cheked already, skip checking internal data.
|
|
|
|
long bestRef = bestNode.id;
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(bestRef, out var bestTile, out var bestPoly);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Get parent poly and tile.
|
|
|
|
long parentRef = 0;
|
|
|
|
if (bestNode.pidx != 0)
|
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
parentRef = m_nodePool.GetNodeAtIdx(bestNode.pidx).id;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Hit test walls.
|
|
|
|
for (int i = 0, j = bestPoly.vertCount - 1; i < bestPoly.vertCount; j = i++)
|
|
|
|
{
|
|
|
|
// Skip non-solid edges.
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((bestPoly.neis[j] & DtNavMesh.DT_EXT_LINK) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Tile border.
|
|
|
|
bool solid = true;
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int k = bestTile.polyLinks[bestPoly.index]; k != DtNavMesh.DT_NULL_LINK; k = bestTile.links[k].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtLink link = bestTile.links[k];
|
2023-03-16 19:48:49 +03:00
|
|
|
if (link.edge == j)
|
|
|
|
{
|
|
|
|
if (link.refs != 0)
|
|
|
|
{
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(link.refs, out var neiTile, out var neiPoly);
|
2023-05-05 02:44:48 +03:00
|
|
|
if (filter.PassFilter(link.refs, neiTile, neiPoly))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
solid = false;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
break;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!solid)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (bestPoly.neis[j] != 0)
|
|
|
|
{
|
|
|
|
// Internal edge
|
|
|
|
int idx = (bestPoly.neis[j] - 1);
|
2023-06-01 18:13:25 +03:00
|
|
|
long refs = m_nav.GetPolyRefBase(bestTile) | (long)idx;
|
2023-05-05 02:44:48 +03:00
|
|
|
if (filter.PassFilter(refs, bestTile, bestTile.data.polys[idx]))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Calc distance to the edge.
|
|
|
|
int vj = bestPoly.verts[j] * 3;
|
|
|
|
int vi = bestPoly.verts[i] * 3;
|
2023-06-01 18:13:25 +03:00
|
|
|
var distSqr = DetourCommon.DistancePtSegSqr2D(centerPos, bestTile.data.verts, vj, vi, out var tseg);
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Edge is too far, skip.
|
|
|
|
if (distSqr > radiusSqr)
|
|
|
|
{
|
2023-03-14 08:02:43 +03:00
|
|
|
continue;
|
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
|
|
|
|
// Hit wall, update radius.
|
|
|
|
radiusSqr = distSqr;
|
|
|
|
// Calculate hit pos.
|
2023-06-18 11:29:36 +03:00
|
|
|
hitPos.x = bestTile.data.verts[vj + 0] + (bestTile.data.verts[vi + 0] - bestTile.data.verts[vj + 0]) * tseg;
|
|
|
|
hitPos.y = bestTile.data.verts[vj + 1] + (bestTile.data.verts[vi + 1] - bestTile.data.verts[vj + 1]) * tseg;
|
|
|
|
hitPos.z = bestTile.data.verts[vj + 2] + (bestTile.data.verts[vi + 2] - bestTile.data.verts[vj + 2]) * tseg;
|
2023-07-15 05:11:03 +03:00
|
|
|
hasBestV = true;
|
2023-06-03 15:47:26 +03:00
|
|
|
bestvj = RcVec3f.Of(bestTile.data.verts, vj);
|
|
|
|
bestvi = RcVec3f.Of(bestTile.data.verts, vi);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
for (int i = bestTile.polyLinks[bestPoly.index]; i != DtNavMesh.DT_NULL_LINK; i = bestTile.links[i].next)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
DtLink link = bestTile.links[i];
|
2023-03-16 19:48:49 +03:00
|
|
|
long neighbourRef = link.refs;
|
|
|
|
// Skip invalid neighbours and do not follow back to parent.
|
|
|
|
if (neighbourRef == 0 || neighbourRef == parentRef)
|
|
|
|
{
|
2023-03-14 08:02:43 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Expand to neighbour.
|
2023-06-10 06:12:02 +03:00
|
|
|
m_nav.GetTileAndPolyByRefUnsafe(neighbourRef, out var neighbourTile, out var neighbourPoly);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Skip off-mesh connections.
|
2023-06-08 15:38:02 +03:00
|
|
|
if (neighbourPoly.GetPolyType() == DtPoly.DT_POLYTYPE_OFFMESH_CONNECTION)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Calc distance to the edge.
|
|
|
|
int va = bestPoly.verts[link.edge] * 3;
|
|
|
|
int vb = bestPoly.verts[(link.edge + 1) % bestPoly.vertCount] * 3;
|
2023-06-01 18:13:25 +03:00
|
|
|
var distSqr = DetourCommon.DistancePtSegSqr2D(centerPos, bestTile.data.verts, va, vb, out var tseg);
|
2023-03-16 19:48:49 +03:00
|
|
|
// If the circle is not touching the next polygon, skip it.
|
|
|
|
if (distSqr > radiusSqr)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-05-05 02:44:48 +03:00
|
|
|
if (!filter.PassFilter(neighbourRef, neighbourTile, neighbourPoly))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode neighbourNode = m_nodePool.GetNode(neighbourRef);
|
2023-06-18 11:29:36 +03:00
|
|
|
if (null == neighbourNode)
|
|
|
|
{
|
|
|
|
status |= DtStatus.DT_OUT_OF_NODES;
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_CLOSED) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Cost
|
|
|
|
if (neighbourNode.flags == 0)
|
|
|
|
{
|
2023-06-18 08:36:38 +03:00
|
|
|
GetEdgeMidPoint(bestRef, bestPoly, bestTile,
|
|
|
|
neighbourRef, neighbourPoly, neighbourTile,
|
|
|
|
ref neighbourNode.pos);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-03 15:47:26 +03:00
|
|
|
float total = bestNode.total + RcVec3f.Distance(bestNode.pos, neighbourNode.pos);
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// The node is already in open list and the new result is worse, skip.
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_OPEN) != 0 && total >= neighbourNode.total)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
neighbourNode.id = neighbourRef;
|
2023-06-08 15:38:02 +03:00
|
|
|
neighbourNode.flags = (neighbourNode.flags & ~DtNode.DT_NODE_CLOSED);
|
2023-05-05 02:44:48 +03:00
|
|
|
neighbourNode.pidx = m_nodePool.GetNodeIdx(bestNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
neighbourNode.total = total;
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
if ((neighbourNode.flags & DtNode.DT_NODE_OPEN) != 0)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Modify(neighbourNode);
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-06-08 15:38:02 +03:00
|
|
|
neighbourNode.flags |= DtNode.DT_NODE_OPEN;
|
2023-05-05 02:44:48 +03:00
|
|
|
m_openList.Push(neighbourNode);
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// Calc hit normal.
|
2023-07-15 05:11:03 +03:00
|
|
|
if (hasBestV)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-07-15 05:11:03 +03:00
|
|
|
var tangent = bestvi.Subtract(bestvj);
|
2023-04-29 06:48:56 +03:00
|
|
|
hitNormal.x = tangent.z;
|
|
|
|
hitNormal.y = 0;
|
|
|
|
hitNormal.z = -tangent.x;
|
2023-05-15 17:25:50 +03:00
|
|
|
hitNormal.Normalize();
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-06-18 11:29:36 +03:00
|
|
|
hitDist = (float)Math.Sqrt(radiusSqr);
|
|
|
|
|
|
|
|
return status;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/// Returns true if the polygon reference is valid and passes the filter restrictions.
|
|
|
|
/// @param[in] ref The polygon reference to check.
|
|
|
|
/// @param[in] filter The filter to apply.
|
2023-06-08 15:38:02 +03:00
|
|
|
public bool IsValidPolyRef(long refs, IDtQueryFilter filter)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-10 19:15:44 +03:00
|
|
|
var status = m_nav.GetTileAndPolyByRef(refs, out var tile, out var poly);
|
|
|
|
if (status.Failed())
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2023-06-11 07:28:05 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
// If cannot pass filter, assume flags has changed and boundary is invalid.
|
2023-06-10 19:15:44 +03:00
|
|
|
if (!filter.PassFilter(refs, tile, poly))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
return false;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
return true;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/// Gets the navigation mesh the query object is using.
|
|
|
|
/// @return The navigation mesh the query object is using.
|
2023-06-08 15:38:02 +03:00
|
|
|
public DtNavMesh GetAttachedNavMesh()
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
return m_nav;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
/**
|
2023-03-14 08:02:43 +03:00
|
|
|
* Gets a path from the explored nodes in the previous search.
|
|
|
|
*
|
|
|
|
* @param endRef
|
|
|
|
* The reference id of the end polygon.
|
|
|
|
* @returns An ordered list of polygon references representing the path. (Start to end.)
|
|
|
|
* @remarks The result of this function depends on the state of the query object. For that reason it should only be
|
|
|
|
* used immediately after one of the two Dijkstra searches, findPolysAroundCircle or findPolysAroundShape.
|
|
|
|
*/
|
2023-06-23 01:54:28 +03:00
|
|
|
public DtStatus GetPathFromDijkstraSearch(long endRef, ref List<long> path)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-23 01:54:28 +03:00
|
|
|
if (!m_nav.IsValidPolyRef(endRef) || null == path)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
2023-06-23 01:54:28 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
2023-07-08 04:54:07 +03:00
|
|
|
|
2023-06-23 01:54:28 +03:00
|
|
|
path.Clear();
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
List<DtNode> nodes = m_nodePool.FindNodes(endRef);
|
2023-03-16 19:48:49 +03:00
|
|
|
if (nodes.Count != 1)
|
|
|
|
{
|
2023-06-23 01:54:28 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode endNode = nodes[0];
|
2023-03-16 19:48:49 +03:00
|
|
|
if ((endNode.flags & DT_NODE_CLOSED) == 0)
|
|
|
|
{
|
2023-06-23 01:54:28 +03:00
|
|
|
return DtStatus.DT_FAILURE | DtStatus.DT_INVALID_PARAM;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
2023-06-23 01:54:28 +03:00
|
|
|
return GetPathToNode(endNode, ref path);
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-23 01:06:01 +03:00
|
|
|
// Gets the path leading to the specified end node.
|
|
|
|
protected DtStatus GetPathToNode(DtNode endNode, ref List<long> path)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
// Reverse the path.
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode curNode = endNode;
|
2023-03-16 19:48:49 +03:00
|
|
|
do
|
|
|
|
{
|
|
|
|
path.Insert(0, curNode.id);
|
2023-06-08 15:38:02 +03:00
|
|
|
DtNode nextNode = m_nodePool.GetNodeAtIdx(curNode.pidx);
|
2023-03-16 19:48:49 +03:00
|
|
|
if (curNode.shortcut != null)
|
|
|
|
{
|
|
|
|
// remove potential duplicates from shortcut path
|
|
|
|
for (int i = curNode.shortcut.Count - 1; i >= 0; i--)
|
|
|
|
{
|
|
|
|
long id = curNode.shortcut[i];
|
|
|
|
if (id != curNode.id && id != nextNode.id)
|
|
|
|
{
|
|
|
|
path.Insert(0, id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
|
2023-03-16 19:48:49 +03:00
|
|
|
curNode = nextNode;
|
|
|
|
} while (curNode != null);
|
|
|
|
|
2023-06-23 01:06:01 +03:00
|
|
|
return DtStatus.DT_SUCCSESS;
|
2023-03-16 19:48:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-03-14 08:02:43 +03:00
|
|
|
* The closed list is the list of polygons that were fully evaluated during the last navigation graph search. (A* or
|
|
|
|
* Dijkstra)
|
|
|
|
*/
|
2023-05-05 02:44:48 +03:00
|
|
|
public bool IsInClosedList(long refs)
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
if (m_nodePool == null)
|
|
|
|
{
|
|
|
|
return false;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
2023-03-16 19:48:49 +03:00
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
foreach (DtNode n in m_nodePool.FindNodes(refs))
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
if ((n.flags & DT_NODE_CLOSED) != 0)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
|
|
|
|
2023-06-08 15:38:02 +03:00
|
|
|
public DtNodePool GetNodePool()
|
2023-03-16 19:48:49 +03:00
|
|
|
{
|
|
|
|
return m_nodePool;
|
|
|
|
}
|
2023-03-14 08:02:43 +03:00
|
|
|
}
|
2023-05-04 18:15:28 +03:00
|
|
|
}
|