Esempio n. 1
0
        public byte[] Build(BaseLog log)
        {
            Log      = log;
            Geometry = new Geometry {
                Transform = true
            };

            {
                var main = GetAdt(World, X, Y);
                Geometry.AddAdt(main);
            }

            if (Geometry.Vertices.Count == 0 && Geometry.Triangles.Count == 0)
            {
                throw new InvalidOperationException("Can't build tile with empty geometry");
            }

            float[] bbMin, bbMax;
            CalculateTileBounds(out bbMin, out bbMax);
            Geometry.CalculateMinMaxHeight(out bbMin[1], out bbMax[1]);

            // again, we load everything - wasteful but who cares
            for (int ty = Y - 1; ty <= Y + 1; ty++)
            {
                for (int tx = X - 1; tx <= X + 1; tx++)
                {
                    try
                    {
                        // don't load main tile again
                        if (tx == X && ty == Y)
                        {
                            continue;
                        }

                        var adt = GetAdt(World, tx, ty);
                        Geometry.AddAdt(adt);
                    }
                    catch (FileNotFoundException)
                    {
                        // don't care - no file means no geometry
                    }
                }
            }

            Context = new RecastContext();
            Context.SetContextHandler(Log);

            // get raw geometry - lots of slowness here
            float[] vertices;
            int[]   triangles;
            byte[]  areas;
            Geometry.GetRawData(out vertices, out triangles, out areas);
            Geometry.Triangles.Clear();
            Geometry.Vertices.Clear();

            // add border
            bbMin[0] -= Config.BorderSize * Config.CellSize;
            bbMin[2] -= Config.BorderSize * Config.CellSize;
            bbMax[0] += Config.BorderSize * Config.CellSize;
            bbMax[2] += Config.BorderSize * Config.CellSize;

            Heightfield hf;
            int         width = Config.TileWidth + (Config.BorderSize * 2);

            if (!Context.CreateHeightfield(out hf, width, width, bbMin, bbMax, Config.CellSize, Config.CellHeight))
            {
                throw new OutOfMemoryException("CreateHeightfield ran out of memory");
            }

            Context.ClearUnwalkableTriangles(Config.WalkableSlopeAngle, ref vertices, ref triangles, areas);
            Context.RasterizeTriangles(ref vertices, ref triangles, ref areas, hf, Config.WalkableClimb);

            // Once all geometry 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.
            Context.FilterLowHangingWalkableObstacles(Config.WalkableClimb, hf);
            Context.FilterLedgeSpans(Config.WalkableHeight, Config.WalkableClimb, hf);
            Context.FilterWalkableLowHeightSpans(Config.WalkableHeight, hf);

            // Compact the heightfield so that it is faster to handle from now on.
            // This will result in more cache coherent data as well as the neighbours
            // between walkable cells will be calculated.
            CompactHeightfield chf;

            if (!Context.BuildCompactHeightfield(Config.WalkableHeight, Config.WalkableClimb, hf, out chf))
            {
                throw new OutOfMemoryException("BuildCompactHeightfield ran out of memory");
            }

            hf.Delete();

            // Erode the walkable area by agent radius.
            if (!Context.ErodeWalkableArea(Config.WalkableRadius, chf))
            {
                throw new OutOfMemoryException("ErodeWalkableArea ran out of memory");
            }

            // Prepare for region partitioning, by calculating distance field along the walkable surface.
            if (!Context.BuildDistanceField(chf))
            {
                throw new OutOfMemoryException("BuildDistanceField ran out of memory");
            }

            // Partition the walkable surface into simple regions without holes.
            if (!Context.BuildRegions(chf, Config.BorderSize, Config.MinRegionArea, Config.MergeRegionArea))
            {
                throw new OutOfMemoryException("BuildRegionsMonotone ran out of memory");
            }

            // Create contours.
            ContourSet cset;

            if (!Context.BuildContours(chf, Config.MaxSimplificationError, Config.MaxEdgeLength, out cset))
            {
                throw new OutOfMemoryException("BuildContours ran out of memory");
            }

            // Build polygon navmesh from the contours.
            PolyMesh pmesh;

            if (!Context.BuildPolyMesh(cset, Config.MaxVertsPerPoly, out pmesh))
            {
                throw new OutOfMemoryException("BuildPolyMesh ran out of memory");
            }

            // Build detail mesh.
            PolyMeshDetail dmesh;

            if (
                !Context.BuildPolyMeshDetail(pmesh, chf, Config.DetailSampleDistance, Config.DetailSampleMaxError,
                                             out dmesh))
            {
                throw new OutOfMemoryException("BuildPolyMeshDetail ran out of memory");
            }

            chf.Delete();
            cset.Delete();

            // Remove padding from the polymesh data. (Remove this odditity)
            pmesh.RemovePadding(Config.BorderSize);

            // Set flags according to area types (e.g. Swim for Water)
            pmesh.MarkAll();

            // get original bounds
            float[] tilebMin, tilebMax;
            CalculateTileBounds(out tilebMin, out tilebMax);
            tilebMin[1] = bbMin[1];
            tilebMax[1] = bbMax[1];

            // build off mesh connections for flightmasters
            // bMax and bMin are switched here because of the coordinate system transformation
            /// This is not needed for our particular use.
            var connections = new List <OffMeshConnection>();

            /*var taxis = TaxiHelper.GetNodesInBBox(MapId, tilebMax.ToWoW(), tilebMin.ToWoW());
             * foreach (var taxi in taxis)
             * {
             *  Log.Log(LogCategory.Warning,
             *          "Flightmaster \"" + taxi.Name + "\", Id: " + taxi.Id + " Horde: " + taxi.IsHorde + " Alliance: " +
             *          taxi.IsAlliance);
             *
             *  var data = TaxiHelper.GetTaxiData(taxi);
             *  var from = taxi.Location.ToRecast().ToFloatArray();
             *  connections.AddRange(data.To.Select(to => new OffMeshConnection
             *                                                {
             *                                                    AreaId = PolyArea.Road,
             *                                                    Flags = PolyFlag.FlightMaster,
             *                                                    From = from,
             *                                                    To = to.Value.Location.ToRecast().ToFloatArray(),
             *                                                    Radius = Config.WorldWalkableRadius,
             *                                                    Type = ConnectionType.OneWay,
             *                                                    UserID = (uint) to.Key
             *                                                }));
             *
             *  foreach (var target in data.To)
             *      Log.Log(LogCategory.Warning,
             *              "\tPath to: \"" + target.Value.Name + "\" Id: " + target.Value.Id + " Path Id: " +
             *              target.Key);
             * }*/

            byte[] tileData;
            if (!Detour.CreateNavMeshData(out tileData, pmesh, dmesh,
                                          X, Y, tilebMin, tilebMax,
                                          Config.WorldWalkableHeight, Config.WorldWalkableRadius,
                                          Config.WorldWalkableClimb, Config.CellSize,
                                          Config.CellHeight, Config.TileWidth,
                                          connections.ToArray()))
            {
                pmesh.Delete();
                dmesh.Delete();
                return(null);
            }

            pmesh.Delete();
            dmesh.Delete();
            Cache.Clear();
            GC.Collect();
            return(tileData);
        }
