// @class dtQueryFilter // // The Default Implementation // // At construction: All area costs default to 1.0. All flags are included // and none are excluded. // // If a polygon has both an include and an exclude flag, it will be excluded. // // The way filtering works, a navigation mesh polygon must have at least one flag // set to ever be considered by a query. So a polygon with no flags will never // be considered. // // Setting the include flags to 0 will result in all polygons being excluded. // // Custom Implementations // // DT_VIRTUAL_QUERYFILTER must be defined in order to extend this class. // // Implement a custom query filter by overriding the virtual passFilter() // and getCost() functions. If this is done, both functions should be as // fast as possible. Use cached local copies of data rather than accessing // your own objects where possible. // // Custom implementations do not need to adhere to the flags or cost logic // used by the default implementation. // // In order for A* searches to work properly, the cost should be proportional to // the travel distance. Implementing a cost modifier less than 1.0 is likely // to lead to problems during pathfinding. // // @see dtNavMeshQuery // using System; using System.Diagnostics; using dtPolyRef = System.UInt64; using dtStatus = System.UInt32; // Define DT_VIRTUAL_QUERYFILTER if you wish to derive a custom filter from dtQueryFilter. // On certain platforms indirect or virtual function call is expensive. The default // setting is to use non-virtual functions, the actual implementations of the functions // are declared as inline for maximum speed. //#define DT_VIRTUAL_QUERYFILTER 1 public static partial class Detour { const float H_SCALE = 0.999f; // Search heuristic scale. /// Defines polygon filtering and traversal costs for navigation mesh query operations. // @ingroup detour public class dtQueryFilter { public float[] m_areaCost = new float[DT_MAX_AREAS]; //< Cost per area type. (Used by default implementation.) public ushort m_includeFlags; //< Flags for polygons that can be visited. (Used by default implementation.) public ushort m_excludeFlags; //< Flags for polygons that should not be visted. (Used by default implementation.) public dtQueryFilter() { m_includeFlags = 0xffff; m_excludeFlags = 0; for (int i = 0; i < DT_MAX_AREAS; ++i) m_areaCost[i] = 1.0f; } /// Returns true if the polygon can be visited. (I.e. Is traversable.) /// @param[in] ref The reference id of the polygon test. /// @param[in] tile The tile containing the polygon. /// @param[in] poly The polygon to test. public bool passFilter(dtPolyRef polyRef, dtMeshTile tile, dtPoly poly) { return (poly.flags & m_includeFlags) != 0 && (poly.flags & m_excludeFlags) == 0; } /// Returns cost to move from the beginning to the end of a line segment /// that is fully contained within a polygon. /// @param[in] pa The start position on the edge of the previous and current polygon. [(x, y, z)] /// @param[in] pb The end position on the edge of the current and next polygon. [(x, y, z)] /// @param[in] prevRef The reference id of the previous polygon. [opt] /// @param[in] prevTile The tile containing the previous polygon. [opt] /// @param[in] prevPoly The previous polygon. [opt] /// @param[in] curRef The reference id of the current polygon. /// @param[in] curTile The tile containing the current polygon. /// @param[in] curPoly The current polygon. /// @param[in] nextRef The refernece id of the next polygon. [opt] /// @param[in] nextTile The tile containing the next polygon. [opt] /// @param[in] nextPoly The next polygon. [opt] public float getCost(float[] pa, float[] pb, dtPolyRef prevRef, dtMeshTile prevTile, dtPoly prevPoly, dtPolyRef curRef, dtMeshTile curTile, dtPoly curPoly, dtPolyRef nextRef, dtMeshTile nextTile, dtPoly nextPoly) { return dtVdist(pa, pb) * m_areaCost[curPoly.getArea()]; } // @name Getters and setters for the default implementation data. ///@{ /// Returns the traversal cost of the area. /// @param[in] i The id of the area. // @returns The traversal cost of the area. public float getAreaCost(int i) { return m_areaCost[i]; } /// Sets the traversal cost of the area. /// @param[in] i The id of the area. /// @param[in] cost The new cost of traversing the area. public void setAreaCost(int i, float cost) { m_areaCost[i] = cost; } /// Returns the include flags for the filter. /// Any polygons that include one or more of these flags will be /// included in the operation. public ushort getIncludeFlags() { return m_includeFlags; } /// Sets the include flags for the filter. // @param[in] flags The new flags. public void setIncludeFlags(ushort flags) { m_includeFlags = flags; } /// Returns the exclude flags for the filter. /// Any polygons that include one ore more of these flags will be /// excluded from the operation. public ushort getExcludeFlags() { return m_excludeFlags; } /// Sets the exclude flags for the filter. // @param[in] flags The new flags. public void setExcludeFlags(ushort flags) { m_excludeFlags = flags; } } /// Provides information about raycast hit /// filled by dtNavMeshQuery::raycast /// @ingroup detour public class dtRaycastHit { /// The hit parameter. (FLT_MAX if no wall hit.) public float t; /// hitNormal The normal of the nearest wall hit. [(x, y, z)] public float[] hitNormal = new float[3]; /// The index of the edge on the final polygon where the wall was hit. public int hitEdgeIndex; /// Pointer to an array of reference ids of the visited polygons. [opt] public dtPolyRef[] path; /// The number of visited polygons. [opt] public int pathCount; /// The maximum number of polygons the @p path array can hold. public int maxPath; /// The cost of the path until hit. public float pathCost; }; ////////////////////////////////////////////////////////////////////////////////////////// // @class dtNavMeshQuery /// Provides the ability to perform pathfinding related queries against /// a navigation mesh. /// /// For methods that support undersized buffers, if the buffer is too small /// to hold the entire result set the return status of the method will include /// the #DT_BUFFER_TOO_SMALL flag. /// /// Constant member functions can be used by multiple clients without side /// effects. (E.g. No change to the closed list. No impact on an in-progress /// sliced path query. Etc.) /// /// Walls and portals: A @e wall is a polygon segment that is /// considered impassable. A @e portal is a passable segment between polygons. /// A portal may be treated as a wall based on the dtQueryFilter used for a query. /// // @see dtNavMesh, dtQueryFilter, #dtAllocNavMeshQuery(), #dtAllocNavMeshQuery() // @ingroup detour public class dtNavMeshQuery { private dtNavMesh m_nav; //< Pointer to navmesh data. private class dtQueryData { public dtStatus status; public dtNode lastBestNode; public float lastBestNodeCost; public dtPolyRef startRef; public dtPolyRef endRef; public float[] startPos = new float[3]; public float[] endPos = new float[3]; public dtQueryFilter filter; public uint options; public float raycastLimitSqr; public void dtcsClear() { status = 0; lastBestNode = null; lastBestNodeCost = .0f; startRef = 0; endRef = 0; for (int i = 0; i < 3; ++i) { startPos[i] = 0f; endPos[i] = 0f; } filter = null; options = 0; raycastLimitSqr = 0f; } } private dtQueryData m_query = new(); //< Sliced query state. private dtNodePool m_tinyNodePool; //< Pointer to small node pool. private dtNodePool m_nodePool; //< Pointer to node pool. private dtNodeQueue m_openList; //< Pointer to open list queue. public dtNavMeshQuery() { } // @par /// /// Must be the first function called after construction, before other /// functions are used. /// /// This function can be used multiple times. /// Initializes the query object. /// @param[in] nav Pointer to the dtNavMesh object to use for all queries. /// @param[in] maxNodes Maximum number of search nodes. [Limits: 0 < value <= 65536] // @returns The status flags for the query. public dtStatus init(dtNavMesh nav, int maxNodes) { m_nav = nav; if (m_nodePool == null || m_nodePool.getMaxNodes() < maxNodes) { if (m_nodePool != null) { //m_nodePool.~dtNodePool(); //dtFree(m_nodePool); m_nodePool = null; } m_nodePool = new dtNodePool(maxNodes, (int)dtNextPow2((uint)(maxNodes / 4)));//(dtAlloc(sizeof(dtNodePool), DT_ALLOC_PERM)) dtNodePool(maxNodes, dtNextPow2(maxNodes/4)); if (m_nodePool == null) return DT_FAILURE | DT_OUT_OF_MEMORY; } else { m_nodePool.clear(); } if (m_tinyNodePool == null) { m_tinyNodePool = new dtNodePool(64, 32);//(dtAlloc(sizeof(dtNodePool), DT_ALLOC_PERM)) dtNodePool(64, 32); if (m_tinyNodePool == null) return DT_FAILURE | DT_OUT_OF_MEMORY; } else { m_tinyNodePool.clear(); } // TODO: check the open list size too. if (m_openList == null || m_openList.getCapacity() < maxNodes) { if (m_openList != null) { //m_openList.~dtNodeQueue(); //dtFree(m_openList); m_openList = null; } m_openList = new dtNodeQueue(maxNodes);//(dtAlloc(sizeof(dtNodeQueue), DT_ALLOC_PERM)) dtNodeQueue(maxNodes); if (m_openList == null) return DT_FAILURE | DT_OUT_OF_MEMORY; } else { m_openList.clear(); } return DT_SUCCESS; } // @name Standard Pathfinding Functions public delegate float randomFloatGenerator(); /// 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. public dtStatus findRandomPoint(dtQueryFilter filter, randomFloatGenerator frand, ref dtPolyRef randomRef, ref float[] randomPt) { Debug.Assert(m_nav != null); // Randomly pick one tile. Assume that all tiles cover roughly the same area. dtMeshTile tile = null; float tsum = 0.0f; for (int i = 0; i < m_nav.getMaxTiles(); i++) { dtMeshTile curTile = m_nav.getTile(i); if (curTile == null || curTile.header == null) continue; // Choose random tile using reservoi sampling. const float area = 1.0f; // Could be tile area too. tsum += area; float u = frand(); if (u * tsum <= area) tile = curTile; } if (tile == null) return DT_FAILURE; // Randomly pick one polygon weighted by polygon area. dtPoly poly = null; dtPolyRef polyRef = 0; dtPolyRef polyRefBase = m_nav.getPolyRefBase(tile); float areaSum = 0.0f; for (int i = 0; i < tile.header.polyCount; ++i) { dtPoly p = tile.polys[i]; // Do not return off-mesh connection polygons. if (p.getType() != (byte)dtPolyTypes.DT_POLYTYPE_GROUND) continue; // Must pass filter dtPolyRef pRef = polyRefBase | (uint)i; if (!filter.passFilter(pRef, tile, p)) continue; // Calc area of the polygon. float polyArea = 0.0f; for (int j = 2; j < p.vertCount; ++j) { //float* va = &tile.verts[p.verts[0]*3]; //float* vb = &tile.verts[p.verts[j-1]*3]; //float* vc = &tile.verts[p.verts[j]*3]; polyArea += Detour.dtTriArea2D(tile.verts, p.verts[0] * 3, tile.verts, p.verts[j - 1] * 3, tile.verts, p.verts[j] * 3); } // Choose random polygon weighted by area, using reservoi sampling. areaSum += polyArea; float u = frand(); if (u * areaSum <= polyArea) { poly = p; polyRef = pRef; } } if (poly == null) return DT_FAILURE; // Randomly pick point on polygon. //const float* v = &tile.verts[poly.verts[0]*3]; int vStart = poly.verts[0] * 3; float[] verts = new float[3 * DT_VERTS_PER_POLYGON]; float[] areas = new float[DT_VERTS_PER_POLYGON]; Detour.dtVcopy(verts, 0 * 3, tile.verts, vStart); for (int j = 1; j < poly.vertCount; ++j) { //v = &tile.verts[poly.verts[j]*3]; Detour.dtVcopy(verts, j * 3, tile.verts, poly.verts[j] * 3); } float s = frand(); float t = frand(); float[] pt = new float[3]; dtRandomPointInConvexPoly(verts, poly.vertCount, areas, s, t, pt); float h = 0.0f; dtStatus status = getPolyHeight(polyRef, pt, ref h); if (dtStatusFailed(status)) return status; pt[1] = h; Detour.dtVcopy(randomPt, 0, pt, 0); randomRef = polyRef; return DT_SUCCESS; } /// 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[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] 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. [(x, y, z)] // @returns The status flags for the query. public dtStatus findRandomPointAroundCircle(dtPolyRef startRef, float[] centerPos, float radius, dtQueryFilter filter, randomFloatGenerator frand, ref dtPolyRef randomRef, ref float[] randomPt) { Debug.Assert(m_nav != null); Debug.Assert(m_nodePool != null); Debug.Assert(m_openList != null); // Validate input if (startRef == 0 || !m_nav.isValidPolyRef(startRef)) return DT_FAILURE | DT_INVALID_PARAM; dtMeshTile startTile = null; dtPoly startPoly = null; m_nav.getTileAndPolyByRefUnsafe(startRef, ref startTile, ref startPoly); if (!filter.passFilter(startRef, startTile, startPoly)) return DT_FAILURE | DT_INVALID_PARAM; m_nodePool.clear(); m_openList.clear(); dtNode startNode = m_nodePool.getNode(startRef); dtVcopy(startNode.pos, centerPos); startNode.pidx = 0; startNode.cost = 0; startNode.total = 0; startNode.id = startRef; startNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; m_openList.push(startNode); dtStatus status = DT_SUCCESS; float radiusSqr = dtSqr(radius); float areaSum = 0.0f; dtMeshTile randomTile = null; dtPoly randomPoly = null; dtPolyRef randomPolyRef = 0; while (!m_openList.empty()) { dtNode bestNode = m_openList.pop(); unchecked { bestNode.flags &= (byte)(~dtNodeFlags.DT_NODE_OPEN); } bestNode.flags |= (byte)dtNodeFlags.DT_NODE_CLOSED; // Get poly and tile. // The API input has been cheked already, skip checking internal data. dtPolyRef bestRef = bestNode.id; dtMeshTile bestTile = null; dtPoly bestPoly = null; m_nav.getTileAndPolyByRefUnsafe(bestRef, ref bestTile, ref bestPoly); // Place random locations on on ground. if (bestPoly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_GROUND) { // Calc area of the polygon. float polyArea = 0.0f; for (int j = 2; j < bestPoly.vertCount; ++j) { //const float* va = &bestTile.verts[bestPoly.verts[0]*3]; //const float* vb = &bestTile.verts[bestPoly.verts[j-1]*3]; //const float* vc = &bestTile.verts[bestPoly.verts[j]*3]; polyArea += dtTriArea2D(bestTile.verts, bestPoly.verts[0] * 3, bestTile.verts, bestPoly.verts[j - 1] * 3, bestTile.verts, bestPoly.verts[j] * 3); } // Choose random polygon weighted by area, using reservoi sampling. areaSum += polyArea; float u = frand(); if (u * areaSum <= polyArea) { randomTile = bestTile; randomPoly = bestPoly; randomPolyRef = bestRef; } } // Get parent poly and tile. dtPolyRef parentRef = 0; dtMeshTile parentTile = null; dtPoly parentPoly = null; if (bestNode.pidx != 0) parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id; if (parentRef != 0) m_nav.getTileAndPolyByRefUnsafe(parentRef, ref parentTile, ref parentPoly); for (uint i = bestPoly.firstLink; i != DT_NULL_LINK; i = bestTile.links[i].next) { dtLink link = bestTile.links[i]; dtPolyRef neighbourRef = link.polyRef; // Skip invalid neighbours and do not follow back to parent. if (neighbourRef == 0 || neighbourRef == parentRef) continue; // Expand to neighbour dtMeshTile neighbourTile = null; dtPoly neighbourPoly = null; m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); // Do not advance if the polygon is excluded by the filter. if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) continue; // Find edge and calc distance to the edge. float[] va = new float[3];//, vb[3]; float[] vb = new float[3]; if (getPortalPoints(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly, neighbourTile, va, vb) == 0) continue; // If the circle is not touching the next polygon, skip it. float tseg = .0f; float distSqr = dtDistancePtSegSqr2D(centerPos, 0, va, 0, vb, 0, ref tseg); if (distSqr > radiusSqr) continue; dtNode neighbourNode = m_nodePool.getNode(neighbourRef); if (neighbourNode == null) { status |= DT_OUT_OF_NODES; continue; } if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_CLOSED) != 0) continue; // Cost if (neighbourNode.flags == 0) { dtVlerp(neighbourNode.pos, va, vb, 0.5f); } float total = bestNode.total + dtVdist(bestNode.pos, neighbourNode.pos); // The node is already in open list and the new result is worse, skip. if (((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_OPEN) != 0) && total >= neighbourNode.total) continue; neighbourNode.id = neighbourRef; unchecked { neighbourNode.flags = (byte)(neighbourNode.flags & (byte)(~dtNodeFlags.DT_NODE_CLOSED)); } neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode); neighbourNode.total = total; if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_OPEN) != 0) { m_openList.modify(neighbourNode); } else { neighbourNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; m_openList.push(neighbourNode); } } } if (randomPoly == null) return DT_FAILURE; // Randomly pick point on polygon. //float* v = &randomTile.verts[randomPoly.verts[0]*3]; float[] verts = new float[3 * DT_VERTS_PER_POLYGON]; float[] areas = new float[DT_VERTS_PER_POLYGON]; dtVcopy(verts, 0 * 3, randomTile.verts, 0); for (int j = 1; j < randomPoly.vertCount; ++j) { //v = &randomTile.verts[randomPoly.verts[j]*3]; dtVcopy(verts, j * 3, randomTile.verts, randomPoly.verts[j] * 3); } float s = frand(); float t = frand(); float[] pt = new float[3]; dtRandomPointInConvexPoly(verts, randomPoly.vertCount, areas, s, t, pt); float h = 0.0f; dtStatus stat = getPolyHeight(randomPolyRef, pt, ref h); if (dtStatusFailed(status)) return stat; pt[1] = h; dtVcopy(randomPt, pt); randomRef = randomPolyRef; return DT_SUCCESS; } ////////////////////////////////////////////////////////////////////////////////////////// /// 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 The closest point on the polygon. [(x, y, z)] /// @param[out] posOverPoly True of the position is over the polygon. // @returns The status flags for the query. // @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. /// /// See closestPointOnPolyBoundary() for a limited but faster option. /// public dtStatus closestPointOnPoly(dtPolyRef polyRef, float[] pos, float[] closest, ref bool posOverPoly) { Debug.Assert(m_nav != null); dtMeshTile tile = null; dtPoly poly = null; uint ip = 0; if (dtStatusFailed(m_nav.getTileAndPolyByRef(polyRef, ref tile, ref poly, ref ip))) return DT_FAILURE | DT_INVALID_PARAM; if (tile == null) return DT_FAILURE | DT_INVALID_PARAM; // Off-mesh connections don't have detail polygons. if (poly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) { //const float* v0 = &tile.verts[poly.verts[0]*3]; //const float* v1 = &tile.verts[poly.verts[1]*3]; int v0Start = poly.verts[0] * 3; int v1Start = poly.verts[1] * 3; float d0 = dtVdist(pos, 0, tile.verts, v0Start); float d1 = dtVdist(pos, 0, tile.verts, v1Start); float u = d0 / (d0 + d1); dtVlerp(closest, 0, tile.verts, v0Start, tile.verts, v1Start, u); //if (posOverPoly) posOverPoly = false; return DT_SUCCESS; } //uint ip = (uint)(poly - tile.polys); dtPolyDetail pd = tile.detailMeshes[ip]; // Clamp point to be inside the polygon. float[] verts = new float[DT_VERTS_PER_POLYGON * 3]; float[] edged = new float[DT_VERTS_PER_POLYGON]; float[] edget = new float[DT_VERTS_PER_POLYGON]; int nv = poly.vertCount; for (int i = 0; i < nv; ++i) { dtVcopy(verts, i * 3, tile.verts, poly.verts[i] * 3); } dtVcopy(closest, pos); if (!dtDistancePtPolyEdgesSqr(pos, 0, verts, nv, edged, edget)) { // Point is outside the polygon, dtClamp to nearest edge. float dmin = float.MaxValue; int imin = -1; for (int i = 0; i < nv; ++i) { if (edged[i] < dmin) { dmin = edged[i]; imin = i; } } //const float* va = &verts[imin*3]; //const float* vb = &verts[((imin+1)%nv)*3]; int vaStart = imin * 3; int vbStart = ((imin + 1) % nv) * 3; dtVlerp(closest, 0, verts, vaStart, verts, vbStart, edget[imin]); //if (posOverPoly) posOverPoly = false; } else { //if (posOverPoly) posOverPoly = true; } // Find height at the location. for (int j = 0; j < pd.triCount; ++j) { //byte[] t = &tile.detailTris[(pd.triBase+j)*4]; //const float* v[3]; int tStart = (int)(pd.triBase + j) * 4; int[] vStarts = new int[3]; float[][] vSrc = new float[3][]; for (int k = 0; k < 3; ++k) { byte tk = tile.detailTris[tStart + k]; byte vCount = poly.vertCount; if (tk < vCount) { //v[k] = &tile.verts[poly.verts[tile.detailTris[tStart + k]]*3]; vStarts[k] = poly.verts[tk] * 3; vSrc[k] = tile.verts; } else { //v[k] = &tile.detailVerts[(pd.vertBase+(t[k]-poly.vertCount))*3]; vStarts[k] = (int)(pd.vertBase + (tk - vCount)) * 3; vSrc[k] = tile.detailVerts; } } float h = .0f; if (dtClosestHeightPointTriangle(closest, 0, vSrc[0], vStarts[0], vSrc[1], vStarts[1], vSrc[2], vStarts[2], ref h)) { closest[1] = h; break; } } return DT_SUCCESS; } /// Returns a point on the boundary closest to the source point if the source point is outside the /// polygon's xz-bounds. /// @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)] // @returns The status flags for the query. // @par /// /// Much faster than closestPointOnPoly(). /// /// If the provided position lies within the polygon's xz-bounds (above or below), /// then @p pos and @p closest will be equal. /// /// The height of @p closest will be the polygon boundary. The height detail is not used. /// // @p pos does not have to be within the bounds of the polybon or the navigation mesh. /// public dtStatus closestPointOnPolyBoundary(dtPolyRef polyRef, float[] pos, float[] closest) { Debug.Assert(m_nav != null); dtMeshTile tile = null; dtPoly poly = null; if (dtStatusFailed(m_nav.getTileAndPolyByRef(polyRef, ref tile, ref poly))) return DT_FAILURE | DT_INVALID_PARAM; // Collect vertices. float[] verts = new float[DT_VERTS_PER_POLYGON * 3]; float[] edged = new float[DT_VERTS_PER_POLYGON]; float[] edget = new float[DT_VERTS_PER_POLYGON]; int nv = 0; for (int i = 0; i < (int)poly.vertCount; ++i) { dtVcopy(verts, nv * 3, tile.verts, poly.verts[i] * 3); nv++; } bool inside = dtDistancePtPolyEdgesSqr(pos, 0, verts, nv, edged, edget); if (inside) { // Point is inside the polygon, return the point. dtVcopy(closest, pos); } else { // Point is outside the polygon, dtClamp to nearest edge. float dmin = float.MaxValue; int imin = -1; for (int i = 0; i < nv; ++i) { if (edged[i] < dmin) { dmin = edged[i]; imin = i; } } //const float* va = &verts[imin*3]; //const float* vb = &verts[((imin+1)%nv)*3]; int vaStart = imin * 3; int vbStart = ((imin + 1) % nv) * 3; dtVlerp(closest, 0, verts, vaStart, verts, vbStart, edget[imin]); } return DT_SUCCESS; } /// 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. // @par /// /// Will return #DT_FAILURE if the provided position is outside the xz-bounds /// of the polygon. /// public dtStatus getPolyHeight(dtPolyRef polyRef, float[] pos, ref float height) { Debug.Assert(m_nav != null); dtMeshTile tile = null; dtPoly poly = null; uint ip = 0; if (dtStatusFailed(m_nav.getTileAndPolyByRef(polyRef, ref tile, ref poly, ref ip))) return DT_FAILURE | DT_INVALID_PARAM; if (poly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) { //const float* v0 = &tile.verts[poly.verts[0]*3]; //const float* v1 = &tile.verts[poly.verts[1]*3]; int v0Start = poly.verts[0] * 3; int v1Start = poly.verts[1] * 3; float d0 = dtVdist2D(pos, 0, tile.verts, v0Start); float d1 = dtVdist2D(pos, 0, tile.verts, v1Start); float u = d0 / (d0 + d1); //if (height) height = tile.verts[v0Start + 1] + (tile.verts[v1Start + 1] - tile.verts[v0Start + 1]) * u; return DT_SUCCESS; } else { //const uint ip = (uint)(poly - tile.polys); dtPolyDetail pd = tile.detailMeshes[ip]; for (int j = 0; j < pd.triCount; ++j) { //byte[] t = tile.detailTris[(pd.triBase+j)*4] ; //float* v[3]; int tStart = (int)(pd.triBase + j) * 4; int[] vStarts = new int[3]; float[][] vSrc = new float[3][]; for (int k = 0; k < 3; ++k) { if (tile.detailTris[tStart + k] < poly.vertCount) { //v[k] = &tile.verts[poly.verts[tile.detailTris[tStart + k]]*3]; vStarts[k] = poly.verts[tile.detailTris[tStart + k]] * 3; vSrc[k] = tile.verts; } else { //v[k] = &tile.detailVerts[(pd.vertBase+(tile.detailTris[tStart + k]-poly.vertCount))*3]; vStarts[k] = (int)(pd.vertBase + (tile.detailTris[tStart + k] - poly.vertCount)) * 3; vSrc[k] = tile.detailVerts; } } float h = .0f; if (dtClosestHeightPointTriangle(pos, 0, vSrc[0], vStarts[0], vSrc[1], vStarts[1], vSrc[2], vStarts[2], ref h)) { //if (height) height = h; return DT_SUCCESS; } } } return DT_FAILURE | DT_INVALID_PARAM; } // @} // @name Local Query Functions ///@{ /// Finds the polygon nearest to the specified center point. /// @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. /// @param[out] nearestPt The nearest point on the polygon. [opt] [(x, y, z)] // @returns The status flags for the query. // @par /// // @note If the search box does not intersect any polygons the search will /// return #DT_SUCCESS, but @p nearestRef will be zero. So if in doubt, check // @p nearestRef before using @p nearestPt. /// // @warning This function is not suitable for large area searches. If the search /// extents overlaps more than 128 polygons it may return an invalid result. /// public dtStatus findNearestPoly(float[] center, float[] halfExtents, dtQueryFilter filter, ref dtPolyRef nearestRef, ref float[] nearestPt) { Debug.Assert(m_nav != null); nearestRef = 0; dtFindNearestPolyQuery query = new(this, center); dtStatus status = queryPolygons(center, halfExtents, filter, query); if (dtStatusFailed(status)) return status; nearestRef = query.nearestRef(); // Only override nearestPt if we actually found a poly so the nearest point // is valid. if (nearestRef != 0) dtVcopy(nearestPt, query.nearestPoint()); return DT_SUCCESS; } /// Queries polygons within a tile. public void queryPolygonsInTile(dtMeshTile tile, float[] qmin, float[] qmax, dtQueryFilter filter, dtFindNearestPolyQuery query) { Debug.Assert(m_nav != null); const int batchSize = 32; dtPolyRef[] polyRefs = new dtPolyRef[batchSize]; dtPoly[] polys = new dtPoly[batchSize]; int n = 0; if (tile.bvTree != null) { dtBVNode node = tile.bvTree[0]; //dtBVNode* end = &tile.bvTree[tile.header.bvNodeCount]; int endIndex = tile.header.bvNodeCount; float[] tbmin = tile.header.bmin; float[] tbmax = tile.header.bmax; float qfac = tile.header.bvQuantFactor; // Calculate quantized box ushort[] bmin = new ushort[3]; ushort[] bmax = new ushort[3]; // dtClamp query box to world box. float minx = dtClamp(qmin[0], tbmin[0], tbmax[0]) - tbmin[0]; float miny = dtClamp(qmin[1], tbmin[1], tbmax[1]) - tbmin[1]; float minz = dtClamp(qmin[2], tbmin[2], tbmax[2]) - tbmin[2]; float maxx = dtClamp(qmax[0], tbmin[0], tbmax[0]) - tbmin[0]; float maxy = dtClamp(qmax[1], tbmin[1], tbmax[1]) - tbmin[1]; float maxz = dtClamp(qmax[2], tbmin[2], tbmax[2]) - tbmin[2]; // Quantize bmin[0] = (ushort)((int)(qfac * minx) & 0xfffe); bmin[1] = (ushort)((int)(qfac * miny) & 0xfffe); bmin[2] = (ushort)((int)(qfac * minz) & 0xfffe); bmax[0] = (ushort)((int)(qfac * maxx + 1) | 1); bmax[1] = (ushort)((int)(qfac * maxy + 1) | 1); bmax[2] = (ushort)((int)(qfac * maxz + 1) | 1); // Traverse tree dtPolyRef polyRefBase = m_nav.getPolyRefBase(tile); int nodeIndex = 0; while (nodeIndex < endIndex) { node = tile.bvTree[nodeIndex]; bool overlap = dtOverlapQuantBounds(bmin, bmax, node.bmin, node.bmax); bool isLeafNode = node.i >= 0; if (isLeafNode && overlap) { dtPolyRef polyRef = polyRefBase | (uint)node.i; if (filter.passFilter(polyRef, tile, tile.polys[node.i])) { polyRefs[n] = polyRef; polys[n] = tile.polys[node.i]; if (n == batchSize - 1) { query.process(tile, polys, polyRefs, batchSize); n = 0; } else { n++; } } } if (overlap || isLeafNode) { nodeIndex++; } else { int escapeIndex = -node.i; nodeIndex += escapeIndex; } } } else { float[] bmin = new float[3]; float[] bmax = new float[3]; dtPolyRef polyRefBase = m_nav.getPolyRefBase(tile); for (int i = 0; i < tile.header.polyCount; ++i) { dtPoly p = tile.polys[i]; // Do not return off-mesh connection polygons. if (p.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) continue; // Must pass filter dtPolyRef polyRef = polyRefBase | (uint)i; if (!filter.passFilter(polyRef, tile, p)) continue; // Calc polygon bounds. //const float* v = &tile.verts[p.verts[0]*3]; int vStart = p.verts[0] * 3; dtVcopy(bmin, 0, tile.verts, vStart); dtVcopy(bmax, 0, tile.verts, vStart); for (int j = 1; j < p.vertCount; ++j) { //v = &tile.verts[p.verts[j]*3]; vStart = p.verts[j] * 3; dtVmin(bmin, 0, tile.verts, vStart); dtVmax(bmax, 0, tile.verts, vStart); } if (dtOverlapBounds(qmin, qmax, bmin, bmax)) { polyRefs[n] = polyRef; polys[n] = p; if (n == batchSize - 1) { query.process(tile, polys, polyRefs, batchSize); n = 0; } else { n++; } } } } // Process the last polygons that didn't make a full batch. if (n > 0) query.process(tile, polys, polyRefs, n); } /// Finds polygons that overlap the search box. /// @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] polys The reference ids of the polygons that overlap the query box. /// @param[out] polyCount The number of polygons in the search result. /// @param[in] maxPolys The maximum number of polygons the search result can hold. // @returns The status flags for the query. // @par /// /// If no polygons are found, the function will return #DT_SUCCESS with a // @p polyCount of zero. /// /// If @p polys is too small to hold the entire result set, then the array will /// be filled to capacity. The method of choosing which polygons from the /// full set are included in the partial result set is undefined. /// public dtStatus queryPolygons(float[] center, float[] halfExtents, dtQueryFilter filter, dtFindNearestPolyQuery query) { Debug.Assert(m_nav != null); float[] bmin = new float[3]; float[] bmax = new float[3]; dtVsub(bmin, center, halfExtents); dtVadd(bmax, center, halfExtents); // Find tiles the query touches. int minx = 0, miny = 0, maxx = 0, maxy = 0; m_nav.calcTileLoc(bmin, ref minx, ref miny); m_nav.calcTileLoc(bmax, ref maxx, ref maxy); int MAX_NEIS = 32; dtMeshTile[] neis = new dtMeshTile[MAX_NEIS]; for (int y = miny; y <= maxy; ++y) { for (int x = minx; x <= maxx; ++x) { int nneis = m_nav.getTilesAt(x, y, neis, MAX_NEIS); for (int j = 0; j < nneis; ++j) { queryPolygonsInTile(neis[j], bmin, bmax, filter, query); } } } return DT_SUCCESS; } /// Finds a path from the start polygon to the end polygon. /// @param[in] startRef The refrence id of the start polygon. /// @param[in] endRef The reference id of the end polygon. /// @param[in] startPos A position within the start polygon. [(x, y, z)] /// @param[in] endPos A position within the end polygon. [(x, y, z)] /// @param[in] filter The polygon filter to apply to the query. /// @param[out] path An ordered list of polygon references representing the path. (Start to end.) /// [(polyRef) * @p pathCount] /// @param[out] pathCount The number of polygons returned in the @p path array. /// @param[in] maxPath The maximum number of polygons the @p path array can hold. [Limit: >= 1] // @par /// /// If the end polygon cannot be reached through the navigation graph, /// the last polygon in the path will be the nearest the end polygon. /// /// If the path array is to small to hold the full result, it will be filled as /// far as possible from the start polygon toward the end polygon. /// /// The start and end positions are used to calculate traversal costs. /// (The y-values impact the result.) /// public dtStatus findPath(dtPolyRef startRef, dtPolyRef endRef, float[] startPos, float[] endPos, dtQueryFilter filter, dtPolyRef[] path, ref uint pathCount, int maxPath) { Debug.Assert(m_nav != null); Debug.Assert(m_nodePool != null); Debug.Assert(m_openList != null); pathCount = 0; if (maxPath <= 0) return DT_FAILURE | DT_INVALID_PARAM; // Validate input if (!m_nav.isValidPolyRef(startRef) || !m_nav.isValidPolyRef(endRef) || startPos == null || endPos == null || filter == null || maxPath <= 0 || path == null) return DT_FAILURE | DT_INVALID_PARAM; if (startRef == endRef) { path[0] = startRef; pathCount = 1; return DT_SUCCESS; } m_nodePool.clear(); m_openList.clear(); dtNode startNode = m_nodePool.getNode(startRef); dtVcopy(startNode.pos, startPos); startNode.pidx = 0; startNode.cost = 0; startNode.total = dtVdist(startPos, endPos) * H_SCALE; startNode.id = startRef; startNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; m_openList.push(startNode); dtNode lastBestNode = startNode; float lastBestNodeCost = startNode.total; bool outOfNodes = false; while (!m_openList.empty()) { // Remove node from open list and put it in closed list. dtNode bestNode = m_openList.pop(); unchecked { bestNode.flags &= (byte)(~dtNodeFlags.DT_NODE_OPEN); } bestNode.flags |= (byte)dtNodeFlags.DT_NODE_CLOSED; // Reached the goal, stop searching. if (bestNode.id == endRef) { lastBestNode = bestNode; break; } // Get current poly and tile. // The API input has been cheked already, skip checking internal data. dtPolyRef bestRef = bestNode.id; dtMeshTile bestTile = null; dtPoly bestPoly = null; m_nav.getTileAndPolyByRefUnsafe(bestRef, ref bestTile, ref bestPoly); // Get parent poly and tile. dtPolyRef parentRef = 0; dtMeshTile parentTile = null; dtPoly parentPoly = null; if (bestNode.pidx != 0) parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id; if (parentRef != 0) m_nav.getTileAndPolyByRefUnsafe(parentRef, ref parentTile, ref parentPoly); for (uint i = bestPoly.firstLink; i != DT_NULL_LINK; i = bestTile.links[i].next) { dtPolyRef neighbourRef = bestTile.links[i].polyRef; // 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. dtMeshTile neighbourTile = null; dtPoly neighbourPoly = null; m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) continue; // deal explicitly with crossing tile boundaries byte crossSide = 0; if (bestTile.links[i].side != 0xff) crossSide = (byte)(bestTile.links[i].side >> 1); dtNode neighbourNode = m_nodePool.getNode(neighbourRef, crossSide); if (neighbourNode == null) { outOfNodes = true; continue; } // If the node is visited the first time, calculate node position. if (neighbourNode.flags == 0) { getEdgeMidPoint(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly, neighbourTile, neighbourNode.pos); } // Calculate cost and heuristic. float cost = 0; float heuristic = 0; // Special case for last node. if (neighbourRef == endRef) { // Cost float curCost = filter.getCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile, parentPoly, bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly); float endCost = filter.getCost(neighbourNode.pos, endPos, bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly, 0, null, null); cost = bestNode.cost + curCost + endCost; heuristic = 0; } else { // Cost float curCost = filter.getCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile, parentPoly, bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly); cost = bestNode.cost + curCost; heuristic = dtVdist(neighbourNode.pos, endPos) * H_SCALE; } float total = cost + heuristic; // The node is already in open list and the new result is worse, skip. if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_OPEN) != 0 && total >= neighbourNode.total) continue; // The node is already visited and process, and the new result is worse, skip. if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total) continue; // Add or update the node. neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode); neighbourNode.id = neighbourRef; unchecked { neighbourNode.flags = (byte)(neighbourNode.flags & ~(byte)dtNodeFlags.DT_NODE_CLOSED); } neighbourNode.cost = cost; neighbourNode.total = total; if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_OPEN) != 0) { // Already in open, update node location. m_openList.modify(neighbourNode); } else { // Put the node in open list. neighbourNode.flags |= (byte)dtNodeFlags.DT_NODE_OPEN; m_openList.push(neighbourNode); } // Update nearest node to target so far. if (heuristic < lastBestNodeCost) { lastBestNodeCost = heuristic; lastBestNode = neighbourNode; } } } dtStatus status = getPathToNode(lastBestNode, path, ref pathCount, maxPath); if (lastBestNode.id != endRef) status |= DT_PARTIAL_RESULT; if (outOfNodes) status |= DT_OUT_OF_NODES; return status; } dtStatus getPathToNode(dtNode endNode, dtPolyRef[] path, ref uint pathCount, int maxPath) { // Find the length of the entire path. dtNode curNode = endNode; int length = 0; do { length++; curNode = m_nodePool.getNodeAtIdx(curNode.pidx); } while (curNode != null); // If the path cannot be fully stored then advance to the last node we will be able to store. curNode = endNode; int writeCount; for (writeCount = length; writeCount > maxPath; writeCount--) { //dtAssert(curNode); curNode = m_nodePool.getNodeAtIdx(curNode.pidx); } // Write path for (int i = writeCount - 1; i >= 0; i--) { //dtAssert(curNode); path[i] = curNode.id; curNode = m_nodePool.getNodeAtIdx(curNode.pidx); } //dtAssert(!curNode); pathCount = (uint)Math.Min(length, maxPath); if (length > maxPath) return DT_SUCCESS | DT_BUFFER_TOO_SMALL; return DT_SUCCESS; } ///@} // @name Sliced Pathfinding Functions /// Common use case: /// -# Call initSlicedFindPath() to initialize the sliced path query. /// -# Call updateSlicedFindPath() until it returns complete. /// -# Call finalizeSlicedFindPath() to get the path. ///@{ /// Intializes a sliced path query. /// @param[in] startRef The refrence id of the start polygon. /// @param[in] endRef The reference id of the end polygon. /// @param[in] startPos A position within the start polygon. [(x, y, z)] /// @param[in] endPos A position within the end polygon. [(x, y, z)] /// @param[in] filter The polygon filter to apply to the query. // @returns The status flags for the query. // @par /// // @warning Calling any non-slice methods before calling finalizeSlicedFindPath() /// or finalizeSlicedFindPathPartial() may result in corrupted data! /// /// The @p filter pointer is stored and used for the duration of the sliced /// path query. /// public dtStatus initSlicedFindPath(dtPolyRef startRef, dtPolyRef endRef, float[] startPos, float[] endPos, dtQueryFilter filter, uint options) { Debug.Assert(m_nav != null); Debug.Assert(m_nodePool != null); Debug.Assert(m_openList != null); // Init path state. //memset(&m_query, 0, sizeof(dtQueryData)); m_query.status = DT_FAILURE; m_query.startRef = startRef; m_query.endRef = endRef; dtVcopy(m_query.startPos, startPos); dtVcopy(m_query.endPos, endPos); m_query.filter = filter; m_query.options = options; m_query.raycastLimitSqr = float.MaxValue; if (startRef == 0 || endRef == 0) return DT_FAILURE | DT_INVALID_PARAM; // Validate input if (!m_nav.isValidPolyRef(startRef) || !m_nav.isValidPolyRef(endRef)) return DT_FAILURE | DT_INVALID_PARAM; // trade quality with performance? if ((options & (int)dtFindPathOptions.DT_FINDPATH_ANY_ANGLE) != 0) { // 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. dtMeshTile tile = m_nav.getTileByRef(startRef); float agentRadius = tile.header.walkableRadius; m_query.raycastLimitSqr = dtSqr(agentRadius * 50.0f); //DT_RAY_CAST_LIMIT_PROPORTIONS; } if (startRef == endRef) { m_query.status = DT_SUCCESS; return DT_SUCCESS; } m_nodePool.clear(); m_openList.clear(); dtNode startNode = m_nodePool.getNode(startRef); dtVcopy(startNode.pos, startPos); startNode.pidx = 0; startNode.cost = 0; startNode.total = dtVdist(startPos, endPos) * H_SCALE; startNode.id = startRef; startNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; m_openList.push(startNode); m_query.status = DT_IN_PROGRESS; m_query.lastBestNode = startNode; m_query.lastBestNodeCost = startNode.total; return m_query.status; } /// Updates an in-progress sliced path query. /// @param[in] maxIter The maximum number of iterations to perform. /// @param[out] doneIters The actual number of iterations completed. [opt] // @returns The status flags for the query. public dtStatus updateSlicedFindPath(int maxIter, ref int doneIters) { if (!dtStatusInProgress(m_query.status)) return m_query.status; // Make sure the request is still valid. if (!m_nav.isValidPolyRef(m_query.startRef) || !m_nav.isValidPolyRef(m_query.endRef)) { m_query.status = DT_FAILURE; return DT_FAILURE; } dtRaycastHit rayHit = new(); rayHit.maxPath = 0; int iter = 0; while (iter < maxIter && !m_openList.empty()) { iter++; // Remove node from open list and put it in closed list. dtNode bestNode = m_openList.pop(); bestNode.dtcsClearFlag(dtNodeFlags.DT_NODE_OPEN); bestNode.dtcsSetFlag(dtNodeFlags.DT_NODE_CLOSED); // Reached the goal, stop searching. if (bestNode.id == m_query.endRef) { m_query.lastBestNode = bestNode; dtStatus details = m_query.status & DT_STATUS_DETAIL_MASK; m_query.status = DT_SUCCESS | details; //if (doneIters) doneIters = iter; return m_query.status; } // Get current poly and tile. // The API input has been cheked already, skip checking internal data. dtPolyRef bestRef = bestNode.id; dtMeshTile bestTile = null; dtPoly bestPoly = null; if (dtStatusFailed(m_nav.getTileAndPolyByRef(bestRef, ref bestTile, ref bestPoly))) { // The polygon has disappeared during the sliced query, fail. m_query.status = DT_FAILURE; //if (doneIters) doneIters = iter; return m_query.status; } // Get parent poly and tile. dtPolyRef parentRef = 0; dtPolyRef grandpaRef = 0; dtMeshTile parentTile = null; dtPoly parentPoly = null; dtNode parentNode = null; if (bestNode.pidx != 0) { parentNode = m_nodePool.getNodeAtIdx(bestNode.pidx); parentRef = parentNode.id; if (parentNode.pidx != 0) grandpaRef = m_nodePool.getNodeAtIdx(parentNode.pidx).id; } if (parentRef != 0) { bool invalidParent = dtStatusFailed(m_nav.getTileAndPolyByRef(parentRef, ref parentTile, ref parentPoly)); if (invalidParent || (grandpaRef != 0 && !m_nav.isValidPolyRef(grandpaRef))) { // The polygon has disappeared during the sliced query, fail. m_query.status = DT_FAILURE; //if (doneIters) doneIters = iter; return m_query.status; } } // decide whether to test raycast to previous nodes bool tryLOS = false; if ((m_query.options & (int)dtFindPathOptions.DT_FINDPATH_ANY_ANGLE) != 0) { if ((parentRef != 0) && (dtVdistSqr(parentNode.pos, bestNode.pos) < m_query.raycastLimitSqr)) tryLOS = true; } for (uint i = bestPoly.firstLink; i != DT_NULL_LINK; i = bestTile.links[i].next) { dtPolyRef neighbourRef = bestTile.links[i].polyRef; // 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. dtMeshTile neighbourTile = null; dtPoly neighbourPoly = null; m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); if (!m_query.filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) continue; dtNode neighbourNode = m_nodePool.getNode(neighbourRef); if (neighbourNode == null) { m_query.status |= DT_OUT_OF_NODES; continue; } // 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. if (neighbourNode.flags == 0) { getEdgeMidPoint(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly, neighbourTile, neighbourNode.pos); } // Calculate cost and heuristic. float cost = 0; float heuristic = 0; // raycast parent bool foundShortCut = false; rayHit.pathCost = rayHit.t = 0; if (tryLOS) { raycast(parentRef, parentNode.pos, neighbourNode.pos, m_query.filter, (int)dtRaycastOptions.DT_RAYCAST_USE_COSTS, rayHit, grandpaRef); foundShortCut = rayHit.t >= 1.0f; } // update move cost if (foundShortCut) { // shortcut found using raycast. Using shorter cost instead cost = parentNode.cost + rayHit.pathCost; } else { // No shortcut found. float curCost = m_query.filter.getCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile, parentPoly, bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly); cost = bestNode.cost + curCost; } // Special case for last node. if (neighbourRef == m_query.endRef) { float endCost = m_query.filter.getCost(neighbourNode.pos, m_query.endPos, bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly, 0, null, null); cost = cost + endCost; heuristic = 0; } else { heuristic = dtVdist(neighbourNode.pos, m_query.endPos) * H_SCALE; } float total = cost + heuristic; // The node is already in open list and the new result is worse, skip. if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_OPEN) != 0 && total >= neighbourNode.total) continue; // The node is already visited and process, and the new result is worse, skip. if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total) continue; // Add or update the node. neighbourNode.pidx = foundShortCut ? bestNode.pidx : m_nodePool.getNodeIdx(bestNode); neighbourNode.id = neighbourRef; neighbourNode.flags = (byte)(neighbourNode.flags & ~(byte)(dtNodeFlags.DT_NODE_CLOSED | dtNodeFlags.DT_NODE_PARENT_DETACHED)); neighbourNode.cost = cost; neighbourNode.total = total; if (foundShortCut) neighbourNode.flags = (byte)(neighbourNode.flags | (byte)dtNodeFlags.DT_NODE_PARENT_DETACHED); if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_OPEN) != 0) { // Already in open, update node location. m_openList.modify(neighbourNode); } else { // Put the node in open list. //neighbourNode.flags |= DT_NODE_OPEN; neighbourNode.dtcsSetFlag(dtNodeFlags.DT_NODE_OPEN); m_openList.push(neighbourNode); } // Update nearest node to target so far. if (heuristic < m_query.lastBestNodeCost) { m_query.lastBestNodeCost = heuristic; m_query.lastBestNode = neighbourNode; } } } // Exhausted all nodes, but could not find path. if (m_openList.empty()) { dtStatus details = m_query.status & DT_STATUS_DETAIL_MASK; m_query.status = DT_SUCCESS | details; } //if (doneIters) doneIters = iter; return m_query.status; } /// 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] /// @param[out] pathCount The number of polygons returned in the @p path array. /// @param[in] maxPath The max number of polygons the path array can hold. [Limit: >= 1] // @returns The status flags for the query. public dtStatus finalizeSlicedFindPath(dtPolyRef[] path, ref int pathCount, int maxPath) { pathCount = 0; if (dtStatusFailed(m_query.status)) { // Reset query. //memset(&m_query, 0, sizeof(dtQueryData)); m_query.dtcsClear(); return DT_FAILURE; } int n = 0; if (m_query.startRef == m_query.endRef) { // Special case: the search starts and ends at same poly. path[n++] = m_query.startRef; } else { // Reverse the path. Debug.Assert(m_query.lastBestNode != null); if (m_query.lastBestNode.id != m_query.endRef) m_query.status |= DT_PARTIAL_RESULT; dtNode prev = null; dtNode node = m_query.lastBestNode; int prevRay = 0; do { dtNode next = m_nodePool.getNodeAtIdx(node.pidx); node.pidx = m_nodePool.getNodeIdx(prev); prev = node; int nextRay = node.flags & (byte)dtNodeFlags.DT_NODE_PARENT_DETACHED; // keep track of whether parent is not adjacent (i.e. due to raycast shortcut) node.flags = (byte)((node.flags & ~(byte)dtNodeFlags.DT_NODE_PARENT_DETACHED) | prevRay); // and store it in the reversed path's node prevRay = nextRay; node = next; } while (node != null); // Store path node = prev; do { dtNode next = m_nodePool.getNodeAtIdx(node.pidx); dtStatus status = 0; if ((node.flags & (byte)dtNodeFlags.DT_NODE_PARENT_DETACHED) != 0) { float t = 0; float[] normal = new float[3]; uint m = 0; dtPolyRef[] temp = new dtPolyRef[path.Length]; status = raycast(node.id, node.pos, next.pos, m_query.filter, ref t, normal, temp, ref m, maxPath - n); for (var i = 0; i < path.Length - n; ++i) path[n + i] = temp[i]; n += (int)m; // raycast ends on poly boundary and the path might include the next poly boundary. if (path[n - 1] == next.id) n--; // remove to avoid duplicates } else { path[n++] = node.id; if (n >= maxPath) status = DT_BUFFER_TOO_SMALL; } if ((status & DT_STATUS_DETAIL_MASK) != 0) { m_query.status |= status & DT_STATUS_DETAIL_MASK; break; } node = next; } while (node != null); } dtStatus details = m_query.status & DT_STATUS_DETAIL_MASK; // Reset query. //memset(&m_query, 0, sizeof(dtQueryData)); m_query.dtcsClear(); pathCount = n; return DT_SUCCESS | details; } /// 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] /// @param[out] pathCount The number of polygons returned in the @p path array. /// @param[in] maxPath The max number of polygons the @p path array can hold. [Limit: >= 1] // @returns The status flags for the query. public dtStatus finalizeSlicedFindPathPartial(dtPolyRef[] existing, int existingSize, dtPolyRef[] path, ref int pathCount, int maxPath) { pathCount = 0; if (existingSize == 0) { return DT_FAILURE; } if (dtStatusFailed(m_query.status)) { // Reset query. //memset(&m_query, 0, sizeof(dtQueryData)); m_query.dtcsClear(); return DT_FAILURE; } int n = 0; if (m_query.startRef == m_query.endRef) { // Special case: the search starts and ends at same poly. path[n++] = m_query.startRef; } else { // Find furthest existing node that was visited. dtNode prev = null; dtNode node = null; for (int i = existingSize - 1; i >= 0; --i) { node = m_nodePool.findNode(existing[i]); if (node != null) break; } if (node == null) { m_query.status |= DT_PARTIAL_RESULT; Debug.Assert(m_query.lastBestNode != null); node = m_query.lastBestNode; } // Reverse the path. int prevRay = 0; do { dtNode next = m_nodePool.getNodeAtIdx(node.pidx); node.pidx = m_nodePool.getNodeIdx(prev); prev = node; int nextRay = node.flags & (byte)dtNodeFlags.DT_NODE_PARENT_DETACHED; // keep track of whether parent is not adjacent (i.e. due to raycast shortcut) node.flags = (byte)((node.flags & ~(byte)dtNodeFlags.DT_NODE_PARENT_DETACHED) | prevRay); // and store it in the reversed path's node prevRay = nextRay; node = next; } while (node != null); // Store path node = prev; do { dtNode next = m_nodePool.getNodeAtIdx(node.pidx); dtStatus status = 0; if ((node.flags & (byte)dtNodeFlags.DT_NODE_PARENT_DETACHED) != 0) { float t = 0; float[] normal = new float[3]; uint m = 0; dtPolyRef[] temp = new dtPolyRef[path.Length - n]; status = raycast(node.id, node.pos, next.pos, m_query.filter, ref t, normal, temp, ref m, maxPath - n); for (var i = 0; i < path.Length - n; ++i) path[n + i] = temp[i]; n += (int)m; // raycast ends on poly boundary and the path might include the next poly boundary. if (path[n - 1] == next.id) n--; // remove to avoid duplicates } else { path[n++] = node.id; if (n >= maxPath) status = DT_BUFFER_TOO_SMALL; } if ((status & DT_STATUS_DETAIL_MASK) != 0) { m_query.status |= status & DT_STATUS_DETAIL_MASK; break; } node = next; } while (node != null); } dtStatus details = m_query.status & DT_STATUS_DETAIL_MASK; // Reset query. //memset(&m_query, 0, sizeof(dtQueryData)); m_query.dtcsClear(); pathCount = n; return DT_SUCCESS | details; } // Appends vertex to a straight path dtStatus appendVertex(float[] pos, byte flags, dtPolyRef polyRef, float[] straightPath, byte[] straightPathFlags, dtPolyRef[] straightPathRefs, ref int straightPathCount, int maxStraightPath) { if (straightPathCount > 0 && dtVequal(straightPath, (straightPathCount - 1) * 3, pos, 0)) { // The vertices are equal, update flags and poly. if (straightPathFlags != null) straightPathFlags[straightPathCount - 1] = flags; if (straightPathRefs != null) straightPathRefs[straightPathCount - 1] = polyRef; } else { // Append new vertex. dtVcopy(straightPath, straightPathCount * 3, pos, 0); if (straightPathFlags != null) straightPathFlags[straightPathCount] = flags; if (straightPathRefs != null) straightPathRefs[straightPathCount] = polyRef; straightPathCount++; // If there is no space to append more vertices, return. if (straightPathCount >= maxStraightPath) { return DT_SUCCESS | DT_BUFFER_TOO_SMALL; } // If reached end of path or there is no space to append more vertices, return. if (flags == (byte)dtStraightPathFlags.DT_STRAIGHTPATH_END) { return DT_SUCCESS; } } return DT_IN_PROGRESS; } // Appends intermediate portal points to a straight path. dtStatus appendPortals(int startIdx, int endIdx, float[] endPos, dtPolyRef[] path, float[] straightPath, byte[] straightPathFlags, dtPolyRef[] straightPathRefs, ref int straightPathCount, int maxStraightPath, int options) { //float* startPos = &straightPath[(*straightPathCount-1)*3]; int startPosStart = (straightPathCount - 1) * 3; // Append or update last vertex dtStatus stat = 0; for (int i = startIdx; i < endIdx; i++) { // Calculate portal dtPolyRef from = path[i]; dtMeshTile fromTile = null; dtPoly fromPoly = null; if (dtStatusFailed(m_nav.getTileAndPolyByRef(from, ref fromTile, ref fromPoly))) return DT_FAILURE | DT_INVALID_PARAM; dtPolyRef to = path[i + 1]; dtMeshTile toTile = null; dtPoly toPoly = null; if (dtStatusFailed(m_nav.getTileAndPolyByRef(to, ref toTile, ref toPoly))) return DT_FAILURE | DT_INVALID_PARAM; float[] left = new float[3];//, right[3]; float[] right = new float[3]; if (dtStatusFailed(getPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, left, right))) break; if ((options & (int)dtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS) != 0) { // Skip intersection if only area crossings are requested. if (fromPoly.getArea() == toPoly.getArea()) continue; } // Append intersection float s = .0f, t = .0f; if (dtIntersectSegSeg2D(straightPath, startPosStart, endPos, 0, left, 0, right, 0, ref s, ref t)) { float[] pt = new float[3]; dtVlerp(pt, left, right, t); stat = appendVertex(pt, 0, path[i + 1], straightPath, straightPathFlags, straightPathRefs, ref straightPathCount, maxStraightPath); if (stat != DT_IN_PROGRESS) return stat; } } return DT_IN_PROGRESS; } /// 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[out] straightPathFlags Flags describing each point. (See: #dtStraightPathFlags) [opt] /// @param[out] straightPathRefs The reference id of the polygon that is being entered at each point. [opt] /// @param[out] straightPathCount The number of points in the straight path. /// @param[in] maxStraightPath The maximum number of points the straight path arrays can hold. [Limit: > 0] /// @param[in] options Query options. (see: #dtStraightPathOptions) // @returns The status flags for the query. // @par /// /// This method peforms what is often called 'string pulling'. /// /// 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 /// normally be within or very near the first and last polygons respectively. /// /// 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 /// off-mesh link points to their representative polygons. /// /// 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 /// position. /// public dtStatus findStraightPath(float[] startPos, float[] endPos, dtPolyRef[] path, int pathSize, float[] straightPath, byte[] straightPathFlags, dtPolyRef[] straightPathRefs, ref int straightPathCount, int maxStraightPath, int options) { Debug.Assert(m_nav != null); straightPathCount = 0; if (maxStraightPath == 0) return DT_FAILURE | DT_INVALID_PARAM; if (path[0] == 0) return DT_FAILURE | DT_INVALID_PARAM; dtStatus stat = 0; // TODO: Should this be callers responsibility? float[] closestStartPos = new float[3]; if (dtStatusFailed(closestPointOnPolyBoundary(path[0], startPos, closestStartPos))) return DT_FAILURE | DT_INVALID_PARAM; float[] closestEndPos = new float[3]; if (dtStatusFailed(closestPointOnPolyBoundary(path[pathSize - 1], endPos, closestEndPos))) return DT_FAILURE | DT_INVALID_PARAM; // Add start point. stat = appendVertex(closestStartPos, (byte)dtStraightPathFlags.DT_STRAIGHTPATH_START, path[0], straightPath, straightPathFlags, straightPathRefs, ref straightPathCount, maxStraightPath); if (stat != DT_IN_PROGRESS) return stat; if (pathSize > 1) { float[] portalApex = new float[3];//, portalLeft[3], portalRight[3]; float[] portalLeft = new float[3]; float[] portalRight = new float[3]; dtVcopy(portalApex, closestStartPos); dtVcopy(portalLeft, portalApex); dtVcopy(portalRight, portalApex); int apexIndex = 0; int leftIndex = 0; int rightIndex = 0; byte leftPolyType = 0; byte rightPolyType = 0; dtPolyRef leftPolyRef = path[0]; dtPolyRef rightPolyRef = path[0]; for (int i = 0; i < pathSize; ++i) { float[] left = new float[3];//, right[3]; float[] right = new float[3]; byte fromType = 0, toType = 0; if (i + 1 < pathSize) { // Next portal. if (dtStatusFailed(getPortalPoints(path[i], path[i + 1], left, right, ref fromType, ref toType))) { // 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. if (dtStatusFailed(closestPointOnPolyBoundary(path[i], endPos, closestEndPos))) { // This should only happen when the first polygon is invalid. return DT_FAILURE | DT_INVALID_PARAM; } // Apeend portals along the current straight path segment. if ((options & (int)(dtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS | dtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0) { stat = appendPortals(apexIndex, i, closestEndPos, path, straightPath, straightPathFlags, straightPathRefs, ref straightPathCount, maxStraightPath, options); } stat = appendVertex(closestEndPos, 0, path[i], straightPath, straightPathFlags, straightPathRefs, ref straightPathCount, maxStraightPath); return DT_SUCCESS | DT_PARTIAL_RESULT | ((straightPathCount >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : (uint)0); } // If starting really close the portal, advance. if (i == 0) { float t = 0.0f; if (dtDistancePtSegSqr2D(portalApex, 0, left, 0, right, 0, ref t) < dtSqr(0.001f)) continue; } } else { // End of the path. dtVcopy(left, closestEndPos); dtVcopy(right, closestEndPos); toType = (byte)dtPolyTypes.DT_POLYTYPE_GROUND; fromType = (byte)dtPolyTypes.DT_POLYTYPE_GROUND; } // Right vertex. if (dtTriArea2D(portalApex, portalRight, right) <= 0.0f) { if (dtVequal(portalApex, portalRight) || dtTriArea2D(portalApex, portalLeft, right) > 0.0f) { dtVcopy(portalRight, right); rightPolyRef = (i + 1 < pathSize) ? path[i + 1] : 0; rightPolyType = toType; rightIndex = i; } else { // Append portals along the current straight path segment. if ((options & (int)(dtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS | dtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0) { stat = appendPortals(apexIndex, leftIndex, portalLeft, path, straightPath, straightPathFlags, straightPathRefs, ref straightPathCount, maxStraightPath, options); if (stat != DT_IN_PROGRESS) return stat; } dtVcopy(portalApex, portalLeft); apexIndex = leftIndex; byte flags = 0; if (leftPolyRef == 0) flags = (byte)dtStraightPathFlags.DT_STRAIGHTPATH_END; else if (leftPolyType == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) flags = (byte)Detour.dtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION; dtPolyRef polyRef = leftPolyRef; // Append or update vertex stat = appendVertex(portalApex, flags, polyRef, straightPath, straightPathFlags, straightPathRefs, ref straightPathCount, maxStraightPath); if (stat != DT_IN_PROGRESS) return stat; dtVcopy(portalLeft, portalApex); dtVcopy(portalRight, portalApex); leftIndex = apexIndex; rightIndex = apexIndex; // Restart i = apexIndex; continue; } } // Left vertex. if (dtTriArea2D(portalApex, portalLeft, left) >= 0.0f) { if (dtVequal(portalApex, portalLeft) || dtTriArea2D(portalApex, portalRight, left) < 0.0f) { dtVcopy(portalLeft, left); leftPolyRef = (i + 1 < pathSize) ? path[i + 1] : 0; leftPolyType = toType; leftIndex = i; } else { // Append portals along the current straight path segment. if ((options & (int)(dtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS | dtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0) { stat = appendPortals(apexIndex, rightIndex, portalRight, path, straightPath, straightPathFlags, straightPathRefs, ref straightPathCount, maxStraightPath, options); if (stat != DT_IN_PROGRESS) return stat; } dtVcopy(portalApex, portalRight); apexIndex = rightIndex; byte flags = 0; if (rightPolyRef == 0) flags = (byte)dtStraightPathFlags.DT_STRAIGHTPATH_END; else if (rightPolyType == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) flags = (byte)dtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION; dtPolyRef polyRef = rightPolyRef; // Append or update vertex stat = appendVertex(portalApex, flags, polyRef, straightPath, straightPathFlags, straightPathRefs, ref straightPathCount, maxStraightPath); if (stat != DT_IN_PROGRESS) return stat; dtVcopy(portalLeft, portalApex); dtVcopy(portalRight, portalApex); leftIndex = apexIndex; rightIndex = apexIndex; // Restart i = apexIndex; continue; } } } // Append portals along the current straight path segment. if ((options & (int)(dtStraightPathOptions.DT_STRAIGHTPATH_AREA_CROSSINGS | dtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS)) != 0) { stat = appendPortals(apexIndex, pathSize - 1, closestEndPos, path, straightPath, straightPathFlags, straightPathRefs, ref straightPathCount, maxStraightPath, options); if (stat != DT_IN_PROGRESS) return stat; } } stat = appendVertex(closestEndPos, (byte)dtStraightPathFlags.DT_STRAIGHTPATH_END, 0, straightPath, straightPathFlags, straightPathRefs, ref straightPathCount, maxStraightPath); return DT_SUCCESS | ((straightPathCount >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : 0); } /// Moves from the start to the end position constrained to the navigation mesh. /// @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. // @par /// /// 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 /// incomplete path. /// // @p resultPos will equal the @p endPos if the end is reached. /// Otherwise the closest reachable position will be returned. /// // @p resultPos is not projected onto the surface of the navigation /// mesh. Use #getPolyHeight if this is needed. /// /// This method treats the end position in the same manner as /// the #raycast method. (As a 2D point.) See that method's documentation /// for details. /// /// 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 /// position. /// public dtStatus moveAlongSurface(dtPolyRef startRef, float[] startPos, float[] endPos, dtQueryFilter filter, float[] resultPos, dtPolyRef[] visited, ref int visitedCount, int maxVisitedSize) { Debug.Assert(m_nav != null); Debug.Assert(m_tinyNodePool != null); visitedCount = 0; // Validate input if (startRef == 0) return DT_FAILURE | DT_INVALID_PARAM; if (!m_nav.isValidPolyRef(startRef)) return DT_FAILURE | DT_INVALID_PARAM; dtStatus status = DT_SUCCESS; const int MAX_STACK = 48; dtNode[] stack = new dtNode[MAX_STACK]; int nstack = 0; m_tinyNodePool.clear(); dtNode startNode = m_tinyNodePool.getNode(startRef); startNode.pidx = 0; startNode.cost = 0; startNode.total = 0; startNode.id = startRef; startNode.flags = (byte)dtNodeFlags.DT_NODE_CLOSED; stack[nstack++] = startNode; float[] bestPos = new float[3]; float bestDist = float.MaxValue; dtNode bestNode = null; dtVcopy(bestPos, startPos); // Search constraints float[] searchPos = new float[3];//, searchRadSqr; float searchRadSqr = .0f; dtVlerp(searchPos, startPos, endPos, 0.5f); searchRadSqr = dtSqr(dtVdist(startPos, endPos) / 2.0f + 0.001f); float[] verts = new float[DT_VERTS_PER_POLYGON * 3]; while (nstack != 0) { // Pop front. dtNode curNode = stack[0]; for (int i = 0; i < nstack - 1; ++i) stack[i] = stack[i + 1]; nstack--; // Get poly and tile. // The API input has been cheked already, skip checking internal data. dtPolyRef curRef = curNode.id; dtMeshTile curTile = null; dtPoly curPoly = null; m_nav.getTileAndPolyByRefUnsafe(curRef, ref curTile, ref curPoly); // Collect vertices. int nverts = curPoly.vertCount; for (int i = 0; i < nverts; ++i) { dtVcopy(verts, i * 3, curTile.verts, curPoly.verts[i] * 3); } // If target is inside the poly, stop search. if (dtPointInPolygon(endPos, verts, nverts)) { bestNode = curNode; dtVcopy(bestPos, endPos); break; } // Find wall edges and find nearest point inside the walls. for (int i = 0, j = (int)curPoly.vertCount - 1; i < (int)curPoly.vertCount; j = i++) { // Find links to neighbours. const int MAX_NEIS = 8; int nneis = 0; dtPolyRef[] neis = new dtPolyRef[MAX_NEIS]; if ((curPoly.neis[j] & DT_EXT_LINK) != 0) { // Tile border. for (uint k = curPoly.firstLink; k != DT_NULL_LINK; k = curTile.links[k].next) { dtLink link = curTile.links[k]; if (link.edge == j) { if (link.polyRef != 0) { dtMeshTile neiTile = null; dtPoly neiPoly = null; m_nav.getTileAndPolyByRefUnsafe(link.polyRef, ref neiTile, ref neiPoly); if (filter.passFilter(link.polyRef, neiTile, neiPoly)) { if (nneis < MAX_NEIS) neis[nneis++] = link.polyRef; } } } } } else if (curPoly.neis[j] != 0) { uint idx = (uint)(curPoly.neis[j] - 1); dtPolyRef polyRef = m_nav.getPolyRefBase(curTile) | idx; if (filter.passFilter(polyRef, curTile, curTile.polys[idx])) { // Internal edge, encode id. neis[nneis++] = polyRef; } } if (nneis == 0) { // Wall edge, calc distance. //const float* vj = &verts[j*3]; //const float* vi = &verts[i*3]; int vjStart = j * 3; int viStart = i * 3; float tseg = .0f; float distSqr = dtDistancePtSegSqr2D(endPos, 0, verts, vjStart, verts, viStart, ref tseg); if (distSqr < bestDist) { // Update nearest distance. dtVlerp(bestPos, 0, verts, vjStart, verts, viStart, tseg); bestDist = distSqr; bestNode = curNode; } } else { for (int k = 0; k < nneis; ++k) { // Skip if no node can be allocated. dtNode neighbourNode = m_tinyNodePool.getNode(neis[k]); if (neighbourNode == null) continue; // Skip if already visited. if ((neighbourNode.flags & (byte)dtNodeFlags.DT_NODE_CLOSED) != 0) continue; // Skip the link if it is too far from search constraint. // TODO: Maybe should use getPortalPoints(), but this one is way faster. int vjStart = j * 3; int viStart = i * 3; float tseg = .0f; float distSqr = dtDistancePtSegSqr2D(searchPos, 0, verts, vjStart, verts, viStart, ref tseg); if (distSqr > searchRadSqr) { continue; } // Mark as the node as visited and push to queue. if (nstack < MAX_STACK) { neighbourNode.pidx = m_tinyNodePool.getNodeIdx(curNode); neighbourNode.dtcsSetFlag(dtNodeFlags.DT_NODE_CLOSED); stack[nstack++] = neighbourNode; } } } } } int n = 0; if (bestNode != null) { // Reverse the path. dtNode prev = null; dtNode node = bestNode; do { dtNode next = m_tinyNodePool.getNodeAtIdx(node.pidx); node.pidx = m_tinyNodePool.getNodeIdx(prev); prev = node; node = next; } while (node != null); // Store result node = prev; do { visited[n++] = node.id; if (n >= maxVisitedSize) { status |= DT_BUFFER_TOO_SMALL; break; } node = m_tinyNodePool.getNodeAtIdx(node.pidx); } while (node != null); } dtVcopy(resultPos, bestPos); visitedCount = n; return status; } /// Returns portal points between two polygons. dtStatus getPortalPoints(dtPolyRef from, dtPolyRef to, float[] left, float[] right, ref byte fromType, ref byte toType) { Debug.Assert(m_nav != null); dtMeshTile fromTile = null; dtPoly fromPoly = null; if (dtStatusFailed(m_nav.getTileAndPolyByRef(from, ref fromTile, ref fromPoly))) return DT_FAILURE | DT_INVALID_PARAM; fromType = fromPoly.getType(); dtMeshTile toTile = null; dtPoly toPoly = null; if (dtStatusFailed(m_nav.getTileAndPolyByRef(to, ref toTile, ref toPoly))) return DT_FAILURE | DT_INVALID_PARAM; toType = toPoly.getType(); return getPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, left, right); } // Returns portal points between two polygons. dtStatus getPortalPoints(dtPolyRef from, dtPoly fromPoly, dtMeshTile fromTile, dtPolyRef to, dtPoly toPoly, dtMeshTile toTile, float[] left, float[] right) { // Find the link that points to the 'to' polygon. dtLink link = null; for (uint i = fromPoly.firstLink; i != DT_NULL_LINK; i = fromTile.links[i].next) { if (fromTile.links[i].polyRef == to) { link = fromTile.links[i]; break; } } if (link == null) return DT_FAILURE | DT_INVALID_PARAM; // Handle off-mesh connections. if (fromPoly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) { // Find link that points to first vertex. for (uint i = fromPoly.firstLink; i != DT_NULL_LINK; i = fromTile.links[i].next) { if (fromTile.links[i].polyRef == to) { int v = fromTile.links[i].edge; dtVcopy(left, 0, fromTile.verts, fromPoly.verts[v] * 3); dtVcopy(right, 0, fromTile.verts, fromPoly.verts[v] * 3); return DT_SUCCESS; } } return DT_FAILURE | DT_INVALID_PARAM; } if (toPoly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) { for (uint i = toPoly.firstLink; i != DT_NULL_LINK; i = toTile.links[i].next) { if (toTile.links[i].polyRef == from) { int v = toTile.links[i].edge; dtVcopy(left, 0, toTile.verts, toPoly.verts[v] * 3); dtVcopy(right, 0, toTile.verts, toPoly.verts[v] * 3); return DT_SUCCESS; } } return DT_FAILURE | DT_INVALID_PARAM; } // Find portal vertices. int v0 = fromPoly.verts[link.edge]; int v1 = fromPoly.verts[(link.edge + 1) % (int)fromPoly.vertCount]; dtVcopy(left, 0, fromTile.verts, v0 * 3); dtVcopy(right, 0, fromTile.verts, v1 * 3); // 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; dtVlerp(left, 0, fromTile.verts, v0 * 3, fromTile.verts, v1 * 3, tmin); dtVlerp(right, 0, fromTile.verts, v0 * 3, fromTile.verts, v1 * 3, tmax); } } return DT_SUCCESS; } // Returns edge mid point between two polygons. dtStatus getEdgeMidPoint(dtPolyRef from, dtPolyRef to, float[] mid) { float[] left = new float[3];//, right[3]; float[] right = new float[3]; byte fromType = 0, toType = 0; if (dtStatusFailed(getPortalPoints(from, to, left, right, ref fromType, ref toType))) return DT_FAILURE | DT_INVALID_PARAM; mid[0] = (left[0] + right[0]) * 0.5f; mid[1] = (left[1] + right[1]) * 0.5f; mid[2] = (left[2] + right[2]) * 0.5f; return DT_SUCCESS; } dtStatus getEdgeMidPoint(dtPolyRef from, dtPoly fromPoly, dtMeshTile fromTile, dtPolyRef to, dtPoly toPoly, dtMeshTile toTile, float[] mid) { float[] left = new float[3];//, right[3]; float[] right = new float[3]; if (dtStatusFailed(getPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, left, right))) return DT_FAILURE | DT_INVALID_PARAM; mid[0] = (left[0] + right[0]) * 0.5f; mid[1] = (left[1] + right[1]) * 0.5f; mid[2] = (left[2] + right[2]) * 0.5f; return DT_SUCCESS; } /// @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. /// /// Using the Hit Parameter (t) /// /// 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 /// /// Use Case Restriction /// /// 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. /// public dtStatus raycast(dtPolyRef startRef, float[] startPos, float[] endPos, dtQueryFilter filter, ref float t, float[] hitNormal, dtPolyRef[] path, ref uint pathCount, int maxPath) { dtRaycastHit hit = new(); hit.path = path; hit.maxPath = maxPath; dtStatus status = raycast(startRef, startPos, endPos, filter, 0, hit); t = hit.t; dtVcopy(hitNormal, hit.hitNormal); pathCount = (uint)hit.pathCount; return status; } /// Casts a 'walkability' ray along the surface of the navigation mesh from /// the start position toward the end position. /// @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. // @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. /// /// Using the Hit Parameter (t) /// /// 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 /// /// Use Case Restriction /// /// 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. /// public dtStatus raycast(dtPolyRef startRef, float[] startPos, float[] endPos, dtQueryFilter filter, uint options, dtRaycastHit hit, dtPolyRef prevRef = 0) { Debug.Assert(m_nav != null); hit.t = 0; hit.pathCount = 0; hit.pathCost = 0; // Validate input if (startRef == 0 || !m_nav.isValidPolyRef(startRef)) return DT_FAILURE | DT_INVALID_PARAM; if (prevRef != 0 && !m_nav.isValidPolyRef(prevRef)) return DT_FAILURE | DT_INVALID_PARAM; float[] dir = new float[3]; float[] curPos = new float[3]; float[] lastPos = new float[3]; float[] verts = new float[DT_VERTS_PER_POLYGON * 3 + 3]; int n = 0; dtVcopy(curPos, startPos); dtVsub(dir, endPos, startPos); dtVset(hit.hitNormal, 0, 0, 0); dtStatus status = DT_SUCCESS; dtMeshTile prevTile = new(); dtMeshTile nextTile; dtPoly prevPoly = new(); dtPoly nextPoly; dtPolyRef curRef; // The API input has been checked already, skip checking internal data. curRef = startRef; dtMeshTile tile = new(); dtPoly poly = new(); m_nav.getTileAndPolyByRefUnsafe(curRef, ref tile, ref poly); nextTile = prevTile = tile; nextPoly = prevPoly = poly; if (prevRef != 0) m_nav.getTileAndPolyByRefUnsafe(prevRef, ref prevTile, ref prevPoly); while (curRef != 0) { // Cast ray against current polygon. // Collect vertices. int nv = 0; for (int i = 0; i < (int)poly.vertCount; ++i) { dtVcopy(verts, nv * 3, tile.verts, poly.verts[i] * 3); nv++; } float tmin = 0, tmax = 0; int segMin = 0, segMax = 0; if (!dtIntersectSegmentPoly2D(startPos, endPos, verts, nv, ref tmin, ref tmax, ref segMin, ref segMax)) { // Could not hit the polygon, keep the old t and report hit. hit.pathCount = n; return status; } hit.hitEdgeIndex = segMax; // Keep track of furthest t so far. if (tmax > hit.t) hit.t = tmax; // Store visited polygons. if (n < hit.maxPath) hit.path[n++] = curRef; else status |= DT_BUFFER_TOO_SMALL; // Ray end is completely inside the polygon. if (segMax == -1) { hit.t = float.MaxValue; hit.pathCount = n; // add the cost if ((options & (int)dtRaycastOptions.DT_RAYCAST_USE_COSTS) != 0) hit.pathCost += filter.getCost(curPos, endPos, prevRef, prevTile, prevPoly, curRef, tile, poly, curRef, tile, poly); return status; } // Follow neighbours. dtPolyRef nextRef = 0; for (uint i = poly.firstLink; i != DT_NULL_LINK; i = tile.links[i].next) { dtLink link = tile.links[i]; // Find link which contains this edge. if ((int)link.edge != segMax) continue; // Get pointer to the next polygon. nextTile = new dtMeshTile(); nextPoly = new dtPoly(); m_nav.getTileAndPolyByRefUnsafe(link.polyRef, ref nextTile, ref nextPoly); // Skip off-mesh connections. if (nextPoly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) continue; // Skip links based on filter. if (!filter.passFilter(link.polyRef, nextTile, nextPoly)) continue; // If the link is internal, just return the ref. if (link.side == 0xff) { nextRef = link.polyRef; break; } // If the link is at tile boundary, // Check if the link spans the whole edge, and accept. if (link.bmin == 0 && link.bmax == 255) { nextRef = link.polyRef; break; } // Check for partial edge links. int v0 = poly.verts[link.edge]; int v1 = poly.verts[(link.edge + 1) % poly.vertCount]; //const float* left = &tile.verts[v0*3]; //const float* right = &tile.verts[v1*3]; int leftStart = v0 * 3; int rightStart = v1 * 3; // Check that the intersection lies inside the link portal. if (link.side == 0 || link.side == 4) { // Calculate link size. const float s = 1.0f / 255.0f; float lmin = tile.verts[leftStart + 2] + (tile.verts[rightStart + 2] - tile.verts[leftStart + 2]) * (link.bmin * s); float lmax = tile.verts[leftStart + 2] + (tile.verts[rightStart + 2] - tile.verts[leftStart + 2]) * (link.bmax * s); if (lmin > lmax) dtSwap(ref lmin, ref lmax); // Find Z intersection. float z = startPos[2] + (endPos[2] - startPos[2]) * tmax; if (z >= lmin && z <= lmax) { nextRef = link.polyRef; break; } } else if (link.side == 2 || link.side == 6) { // Calculate link size. const float s = 1.0f / 255.0f; float lmin = tile.verts[leftStart + 0] + (tile.verts[rightStart + 0] - tile.verts[leftStart + 0]) * (link.bmin * s); float lmax = tile.verts[leftStart + 0] + (tile.verts[rightStart + 0] - tile.verts[leftStart + 0]) * (link.bmax * s); if (lmin > lmax) dtSwap(ref lmin, ref lmax); // Find X intersection. float x = startPos[0] + (endPos[0] - startPos[0]) * tmax; if (x >= lmin && x <= lmax) { nextRef = link.polyRef; break; } } } // add the cost if ((options & (int)dtRaycastOptions.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) dtVcopy(lastPos, curPos); dtVmad(curPos, startPos, dir, hit.t); int e1Start = segMax * 3; int e2Start = ((segMax + 1) % nv) * 3; float[] eDir = new float[3]; float[] diff = new float[3]; dtVsub(eDir, 0, verts, e2Start, verts, e1Start); dtVsub(diff, 0, curPos, 0, verts, e1Start); float s = dtSqr(eDir[0]) > dtSqr(eDir[2]) ? diff[0] / eDir[0] : diff[2] / eDir[2]; curPos[1] = verts[e1Start + 1] + eDir[1] * s; hit.pathCost += filter.getCost(lastPos, curPos, prevRef, prevTile, prevPoly, curRef, tile, poly, nextRef, nextTile, nextPoly); } if (nextRef == 0) { // No neighbour, we hit a wall. // Calculate hit normal. int a = segMax; int b = segMax + 1 < nv ? segMax + 1 : 0; //const float* va = &verts[a*3]; //const float* vb = &verts[b*3]; int vaStart = a * 3; int vbStart = b * 3; float dx = verts[vbStart + 0] - verts[vaStart + 0]; float dz = verts[vbStart + 2] - verts[vaStart + 2]; hit.hitNormal[0] = dz; hit.hitNormal[1] = 0; hit.hitNormal[2] = -dx; dtVnormalize(hit.hitNormal); hit.pathCount = n; return status; } // No hit, advance to neighbour polygon. prevRef = curRef; curRef = nextRef; prevTile = tile; tile = nextTile; prevPoly = poly; poly = nextPoly; } hit.pathCount = n; return status; } ///@} // @name Dijkstra Search Functions // @{ /// Finds the polygons along the navigation graph that touch the specified circle. /// @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. // @returns The status flags for the query. // @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. /// dtStatus findPolysAroundCircle(dtPolyRef startRef, float[] centerPos, float radius, dtQueryFilter filter, dtPolyRef[] resultRef, dtPolyRef[] resultParent, float[] resultCost, ref int resultCount, int maxResult) { Debug.Assert(m_nav != null); Debug.Assert(m_nodePool != null); Debug.Assert(m_openList != null); resultCount = 0; // Validate input if (startRef == 0 || !m_nav.isValidPolyRef(startRef)) return DT_FAILURE | DT_INVALID_PARAM; m_nodePool.clear(); m_openList.clear(); dtNode startNode = m_nodePool.getNode(startRef); dtVcopy(startNode.pos, centerPos); startNode.pidx = 0; startNode.cost = 0; startNode.total = 0; startNode.id = startRef; startNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; m_openList.push(startNode); dtStatus status = DT_SUCCESS; int n = 0; if (n < maxResult) { if (resultRef != null) resultRef[n] = startNode.id; if (resultParent != null) resultParent[n] = 0; if (resultCost != null) resultCost[n] = 0; ++n; } else { status |= DT_BUFFER_TOO_SMALL; } float radiusSqr = dtSqr(radius); while (!m_openList.empty()) { dtNode bestNode = m_openList.pop(); //bestNode.flags &= ~DT_NODE_OPEN; //bestNode.flags |= DT_NODE_CLOSED; bestNode.dtcsClearFlag(dtNodeFlags.DT_NODE_OPEN); bestNode.dtcsSetFlag(dtNodeFlags.DT_NODE_CLOSED); // Get poly and tile. // The API input has been cheked already, skip checking internal data. dtPolyRef bestRef = bestNode.id; dtMeshTile bestTile = null; dtPoly bestPoly = null; m_nav.getTileAndPolyByRefUnsafe(bestRef, ref bestTile, ref bestPoly); // Get parent poly and tile. dtPolyRef parentRef = 0; dtMeshTile parentTile = null; dtPoly parentPoly = null; if (bestNode.pidx != 0) parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id; if (parentRef != 0) m_nav.getTileAndPolyByRefUnsafe(parentRef, ref parentTile, ref parentPoly); for (uint i = bestPoly.firstLink; i != DT_NULL_LINK; i = bestTile.links[i].next) { dtLink link = bestTile.links[i]; dtPolyRef neighbourRef = link.polyRef; // Skip invalid neighbours and do not follow back to parent. if (neighbourRef == 0 || neighbourRef == parentRef) continue; // Expand to neighbour dtMeshTile neighbourTile = null; dtPoly neighbourPoly = null; m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); // Do not advance if the polygon is excluded by the filter. if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) continue; // Find edge and calc distance to the edge. float[] va = new float[3];//, vb[3]; float[] vb = new float[3]; if (getPortalPoints(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly, neighbourTile, va, vb) == 0) continue; // If the circle is not touching the next polygon, skip it. float tseg = 0.0f; float distSqr = dtDistancePtSegSqr2D(centerPos, 0, va, 0, vb, 0, ref tseg); if (distSqr > radiusSqr) continue; dtNode neighbourNode = m_nodePool.getNode(neighbourRef); if (neighbourNode == null) { status |= DT_OUT_OF_NODES; continue; } if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_CLOSED)) continue; // Cost if (neighbourNode.flags == 0) dtVlerp(neighbourNode.pos, va, vb, 0.5f); float total = bestNode.total + dtVdist(bestNode.pos, neighbourNode.pos); // The node is already in open list and the new result is worse, skip. if ((neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_OPEN)) && total >= neighbourNode.total) continue; neighbourNode.id = neighbourRef; //neighbourNode.flags = (neighbourNode.flags & ~DT_NODE_CLOSED); neighbourNode.dtcsClearFlag(dtNodeFlags.DT_NODE_CLOSED); neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode); neighbourNode.total = total; if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_OPEN)) { m_openList.modify(neighbourNode); } else { if (n < maxResult) { if (resultRef != null) resultRef[n] = neighbourNode.id; if (resultParent != null) resultParent[n] = m_nodePool.getNodeAtIdx(neighbourNode.pidx).id; if (resultCost != null) resultCost[n] = neighbourNode.total; ++n; } else { status |= DT_BUFFER_TOO_SMALL; } neighbourNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; m_openList.push(neighbourNode); } } } resultCount = n; return status; } /// Finds the polygons along the naviation graph that touch the specified convex polygon. /// @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. // @returns The status flags for the query. // @par /// /// The order of the result set is from least to highest cost. /// /// At least one result array must be provided. /// /// 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. /// /// The same intersection test restrictions that apply to findPolysAroundCircle() /// method apply to this method. /// /// The 3D centroid of the search polygon is used as the start position for cost /// calculations. /// /// Intersection tests occur in 2D. All polygons are projected onto the /// xz-plane. So the y-values of the vertices do not effect intersection tests. /// /// If the result arrays are is too small to hold the entire result set, they will /// be filled to capacity. /// dtStatus findPolysAroundShape(dtPolyRef startRef, float[] verts, int nverts, dtQueryFilter filter, dtPolyRef[] resultRef, dtPolyRef[] resultParent, float[] resultCost, ref int resultCount, int maxResult) { Debug.Assert(m_nav != null); Debug.Assert(m_nodePool != null); Debug.Assert(m_openList != null); resultCount = 0; // Validate input if (startRef == 0 || !m_nav.isValidPolyRef(startRef)) return DT_FAILURE | DT_INVALID_PARAM; m_nodePool.clear(); m_openList.clear(); float[] centerPos = new float[] { 0, 0, 0 }; for (int i = 0; i < nverts; ++i) { dtVadd(centerPos, 0, centerPos, 0, verts, i * 3); } dtVscale(centerPos, centerPos, 1.0f / nverts); dtNode startNode = m_nodePool.getNode(startRef); dtVcopy(startNode.pos, centerPos); startNode.pidx = 0; startNode.cost = 0; startNode.total = 0; startNode.id = startRef; startNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; m_openList.push(startNode); dtStatus status = DT_SUCCESS; int n = 0; if (n < maxResult) { if (resultRef != null) resultRef[n] = startNode.id; if (resultParent != null) resultParent[n] = 0; if (resultCost != null) resultCost[n] = 0; ++n; } else { status |= DT_BUFFER_TOO_SMALL; } while (!m_openList.empty()) { dtNode bestNode = m_openList.pop(); //bestNode.flags &= ~DT_NODE_OPEN; //bestNode.flags |= DT_NODE_CLOSED; bestNode.dtcsClearFlag(dtNodeFlags.DT_NODE_OPEN); bestNode.dtcsSetFlag(dtNodeFlags.DT_NODE_CLOSED); // Get poly and tile. // The API input has been cheked already, skip checking internal data. dtPolyRef bestRef = bestNode.id; dtMeshTile bestTile = null; dtPoly bestPoly = null; m_nav.getTileAndPolyByRefUnsafe(bestRef, ref bestTile, ref bestPoly); // Get parent poly and tile. dtPolyRef parentRef = 0; dtMeshTile parentTile = null; dtPoly parentPoly = null; if (bestNode.pidx != 0) parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id; if (parentRef != 0) m_nav.getTileAndPolyByRefUnsafe(parentRef, ref parentTile, ref parentPoly); for (uint i = bestPoly.firstLink; i != DT_NULL_LINK; i = bestTile.links[i].next) { dtLink link = bestTile.links[i]; dtPolyRef neighbourRef = link.polyRef; // Skip invalid neighbours and do not follow back to parent. if (neighbourRef == 0 || neighbourRef == parentRef) continue; // Expand to neighbour dtMeshTile neighbourTile = null; dtPoly neighbourPoly = null; m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); // Do not advance if the polygon is excluded by the filter. if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) continue; // Find edge and calc distance to the edge. float[] va = new float[3];//, vb[3]; float[] vb = new float[3]; if (getPortalPoints(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly, neighbourTile, va, vb) == 0) continue; // If the poly is not touching the edge to the next polygon, skip the connection it. float tmin = 0, tmax = 0; int segMin = 0, segMax = 0; if (dtIntersectSegmentPoly2D(va, vb, verts, nverts, ref tmin, ref tmax, ref segMin, ref segMax)) continue; if (tmin > 1.0f || tmax < 0.0f) continue; dtNode neighbourNode = m_nodePool.getNode(neighbourRef); if (neighbourNode == null) { status |= DT_OUT_OF_NODES; continue; } if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_CLOSED)) continue; // Cost if (neighbourNode.flags == 0) dtVlerp(neighbourNode.pos, va, vb, 0.5f); float total = bestNode.total + dtVdist(bestNode.pos, neighbourNode.pos); // The node is already in open list and the new result is worse, skip. if ((neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_OPEN)) && total >= neighbourNode.total) continue; neighbourNode.id = neighbourRef; neighbourNode.dtcsClearFlag(dtNodeFlags.DT_NODE_CLOSED);// = (neighbourNode.flags & ~DT_NODE_CLOSED); neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode); neighbourNode.total = total; if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_OPEN))// .flags & DT_NODE_OPEN) { m_openList.modify(neighbourNode); } else { if (n < maxResult) { if (resultRef != null) resultRef[n] = neighbourNode.id; if (resultParent != null) resultParent[n] = m_nodePool.getNodeAtIdx(neighbourNode.pidx).id; if (resultCost != null) resultCost[n] = neighbourNode.total; ++n; } else { status |= DT_BUFFER_TOO_SMALL; } neighbourNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; m_openList.push(neighbourNode); } } } resultCount = n; return status; } /// Finds the non-overlapping navigation polygons in the local neighbourhood around the center position. /// @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. /// Zero if a result polygon has no parent. [opt] /// @param[out] resultCount The number of polygons found. /// @param[in] maxResult The maximum number of polygons the result arrays can hold. // @returns The status flags for the query. // @par /// /// This method is optimized for a small search radius and small number of result /// polygons. /// /// Candidate polygons are found by searching the navigation graph beginning at /// the start polygon. /// /// The same intersection test restrictions that apply to the findPolysAroundCircle /// mehtod applies to this method. /// /// 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 /// 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 is too small to hold the entire result set, they will /// be filled to capacity. /// dtStatus findLocalNeighbourhood(dtPolyRef startRef, float[] centerPos, float radius, dtQueryFilter filter, dtPolyRef[] resultRef, dtPolyRef[] resultParent, ref int resultCount, int maxResult) { Debug.Assert(m_nav != null); Debug.Assert(m_tinyNodePool != null); resultCount = 0; // Validate input if (startRef == 0 || !m_nav.isValidPolyRef(startRef)) return DT_FAILURE | DT_INVALID_PARAM; const int MAX_STACK = 48; dtNode[] stack = new dtNode[MAX_STACK]; dtcsArrayItemsCreate(stack); int nstack = 0; m_tinyNodePool.clear(); dtNode startNode = m_tinyNodePool.getNode(startRef); startNode.pidx = 0; startNode.id = startRef; startNode.flags = (byte)dtNodeFlags.DT_NODE_CLOSED; stack[nstack++] = startNode; float radiusSqr = dtSqr(radius); float[] pa = new float[DT_VERTS_PER_POLYGON * 3]; float[] pb = new float[DT_VERTS_PER_POLYGON * 3]; dtStatus status = DT_SUCCESS; int n = 0; if (n < maxResult) { resultRef[n] = startNode.id; if (resultParent != null) resultParent[n] = 0; ++n; } else { status |= DT_BUFFER_TOO_SMALL; } while (nstack != 0) { // Pop front. dtNode curNode = stack[0]; for (int i = 0; i < nstack - 1; ++i) stack[i] = stack[i + 1]; nstack--; // Get poly and tile. // The API input has been cheked already, skip checking internal data. dtPolyRef curRef = curNode.id; dtMeshTile curTile = null; dtPoly curPoly = null; m_nav.getTileAndPolyByRefUnsafe(curRef, ref curTile, ref curPoly); for (uint i = curPoly.firstLink; i != DT_NULL_LINK; i = curTile.links[i].next) { dtLink link = curTile.links[i]; dtPolyRef neighbourRef = link.polyRef; // Skip invalid neighbours. if (neighbourRef == 0) continue; // Skip if cannot alloca more nodes. dtNode neighbourNode = m_tinyNodePool.getNode(neighbourRef); if (neighbourNode == null) continue; // Skip visited. if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_CLOSED))// .flags & DT_NODE_CLOSED) continue; // Expand to neighbour dtMeshTile neighbourTile = null; dtPoly neighbourPoly = null; m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); // Skip off-mesh connections. if (neighbourPoly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) continue; // Do not advance if the polygon is excluded by the filter. if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) continue; // Find edge and calc distance to the edge. float[] va = new float[3];//, vb[3]; float[] vb = new float[3]; if (getPortalPoints(curRef, curPoly, curTile, neighbourRef, neighbourPoly, neighbourTile, va, vb) == 0) continue; // If the circle is not touching the next polygon, skip it. float tseg = .0f; float distSqr = dtDistancePtSegSqr2D(centerPos, 0, va, 0, vb, 0, ref tseg); if (distSqr > radiusSqr) continue; // Mark node visited, this is done before the overlap test so that // we will not visit the poly again if the test fails. //neighbourNode.flags |= DT_NODE_CLOSED; neighbourNode.dtcsSetFlag(dtNodeFlags.DT_NODE_CLOSED); neighbourNode.pidx = m_tinyNodePool.getNodeIdx(curNode); // 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) { dtVcopy(pa, k * 3, neighbourTile.verts, neighbourPoly.verts[k] * 3); } bool overlap = false; for (int j = 0; j < n; ++j) { dtPolyRef pastRef = resultRef[j]; // Connected polys do not overlap. bool connected = false; for (uint k = curPoly.firstLink; k != DT_NULL_LINK; k = curTile.links[k].next) { if (curTile.links[k].polyRef == pastRef) { connected = true; break; } } if (connected) continue; // Potentially overlapping. dtMeshTile pastTile = null; dtPoly pastPoly = null; m_nav.getTileAndPolyByRefUnsafe(pastRef, ref pastTile, ref pastPoly); // Get vertices and test overlap int npb = pastPoly.vertCount; for (int k = 0; k < npb; ++k) { dtVcopy(pb, k * 3, pastTile.verts, pastPoly.verts[k] * 3); } if (dtOverlapPolyPoly2D(pa, npa, pb, npb)) { overlap = true; break; } } if (overlap) continue; // This poly is fine, store and advance to the poly. if (n < maxResult) { resultRef[n] = neighbourRef; if (resultParent != null) resultParent[n] = curRef; ++n; } else { status |= DT_BUFFER_TOO_SMALL; } if (nstack < MAX_STACK) { stack[nstack++] = neighbourNode; } } } resultCount = n; return status; } class dtSegInterval { public dtPolyRef polyRef; public short tmin; public short tmax; }; static void insertInterval(dtSegInterval[] ints, ref int nints, int maxInts, short tmin, short tmax, dtPolyRef polyRef) { if (nints + 1 > maxInts) return; // Find insertion point. int idx = 0; while (idx < nints) { if (tmax <= ints[idx].tmin) break; idx++; } // Move current results. if (nints - idx != 0) { //memmove(ints+idx+1, ints+idx, sizeof(dtSegInterval)*(nints-idx)); for (int i = 0; i < (nints - idx); ++i) { ints[idx + 1 + i] = ints[idx + i]; } } // Store ints[idx].polyRef = polyRef; ints[idx].tmin = tmin; ints[idx].tmax = tmax; nints++; } /// 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. // @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. /// dtStatus getPolyWallSegments(dtPolyRef polyRef, dtQueryFilter filter, float[] segmentVerts, dtPolyRef[] segmentRefs, ref int segmentCount, int maxSegments) { Debug.Assert(m_nav != null); segmentCount = 0; dtMeshTile tile = null; dtPoly poly = null; if (dtStatusFailed(m_nav.getTileAndPolyByRef(polyRef, ref tile, ref poly))) return DT_FAILURE | DT_INVALID_PARAM; int n = 0; const int MAX_INTERVAL = 16; dtSegInterval[] ints = new dtSegInterval[MAX_INTERVAL]; dtcsArrayItemsCreate(ints); int nints; bool storePortals = segmentRefs != null; dtStatus status = DT_SUCCESS; for (int i = 0, j = (int)poly.vertCount - 1; i < (int)poly.vertCount; j = i++) { // Skip non-solid edges. nints = 0; if ((poly.neis[j] & DT_EXT_LINK) != 0) { // Tile border. for (uint k = poly.firstLink; k != DT_NULL_LINK; k = tile.links[k].next) { dtLink link = tile.links[k]; if (link.edge == j) { if (link.polyRef != 0) { dtMeshTile neiTile = null; dtPoly neiPoly = null; m_nav.getTileAndPolyByRefUnsafe(link.polyRef, ref neiTile, ref neiPoly); if (filter.passFilter(link.polyRef, neiTile, neiPoly)) { insertInterval(ints, ref nints, MAX_INTERVAL, link.bmin, link.bmax, link.polyRef); } } } } } else { // Internal edge dtPolyRef neiRef = 0; if (poly.neis[j] != 0) { uint idx = (uint)(poly.neis[j] - 1); neiRef = m_nav.getPolyRefBase(tile) | idx; if (!filter.passFilter(neiRef, tile, tile.polys[idx])) neiRef = 0; } // If the edge leads to another polygon and portals are not stored, skip. if (neiRef != 0 && !storePortals) continue; if (n < maxSegments) { //const float* vj = &tile.verts[poly.verts[j]*3]; //const float* vi = &tile.verts[poly.verts[i]*3]; //float* seg = &segmentVerts[n*6]; int vjStart = poly.verts[j] * 3; int viStart = poly.verts[i] * 3; int segStart = n * 6; dtVcopy(segmentVerts, segStart, tile.verts, vjStart); dtVcopy(segmentVerts, segStart + 3, tile.verts, viStart); if (segmentRefs != null) segmentRefs[n] = neiRef; n++; } else { status |= DT_BUFFER_TOO_SMALL; } continue; } // Add sentinels insertInterval(ints, ref nints, MAX_INTERVAL, -1, 0, 0); insertInterval(ints, ref nints, MAX_INTERVAL, 255, 256, 0); // Store segments. //const float* vj = &tile.verts[poly.verts[j]*3]; //const float* vi = &tile.verts[poly.verts[i]*3]; int vjStart2 = poly.verts[j] * 3; int viStart2 = poly.verts[i] * 3; for (int k = 1; k < nints; ++k) { // Portal segment. if (storePortals && ints[k].polyRef != 0) { float tmin = ints[k].tmin / 255.0f; float tmax = ints[k].tmax / 255.0f; if (n < maxSegments) { //float* seg = &segmentVerts[n*6]; int segStart = n * 6; dtVlerp(segmentVerts, segStart, tile.verts, vjStart2, tile.verts, viStart2, tmin); dtVlerp(segmentVerts, segStart + 3, tile.verts, vjStart2, tile.verts, viStart2, tmax); if (segmentRefs != null) segmentRefs[n] = ints[k].polyRef; n++; } else { status |= DT_BUFFER_TOO_SMALL; } } // 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; if (n < maxSegments) { //float* seg = &segmentVerts[n*6]; int segStart = n * 6; dtVlerp(segmentVerts, segStart, tile.verts, vjStart2, tile.verts, viStart2, tmin); dtVlerp(segmentVerts, segStart + 3, tile.verts, vjStart2, tile.verts, viStart2, tmax); if (segmentRefs != null) segmentRefs[n] = 0; n++; } else { status |= DT_BUFFER_TOO_SMALL; } } } } segmentCount = n; return status; } /// Finds the distance from the specified position to the nearest polygon wall. /// @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)] // @returns The status flags for the query. // @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. /// dtStatus findDistanceToWall(dtPolyRef startRef, float[] centerPos, float maxRadius, dtQueryFilter filter, ref float hitDist, float[] hitPos, float[] hitNormal) { Debug.Assert(m_nav != null); Debug.Assert(m_nodePool != null); Debug.Assert(m_openList != null); // Validate input if (startRef == 0 || !m_nav.isValidPolyRef(startRef)) return DT_FAILURE | DT_INVALID_PARAM; m_nodePool.clear(); m_openList.clear(); dtNode startNode = m_nodePool.getNode(startRef); dtVcopy(startNode.pos, centerPos); startNode.pidx = 0; startNode.cost = 0; startNode.total = 0; startNode.id = startRef; startNode.flags = (byte)dtNodeFlags.DT_NODE_OPEN; m_openList.push(startNode); float radiusSqr = dtSqr(maxRadius); dtStatus status = DT_SUCCESS; while (!m_openList.empty()) { dtNode bestNode = m_openList.pop(); //bestNode.flags &= ~DT_NODE_OPEN; //bestNode.flags |= DT_NODE_CLOSED; bestNode.dtcsClearFlag(dtNodeFlags.DT_NODE_OPEN); bestNode.dtcsSetFlag(dtNodeFlags.DT_NODE_CLOSED); // Get poly and tile. // The API input has been cheked already, skip checking internal data. dtPolyRef bestRef = bestNode.id; dtMeshTile bestTile = null; dtPoly bestPoly = null; m_nav.getTileAndPolyByRefUnsafe(bestRef, ref bestTile, ref bestPoly); // Get parent poly and tile. dtPolyRef parentRef = 0; dtMeshTile parentTile = null; dtPoly parentPoly = null; if (bestNode.pidx != 0) parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id; if (parentRef != 0) m_nav.getTileAndPolyByRefUnsafe(parentRef, ref parentTile, ref parentPoly); // Hit test walls. for (int i = 0, j = (int)bestPoly.vertCount - 1; i < (int)bestPoly.vertCount; j = i++) { // Skip non-solid edges. if ((bestPoly.neis[j] & DT_EXT_LINK) != 0) { // Tile border. bool solid = true; for (uint k = bestPoly.firstLink; k != DT_NULL_LINK; k = bestTile.links[k].next) { dtLink link = bestTile.links[k]; if (link.edge == j) { if (link.polyRef != 0) { dtMeshTile neiTile = null; dtPoly neiPoly = null; m_nav.getTileAndPolyByRefUnsafe(link.polyRef, ref neiTile, ref neiPoly); if (filter.passFilter(link.polyRef, neiTile, neiPoly)) solid = false; } break; } } if (!solid) continue; } else if (bestPoly.neis[j] != 0) { // Internal edge uint idx = (uint)(bestPoly.neis[j] - 1); dtPolyRef polyRef = m_nav.getPolyRefBase(bestTile) | idx; if (filter.passFilter(polyRef, bestTile, bestTile.polys[idx])) continue; } // Calc distance to the edge. //const float* vj = &bestTile.verts[bestPoly.verts[j]*3]; //const float* vi = &bestTile.verts[bestPoly.verts[i]*3]; int vjStart = bestPoly.verts[j] * 3; int viStart = bestPoly.verts[i] * 3; float tseg = .0f; float distSqr = dtDistancePtSegSqr2D(centerPos, 0, bestTile.verts, vjStart, bestTile.verts, viStart, ref tseg); // Edge is too far, skip. if (distSqr > radiusSqr) continue; // Hit wall, update radius. radiusSqr = distSqr; // Calculate hit pos. hitPos[0] = bestTile.verts[vjStart + 0] + (bestTile.verts[viStart + 0] - bestTile.verts[vjStart + 0]) * tseg; hitPos[1] = bestTile.verts[vjStart + 1] + (bestTile.verts[viStart + 1] - bestTile.verts[vjStart + 1]) * tseg; hitPos[2] = bestTile.verts[vjStart + 2] + (bestTile.verts[viStart + 2] - bestTile.verts[vjStart + 2]) * tseg; } for (uint i = bestPoly.firstLink; i != DT_NULL_LINK; i = bestTile.links[i].next) { dtLink link = bestTile.links[i]; dtPolyRef neighbourRef = link.polyRef; // Skip invalid neighbours and do not follow back to parent. if (neighbourRef != 0 || neighbourRef == parentRef) continue; // Expand to neighbour. dtMeshTile neighbourTile = null; dtPoly neighbourPoly = null; m_nav.getTileAndPolyByRefUnsafe(neighbourRef, ref neighbourTile, ref neighbourPoly); // Skip off-mesh connections. if (neighbourPoly.getType() == (byte)dtPolyTypes.DT_POLYTYPE_OFFMESH_CONNECTION) continue; // Calc distance to the edge. //const float* va = &bestTile.verts[bestPoly.verts[link.edge]*3]; //const float* vb = &bestTile.verts[bestPoly.verts[(link.edge+1) % bestPoly.vertCount]*3]; int vaStart = bestPoly.verts[link.edge] * 3; int vbStart = bestPoly.verts[(link.edge + 1) % bestPoly.vertCount] * 3; float tseg = .0f; float distSqr = dtDistancePtSegSqr2D(centerPos, 0, bestTile.verts, vaStart, bestTile.verts, vbStart, ref tseg); // If the circle is not touching the next polygon, skip it. if (distSqr > radiusSqr) continue; if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) continue; dtNode neighbourNode = m_nodePool.getNode(neighbourRef); if (neighbourNode == null) { status |= DT_OUT_OF_NODES; continue; } if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_CLOSED))// .flags & DT_NODE_CLOSED) continue; // Cost if (neighbourNode.flags == 0) { getEdgeMidPoint(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly, neighbourTile, neighbourNode.pos); } float total = bestNode.total + dtVdist(bestNode.pos, neighbourNode.pos); // The node is already in open list and the new result is worse, skip. if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_OPEN) && total >= neighbourNode.total) continue; neighbourNode.id = neighbourRef; //neighbourNode.flags = (neighbourNode.flags & ~DT_NODE_CLOSED); neighbourNode.dtcsClearFlag(dtNodeFlags.DT_NODE_CLOSED); neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode); neighbourNode.total = total; if (neighbourNode.dtcsTestFlag(dtNodeFlags.DT_NODE_OPEN))// .flags & DT_NODE_OPEN) { m_openList.modify(neighbourNode); } else { //neighbourNode.flags |= DT_NODE_OPEN; neighbourNode.dtcsSetFlag(dtNodeFlags.DT_NODE_OPEN); m_openList.push(neighbourNode); } } } // Calc hit normal. dtVsub(hitNormal, centerPos, hitPos); dtVnormalize(hitNormal); hitDist = (float)Math.Sqrt(radiusSqr); return status; } // @} // @name Miscellaneous Functions // @{ /// 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. bool isValidPolyRef(dtPolyRef polyRef, dtQueryFilter filter) { dtMeshTile tile = null; dtPoly poly = null; dtStatus status = m_nav.getTileAndPolyByRef(polyRef, ref tile, ref poly); // If cannot get polygon, assume it does not exists and boundary is invalid. if (dtStatusFailed(status)) return false; // If cannot pass filter, assume flags has changed and boundary is invalid. if (!filter.passFilter(polyRef, tile, poly)) return false; return true; } /// Returns true if the polygon reference is in the closed list. /// @param[in] ref The reference id of the polygon to check. // @returns True if the polygon is in closed list. // @par /// /// The closed list is the list of polygons that were fully evaluated during /// the last navigation graph search. (A* or Dijkstra) /// bool isInClosedList(dtPolyRef polyRef) { if (m_nodePool == null) return false; dtNode node = m_nodePool.findNode(polyRef); return node != null && node.dtcsTestFlag(dtNodeFlags.DT_NODE_CLOSED);// .flags & DT_NODE_CLOSED; } /// Gets the navigation mesh the query object is using. // @return The navigation mesh the query object is using. public dtNavMesh getAttachedNavMesh() { return m_nav; } } public class dtFindNearestPolyQuery { dtNavMeshQuery m_query; float[] m_center; float m_nearestDistanceSqr; dtPolyRef m_nearestRef; float[] m_nearestPoint = new float[3]; public dtFindNearestPolyQuery(dtNavMeshQuery query, float[] center) { m_query = query; m_center = center; m_nearestDistanceSqr = float.MaxValue; m_nearestRef = 0; } public dtPolyRef nearestRef() { return m_nearestRef; } public float[] nearestPoint() { return m_nearestPoint; } public void process(dtMeshTile tile, dtPoly[] polys, dtPolyRef[] refs, int count) { for (int i = 0; i < count; ++i) { dtPolyRef refe = refs[i]; float[] closestPtPoly = new float[3]; float[] diff = new float[3]; bool posOverPoly = false; float d; m_query.closestPointOnPoly(refe, m_center, closestPtPoly, ref posOverPoly); // If a point is directly over a polygon and closer than // climb height, favor that instead of straight line nearest point. dtVsub(diff, m_center, closestPtPoly); if (posOverPoly) { d = Math.Abs(diff[1]) - tile.header.walkableClimb; d = d > 0 ? d * d : 0; } else { d = dtVlenSqr(diff); } if (d < m_nearestDistanceSqr) { dtVcopy(m_nearestPoint, closestPtPoly); m_nearestDistanceSqr = d; m_nearestRef = refe; } } } } }