/// <summary>
        /// Marks tiles that should be built according to how much their geometry affects the navigation mesh and the bounding boxes specified for building
        /// </summary>
        private void MarkTiles(NavigationMeshInputBuilder inputBuilder, ref NavigationMeshBuildSettings buildSettings, ref NavigationAgentSettings agentSettings, HashSet <Point> tilesToBuild)
        {
            // Extend bounding box for agent size
            BoundingBox boundingBoxToCheck = inputBuilder.BoundingBox;

            NavigationMeshBuildUtils.ExtendBoundingBox(ref boundingBoxToCheck, new Vector3(agentSettings.Radius));

            List <Point> newTileList = NavigationMeshBuildUtils.GetOverlappingTiles(buildSettings, boundingBoxToCheck);

            foreach (Point p in newTileList)
            {
                tilesToBuild.Add(p);
            }
        }
        /// <summary>
        /// Calculates X-Z span for a navigation mesh tile. The Y-axis will span from <see cref="float.MinValue"/> to <see cref="float.MaxValue"/>
        /// </summary>
        public static BoundingBox CalculateTileBoundingBox(NavigationMeshBuildSettings settings, Point tileCoord)
        {
            float   tcs     = settings.TileSize * settings.CellSize;
            Vector2 tileMin = new Vector2(tileCoord.X * tcs, tileCoord.Y * tcs);
            Vector2 tileMax = tileMin + new Vector2(tcs);

            BoundingBox boundingBox = BoundingBox.Empty;

            boundingBox.Minimum.X = tileMin.X;
            boundingBox.Minimum.Z = tileMin.Y;
            boundingBox.Maximum.X = tileMax.X;
            boundingBox.Maximum.Z = tileMax.Y;
            boundingBox.Minimum.Y = float.MinValue;
            boundingBox.Maximum.Y = float.MaxValue;

            return(boundingBox);
        }
        /// <summary>
        /// Check which tiles overlap a given bounding box
        /// </summary>
        /// <param name="settings"></param>
        /// <param name="boundingBox"></param>
        /// <returns></returns>
        public static List <Point> GetOverlappingTiles(NavigationMeshBuildSettings settings, BoundingBox boundingBox)
        {
            List <Point> ret       = new List <Point>();
            float        tcs       = settings.TileSize * settings.CellSize;
            Vector2      start     = boundingBox.Minimum.XZ() / tcs;
            Vector2      end       = boundingBox.Maximum.XZ() / tcs;
            Point        startTile = new Point(
                (int)Math.Floor(start.X),
                (int)Math.Floor(start.Y));
            Point endTile = new Point(
                (int)Math.Ceiling(end.X),
                (int)Math.Ceiling(end.Y));

            for (int y = startTile.Y; y < endTile.Y; y++)
            {
                for (int x = startTile.X; x < endTile.X; x++)
                {
                    ret.Add(new Point(x, y));
                }
            }
            return(ret);
        }