Esempio n. 2
0
        public byte[] Build(int i = 0, int j = 0)
        {
            float[] bbMin, bbMax;
            CalculateTileBounds(out bbMin, out bbMax, false, i, j);

#if (TIMETHIS)
            System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
            stopWatch.Start();
            long cur = 0;
            #endif
            // add border
            bbMin[0] -= Config.BorderSize * Config.CellSize;
            bbMin[2] -= Config.BorderSize * Config.CellSize;
            bbMax[0] += Config.BorderSize * Config.CellSize;
            bbMax[2] += Config.BorderSize * Config.CellSize;

            // get raw geometry - lots of slowness here
            float[] vertices;
            int[]   triangles;
            byte[]  areas;

            Geometry.GetRawData(out vertices, out triangles, out areas);
#if (TIMETHIS)
            Console.WriteLine("GetRawData: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            cur = stopWatch.ElapsedMilliseconds;
#endif
            // Following code would check if we have Index out of range error while computing the sum. the result itself is useless.

            /*float sum = 0;
             * foreach (int i2 in triangles)
             *  sum += vertices[i2*3+0] + vertices[i2*3+1] + vertices[i2*3+2];
             * Console.WriteLine(sum);*/

            // now we can find the min/max height for THIS tile
            float MinHeight, MaxHeight;
            Geometry.CalculateMinMaxHeight(out MinHeight, out MaxHeight, bbMin, bbMax);
            //Geometry.Triangles.Clear(); // We should not clear triangles and stuff because we have severals files for one tile.
            //Geometry.Vertices.Clear(); // It will be reset at the next tile anyway, no memory issues.
            bbMin[1] = MinHeight;
            bbMax[1] = MaxHeight;

            Heightfield hf;
            int         width = Config.TileWidth + (Config.BorderSize * 2);
            if (!Context.CreateHeightfield(out hf, width, width, bbMin, bbMax, Config.CellSize, Config.CellHeight))
            {
                throw new OutOfMemoryException("CreateHeightfield ran out of memory");
            }

#if (TIMETHIS)
            Console.WriteLine("CreateHeightfield: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            cur = stopWatch.ElapsedMilliseconds;
            #endif

            if (triangles.Count() > 0)
            {
                /*Console.WriteLine("Context.ClearUnwalkableTriangles: verticles: " + vertices.Length + ", triangles: " + triangles.Length + ", areas: " + areas.Length);
                 * Console.WriteLine("Memory allocated GC: " + GC.GetTotalMemory(true));/*/
                Context.ClearUnwalkableTriangles(Config.WalkableSlopeAngle, ref vertices, ref triangles, areas);
#if (TIMETHIS)
                Console.WriteLine("ClearUnwalkableTriangles: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
                cur = stopWatch.ElapsedMilliseconds;
                #endif
                Context.RasterizeTriangles(ref vertices, ref triangles, ref areas, hf, Config.WalkableClimb);
#if (TIMETHIS)
                Console.WriteLine("RasterizeTriangles: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
                cur = stopWatch.ElapsedMilliseconds;
                #endif

                GC.KeepAlive(vertices); // force C# to keep vertices, triangles and areas alive while it's in unamanaged code.
                GC.KeepAlive(triangles);
                GC.KeepAlive(areas);
            }
            vertices  = null;
            triangles = null;
            areas     = null;
            GC.Collect();

            // Once all geometry 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.
            Context.FilterLowHangingWalkableObstacles(Config.WalkableClimb, hf);
#if (TIMETHIS)
            Console.WriteLine("FilterLowHangingWalkableObstacles: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            cur = stopWatch.ElapsedMilliseconds;
            #endif
            Context.FilterLedgeSpans(Config.WalkableHeight, Config.WalkableClimb, hf);
#if (TIMETHIS)
            Console.WriteLine("FilterLedgeSpans: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            cur = stopWatch.ElapsedMilliseconds;
            #endif
            Context.FilterWalkableLowHeightSpans(Config.WalkableHeight, hf);
#if (TIMETHIS)
            Console.WriteLine("FilterWalkableLowHeightSpans: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            cur = stopWatch.ElapsedMilliseconds;
            #endif

            // Rasterize once again after the cleanup we did
            //Context.RasterizeTriangles(ref vertices, ref triangles, ref areas, hf, Config.WalkableClimb);
            //#if (TIMETHIS)
            //    Console.WriteLine("RasterizeTriangles: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            //    cur = stopWatch.ElapsedMilliseconds;
            //#endif

            // Compact the heightfield so that it is faster to handle from now on.
            // This will result in more cache coherent data as well as the neighbours
            // between walkable cells will be calculated.
            CompactHeightfield chf;
            if (!Context.BuildCompactHeightfield(Config.WalkableHeight, Config.WalkableClimb, hf, out chf))
            {
                throw new OutOfMemoryException("BuildCompactHeightfield ran out of memory");
            }
#if (TIMETHIS)
            Console.WriteLine("BuildCompactHeightfield: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            cur = stopWatch.ElapsedMilliseconds;
            #endif
            hf.Delete();

            // Erode the walkable area by agent radius.
            if (!Context.ErodeWalkableArea(Config.WalkableRadius, chf))
            {
                throw new OutOfMemoryException("ErodeWalkableArea ran out of memory");
            }
#if (TIMETHIS)
            Console.WriteLine("ErodeWalkableArea: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            cur = stopWatch.ElapsedMilliseconds;
            #endif

            // Prepare for region partitioning, by calculating distance field along the walkable surface.
            if (!Context.BuildDistanceField(chf))
            {
                throw new OutOfMemoryException("BuildDistanceField ran out of memory");
            }
#if (TIMETHIS)
            Console.WriteLine("BuildDistanceField: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            cur = stopWatch.ElapsedMilliseconds;
            #endif

            // Partition the walkable surface into simple regions without holes.
            if (!Context.BuildRegions(chf, Config.BorderSize, Config.MinRegionArea, Config.MergeRegionArea))
            {
                throw new OutOfMemoryException("BuildRegions ran out of memory");
            }
#if (TIMETHIS)
            Console.WriteLine("BuildRegions: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            cur = stopWatch.ElapsedMilliseconds;
            #endif

            // Create contours.
            ContourSet cset;
            if (!Context.BuildContours(chf, Config.MaxSimplificationError, Config.MaxEdgeLength, out cset))
            {
                throw new OutOfMemoryException("BuildContours ran out of memory");
            }
#if (TIMETHIS)
            Console.WriteLine("BuildContours: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            cur = stopWatch.ElapsedMilliseconds;
            #endif

            // Build polygon navmesh from the contours.
            PolyMesh pmesh;
            if (!Context.BuildPolyMesh(cset, Config.MaxVertsPerPoly, out pmesh))
            {
                throw new OutOfMemoryException("BuildPolyMesh ran out of memory");
            }
#if (TIMETHIS)
            Console.WriteLine("BuildPolyMesh: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            cur = stopWatch.ElapsedMilliseconds;
            #endif

            // Build detail mesh.
            PolyMeshDetail dmesh = null;
            //if (!Context.BuildPolyMeshDetail(pmesh, chf, Config.DetailSampleDistance, Config.DetailSampleMaxError,out dmesh))
            //    throw new OutOfMemoryException("BuildPolyMeshDetail ran out of memory");
            //#if (TIMETHIS)
            //    Console.WriteLine("BuildPolyMeshDetail: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            //   cur = stopWatch.ElapsedMilliseconds;
            //#endif

            chf.Delete();
            cset.Delete();

            // Set flags according to area types (e.g. Swim for Water)
            pmesh.MarkAll();

            // get original bounds
            float[] tilebMin, tilebMax;
            CalculateTileBounds(out tilebMin, out tilebMax, false, i, j);
            tilebMin[1] = bbMin[1];
            tilebMax[1] = bbMax[1];

            // build off mesh connections for flightmasters
            // bMax and bMin are switched here because of the coordinate system transformation

            var connections = new List <OffMeshConnection>();

            if (MapId == 1220)
            {
                SlimDX.Vector3    from = new SlimDX.Vector3(1685.681f, 4709.121f, 139.4296f);
                SlimDX.Vector3    to   = new SlimDX.Vector3(1682.194f, 4712.713f, 139.6532f);
                OffMeshConnection conn = new OffMeshConnection
                {
                    AreaId = PolyArea.Road,
                    Flags  = PolyFlag.Walk,
                    From   = from.ToRecast().ToFloatArray(),
                    To     = to.ToRecast().ToFloatArray(),
                    Radius = 5,
                    Type   = ConnectionType.BiDirectional,
                    UserID = 1,
                };
                connections.Add(conn);
                from = new SlimDX.Vector3(1661.302f, 4727.02f, 139.4618f);
                to   = new SlimDX.Vector3(1669.568f, 4733.722f, 138.7087f);
                conn = new OffMeshConnection
                {
                    AreaId = PolyArea.Road,
                    Flags  = PolyFlag.Walk,
                    From   = from.ToRecast().ToFloatArray(),
                    To     = to.ToRecast().ToFloatArray(),
                    Radius = 5f,
                    Type   = ConnectionType.BiDirectional,
                    UserID = 2,
                };
                connections.Add(conn);
            }

            /*if (MapId == 0 && X == 31 && Y == 58)
             * {
             *  //<X>-14250.71</X>
             *  //<Y>329.7578</Y>
             *  //<Z>24.17678</Z>
             *
             *  //<X>-14254.86</X>
             *  //<Y>336.1039</Y>
             *  //<Z>26.79213</Z>
             *
             *  SlimDX.Vector3 from = new SlimDX.Vector3(1.0f, 1.0f, 1.0f);
             *  SlimDX.Vector3 to   = new SlimDX.Vector3(1.0f, 1.0f, 1.0f);
             *  OffMeshConnection conn = new OffMeshConnection
             *          {
             *              From = from.ToRecast().ToFloatArray(),
             *              To = to.ToRecast().ToFloatArray(),
             *              Radius = 2.5f,
             *              Type = ConnectionType.BiDirectional,
             *              UserID = 88,
             *          };
             *  connections.Add(conn);
             * }*/

            /* // Disable mesh connexion RIVAL
             * var taxis = TaxiHelper.GetNodesInBBox(MapId, tilebMax.ToWoW(), tilebMin.ToWoW());
             * foreach (var taxi in taxis)
             * {
             *  Log.Log(LogCategory.Warning,
             *          "Flightmaster \"" + taxi.Name + "\", Id: " + taxi.Id + " Horde: " + taxi.IsHorde + " Alliance: " +
             *          taxi.IsAlliance);
             *
             *  var data = TaxiHelper.GetTaxiData(taxi);
             *  var from = taxi.Location.ToRecast().ToFloatArray();
             *  connections.AddRange(data.To.Select(to => new OffMeshConnection
             *                                                {
             *                                                    AreaId = PolyArea.Road,
             *                                                    Flags = PolyFlag.FlightMaster,
             *                                                    From = from,
             *                                                    To = to.Value.Location.ToRecast().ToFloatArray(),
             *                                                    Radius = Config.WorldWalkableRadius,
             *                                                    Type = ConnectionType.OneWay,
             *                                                    UserID = (uint) to.Key
             *                                                }));
             *
             *  foreach (var target in data.To)
             *      Log.Log(LogCategory.Warning,
             *              "\tPath to: \"" + target.Value.Name + "\" Id: " + target.Value.Id + " Path Id: " +
             *              target.Key);
             * }*/


            byte[] tileData;
            if (!Detour.CreateNavMeshData(out tileData, pmesh, dmesh,
                                          X * Constant.Division + i, Y * Constant.Division + j, tilebMin, tilebMax,
                                          Config.WorldWalkableHeight, Config.WorldWalkableRadius,
                                          Config.WorldWalkableClimb, Config.CellSize,
                                          Config.CellHeight, Config.BuildBvTree,
                                          connections.ToArray()))
            {
                pmesh.Delete();
                //dmesh.Delete();
                return(null);
            }
#if (TIMETHIS)
            Console.WriteLine("CreateNavMeshData: " + (stopWatch.ElapsedMilliseconds - cur) + ", total: " + stopWatch.ElapsedMilliseconds);
            #endif
            pmesh.Delete();
            //dmesh.Delete();
            GC.Collect();

            return(tileData);
        }
Esempio n. 3
0
        public byte[] Build(BaseLog log)
        {
            Log = log;
            Geometry = new Geometry {Transform = true};

            {
                var main = new ADT(GetAdtPath(World, X, Y));
                main.Read();
                Geometry.AddAdt(main);
            }

            if (Geometry.Vertices.Count == 0 && Geometry.Triangles.Count == 0)
                throw new InvalidOperationException("Can't build tile with empty geometry");

            float[] bbMin, bbMax;
            CalculateTileBounds(out bbMin, out bbMax);
            Geometry.CalculateMinMaxHeight(out bbMin[1], out bbMax[1]);

            // again, we load everything - wasteful but who cares
            for (int ty = Y - 1; ty <= Y + 1; ty++)
            {
                for (int tx = X - 1; tx <= X + 1; tx++)
                {
                    try
                    {
                        // don't load main tile again
                        if (tx == X && ty == Y)
                            continue;

                        var adt = new ADT(GetAdtPath(World, tx, ty));
                        adt.Read();
                        Geometry.AddAdt(adt);
                    }
                    catch (FileNotFoundException)
                    {
                        // don't care - no file means no geometry
                    }
                }
            }

            Context = new RecastContext();
            Context.SetContextHandler(Log);

            // get raw geometry - lots of slowness here
            float[] vertices;
            int[] triangles;
            byte[] areas;
            Geometry.GetRawData(out vertices, out triangles, out areas);
            Geometry.Triangles.Clear();
            Geometry.Vertices.Clear();

            // add border
            bbMin[0] -= Config.BorderSize*Config.CellSize;
            bbMin[2] -= Config.BorderSize*Config.CellSize;
            bbMax[0] += Config.BorderSize*Config.CellSize;
            bbMax[2] += Config.BorderSize*Config.CellSize;

            Heightfield hf;
            int width = Config.TileWidth + (Config.BorderSize*2);
            if (!Context.CreateHeightfield(out hf, width, width, bbMin, bbMax, Config.CellSize, Config.CellHeight))
                throw new OutOfMemoryException("CreateHeightfield ran out of memory");

            Context.ClearUnwalkableTriangles(Config.WalkableSlopeAngle, ref vertices, ref triangles, areas);
            Context.RasterizeTriangles(ref vertices, ref triangles, ref areas, hf, Config.WalkableClimb);

            // Once all geometry 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.
            Context.FilterLowHangingWalkableObstacles(Config.WalkableClimb, hf);
            Context.FilterLedgeSpans(Config.WalkableHeight, Config.WalkableClimb, hf);
            Context.FilterWalkableLowHeightSpans(Config.WalkableHeight, hf);

            // Compact the heightfield so that it is faster to handle from now on.
            // This will result in more cache coherent data as well as the neighbours
            // between walkable cells will be calculated.
            CompactHeightfield chf;
            if (!Context.BuildCompactHeightfield(Config.WalkableHeight, Config.WalkableClimb, hf, out chf))
                throw new OutOfMemoryException("BuildCompactHeightfield ran out of memory");

            hf.Delete();

            // Erode the walkable area by agent radius.
            if (!Context.ErodeWalkableArea(Config.WalkableRadius, chf))
                throw new OutOfMemoryException("ErodeWalkableArea ran out of memory");

            // Prepare for region partitioning, by calculating distance field along the walkable surface.
            if (!Context.BuildDistanceField(chf))
                throw new OutOfMemoryException("BuildDistanceField ran out of memory");

            // Partition the walkable surface into simple regions without holes.
            if (!Context.BuildRegions(chf, Config.BorderSize, Config.MinRegionArea, Config.MergeRegionArea))
                throw new OutOfMemoryException("BuildRegionsMonotone ran out of memory");

            // Create contours.
            ContourSet cset;
            if (!Context.BuildContours(chf, Config.MaxSimplificationError, Config.MaxEdgeLength, out cset))
                throw new OutOfMemoryException("BuildContours ran out of memory");

            // Build polygon navmesh from the contours.
            PolyMesh pmesh;
            if (!Context.BuildPolyMesh(cset, Config.MaxVertsPerPoly, out pmesh))
                throw new OutOfMemoryException("BuildPolyMesh ran out of memory");

            // Build detail mesh.
            PolyMeshDetail dmesh;
            if (
                !Context.BuildPolyMeshDetail(pmesh, chf, Config.DetailSampleDistance, Config.DetailSampleMaxError,
                                             out dmesh))
                throw new OutOfMemoryException("BuildPolyMeshDetail ran out of memory");

            chf.Delete();
            cset.Delete();

            // Remove padding from the polymesh data. (Remove this odditity)
            pmesh.RemovePadding(Config.BorderSize);

            // Set flags according to area types (e.g. Swim for Water)
            pmesh.MarkAll();

            // get original bounds
            float[] tilebMin, tilebMax;
            CalculateTileBounds(out tilebMin, out tilebMax);
            tilebMin[1] = bbMin[1];
            tilebMax[1] = bbMax[1];

            // build off mesh connections for flightmasters
            // bMax and bMin are switched here because of the coordinate system transformation
            var taxis = TaxiHelper.GetNodesInBBox(MapId, tilebMax.ToWoW(), tilebMin.ToWoW());
            var connections = new List<OffMeshConnection>();
            foreach (var taxi in taxis)
            {
                Log.Log(LogCategory.Warning,
                        "Flightmaster \"" + taxi.Name + "\", Id: " + taxi.Id + " Horde: " + taxi.IsHorde + " Alliance: " +
                        taxi.IsAlliance);

                var data = TaxiHelper.GetTaxiData(taxi);
                var from = taxi.Location.ToRecast().ToFloatArray();
                connections.AddRange(data.To.Select(to => new OffMeshConnection
                                                              {
                                                                  AreaId = PolyArea.Road,
                                                                  Flags = PolyFlag.FlightMaster,
                                                                  From = from,
                                                                  To = to.Value.Location.ToRecast().ToFloatArray(),
                                                                  Radius = Config.WorldWalkableRadius,
                                                                  Type = ConnectionType.OneWay,
                                                                  UserID = (uint) to.Key
                                                              }));

                foreach (var target in data.To)
                    Log.Log(LogCategory.Warning,
                            "\tPath to: \"" + target.Value.Name + "\" Id: " + target.Value.Id + " Path Id: " +
                            target.Key);
            }

            byte[] tileData;
            if (!Detour.CreateNavMeshData(out tileData, pmesh, dmesh,
                                          X, Y, tilebMin, tilebMax,
                                          Config.WorldWalkableHeight, Config.WorldWalkableRadius,
                                          Config.WorldWalkableClimb, Config.CellSize,
                                          Config.CellHeight, Config.TileWidth,
                                          connections.ToArray()))
            {
                pmesh.Delete();
                dmesh.Delete();
                return null;
            }

            pmesh.Delete();
            dmesh.Delete();
            Cache.Clear();
            GC.Collect();
            return tileData;
        }