public bool unloadMap(uint mapId) { if (!loadedMMaps.ContainsKey(mapId)) { // file may not exist, therefore not loaded Log.outDebug(LogFilter.Maps, "MMAP:unloadMap: Asked to unload not loaded navmesh map {0:D4}", mapId); return(false); } // unload all tiles from given map MMapData mmap = loadedMMaps.LookupByKey(mapId); foreach (var i in mmap.loadedTileRefs) { uint x = (i.Key >> 16); uint y = (i.Key & 0x0000FFFF); Detour.dtRawTileData data; if (Detour.dtStatusFailed(mmap.navMesh.removeTile(i.Value, out data))) { Log.outError(LogFilter.Maps, "MMAP:unloadMap: Could not unload {0:D4}{1:D2}{2:D2}.mmtile from navmesh", mapId, x, y); } else { --loadedTiles; Log.outInfo(LogFilter.Maps, "MMAP:unloadMap: Unloaded mmtile {0:D4} [{1:D2}, {2:D2}] from {3:D4}", mapId, x, y, mapId); } } loadedMMaps.Remove(mapId); Log.outInfo(LogFilter.Maps, "MMAP:unloadMap: Unloaded {0:D4}.mmap", mapId); return(true); }
public Detour.dtNavMeshQuery GetNavMeshQuery(uint mapId, uint instanceId, List <uint> swaps) { MMapData mmap = GetMMapData(mapId); if (mmap == null) { return(null); } if (!mmap.navMeshQueries.ContainsKey(instanceId)) { // allocate mesh query Detour.dtNavMeshQuery query = new Detour.dtNavMeshQuery(); if (Detour.dtStatusFailed(query.init(mmap.GetNavMesh(swaps), 1024))) { Log.outError(LogFilter.Maps, "MMAP:GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId {0} instanceId {1}", mapId, instanceId); return(null); } Log.outInfo(LogFilter.Maps, "MMAP:GetNavMeshQuery: created dtNavMeshQuery for mapId {0} instanceId {1}", mapId, instanceId); mmap.navMeshQueries.Add(instanceId, query); } return(mmap.navMeshQueries[instanceId]); }
bool loadMapInstanceImpl(string basePath, uint mapId, uint instanceId) { if (!loadMapData(basePath, mapId)) { return(false); } MMapData mmap = loadedMMaps[mapId]; if (mmap.navMeshQueries.ContainsKey(instanceId)) { return(true); } // allocate mesh query Detour.dtNavMeshQuery query = new Detour.dtNavMeshQuery(); if (Detour.dtStatusFailed(query.init(mmap.navMesh, 1024))) { Log.outError(LogFilter.Maps, "MMAP.GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId {0:D4} instanceId {1}", mapId, instanceId); return(false); } Log.outDebug(LogFilter.Maps, "MMAP.GetNavMeshQuery: created dtNavMeshQuery for mapId {0:D4} instanceId {1}", mapId, instanceId); mmap.navMeshQueries.Add(instanceId, query); return(true); }
// This function checks if the path has a small U-turn, that is, // a polygon further in the path is adjacent to the first polygon // in the path. If that happens, a shortcut is taken. // This can happen if the target (T) location is at tile boundary, // and we're (S) approaching it parallel to the tile edge. // The choice at the vertex can be arbitrary, // +---+---+ // |:::|:::| // +-S-+-T-+ // |:::| | <-- the step can end up in here, resulting U-turn path. // +---+---+ static int fixupShortcuts(dtPolyRef[] path, int npath, Detour.dtNavMeshQuery navQuery) { if (npath < 3) { return(npath); } // Get connected polygons const int maxNeis = 16; dtPolyRef[] neis = new dtPolyRef[maxNeis]; int nneis = 0; Detour.dtMeshTile tile = null; Detour.dtPoly poly = null; if (Detour.dtStatusFailed(navQuery.getAttachedNavMesh().getTileAndPolyByRef(path[0], ref tile, ref poly))) { return(npath); } for (uint k = poly.firstLink; k != Detour.DT_NULL_LINK; k = tile.links[k].next) { Detour.dtLink link = tile.links[k]; if (link.polyRef != 0) { if (nneis < maxNeis) { neis[nneis++] = link.polyRef; } } } // If any of the neighbour polygons is within the next few polygons // in the path, short cut to that polygon directly. const int maxLookAhead = 6; int cut = 0; for (int i = Math.Min(maxLookAhead, npath) - 1; i > 1 && cut == 0; i--) { for (int j = 0; j < nneis; j++) { if (path[i] == neis[j]) { cut = i; break; } } } if (cut > 1) { int offset = cut - 1; npath -= offset; for (int i = 1; i < npath; i++) { path[i] = path[i + offset]; } } return(npath); }
void AddSwap(PhasedTile ptile, uint swap, uint packedXY) { uint x = (packedXY >> 16); uint y = (packedXY & 0x0000FFFF); if (!loadedTileRefs.ContainsKey(packedXY)) { Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: phased mmtile {0:D4}[{1:D2}, {2:D2}] load skipped, due to not loaded base tile on map {3}", swap, x, y, _mapId); return; } if (loadedPhasedTiles[swap].Contains(packedXY)) { Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: WARNING! phased mmtile {0:D4}[{1:D2}, {2:D2}] load skipped, due to already loaded on map {3}", swap, x, y, _mapId); return; } Detour.dtMeshHeader header = ptile.data.header; Detour.dtMeshTile oldTile = navMesh.getTileByRef(loadedTileRefs[packedXY]); if (oldTile == null) { Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: phased mmtile {0:D4}[{1:D2}, {2:D2}] load skipped, due to not loaded base tile ref on map {3}", swap, x, y, _mapId); return; } // header xy is based on the swap map's tile set, wich doesn't have all the same tiles as root map, so copy the xy from the orignal header header.x = oldTile.header.x; header.y = oldTile.header.y; Detour.dtRawTileData data; // remove old tile if (Detour.dtStatusFailed(navMesh.removeTile(loadedTileRefs[packedXY], out data))) { Log.outError(LogFilter.Maps, "MMapData.AddSwap: Could not unload {0:D4}{1:D2}{2:D2}.mmtile from navmesh", _mapId, x, y); } else { Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: Unloaded {0:D4}{1:D2}{2:D2}.mmtile from navmesh", _mapId, x, y); _activeSwaps.Add(swap); loadedPhasedTiles.Add(swap, packedXY); // add new swapped tile ulong loadedRef = 0; if (Detour.dtStatusSucceed(navMesh.addTile(ptile.data, 0, 0, ref loadedRef))) { Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: Loaded phased mmtile {0:D4}[{1:D2}, {2:D2}] into {0:D4}[{1:D2}, {2:D2}]", swap, x, y, _mapId, header.x, header.y); } else { Log.outError(LogFilter.Maps, "MMapData.AddSwap: Could not load {0:D4}{1:D2}{2:D2}.mmtile to navmesh", swap, x, y); } loadedTileRefs[packedXY] = loadedRef; } }
public bool unloadMap(uint mapId, uint x, uint y) { // check if we have this map loaded MMapData mmap = GetMMapData(mapId); if (mmap == null) { // file may not exist, therefore not loaded Log.outDebug(LogFilter.Maps, "MMAP:unloadMap: Asked to unload not loaded navmesh map. {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y); return(false); } // check if we have this tile loaded uint packedGridPos = packTileID((int)x, (int)y); if (!mmap.loadedTileRefs.ContainsKey(packedGridPos)) { // file may not exist, therefore not loaded Log.outDebug(LogFilter.Maps, "MMAP:unloadMap: Asked to unload not loaded navmesh tile. {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y); return(false); } ulong tileRef = mmap.loadedTileRefs.LookupByKey(packedGridPos); // unload, and mark as non loaded Detour.dtRawTileData data; if (Detour.dtStatusFailed(mmap.navMesh.removeTile(tileRef, out data))) { // this is technically a memory leak // if the grid is later reloaded, dtNavMesh.addTile will return error but no extra memory is used // we cannot recover from this error - assert out Log.outError(LogFilter.Maps, "MMAP:unloadMap: Could not unload {0:D4}{1:D2}{2:D2}.mmtile from navmesh", mapId, x, y); Contract.Assert(false); } else { mmap.loadedTileRefs.Remove(packedGridPos); --loadedTiles; Log.outInfo(LogFilter.Maps, "MMAP:unloadMap: Unloaded mmtile {0:D4}[{1:D2}, {2:D2}] from {3:D4}", mapId, x, y, mapId); var phasedMaps = phaseMapData.LookupByKey(mapId); if (!phasedMaps.Empty()) { mmap.DeleteBaseTile(packedGridPos); UnloadPhaseTile(phasedMaps, (int)x, (int)y); } return(true); } return(false); }
bool GetSteerTarget(float[] startPos, float[] endPos, float minTargetDist, ulong[] path, uint pathSize, out float[] steerPos, out Detour.dtStraightPathFlags steerPosFlag, out ulong steerPosRef) { steerPosRef = 0; steerPos = new float[3]; steerPosFlag = 0; // Find steer target. float[] steerPath = new float[3 * 3]; byte[] steerPathFlags = new byte[3]; ulong[] steerPathPolys = new ulong[3]; int nsteerPath = 0; uint dtResult = _navMeshQuery.findStraightPath(startPos, endPos, path, (int)pathSize, steerPath, steerPathFlags, steerPathPolys, ref nsteerPath, 3, 0); if (nsteerPath == 0 || Detour.dtStatusFailed(dtResult)) { return(false); } // Find vertex far enough to steer to. uint ns = 0; while (ns < nsteerPath) { Span <float> span = steerPath; // Stop at Off-Mesh link or when point is further than slop away. if ((steerPathFlags[ns].HasAnyFlag((byte)Detour.dtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION) || !InRangeYZX(span.Slice((int)ns * 3).ToArray(), startPos, minTargetDist, 1000.0f))) { break; } ns++; } // Failed to find good point to steer to. if (ns >= nsteerPath) { return(false); } Detour.dtVcopy(steerPos, 0, steerPath, (int)ns * 3); steerPos[1] = startPos[1]; // keep Z value steerPosFlag = (Detour.dtStraightPathFlags)steerPathFlags[ns]; steerPosRef = steerPathPolys[ns]; return(true); }
void RemoveSwap(PhasedTile ptile, uint swap, uint packedXY) { uint x = (packedXY >> 16); uint y = (packedXY & 0x0000FFFF); if (!loadedPhasedTiles[swap].Contains(packedXY)) { Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: mmtile {0:D4}[{1:D2}, {2:D2}] unload skipped, due to not loaded", swap, x, y); return; } Detour.dtMeshHeader header = ptile.data.header; Detour.dtRawTileData data; // remove old tile if (Detour.dtStatusFailed(navMesh.removeTile(loadedTileRefs[packedXY], out data))) { Log.outError(LogFilter.Maps, "MMapData.RemoveSwap: Could not unload phased {0:D4}{1:D2}{2:D2}.mmtile from navmesh", swap, x, y); } else { Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: Unloaded phased {0:D4}{1:D2}{2:D2}.mmtile from navmesh", swap, x, y); // restore base tile ulong loadedRef = 0; if (Detour.dtStatusSucceed(navMesh.addTile(_baseTiles[packedXY].data, 0, 0, ref loadedRef))) { Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: Loaded base mmtile {0:D4}[{1:D2}, {2:D2}] into {0:D4}[{1:D2}, {2:D2}]", _mapId, x, y, _mapId, header.x, header.y); } else { Log.outError(LogFilter.Maps, "MMapData.RemoveSwap: Could not load base {0:D4}{1:D2}{2:D2}.mmtile to navmesh", _mapId, x, y); } loadedTileRefs[packedXY] = loadedRef; } loadedPhasedTiles.Remove(swap, packedXY); if (loadedPhasedTiles[swap].Empty()) { _activeSwaps.Remove(swap); Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: Fully removed swap {0} from map {1}", swap, _mapId); } }
public bool ComputeSystem(byte[] tileRawData, int start) { m_ctx.enableLog(true); m_ctx.resetTimers(); // Start the build process. m_ctx.startTimer(Recast.rcTimerLabel.RC_TIMER_TOTAL); m_rawTileData = new Detour.dtRawTileData(); m_rawTileData.FromBytes(tileRawData, start); m_navMesh = new Detour.dtNavMesh(); if (m_navMesh == null) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "Could not create Detour navmesh"); return(false); } dtStatus status; status = m_navMesh.init(m_rawTileData, (int)Detour.dtTileFlags.DT_TILE_FREE_DATA); if (Detour.dtStatusFailed(status)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "Could not init Detour navmesh"); return(false); } m_navQuery = new Detour.dtNavMeshQuery(); status = m_navQuery.init(m_navMesh, 2048); if (Detour.dtStatusFailed(status)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "Could not init Detour navmesh query"); return(false); } m_ctx.stopTimer(Recast.rcTimerLabel.RC_TIMER_TOTAL); //m_ctx.log(Recast.rcLogCategory.RC_LOG_PROGRESS, ">> Polymesh: " + m_pmesh.nverts + " vertices " + m_pmesh.npolys + " polygons"); m_totalBuildTimeMs = (float)m_ctx.getAccumulatedTime(Recast.rcTimerLabel.RC_TIMER_TOTAL); return(true); }
bool loadMapData(uint mapId) { // we already have this map loaded? if (loadedMMaps.ContainsKey(mapId) && loadedMMaps[mapId] != null) { return(true); } // load and init dtNavMesh - read parameters from file string filename = string.Format(MAP_FILE_NAME_FORMAT, Global.WorldMgr.GetDataPath(), mapId); if (!File.Exists(filename)) { Log.outError(LogFilter.Maps, "Could not open mmap file {0}", filename); return(false); } using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read), Encoding.UTF8)) { Detour.dtNavMeshParams Params = new Detour.dtNavMeshParams(); Params.orig[0] = reader.ReadSingle(); Params.orig[1] = reader.ReadSingle(); Params.orig[2] = reader.ReadSingle(); Params.tileWidth = reader.ReadSingle(); Params.tileHeight = reader.ReadSingle(); Params.maxTiles = reader.ReadInt32(); Params.maxPolys = reader.ReadInt32(); Detour.dtNavMesh mesh = new Detour.dtNavMesh(); if (Detour.dtStatusFailed(mesh.init(Params))) { Log.outError(LogFilter.Maps, "MMAP:loadMapData: Failed to initialize dtNavMesh for mmap {0:D4} from file {1}", mapId, filename); return(false); } Log.outInfo(LogFilter.Maps, "MMAP:loadMapData: Loaded {0:D4}.mmap", mapId); // store inside our map list loadedMMaps[mapId] = new MMapData(mesh, mapId); return(true); } }
ulong GetPathPolyByPosition(ulong[] polyPath, uint polyPathSize, float[] point, ref float distance) { if (polyPath == null || polyPathSize == 0) { return(0); } ulong nearestPoly = 0; float minDist2d = float.MaxValue; float minDist3d = 0.0f; for (uint i = 0; i < polyPathSize; ++i) { float[] closestPoint = new float[3]; bool posOverPoly = false; if (Detour.dtStatusFailed(_navMeshQuery.closestPointOnPoly(polyPath[i], point, closestPoint, ref posOverPoly))) { continue; } float d = Detour.dtVdist2DSqr(point, closestPoint); if (d < minDist2d) { minDist2d = d; nearestPoly = polyPath[i]; minDist3d = Detour.dtVdistSqr(point, closestPoint); } if (minDist2d < 1.0f) // shortcut out - close enough for us { break; } } distance = (float)Math.Sqrt(minDist3d); return((minDist2d < 3.0f) ? nearestPoly : 0u); }
uint FindSmoothPath(float[] startPos, float[] endPos, ulong[] polyPath, uint polyPathSize, out float[] smoothPath, out int smoothPathSize, uint maxSmoothPathSize) { smoothPathSize = 0; int nsmoothPath = 0; smoothPath = new float[74 * 3]; ulong[] polys = new ulong[74]; Array.Copy(polyPath, polys, polyPathSize); uint npolys = polyPathSize; float[] iterPos = new float[3]; float[] targetPos = new float[3]; if (Detour.dtStatusFailed(_navMeshQuery.closestPointOnPolyBoundary(polys[0], startPos, iterPos))) { return(Detour.DT_FAILURE); } if (Detour.dtStatusFailed(_navMeshQuery.closestPointOnPolyBoundary(polys[npolys - 1], endPos, targetPos))) { return(Detour.DT_FAILURE); } Detour.dtVcopy(smoothPath, nsmoothPath * 3, iterPos, 0); nsmoothPath++; // Move towards target a small advancement at a time until target reached or // when ran out of memory to store the path. while (npolys != 0 && nsmoothPath < maxSmoothPathSize) { // Find location to steer towards. float[] steerPos; Detour.dtStraightPathFlags steerPosFlag; ulong steerPosRef = 0; if (!GetSteerTarget(iterPos, targetPos, 0.3f, polys, npolys, out steerPos, out steerPosFlag, out steerPosRef)) { break; } bool endOfPath = steerPosFlag.HasAnyFlag(Detour.dtStraightPathFlags.DT_STRAIGHTPATH_END); bool offMeshConnection = steerPosFlag.HasAnyFlag(Detour.dtStraightPathFlags.DT_STRAIGHTPATH_OFFMESH_CONNECTION); // Find movement delta. float[] delta = new float[3]; Detour.dtVsub(delta, steerPos, iterPos); float len = (float)Math.Sqrt(Detour.dtVdot(delta, delta)); // If the steer target is end of path or off-mesh link, do not move past the location. if ((endOfPath || offMeshConnection) && len < 4.0f) { len = 1.0f; } else { len = 4.0f / len; } float[] moveTgt = new float[3]; Detour.dtVmad(moveTgt, iterPos, delta, len); // Move float[] result = new float[3]; int MAX_VISIT_POLY = 16; ulong[] visited = new ulong[MAX_VISIT_POLY]; int nvisited = 0; _navMeshQuery.moveAlongSurface(polys[0], iterPos, moveTgt, _filter, result, visited, ref nvisited, MAX_VISIT_POLY); npolys = FixupCorridor(polys, npolys, 74, visited, nvisited); _navMeshQuery.getPolyHeight(polys[0], result, ref result[1]); result[1] += 0.5f; Detour.dtVcopy(iterPos, result); // Handle end of path and off-mesh links when close enough. if (endOfPath && InRangeYZX(iterPos, steerPos, 0.3f, 1.0f)) { // Reached end of path. Detour.dtVcopy(iterPos, targetPos); if (nsmoothPath < maxSmoothPathSize) { Detour.dtVcopy(smoothPath, nsmoothPath * 3, iterPos, 0); nsmoothPath++; } break; } else if (offMeshConnection && InRangeYZX(iterPos, steerPos, 0.3f, 1.0f)) { // Advance the path up to and over the off-mesh connection. ulong prevRef = 0; ulong polyRef = polys[0]; uint npos = 0; while (npos < npolys && polyRef != steerPosRef) { prevRef = polyRef; polyRef = polys[npos]; npos++; } for (uint i = npos; i < npolys; ++i) { polys[i - npos] = polys[i]; } npolys -= npos; // Handle the connection. float[] connectionStartPos = new float[3]; float[] connectionEndPos = new float[3]; if (Detour.dtStatusSucceed(_navMesh.getOffMeshConnectionPolyEndPoints(prevRef, polyRef, connectionStartPos, connectionEndPos))) { if (nsmoothPath < maxSmoothPathSize) { Detour.dtVcopy(smoothPath, nsmoothPath * 3, connectionStartPos, 0); nsmoothPath++; } // Move position at the other side of the off-mesh link. Detour.dtVcopy(iterPos, connectionEndPos); _navMeshQuery.getPolyHeight(polys[0], iterPos, ref iterPos[1]); iterPos[1] += 0.5f; } } // Store results. if (nsmoothPath < maxSmoothPathSize) { Detour.dtVcopy(smoothPath, nsmoothPath * 3, iterPos, 0); nsmoothPath++; } } smoothPathSize = nsmoothPath; // this is most likely a loop return(nsmoothPath < 74 ? Detour.DT_SUCCESS : Detour.DT_FAILURE); }
public Queue <Position> FindPathBetween(Position start, Position end, bool useStraightPath = false) { var path = new Queue <Position>(); if (dtNavMesh == null) { EasyFarm.ViewModels.LogViewModel.Write("FindPathBetween: Unable to path due to lacking navigation mesh for zone " + _zone.ToString()); return(path); } var startDetourPosition = start.ToDetourPosition(); var endDetourPosition = end.ToDetourPosition(); var queryFilter = new Detour.dtQueryFilter(); var navMeshQuery = new Detour.dtNavMeshQuery(); var status = navMeshQuery.init(dtNavMesh, 256); if (Detour.dtStatusFailed(status)) { return(path); } queryFilter.setIncludeFlags(0xffff); queryFilter.setExcludeFlags(0x0); uint startRef = 0; uint endRef = 0; float[] startNearest = new float[3]; float[] endNearest = new float[3]; float[] extents = new float[] { 10.0F, (float)EasyFarm.UserSettings.Config.Instance.HeightThreshold, 10.0F }; status = navMeshQuery.findNearestPoly(startDetourPosition, extents, queryFilter, ref startRef, ref startNearest); if (Detour.dtStatusFailed(status)) { return(path); } status = navMeshQuery.findNearestPoly(endDetourPosition, extents, queryFilter, ref endRef, ref endNearest); if (Detour.dtStatusFailed(status)) { return(path); } if (!dtNavMesh.isValidPolyRef(startRef) || !dtNavMesh.isValidPolyRef(endRef)) { return(path); } uint[] pathPolys = new uint[256]; int pathCount = 0; status = navMeshQuery.findPath(startRef, endRef, startNearest, endNearest, queryFilter, pathPolys, ref pathCount, 256); if (Detour.dtStatusFailed(status)) { return(path); } if (path.Count < 1) { float[] straightPath = new float[256 * 3]; byte[] straightPathFlags = new byte[256]; uint[] straightPathPolys = new uint[256]; int straightPathCount = 256 * 3; status = navMeshQuery.findStraightPath( startNearest, endNearest, pathPolys, pathCount, straightPath, straightPathFlags, straightPathPolys, ref straightPathCount, 256, 0 ); if (straightPathCount > 1) { if (Detour.dtStatusFailed(status)) { return(path); } path.Clear(); // i starts at 3 so the start position is ignored for (int i = 3; i < straightPathCount * 3;) { float[] pathPos = new float[3]; pathPos[0] = straightPath[i++]; pathPos[1] = straightPath[i++]; pathPos[2] = straightPath[i++]; var position = ToFFXIPosition(pathPos); path.Enqueue(position); } } } else { // i starts at 3 so the start position is ignored for (int i = 1; i < pathCount; i++) { float[] pathPos = new float[3]; bool posOverPoly = false; if (Detour.dtStatusFailed(navMeshQuery.closestPointOnPoly(pathPolys[i], startDetourPosition, pathPos, ref posOverPoly))) { return(path); } if (path.Count < 1) { if (Detour.dtStatusFailed(navMeshQuery.closestPointOnPolyBoundary(pathPolys[i], startDetourPosition, pathPos))) { return(path); } } var position = ToFFXIPosition(pathPos); path.Enqueue(position); } } return(path); }
void BuildPolyPath(Vector3 startPos, Vector3 endPos) { // *** getting start/end poly logic *** float distToStartPoly = 0; float distToEndPoly = 0; float[] startPoint = { startPos.Y, startPos.Z, startPos.X }; float[] endPoint = { endPos.Y, endPos.Z, endPos.X }; ulong startPoly = GetPolyByLocation(startPoint, ref distToStartPoly); ulong endPoly = GetPolyByLocation(endPoint, ref distToEndPoly); // we have a hole in our mesh // make shortcut path and mark it as NOPATH ( with flying and swimming exception ) // its up to caller how he will use this info if (startPoly == 0 || endPoly == 0) { Log.outDebug(LogFilter.Maps, "++ BuildPolyPath . (startPoly == 0 || endPoly == 0)\n"); BuildShortcut(); bool path = _sourceUnit.IsTypeId(TypeId.Unit) && _sourceUnit.ToCreature().CanFly(); bool waterPath = _sourceUnit.IsTypeId(TypeId.Unit) && _sourceUnit.ToCreature().CanSwim(); if (waterPath) { // Check both start and end points, if they're both in water, then we can *safely* let the creature move for (uint i = 0; i < _pathPoints.Length; ++i) { ZLiquidStatus status = _sourceUnit.GetMap().getLiquidStatus(_pathPoints[i].X, _pathPoints[i].Y, _pathPoints[i].Z, MapConst.MapAllLiquidTypes); // One of the points is not in the water, cancel movement. if (status == ZLiquidStatus.NoWater) { waterPath = false; break; } } } pathType = (path || waterPath) ? (PathType.Normal | PathType.NotUsingPath) : PathType.NoPath; return; } // we may need a better number here bool farFromPoly = (distToStartPoly > 7.0f || distToEndPoly > 7.0f); if (farFromPoly) { Log.outDebug(LogFilter.Maps, "++ BuildPolyPath . farFromPoly distToStartPoly={0:F3} distToEndPoly={1:F3}\n", distToStartPoly, distToEndPoly); bool buildShotrcut = false; if (_sourceUnit.IsTypeId(TypeId.Unit)) { Creature owner = _sourceUnit.ToCreature(); Vector3 p = (distToStartPoly > 7.0f) ? startPos : endPos; if (_sourceUnit.GetMap().IsUnderWater(p.X, p.Y, p.Z)) { Log.outDebug(LogFilter.Maps, "++ BuildPolyPath . underWater case\n"); if (owner.CanSwim()) { buildShotrcut = true; } } else { Log.outDebug(LogFilter.Maps, "++ BuildPolyPath . flying case\n"); if (owner.CanFly()) { buildShotrcut = true; } } } if (buildShotrcut) { BuildShortcut(); pathType = (PathType.Normal | PathType.NotUsingPath); return; } else { float[] closestPoint = new float[3]; // we may want to use closestPointOnPolyBoundary instead bool posOverPoly = false; if (Detour.dtStatusSucceed(_navMeshQuery.closestPointOnPoly(endPoly, endPoint, closestPoint, ref posOverPoly))) { Detour.dtVcopy(endPoint, closestPoint); SetActualEndPosition(new Vector3(endPoint[2], endPoint[0], endPoint[1])); } pathType = PathType.Incomplete; } } // *** poly path generating logic *** // start and end are on same polygon // just need to move in straight line if (startPoly == endPoly) { Log.outDebug(LogFilter.Maps, "++ BuildPolyPath . (startPoly == endPoly)\n"); BuildShortcut(); _pathPolyRefs[0] = startPoly; _polyLength = 1; pathType = farFromPoly ? PathType.Incomplete : PathType.Normal; Log.outDebug(LogFilter.Maps, "BuildPolyPath . path type {0}\n", pathType); return; } // look for startPoly/endPoly in current path /// @todo we can merge it with getPathPolyByPosition() loop bool startPolyFound = false; bool endPolyFound = false; uint pathStartIndex = 0; uint pathEndIndex = 0; if (_polyLength != 0) { for (; pathStartIndex < _polyLength; ++pathStartIndex) { // here to carch few bugs if (_pathPolyRefs[pathStartIndex] == 0) { Log.outError(LogFilter.Maps, "Invalid poly ref in BuildPolyPath. _polyLength: {0}, pathStartIndex: {1}," + " startPos: {2}, endPos: {3}, mapid: {4}", _polyLength, pathStartIndex, startPos, endPos, _sourceUnit.GetMapId()); break; } if (_pathPolyRefs[pathStartIndex] == startPoly) { startPolyFound = true; break; } } for (pathEndIndex = _polyLength - 1; pathEndIndex > pathStartIndex; --pathEndIndex) { if (_pathPolyRefs[pathEndIndex] == endPoly) { endPolyFound = true; break; } } } if (startPolyFound && endPolyFound) { Log.outDebug(LogFilter.Maps, "BuildPolyPath : (startPolyFound && endPolyFound)\n"); // we moved along the path and the target did not move out of our old poly-path // our path is a simple subpath case, we have all the data we need // just "cut" it out _polyLength = pathEndIndex - pathStartIndex + 1; Array.Copy(_pathPolyRefs, pathStartIndex, _pathPolyRefs, 0, _polyLength); } else if (startPolyFound && !endPolyFound) { Log.outDebug(LogFilter.Maps, "BuildPolyPath : (startPolyFound && !endPolyFound)\n"); // we are moving on the old path but target moved out // so we have atleast part of poly-path ready _polyLength -= pathStartIndex; // try to adjust the suffix of the path instead of recalculating entire length // at given interval the target cannot get too far from its last location // thus we have less poly to cover // sub-path of optimal path is optimal // take ~80% of the original length /// @todo play with the values here uint prefixPolyLength = (uint)(_polyLength * 0.8f + 0.5f); Array.Copy(_pathPolyRefs, pathStartIndex, _pathPolyRefs, 0, prefixPolyLength); ulong suffixStartPoly = _pathPolyRefs[prefixPolyLength - 1]; // we need any point on our suffix start poly to generate poly-path, so we need last poly in prefix data float[] suffixEndPoint = new float[3]; bool posOverPoly = false; if (Detour.dtStatusFailed(_navMeshQuery.closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, ref posOverPoly))) { // we can hit offmesh connection as last poly - closestPointOnPoly() don't like that // try to recover by using prev polyref --prefixPolyLength; suffixStartPoly = _pathPolyRefs[prefixPolyLength - 1]; if (Detour.dtStatusFailed(_navMeshQuery.closestPointOnPoly(suffixStartPoly, endPoint, suffixEndPoint, ref posOverPoly))) { // suffixStartPoly is still invalid, error state BuildShortcut(); pathType = PathType.NoPath; return; } } // generate suffix uint suffixPolyLength = 0; uint dtResult; if (_straightLine) { float hit = 0; float[] hitNormal = new float[3]; dtResult = _navMeshQuery.raycast( suffixStartPoly, suffixEndPoint, endPoint, _filter, ref hit, hitNormal, _pathPolyRefs, ref suffixPolyLength, 74 - (int)prefixPolyLength); // raycast() sets hit to FLT_MAX if there is a ray between start and end if (hit != float.MaxValue) { // the ray hit something, return no path instead of the incomplete one pathType = PathType.NoPath; return; } } else { dtResult = _navMeshQuery.findPath( suffixStartPoly, // start polygon endPoly, // end polygon suffixEndPoint, // start position endPoint, // end position _filter, // polygon search filter _pathPolyRefs, ref suffixPolyLength, 74 - (int)prefixPolyLength); } if (suffixPolyLength == 0 || Detour.dtStatusFailed(dtResult)) { // this is probably an error state, but we'll leave it // and hopefully recover on the next Update // we still need to copy our preffix Log.outError(LogFilter.Maps, "{0}'s Path Build failed: 0 length path", _sourceUnit.GetGUID().ToString()); } Log.outDebug(LogFilter.Maps, "m_polyLength={0} prefixPolyLength={1} suffixPolyLength={2} \n", _polyLength, prefixPolyLength, suffixPolyLength); // new path = prefix + suffix - overlap _polyLength = prefixPolyLength + suffixPolyLength - 1; } else { Log.outDebug(LogFilter.Maps, "++ BuildPolyPath . (!startPolyFound && !endPolyFound)\n"); // either we have no path at all . first run // or something went really wrong . we aren't moving along the path to the target // just generate new path // free and invalidate old path data Clear(); uint dtResult; if (_straightLine) { float hit = 0; float[] hitNormal = new float[3]; dtResult = _navMeshQuery.raycast( startPoly, startPoint, endPoint, _filter, ref hit, hitNormal, _pathPolyRefs, ref _polyLength, 74); // raycast() sets hit to FLT_MAX if there is a ray between start and end if (hit != float.MaxValue) { // the ray hit something, return no path instead of the incomplete one pathType = PathType.NoPath; return; } } else { dtResult = _navMeshQuery.findPath( startPoly, // start polygon endPoly, // end polygon startPoint, // start position endPoint, // end position _filter, // polygon search filter _pathPolyRefs, // [out] path ref _polyLength, 74); // max number of polygons in output path } if (_polyLength == 0 || Detour.dtStatusFailed(dtResult)) { // only happens if we passed bad data to findPath(), or navmesh is messed up Log.outError(LogFilter.Maps, "{0}'s Path Build failed: 0 length path", _sourceUnit.GetGUID().ToString()); BuildShortcut(); pathType = PathType.NoPath; return; } } // by now we know what type of path we can get if (_pathPolyRefs[_polyLength - 1] == endPoly && !pathType.HasAnyFlag(PathType.Incomplete)) { pathType = PathType.Normal; } else { pathType = PathType.Incomplete; } // generate the point-path out of our up-to-date poly-path BuildPointPath(startPoint, endPoint); }
void BuildPointPath(float[] startPoint, float[] endPoint) { float[] pathPoints = new float[74 * 3]; int pointCount = 0; uint dtResult = Detour.DT_FAILURE; if (_straightLine) { dtResult = Detour.DT_SUCCESS; pointCount = 1; Array.Copy(startPoint, pathPoints, 3); // first point // path has to be split into polygons with dist SMOOTH_PATH_STEP_SIZE between them Vector3 startVec = new Vector3(startPoint[0], startPoint[1], startPoint[2]); Vector3 endVec = new Vector3(endPoint[0], endPoint[1], endPoint[2]); Vector3 diffVec = (endVec - startVec); Vector3 prevVec = startVec; float len = diffVec.GetLength(); diffVec *= 4.0f / len; while (len > 4.0f) { len -= 4.0f; prevVec += diffVec; pathPoints[3 * pointCount + 0] = prevVec.X; pathPoints[3 * pointCount + 1] = prevVec.Y; pathPoints[3 * pointCount + 2] = prevVec.Z; ++pointCount; } Array.Copy(endPoint, 0, pathPoints, 3 * pointCount, 3); // last point ++pointCount; } else if (_useStraightPath) { dtResult = _navMeshQuery.findStraightPath( startPoint, // start position endPoint, // end position _pathPolyRefs, (int)_polyLength, pathPoints, // [out] path corner points null, // [out] flags null, // [out] shortened path ref pointCount, (int)_pointPathLimit, 0); // maximum number of points/polygons to use } else { dtResult = FindSmoothPath( startPoint, // start position endPoint, // end position _pathPolyRefs, // current path _polyLength, // length of current path out pathPoints, // [out] path corner points out pointCount, _pointPathLimit); // maximum number of points } if (pointCount < 2 || Detour.dtStatusFailed(dtResult)) { // only happens if pass bad data to findStraightPath or navmesh is broken // single point paths can be generated here /// @todo check the exact cases Log.outDebug(LogFilter.Maps, "++ PathGenerator.BuildPointPath FAILED! path sized {0} returned\n", pointCount); BuildShortcut(); pathType = PathType.NoPath; return; } else if (pointCount == _pointPathLimit) { Log.outDebug(LogFilter.Maps, "++ PathGenerator.BuildPointPath FAILED! path sized {0} returned, lower than limit set to {1}\n", pointCount, _pointPathLimit); BuildShortcut(); pathType = PathType.Short; return; } _pathPoints = new Vector3[pointCount]; for (uint i = 0; i < pointCount; ++i) { _pathPoints[i] = new Vector3(pathPoints[i * 3 + 2], pathPoints[i * 3], pathPoints[i * 3 + 1]); } NormalizePath(); // first point is always our current location - we need the next one SetActualEndPosition(_pathPoints[pointCount - 1]); // force the given destination, if needed if (_forceDestination && (!pathType.HasAnyFlag(PathType.Normal) || !InRange(GetEndPosition(), GetActualEndPosition(), 1.0f, 1.0f))) { // we may want to keep partial subpath if (Dist3DSqr(GetActualEndPosition(), GetEndPosition()) < 0.3f * Dist3DSqr(GetStartPosition(), GetEndPosition())) { SetActualEndPosition(GetEndPosition()); _pathPoints[_pathPoints.Length - 1] = GetEndPosition(); } else { SetActualEndPosition(GetEndPosition()); BuildShortcut(); } pathType = (PathType.Normal | PathType.NotUsingPath); } Log.outDebug(LogFilter.Maps, "PathGenerator.BuildPointPath path type {0} size {1} poly-size {2}\n", pathType, pointCount, _polyLength); }
public Queue <Position> FindPathBetween(Position start, Position end, bool useStraightPath = false) { var path = new Queue <Position>(); if (dtNavMesh == null) { return(path); } var startDetourPosition = start.ToDetourPosition(); var endDetourPosition = end.ToDetourPosition(); var queryFilter = new Detour.dtQueryFilter(); var navMeshQuery = new Detour.dtNavMeshQuery(); var status = navMeshQuery.init(dtNavMesh, MAX_PATH); if (Detour.dtStatusFailed(status)) { return(path); } queryFilter.setIncludeFlags(0xffff); queryFilter.setExcludeFlags(0x0); uint startRef = 0; uint endRef = 0; float[] startNearest = new float[3]; float[] endNearest = new float[3]; float[] extents = new float[] { 10.0F, 25.0F, 10.0F }; status = navMeshQuery.findNearestPoly(startDetourPosition, extents, queryFilter, ref startRef, ref startNearest); if (Detour.dtStatusFailed(status)) { return(path); } status = navMeshQuery.findNearestPoly(endDetourPosition, extents, queryFilter, ref endRef, ref endNearest); if (Detour.dtStatusFailed(status)) { return(path); } if (!dtNavMesh.isValidPolyRef(startRef) || !dtNavMesh.isValidPolyRef(endRef)) { return(path); } uint[] pathPolys = new uint[MAX_PATH]; int pathCount = 0; float[] straightPath = new float[MAX_PATH * 3]; byte[] straightPathFlags = new byte[MAX_PATH]; uint[] straightPathPolys = new uint[MAX_PATH]; int straightPathCount = 0; status = navMeshQuery.findPath( startRef, endRef, startNearest, endNearest, queryFilter, pathPolys, ref pathCount, MAX_PATH ); if (Detour.dtStatusFailed(status)) { path.Enqueue(start); path.Enqueue(end); return(path); } status = navMeshQuery.findStraightPath( startNearest, endNearest, pathPolys, pathCount, straightPath, straightPathFlags, straightPathPolys, ref straightPathCount, MAX_PATH, (int)Detour.dtStraightPathOptions.DT_STRAIGHTPATH_ALL_CROSSINGS ); if (Detour.dtStatusFailed(status)) { path.Enqueue(start); path.Enqueue(end); return(path); } if (straightPathCount > 0) { if (Detour.dtStatusFailed(status)) { return(path); } for (int i = 3; i < straightPathCount * 3;) { float[] pathPos = new float[3]; pathPos[0] = straightPath[i++]; pathPos[1] = straightPath[i++]; pathPos[2] = straightPath[i++]; var position = ToFFXIPosition(pathPos); path.Enqueue(position); } } else { for (int i = 1; i < pathCount; i++) { float[] pathPos = new float[3]; bool posOverPoly = false; if (Detour.dtStatusFailed(navMeshQuery.closestPointOnPoly(pathPolys[i], startDetourPosition, pathPos, ref posOverPoly))) { return(path); } if (path.Count < 1) { if (Detour.dtStatusFailed(navMeshQuery.closestPointOnPolyBoundary(pathPolys[i], startDetourPosition, pathPos))) { return(path); } } var position = ToFFXIPosition(pathPos); path.Enqueue(position); } } if (path.Count < 1) { path.Enqueue(end); } return(path); }
public bool LoadZone(Zone zone) { if (zone == Zone.Unknown) { Unload(); return(false); } string path = "navmeshes\\" + zone.ToString() + ".nav"; if (_zone == zone && dtNavMesh != null) { return(true); } else { Unload(); } _zone = zone; var headerBufferSize = NavMeshSetHeader.ByteSize(); var headerBuffer = new byte[headerBufferSize]; if (!File.Exists(path)) { return(false); } var file = File.OpenRead(path); file.Read(headerBuffer, 0, headerBufferSize); NavMeshSetHeader header = new NavMeshSetHeader(); var headerBytesRead = header.FromBytes(headerBuffer, 0); if (header.magic != NAVMESHSET_MAGIC) { return(false); } if (header.version != NAVMESHSET_VERSION) { return(false); } var navMesh = new Detour.dtNavMesh(); navMesh.init(header.meshParams); for (int i = 0; i < header.numTiles; ++i) { var tileHeaderBuffer = new byte[NavMeshTileHeader.ByteSize()]; file.Read(tileHeaderBuffer, 0, tileHeaderBuffer.Length); var tileHeader = new NavMeshTileHeader(); tileHeader.FromBytes(tileHeaderBuffer, 0); if (tileHeader.dataSize == 0 || tileHeader.tileRef == 0) { break; } var rawTileData = new Detour.dtRawTileData(); var data = new byte[tileHeader.dataSize]; file.Read(data, 0, data.Length); rawTileData.FromBytes(data, 0); uint result = 0; navMesh.addTile(rawTileData, tileHeader.dataSize, 0x01 /*DT_TILE_FREE_DATA*/, tileHeader.tileRef, ref result); if (Detour.dtStatusFailed(result)) { return(false); } } // hard-code to make sure it is compatible with expectation. var maxPolys = header.meshParams.maxPolys; var status = new Detour.dtNavMeshQuery().init(navMesh, maxPolys); if (Detour.dtStatusFailed(status)) { return(false); } dtNavMesh = navMesh; return(headerBytesRead > 0); }
void BuildPointPath(float[] startPoint, float[] endPoint) { float[] pathPoints = new float[74 * 3]; int pointCount = 0; uint dtResult; if (_useRaycast) { // _straightLine uses raycast and it currently doesn't support building a point path, only a 2-point path with start and hitpoint/end is returned Log.outError(LogFilter.Maps, $"PathGenerator::BuildPointPath() called with _useRaycast for unit {_source.GetGUID()}"); BuildShortcut(); pathType = PathType.NoPath; return; } else if (_useStraightPath) { dtResult = _navMeshQuery.findStraightPath( startPoint, // start position endPoint, // end position _pathPolyRefs, (int)_polyLength, pathPoints, // [out] path corner points null, // [out] flags null, // [out] shortened path ref pointCount, (int)_pointPathLimit, 0); // maximum number of points/polygons to use } else { dtResult = FindSmoothPath( startPoint, // start position endPoint, // end position _pathPolyRefs, // current path _polyLength, // length of current path out pathPoints, // [out] path corner points out pointCount, _pointPathLimit); // maximum number of points } // Special case with start and end positions very close to each other if (_polyLength == 1 && pointCount == 1) { // First point is start position, append end position Detour.dtVcopy(pathPoints, 1 * 3, endPoint, 0); pointCount++; } else if (pointCount < 2 || Detour.dtStatusFailed(dtResult)) { // only happens if pass bad data to findStraightPath or navmesh is broken // single point paths can be generated here // @todo check the exact cases Log.outDebug(LogFilter.Maps, "++ PathGenerator.BuildPointPath FAILED! path sized {0} returned\n", pointCount); BuildShortcut(); pathType |= PathType.NoPath; return; } else if (pointCount == _pointPathLimit) { Log.outDebug(LogFilter.Maps, "++ PathGenerator.BuildPointPath FAILED! path sized {0} returned, lower than limit set to {1}\n", pointCount, _pointPathLimit); BuildShortcut(); pathType |= PathType.Short; return; } _pathPoints = new Vector3[pointCount]; for (uint i = 0; i < pointCount; ++i) { _pathPoints[i] = new Vector3(pathPoints[i * 3 + 2], pathPoints[i * 3], pathPoints[i * 3 + 1]); } NormalizePath(); // first point is always our current location - we need the next one SetActualEndPosition(_pathPoints[pointCount - 1]); // force the given destination, if needed if (_forceDestination && (!pathType.HasAnyFlag(PathType.Normal) || !InRange(GetEndPosition(), GetActualEndPosition(), 1.0f, 1.0f))) { // we may want to keep partial subpath if (Dist3DSqr(GetActualEndPosition(), GetEndPosition()) < 0.3f * Dist3DSqr(GetStartPosition(), GetEndPosition())) { SetActualEndPosition(GetEndPosition()); _pathPoints[^ 1] = GetEndPosition();
//Compute Recast and Detour navmesh public bool ComputeSystem() { ClearComputedData(); Recast.rcCalcBounds(m_verts, m_vertCount, m_bmin, m_bmax); // // Step 1. Initialize build config. // // Init build configuration from GUI m_cfg = new Recast.rcConfig(); m_cfg.cs = m_RecastMeshParams.m_cellSize; m_cfg.ch = m_RecastMeshParams.m_cellHeight; m_cfg.walkableSlopeAngle = m_RecastMeshParams.m_agentMaxSlope; m_cfg.walkableHeight = (int)Math.Ceiling(m_RecastMeshParams.m_agentHeight / m_cfg.ch); m_cfg.walkableClimb = (int)Math.Floor(m_RecastMeshParams.m_agentMaxClimb / m_cfg.ch); m_cfg.walkableRadius = (int)Math.Ceiling(m_RecastMeshParams.m_agentRadius / m_cfg.cs); m_cfg.maxEdgeLen = (int)(m_RecastMeshParams.m_edgeMaxLen / m_RecastMeshParams.m_cellSize); m_cfg.maxSimplificationError = m_RecastMeshParams.m_edgeMaxError; m_cfg.minRegionArea = (int)(m_RecastMeshParams.m_regionMinSize * m_RecastMeshParams.m_regionMinSize); // Note: area = size*size m_cfg.mergeRegionArea = (int)(m_RecastMeshParams.m_regionMergeSize * m_RecastMeshParams.m_regionMergeSize); // Note: area = size*size m_cfg.maxVertsPerPoly = (int)m_RecastMeshParams.m_vertsPerPoly; m_cfg.detailSampleDist = m_RecastMeshParams.m_detailSampleDist < 0.9f ? 0 : m_RecastMeshParams.m_cellSize * m_RecastMeshParams.m_detailSampleDist; m_cfg.detailSampleMaxError = m_RecastMeshParams.m_cellHeight * m_RecastMeshParams.m_detailSampleMaxError; // Set the area where the navigation will be build. // Here the bounds of the input mesh are used, but the // area could be specified by an user defined box, etc. Recast.rcVcopy(m_cfg.bmin, m_bmin); Recast.rcVcopy(m_cfg.bmax, m_bmax); Recast.rcCalcGridSize(m_cfg.bmin, m_cfg.bmax, m_cfg.cs, out m_cfg.width, out m_cfg.height); // Reset build times gathering. m_ctx.resetTimers(); // Start the build process. m_ctx.startTimer(Recast.rcTimerLabel.RC_TIMER_TOTAL); m_ctx.log(Recast.rcLogCategory.RC_LOG_PROGRESS, "Building navigation:"); m_ctx.log(Recast.rcLogCategory.RC_LOG_PROGRESS, " - " + m_cfg.width + " x " + m_cfg.height + " cells"); m_ctx.log(Recast.rcLogCategory.RC_LOG_PROGRESS, " - " + m_vertCount / 1000.0f + "K verts, " + m_triCount / 1000.0f + "K tris"); // // Step 2. Rasterize input polygon soup. // // Allocate voxel heightfield where we rasterize our input data to. m_solid = new Recast.rcHeightfield(); if (m_solid == null) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Out of memory 'solid'."); return(false); } if (!Recast.rcCreateHeightfield(m_ctx, m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Could not create solid heightfield."); return(false); } // Allocate array that can hold triangle area types. // If you have multiple meshes you need to process, allocate // and array which can hold the max number of triangles you need to process. m_triareas = new byte[m_triCount]; if (m_triareas == null) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Out of memory 'm_triareas' (" + m_triCount + ")."); return(false); } // Find triangles which are walkable based on their slope and rasterize them. // If your input data is multiple meshes, you can transform them here, calculate // the are type for each of the meshes and rasterize them. //memset(m_triareas, 0, ntris*sizeof(byte)); Recast.rcMarkWalkableTriangles(m_ctx, m_cfg.walkableSlopeAngle, m_verts, m_vertCount, m_tris, m_triCount, m_triareas); Recast.rcRasterizeTriangles(m_ctx, m_verts, m_vertCount, m_tris, m_triareas, m_triCount, m_solid, m_cfg.walkableClimb); if (!m_keepInterResults) { m_triareas = null; } // // Step 3. Filter walkables surfaces. // // Once all geoemtry is rasterized, we do initial pass of filtering to // remove unwanted overhangs caused by the conservative rasterization // as well as filter spans where the character cannot possibly stand. Recast.rcFilterLowHangingWalkableObstacles(m_ctx, m_cfg.walkableClimb, m_solid); Recast.rcFilterLedgeSpans(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, m_solid); Recast.rcFilterWalkableLowHeightSpans(m_ctx, m_cfg.walkableHeight, m_solid); // // Step 4. Partition walkable surface to simple regions. // // Compact the heightfield so that it is faster to handle from now on. // This will result more cache coherent data as well as the neighbours // between walkable cells will be calculated. m_chf = new Recast.rcCompactHeightfield(); if (m_chf == null) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'."); return(false); } if (!Recast.rcBuildCompactHeightfield(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, m_solid, m_chf)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Could not build compact data."); return(false); } if (!m_keepInterResults) { m_solid = null; } // Erode the walkable area by agent radius. if (!Recast.rcErodeWalkableArea(m_ctx, m_cfg.walkableRadius, m_chf)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Could not erode."); return(false); } /* * // (Optional) Mark areas. * ConvexVolume[] vols = m_geom.getConvexVolumes(); * for (int i = 0; i < m_geom.getConvexVolumeCount(); ++i) * rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (byte)vols[i].area, *m_chf); */ if (m_RecastMeshParams.m_monotonePartitioning) { // Partition the walkable surface into simple regions without holes. // Monotone partitioning does not need distancefield. if (!Recast.rcBuildRegionsMonotone(m_ctx, m_chf, 0, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Could not build regions."); return(false); } } else { // Prepare for region partitioning, by calculating distance field along the walkable surface. if (!Recast.rcBuildDistanceField(m_ctx, m_chf)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Could not build distance field."); return(false); } // Partition the walkable surface into simple regions without holes. if (!Recast.rcBuildRegions(m_ctx, m_chf, 0, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Could not build regions."); return(false); } } // // Step 5. Trace and simplify region contours. // // Create contours. m_cset = new Recast.rcContourSet(); if (m_cset == null) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'."); return(false); } if (!Recast.rcBuildContours(m_ctx, m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, m_cset, -1)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Could not create contours."); return(false); } //m_cset.dumpToTxt("Data/CSET_dump.txt"); // // Step 6. Build polygons mesh from contours. // // Build polygon navmesh from the contours. m_pmesh = new Recast.rcPolyMesh(); if (m_pmesh == null) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'."); return(false); } if (!Recast.rcBuildPolyMesh(m_ctx, m_cset, m_cfg.maxVertsPerPoly, m_pmesh)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Could not triangulate contours."); return(false); } //m_pmesh.dumpToObj("Data/navmesh.obj"); //m_pmesh.dumpToText("Data/navmesh.txt"); // // Step 7. Create detail mesh which allows to access approximate height on each polygon. // m_dmesh = new Recast.rcPolyMeshDetail(); //rcAllocPolyMeshDetail(); if (m_dmesh == null) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Out of memory 'pmdtl'."); return(false); } if (!Recast.rcBuildPolyMeshDetail(m_ctx, m_pmesh, m_chf, m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, m_dmesh)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "buildNavigation: Could not build detail mesh."); return(false); } //m_dmesh.dumpToText("Data/polymeshdetail_cs.txt"); //m_dmesh.dumpToObj("Data/polymeshdetail_cs.obj"); if (!m_keepInterResults) { m_chf = null; m_cset = null; } // At this point the navigation mesh data is ready, you can access it from m_pmesh. // See duDebugDrawPolyMesh or dtCreateNavMeshData as examples how to access the data. // // (Optional) Step 8. Create Detour data from Recast poly mesh. // // The GUI may allow more max points per polygon than Detour can handle. // Only build the detour navmesh if we do not exceed the limit. if (m_cfg.maxVertsPerPoly <= Detour.DT_VERTS_PER_POLYGON) { //unsigned char* navData = 0; Detour.dtRawTileData navData = null; //int navDataSize = 0; // Update poly flags from areas. for (int i = 0; i < m_pmesh.npolys; ++i) { if (m_pmesh.areas[i] == Recast.RC_WALKABLE_AREA) { m_pmesh.areas[i] = (byte)SamplePolyAreas.GROUND; } if (m_pmesh.areas[i] == (byte)SamplePolyAreas.GROUND) { m_pmesh.flags[i] = (ushort)SamplePolyFlags.WALK; } /* * if (m_pmesh.areas[i] == Recast.RC_WALKABLE_AREA) * m_pmesh.areas[i] = SAMPLE_POLYAREA_GROUND; * * if (m_pmesh.areas[i] == SAMPLE_POLYAREA_GROUND || * m_pmesh.areas[i] == SAMPLE_POLYAREA_GRASS || * m_pmesh.areas[i] == SAMPLE_POLYAREA_ROAD) * { * m_pmesh.flags[i] = SAMPLE_POLYFLAGS_WALK; * } * else if (m_pmesh.areas[i] == SAMPLE_POLYAREA_WATER) * { * m_pmesh.flags[i] = SAMPLE_POLYFLAGS_SWIM; * } * else if (m_pmesh.areas[i] == SAMPLE_POLYAREA_DOOR) * { * m_pmesh.flags[i] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR; * }*/ } Detour.dtNavMeshCreateParams navMeshCreateParams = new Detour.dtNavMeshCreateParams(); navMeshCreateParams.verts = m_pmesh.verts; navMeshCreateParams.vertCount = m_pmesh.nverts; navMeshCreateParams.polys = m_pmesh.polys; navMeshCreateParams.polyAreas = m_pmesh.areas; navMeshCreateParams.polyFlags = m_pmesh.flags; navMeshCreateParams.polyCount = m_pmesh.npolys; navMeshCreateParams.nvp = m_pmesh.nvp; navMeshCreateParams.detailMeshes = m_dmesh.meshes; navMeshCreateParams.detailVerts = m_dmesh.verts; navMeshCreateParams.detailVertsCount = m_dmesh.nverts; navMeshCreateParams.detailTris = m_dmesh.tris; navMeshCreateParams.detailTriCount = m_dmesh.ntris; navMeshCreateParams.offMeshConVerts = null; //m_geom.getOffMeshConnectionVerts(); navMeshCreateParams.offMeshConRad = null; //m_geom.getOffMeshConnectionRads(); navMeshCreateParams.offMeshConDir = null; //m_geom.getOffMeshConnectionDirs(); navMeshCreateParams.offMeshConAreas = null; //m_geom.getOffMeshConnectionAreas(); navMeshCreateParams.offMeshConFlags = null; //m_geom.getOffMeshConnectionFlags(); navMeshCreateParams.offMeshConUserID = null; //m_geom.getOffMeshConnectionId(); navMeshCreateParams.offMeshConCount = 0; //m_geom.getOffMeshConnectionCount(); navMeshCreateParams.walkableHeight = m_RecastMeshParams.m_agentHeight; navMeshCreateParams.walkableRadius = m_RecastMeshParams.m_agentRadius; navMeshCreateParams.walkableClimb = m_RecastMeshParams.m_agentMaxClimb; Recast.rcVcopy(navMeshCreateParams.bmin, m_pmesh.bmin); Recast.rcVcopy(navMeshCreateParams.bmax, m_pmesh.bmax); navMeshCreateParams.cs = m_cfg.cs; navMeshCreateParams.ch = m_cfg.ch; navMeshCreateParams.buildBvTree = true; if (!Detour.dtCreateNavMeshData(navMeshCreateParams, out navData)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "Could not build Detour navmesh."); return(false); } m_navMesh = new Detour.dtNavMesh(); if (m_navMesh == null) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "Could not create Detour navmesh"); return(false); } dtStatus status; status = m_navMesh.init(navData, (int)Detour.dtTileFlags.DT_TILE_FREE_DATA); if (Detour.dtStatusFailed(status)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "Could not init Detour navmesh"); return(false); } m_navQuery = new Detour.dtNavMeshQuery(); status = m_navQuery.init(m_navMesh, 2048); if (Detour.dtStatusFailed(status)) { m_ctx.log(Recast.rcLogCategory.RC_LOG_ERROR, "Could not init Detour navmesh query"); return(false); } m_rawTileData = navData; } else { m_ctx.log(Recast.rcLogCategory.RC_LOG_WARNING, "Detour does not support more than " + Detour.DT_VERTS_PER_POLYGON + " verts per polygon. A navmesh has not been generated."); } m_ctx.stopTimer(Recast.rcTimerLabel.RC_TIMER_TOTAL); // Show performance stats. m_ctx.logBuildTimes(); m_ctx.log(Recast.rcLogCategory.RC_LOG_PROGRESS, ">> Polymesh: " + m_pmesh.nverts + " vertices " + m_pmesh.npolys + " polygons"); m_totalBuildTimeMs = (float)m_ctx.getAccumulatedTime(Recast.rcTimerLabel.RC_TIMER_TOTAL); return(true); }
static bool LocCommand(StringArguments args, CommandHandler handler) { handler.SendSysMessage("mmap tileloc:"); // grid tile location Player player = handler.GetPlayer(); int gx = (int)(32 - player.GetPositionX() / MapConst.SizeofGrids); int gy = (int)(32 - player.GetPositionY() / MapConst.SizeofGrids); float x, y, z; player.GetPosition(out x, out y, out z); handler.SendSysMessage("{0:D4}{1:D2}{2:D2}.mmtile", player.GetMapId(), gy, gx); handler.SendSysMessage("gridloc [{0}, {1}]", gx, gy); // calculate navmesh tile location uint terrainMapId = PhasingHandler.GetTerrainMapId(player.GetPhaseShift(), player.GetMap(), x, y); Detour.dtNavMesh navmesh = Global.MMapMgr.GetNavMesh(terrainMapId); Detour.dtNavMeshQuery navmeshquery = Global.MMapMgr.GetNavMeshQuery(terrainMapId, player.GetInstanceId()); if (navmesh == null || navmeshquery == null) { handler.SendSysMessage("NavMesh not loaded for current map."); return(true); } float[] min = navmesh.getParams().orig; float[] location = { y, z, x }; float[] extents = { 3.0f, 5.0f, 3.0f }; int tilex = (int)((y - min[0]) / MapConst.SizeofGrids); int tiley = (int)((x - min[2]) / MapConst.SizeofGrids); handler.SendSysMessage("Calc [{0:D2}, {1:D2}]", tilex, tiley); // navmesh poly . navmesh tile location Detour.dtQueryFilter filter = new Detour.dtQueryFilter(); float[] nothing = new float[3]; ulong polyRef = 0; if (Detour.dtStatusFailed(navmeshquery.findNearestPoly(location, extents, filter, ref polyRef, ref nothing))) { handler.SendSysMessage("Dt [??,??] (invalid poly, probably no tile loaded)"); return(true); } if (polyRef == 0) { handler.SendSysMessage("Dt [??, ??] (invalid poly, probably no tile loaded)"); } else { Detour.dtMeshTile tile = new Detour.dtMeshTile(); Detour.dtPoly poly = new Detour.dtPoly(); if (Detour.dtStatusSucceed(navmesh.getTileAndPolyByRef(polyRef, ref tile, ref poly))) { if (tile != null) { handler.SendSysMessage("Dt [{0:D2},{1:D2}]", tile.header.x, tile.header.y); return(false); } } handler.SendSysMessage("Dt [??,??] (no tile loaded)"); } return(true); }