Esempio n. 4
0
 public bool Equals(NavigationMeshBuildSettings other)
 {
     return(CellHeight.Equals(other.CellHeight) && CellSize.Equals(other.CellSize) && TileSize == other.TileSize && MinRegionArea.Equals(other.MinRegionArea) &&
            RegionMergeArea.Equals(other.RegionMergeArea) && MaxEdgeLen.Equals(other.MaxEdgeLen) && MaxEdgeError.Equals(other.MaxEdgeError) &&
            DetailSamplingDistance.Equals(other.DetailSamplingDistance) && MaxDetailSamplingError.Equals(other.MaxDetailSamplingError));
 }
 /// <summary>
 /// Snaps a <see cref="BoundingBox"/>'s height according to the given <see cref="NavigationMeshBuildSettings"/>
 /// </summary>
 /// <param name="settings">The build settings</param>
 /// <param name="boundingBox">Reference to the bounding box to snap</param>
 public static void SnapBoundingBoxToCellHeight(NavigationMeshBuildSettings settings, ref BoundingBox boundingBox)
 {
     // Snap Y to tile height to avoid height differences between tiles
     boundingBox.Minimum.Y = MathF.Floor(boundingBox.Minimum.Y / settings.CellHeight) * settings.CellHeight;
     boundingBox.Maximum.Y = MathF.Ceiling(boundingBox.Maximum.Y / settings.CellHeight) * settings.CellHeight;
 }
        /// <summary>
        /// Performs the build of a navigation mesh
        /// </summary>
        /// <param name="buildSettings">The build settings to pass to recast</param>
        /// <param name="agentSettings">A collection of agent settings to use, this will generate a layer in the navigation mesh for every agent settings in this collection (in the same order)</param>
        /// <param name="includedCollisionGroups">The collision groups that will affect which colliders are considered solid</param>
        /// <param name="boundingBoxes">A collection of bounding boxes to use as the region for which to generate navigation mesh tiles</param>
        /// <param name="cancellationToken">A cancellation token to interrupt the build process</param>
        /// <returns>The build result</returns>
        public NavigationMeshBuildResult Build(NavigationMeshBuildSettings buildSettings, ICollection <NavigationMeshGroup> groups, CollisionFilterGroupFlags includedCollisionGroups,
                                               ICollection <BoundingBox> boundingBoxes, CancellationToken cancellationToken)
        {
            var lastCache = oldNavigationMesh?.Cache;
            var result    = new NavigationMeshBuildResult();

            if (groups.Count == 0)
            {
                Logger?.Warning("No group settings found");
                result.Success        = true;
                result.NavigationMesh = new NavigationMesh();
                return(result);
            }

            if (boundingBoxes.Count == 0)
            {
                Logger?.Warning("No bounding boxes found");
            }

            var settingsHash = groups?.ComputeHash() ?? 0;

            settingsHash = (settingsHash * 397) ^ buildSettings.GetHashCode();
            if (lastCache != null && lastCache.SettingsHash != settingsHash)
            {
                // Start from scratch if settings changed
                oldNavigationMesh = null;
                Logger?.Info("Build settings changed, doing a full rebuild");
            }

            // Copy colliders so the collection doesn't get modified
            StaticColliderData[] collidersLocal;
            lock (colliders)
            {
                collidersLocal = colliders.ToArray();
            }

            BuildInput(collidersLocal, includedCollisionGroups);

            // Check if cache was cleared while building the input
            lastCache = oldNavigationMesh?.Cache;

            // The new navigation mesh that will be created
            result.NavigationMesh          = new NavigationMesh();
            result.NavigationMesh.CellSize = buildSettings.CellSize;
            result.NavigationMesh.TileSize = buildSettings.TileSize;

            // Tile cache for this new navigation mesh
            NavigationMeshCache newCache = result.NavigationMesh.Cache = new NavigationMeshCache();

            newCache.SettingsHash = settingsHash;

            // Generate global bounding box for planes
            BoundingBox globalBoundingBox = BoundingBox.Empty;

            foreach (var boundingBox in boundingBoxes)
            {
                globalBoundingBox = BoundingBox.Merge(boundingBox, globalBoundingBox);
            }

            // Combine input and collect tiles to build
            NavigationMeshInputBuilder sceneNavigationMeshInputBuilder = new NavigationMeshInputBuilder();

            foreach (var colliderData in collidersLocal)
            {
                if (colliderData.InputBuilder == null)
                {
                    continue;
                }

                // Otherwise, skip building these tiles
                sceneNavigationMeshInputBuilder.AppendOther(colliderData.InputBuilder);
                newCache.Add(colliderData.Component, colliderData.InputBuilder, colliderData.Planes, colliderData.ParameterHash);

                // Generate geometry for planes
                foreach (var plane in colliderData.Planes)
                {
                    sceneNavigationMeshInputBuilder.AppendOther(BuildPlaneGeometry(plane, globalBoundingBox));
                }
            }

            // TODO: Generate tile local mesh input data
            var inputVertices = sceneNavigationMeshInputBuilder.Points.ToArray();
            var inputIndices  = sceneNavigationMeshInputBuilder.Indices.ToArray();

            // Enumerate over every layer, and build tiles for each of those layers using the provided agent settings
            using (var groupEnumerator = groups.NotNull().GetEnumerator())
            {
                for (int layerIndex = 0; layerIndex < groups.Count; layerIndex++)
                {
                    groupEnumerator.MoveNext();
                    var currentGroup         = groupEnumerator.Current;
                    var currentAgentSettings = currentGroup.AgentSettings;

                    if (result.NavigationMesh.LayersInternal.ContainsKey(currentGroup.Id))
                    {
                        Logger.Error($"The same group can't be selected twice: {currentGroup}");
                        return(result);
                    }

                    HashSet <Point> tilesToBuild = new HashSet <Point>();

                    foreach (var colliderData in collidersLocal)
                    {
                        if (colliderData.InputBuilder == null)
                        {
                            continue;
                        }

                        if (colliderData.Processed)
                        {
                            MarkTiles(colliderData.InputBuilder, ref buildSettings, ref currentAgentSettings, tilesToBuild);
                            if (colliderData.Previous != null)
                            {
                                MarkTiles(colliderData.Previous.InputBuilder, ref buildSettings, ref currentAgentSettings, tilesToBuild);
                            }
                        }
                    }

                    // Check for removed colliders
                    if (lastCache != null)
                    {
                        foreach (var obj in lastCache.Objects)
                        {
                            if (!newCache.Objects.ContainsKey(obj.Key))
                            {
                                MarkTiles(obj.Value.InputBuilder, ref buildSettings, ref currentAgentSettings, tilesToBuild);
                            }
                        }
                    }

                    // Calculate updated/added bounding boxes
                    foreach (var boundingBox in boundingBoxes)
                    {
                        if (!lastCache?.BoundingBoxes.Contains(boundingBox) ?? true) // In the case of no case, mark all tiles in all bounding boxes to be rebuilt
                        {
                            var tiles = NavigationMeshBuildUtils.GetOverlappingTiles(buildSettings, boundingBox);
                            foreach (var tile in tiles)
                            {
                                tilesToBuild.Add(tile);
                            }
                        }
                    }

                    // Check for removed bounding boxes
                    if (lastCache != null)
                    {
                        foreach (var boundingBox in lastCache.BoundingBoxes)
                        {
                            if (!boundingBoxes.Contains(boundingBox))
                            {
                                var tiles = NavigationMeshBuildUtils.GetOverlappingTiles(buildSettings, boundingBox);
                                foreach (var tile in tiles)
                                {
                                    tilesToBuild.Add(tile);
                                }
                            }
                        }
                    }

                    long buildTimeStamp = DateTime.UtcNow.Ticks;

                    ConcurrentCollector <Tuple <Point, NavigationMeshTile> > builtTiles = new ConcurrentCollector <Tuple <Point, NavigationMeshTile> >(tilesToBuild.Count);
                    Dispatcher.ForEach(tilesToBuild.ToArray(), tileCoordinate =>
                    {
                        // Allow cancellation while building tiles
                        if (cancellationToken.IsCancellationRequested)
                        {
                            return;
                        }

                        // Builds the tile, or returns null when there is nothing generated for this tile (empty tile)
                        NavigationMeshTile meshTile = BuildTile(tileCoordinate, buildSettings, currentAgentSettings, boundingBoxes,
                                                                inputVertices, inputIndices, buildTimeStamp);

                        // Add the result to the list of built tiles
                        builtTiles.Add(new Tuple <Point, NavigationMeshTile>(tileCoordinate, meshTile));
                    });

                    if (cancellationToken.IsCancellationRequested)
                    {
                        Logger?.Warning("Operation cancelled");
                        return(result);
                    }

                    // Add layer to the navigation mesh
                    var layer = new NavigationMeshLayer();
                    result.NavigationMesh.LayersInternal.Add(currentGroup.Id, layer);

                    // Copy tiles from from the previous build into the current
                    NavigationMeshLayer sourceLayer = null;
                    if (oldNavigationMesh != null && oldNavigationMesh.LayersInternal.TryGetValue(currentGroup.Id, out sourceLayer))
                    {
                        foreach (var sourceTile in sourceLayer.Tiles)
                        {
                            layer.TilesInternal.Add(sourceTile.Key, sourceTile.Value);
                        }
                    }

                    foreach (var p in builtTiles)
                    {
                        if (p.Item2 == null)
                        {
                            // Remove a tile
                            if (layer.TilesInternal.ContainsKey(p.Item1))
                            {
                                layer.TilesInternal.Remove(p.Item1);
                            }
                        }
                        else
                        {
                            // Set or update tile
                            layer.TilesInternal[p.Item1] = p.Item2;
                        }
                    }

                    // Add information about which tiles were updated to the result
                    if (tilesToBuild.Count > 0)
                    {
                        var layerUpdateInfo = new NavigationMeshLayerUpdateInfo();
                        layerUpdateInfo.GroupId      = currentGroup.Id;
                        layerUpdateInfo.UpdatedTiles = tilesToBuild.ToList();
                        result.UpdatedLayers.Add(layerUpdateInfo);
                    }
                }
            }

            // Check for removed layers
            if (oldNavigationMesh != null)
            {
                var newGroups = groups.ToLookup(x => x.Id);
                foreach (var oldLayer in oldNavigationMesh.Layers)
                {
                    if (!newGroups.Contains(oldLayer.Key))
                    {
                        var updateInfo = new NavigationMeshLayerUpdateInfo();
                        updateInfo.UpdatedTiles.Capacity = oldLayer.Value.Tiles.Count;

                        foreach (var tile in oldLayer.Value.Tiles)
                        {
                            updateInfo.UpdatedTiles.Add(tile.Key);
                        }

                        result.UpdatedLayers.Add(updateInfo);
                    }
                }
            }

            // Store bounding boxes in new tile cache
            newCache.BoundingBoxes = new List <BoundingBox>(boundingBoxes);

            // Update navigation mesh
            oldNavigationMesh = result.NavigationMesh;

            result.Success = true;
            return(result);
        }
        private NavigationMeshTile BuildTile(Point tileCoordinate, NavigationMeshBuildSettings buildSettings, NavigationAgentSettings agentSettings,
                                             ICollection <BoundingBox> boundingBoxes, Vector3[] inputVertices, int[] inputIndices, long buildTimeStamp)
        {
            NavigationMeshTile meshTile = null;

            // Include bounding boxes in tile height range
            BoundingBox tileBoundingBox = NavigationMeshBuildUtils.CalculateTileBoundingBox(buildSettings, tileCoordinate);
            float       minimumHeight   = float.MaxValue;
            float       maximumHeight   = float.MinValue;
            bool        shouldBuildTile = false;

            foreach (var boundingBox in boundingBoxes)
            {
                if (boundingBox.Intersects(ref tileBoundingBox))
                {
                    maximumHeight   = Math.Max(maximumHeight, boundingBox.Maximum.Y);
                    minimumHeight   = Math.Min(minimumHeight, boundingBox.Minimum.Y);
                    shouldBuildTile = true;
                }
            }

            NavigationMeshBuildUtils.SnapBoundingBoxToCellHeight(buildSettings, ref tileBoundingBox);

            // Skip tiles that do not overlap with any bounding box
            if (shouldBuildTile)
            {
                // Set tile's minimum and maximum height
                tileBoundingBox.Minimum.Y = minimumHeight;
                tileBoundingBox.Maximum.Y = maximumHeight;

                unsafe
                {
                    IntPtr builder = Navigation.CreateBuilder();

                    // Turn build settings into native structure format
                    Navigation.BuildSettings internalBuildSettings = new Navigation.BuildSettings
                    {
                        // Tile settings
                        BoundingBox  = tileBoundingBox,
                        TilePosition = tileCoordinate,
                        TileSize     = buildSettings.TileSize,

                        // General build settings
                        CellHeight           = buildSettings.CellHeight,
                        CellSize             = buildSettings.CellSize,
                        RegionMinArea        = buildSettings.MinRegionArea,
                        RegionMergeArea      = buildSettings.RegionMergeArea,
                        EdgeMaxLen           = buildSettings.MaxEdgeLen,
                        EdgeMaxError         = buildSettings.MaxEdgeError,
                        DetailSampleDist     = buildSettings.DetailSamplingDistance,
                        DetailSampleMaxError = buildSettings.MaxDetailSamplingError,

                        // Agent settings
                        AgentHeight   = agentSettings.Height,
                        AgentRadius   = agentSettings.Radius,
                        AgentMaxClimb = agentSettings.MaxClimb,
                        AgentMaxSlope = agentSettings.MaxSlope.Degrees,
                    };

                    Navigation.SetSettings(builder, new IntPtr(&internalBuildSettings));
                    IntPtr buildResultPtr = Navigation.Build(builder, inputVertices, inputVertices.Length, inputIndices, inputIndices.Length);
                    Navigation.GeneratedData *generatedDataPtr = (Navigation.GeneratedData *)buildResultPtr;
                    if (generatedDataPtr->Success && generatedDataPtr->NavmeshDataLength > 0)
                    {
                        meshTile = new NavigationMeshTile();

                        // Copy the generated navigationMesh data
                        meshTile.Data = new byte[generatedDataPtr->NavmeshDataLength + sizeof(long)];
                        Marshal.Copy(generatedDataPtr->NavmeshData, meshTile.Data, 0, generatedDataPtr->NavmeshDataLength);

                        // Append time stamp
                        byte[] timeStamp = BitConverter.GetBytes(buildTimeStamp);
                        for (int i = 0; i < timeStamp.Length; i++)
                        {
                            meshTile.Data[meshTile.Data.Length - sizeof(long) + i] = timeStamp[i];
                        }

                        List <Vector3> outputVerts = new List <Vector3>();
                        if (generatedDataPtr->NumNavmeshVertices > 0)
                        {
                            Vector3 *navmeshVerts = (Vector3 *)generatedDataPtr->NavmeshVertices;
                            for (int j = 0; j < generatedDataPtr->NumNavmeshVertices; j++)
                            {
                                outputVerts.Add(navmeshVerts[j]);
                            }
                        }

                        Navigation.DestroyBuilder(builder);
                    }
                }
            }

            return(meshTile);
        }