/// <summary> /// Finds the path from the start to the goal region of move actions that can be performed by the creature. /// </summary> /// <param name="mover">The creature following the path.</param> /// <param name="start">The voxel the path starts with.</param> /// <param name="goal">Goal conditions that must be satisfied by the path.</param> /// <param name="chunks">The chunks the creature is moving through.</param> /// <param name="maxExpansions">Maximum number of voxels to explore before giving up.</param> /// <param name="weight"> /// The heuristic weight of the planner. If 1.0, the path returned is optimal. /// Higher values result in suboptimal paths, but the search may be faster. /// </param> /// <param name="numPlans"></param> /// <param name="continueFunc"></param> /// <returns>The path of movements the creature must take to reach the goal. Returns null if no such path exists.</returns> public static List <MoveAction> FindPath(CreatureMovement mover, VoxelHandle start, GoalRegion goal, ChunkManager chunks, int maxExpansions, float weight, int numPlans, Func <bool> continueFunc, out PlanResultCode resultCode) { var p = new List <MoveAction>(); bool use_inverse = goal.IsReversible() && OpennessHeuristic(goal.GetVoxel()) < OpennessHeuristic(start); //bool use_inverse = false; var result = use_inverse ? InversePath(mover, start, goal, chunks, maxExpansions, ref p, weight, continueFunc) : Path(mover, start, goal, chunks, maxExpansions, ref p, weight, continueFunc); resultCode = result.Result; var length = (start.WorldPosition - goal.GetVoxel().WorldPosition).Length(); if (result.Result == PlanResultCode.Success) { return(p); } if (result.Result == PlanResultCode.Invalid || result.Result == PlanResultCode.NoSolution || result.Result == PlanResultCode.Cancelled) { return(null); } if (!goal.IsReversible()) { return(null); } result = use_inverse ? Path(mover, start, goal, chunks, maxExpansions, ref p, weight, continueFunc) : InversePath(mover, start, goal, chunks, maxExpansions, ref p, weight, continueFunc); resultCode = result.Result; return(result.Result == PlanResultCode.Success ? p : null); }
/// <summary> /// Creates a path from the start voxel to some goal region, returning a list of movements that can /// take a mover from the start voxel to the goal region. /// </summary> /// <param name="mover">The agent that we want to find a path for.</param> /// <param name="start">The voxel that the agent starts in.</param> /// <param name="goal">Goal conditions that the agent is trying to satisfy.</param> /// <param name="chunks">The voxels that the agent is moving through</param> /// <param name="maxExpansions">Maximum number of voxels to consider before giving up.</param> /// <param name="toReturn">The path of movements that the mover should take to reach the goal.</param> /// <param name="weight"> /// A heuristic weight to apply to the planner. If 1.0, the path is garunteed to be optimal. Higher values /// usually result in faster plans that are suboptimal. /// </param> /// <returns>True if a path could be found, or false otherwise.</returns> private static bool Path(CreatureMovement mover, Voxel start, GoalRegion goal, ChunkManager chunks, int maxExpansions, ref List <Creature.MoveAction> toReturn, float weight) { // Sometimes a goal may not even be achievable a.priori. If this is true, we know there can't be a path // which satisifies that goal. if (!goal.IsPossible()) { toReturn = null; return(false); } // Voxels that have already been explored. var closedSet = new HashSet <Voxel>(); // Voxels which should be explored. var openSet = new HashSet <Voxel> { start }; // Dictionary of voxels to the optimal action that got the mover to that voxel. var cameFrom = new Dictionary <Voxel, Creature.MoveAction>(); // Optimal score of a voxel based on the path it took to get there. var gScore = new Dictionary <Voxel, float>(); // Expansion priority of voxels. var fScore = new PriorityQueue <Voxel>(); // Starting conditions of the search. gScore[start] = 0.0f; fScore.Enqueue(start, gScore[start] + weight * goal.Heuristic(start)); // Keep count of the number of expansions we've taken to get to the goal. int numExpansions = 0; // Check the voxels adjacent to the current voxel as a quick test of adjacency to the goal. var manhattanNeighbors = new List <Voxel>(6); for (int i = 0; i < 6; i++) { manhattanNeighbors.Add(new Voxel()); } // Loop until we've either checked every possible voxel, or we've exceeded the maximum number of // expansions. while (openSet.Count > 0 && numExpansions < maxExpansions) { // Check the next voxel to explore. Voxel current = GetVoxelWithMinimumFScore(fScore); if (current == null) { // If there wasn't a voxel to explore, just try to expand from // the start again. current = start; numExpansions++; } numExpansions++; // If we've reached the goal already, reconstruct the path from the start to the // goal. if (goal.IsInGoalRegion(current)) { // Assume that the last action in the path involves walking to the goal. var first = new Creature.MoveAction { Voxel = current, MoveType = Creature.MoveType.Walk }; toReturn = ReconstructPath(cameFrom, first); return(true); } // We've already considered the voxel, so add it to the closed set. openSet.Remove(current); closedSet.Add(current); VoxelChunk currentChunk = chunks.ChunkData.ChunkMap[current.ChunkID]; List <Creature.MoveAction> neighbors = null; // Get the voxels that can be moved to from the current voxel. neighbors = mover.GetMoveActions(current); currentChunk.GetNeighborsManhattan(current, manhattanNeighbors); // A quick test to see if we're already adjacent to the goal. If we are, assume // that we can just walk to it. if (manhattanNeighbors.Contains(goal.GetVoxel())) { var first = new Creature.MoveAction { Voxel = current, MoveType = Creature.MoveType.Walk }; var last = new Creature.MoveAction { Voxel = goal.GetVoxel(), MoveType = Creature.MoveType.Walk }; List <Creature.MoveAction> subPath = ReconstructPath(cameFrom, first); subPath.Add(last); toReturn = subPath; return(true); } // Otherwise, consider all of the neighbors of the current voxel that can be moved to, // and determine how to add them to the list of expansions. foreach (Creature.MoveAction n in neighbors) { // If we've already explored that voxel, don't explore it again. if (closedSet.Contains(n.Voxel)) { continue; } // Otherwise, consider the case of moving to that neighbor. float tenativeGScore = gScore[current] + GetDistance(current, n.Voxel, n.MoveType, mover); // IF the neighbor can already be reached more efficiently, ignore it. if (openSet.Contains(n.Voxel) && !(tenativeGScore < gScore[n.Voxel])) { continue; } // Otherwise, add it to the list of voxels for consideration. openSet.Add(n.Voxel); // Add an edge to the voxel from the current voxel. Creature.MoveAction cameAction = n; cameAction.Voxel = current; cameFrom[n.Voxel] = cameAction; // Update the expansion scores for the next voxel. gScore[n.Voxel] = tenativeGScore; fScore.Enqueue(n.Voxel, gScore[n.Voxel] + weight * goal.Heuristic(n.Voxel)); } // If we've expanded too many voxels, just give up. if (numExpansions >= maxExpansions) { return(false); } } // Somehow we've reached this code without having found a path. Return false. toReturn = null; return(false); }
// Find a path from the start to the goal by computing an inverse path from goal to the start. Should only be used // if the forward path fails. private static PlanResult InversePath(CreatureMovement mover, VoxelHandle startVoxel, GoalRegion goal, ChunkManager chunks, int maxExpansions, ref List <MoveAction> toReturn, float weight, Func <bool> continueFunc) { // Create a local clone of the octree, using only the objects belonging to the player. var octree = new OctTreeNode(mover.Creature.World.ChunkManager.Bounds.Min, mover.Creature.World.ChunkManager.Bounds.Max); List <Body> playerObjects = new List <Body>(mover.Creature.World.PlayerFaction.OwnedObjects); foreach (var obj in playerObjects) { octree.Add(obj, obj.GetBoundingBox()); } MoveState start = new MoveState() { Voxel = startVoxel }; var startTime = DwarfTime.Tick(); if (mover.IsSessile) { return(new PlanResult() { Result = PlanResultCode.Invalid, Expansions = 0, TimeSeconds = 0 }); } if (!start.IsValid) { return(new PlanResult() { Result = PlanResultCode.Invalid, Expansions = 0, TimeSeconds = 0 }); } // Sometimes a goal may not even be achievable a.priori. If this is true, we know there can't be a path // which satisifies that goal. // It only makes sense to do inverse plans for goals that have an associated voxel. if (!goal.IsPossible() || !goal.GetVoxel().IsValid) { toReturn = null; return(new PlanResult() { Result = PlanResultCode.Invalid, Expansions = 0, TimeSeconds = 0 }); } if (goal.IsInGoalRegion(start.Voxel)) { toReturn = new List <MoveAction>(); return(new PlanResult() { Result = PlanResultCode.Success, Expansions = 0, TimeSeconds = 0 }); } // Voxels that have already been explored. var closedSet = new HashSet <MoveState>(); // Voxels which should be explored. var openSet = new HashSet <MoveState>(); // Dictionary of voxels to the optimal action that got the mover to that voxel. var cameFrom = new Dictionary <MoveState, MoveAction>(); // Optimal score of a voxel based on the path it took to get there. var gScore = new Dictionary <MoveState, float>(); // Expansion priority of voxels. var fScore = new PriorityQueue <MoveState>(); var goalVoxels = new List <VoxelHandle>(); goalVoxels.Add(goal.GetVoxel()); // Starting conditions of the search. foreach (var goalVoxel in VoxelHelpers.EnumerateCube(goal.GetVoxel().Coordinate) .Select(c => new VoxelHandle(start.Voxel.Chunk.Manager.ChunkData, c))) { if (!goalVoxel.IsValid) { continue; } var goalState = new MoveState() { Voxel = goalVoxel }; if (goal.IsInGoalRegion(goalVoxel)) { goalVoxels.Add(goalVoxel); openSet.Add(goalState); gScore[goalState] = 0.0f; fScore.Enqueue(goalState, gScore[goalState] + weight * (goalVoxel.WorldPosition - start.Voxel.WorldPosition).LengthSquared()); } } // Keep count of the number of expansions we've taken to get to the goal. int numExpansions = 0; // Loop until we've either checked every possible voxel, or we've exceeded the maximum number of // expansions. while (openSet.Count > 0 && numExpansions < maxExpansions) { if (numExpansions % 10 == 0 && !continueFunc()) { return new PlanResult { Result = PlanResultCode.Cancelled, Expansions = numExpansions, TimeSeconds = DwarfTime.Tock(startTime) } } ; // Check the next voxel to explore. var current = GetStateWithMinimumFScore(fScore); if (!current.IsValid) { continue; } //Drawer3D.DrawBox(current.GetBoundingBox(), Color.Blue, 0.1f); numExpansions++; // If we've reached the goal already, reconstruct the path from the start to the // goal. if (current.Equals(start)) { toReturn = ReconstructInversePath(goal, cameFrom, cameFrom[start]); return(new PlanResult() { Result = PlanResultCode.Success, Expansions = numExpansions, TimeSeconds = DwarfTime.Tock(startTime) }); } // We've already considered the voxel, so add it to the closed set. openSet.Remove(current); closedSet.Add(current); IEnumerable <MoveAction> neighbors = null; // Get the voxels that can be moved to from the current voxel. neighbors = mover.GetInverseMoveActions(current, octree); // Otherwise, consider all of the neighbors of the current voxel that can be moved to, // and determine how to add them to the list of expansions. foreach (MoveAction n in neighbors) { //Drawer3D.DrawBox(n.SourceVoxel.GetBoundingBox(), Color.Red, 0.1f); // If we've already explored that voxel, don't explore it again. if (closedSet.Contains(n.SourceState)) { continue; } // Otherwise, consider the case of moving to that neighbor. float tenativeGScore = gScore[current] + GetDistance(current.Voxel, n.SourceState.Voxel, n.MoveType, mover); // IF the neighbor can already be reached more efficiently, ignore it. if (openSet.Contains(n.SourceState) && gScore.ContainsKey(n.SourceState) && !(tenativeGScore < gScore[n.SourceState])) { continue; } // Otherwise, add it to the list of voxels for consideration. openSet.Add(n.SourceState); // Add an edge to the voxel from the current voxel. var cameAction = n; cameFrom[n.SourceState] = cameAction; // Update the expansion scores for the next voxel. gScore[n.SourceState] = tenativeGScore; fScore.Enqueue(n.SourceState, gScore[n.SourceState] + weight * ((n.SourceState.Voxel.WorldPosition - start.Voxel.WorldPosition).LengthSquared() + (!n.SourceState.VehicleState.IsRidingVehicle ? 100.0f : 0.0f))); } // If we've expanded too many voxels, just give up. if (numExpansions >= maxExpansions) { return(new PlanResult() { Result = PlanResultCode.MaxExpansionsReached, Expansions = numExpansions, TimeSeconds = DwarfTime.Tock(startTime) }); } } // Somehow we've reached this code without having found a path. Return false. toReturn = null; return(new PlanResult() { Result = PlanResultCode.NoSolution, Expansions = numExpansions, TimeSeconds = DwarfTime.Tock(startTime) }); }
private static bool Path(CreatureMovement mover, Voxel start, GoalRegion goal, ChunkManager chunks, int maxExpansions, ref List <Creature.MoveAction> toReturn, bool reverse) { VoxelChunk startChunk = chunks.ChunkData.ChunkMap[start.ChunkID]; VoxelChunk endChunk = chunks.ChunkData.ChunkMap[goal.GetVoxel().ChunkID]; if (startChunk.IsCompletelySurrounded(start) || endChunk.IsCompletelySurrounded(goal.GetVoxel())) { toReturn = null; return(false); } HashSet <Voxel> closedSet = new HashSet <Voxel>(); HashSet <Voxel> openSet = new HashSet <Voxel> { start }; Dictionary <Voxel, Creature.MoveAction> cameFrom = new Dictionary <Voxel, Creature.MoveAction>(); Dictionary <Voxel, float> gScore = new Dictionary <Voxel, float>(); PriorityQueue <Voxel> fScore = new PriorityQueue <Voxel>(); gScore[start] = 0.0f; fScore.Enqueue(start, gScore[start] + Heuristic(start, goal.GetVoxel())); int numExpansions = 0; List <Voxel> manhattanNeighbors = new List <Voxel>(6); for (int i = 0; i < 6; i++) { manhattanNeighbors.Add(new Voxel()); } while (openSet.Count > 0 && numExpansions < maxExpansions) { Voxel current = GetVoxelWithMinimumFScore(fScore, openSet); if (current == null) { current = start; numExpansions++; } numExpansions++; if (goal.IsInGoalRegion(current)) { Creature.MoveAction first = new Creature.MoveAction() { Voxel = current, MoveType = Creature.MoveType.Walk }; toReturn = ReconstructPath(cameFrom, first); return(true); } openSet.Remove(current); closedSet.Add(current); VoxelChunk currentChunk = chunks.ChunkData.ChunkMap[current.ChunkID]; List <Creature.MoveAction> neighbors = null; neighbors = mover.GetMoveActions(current); currentChunk.GetNeighborsManhattan(current, manhattanNeighbors); if (manhattanNeighbors.Contains(goal.GetVoxel())) { Creature.MoveAction first = new Creature.MoveAction() { Voxel = current, MoveType = Creature.MoveType.Walk }; Creature.MoveAction last = new Creature.MoveAction() { Voxel = goal.GetVoxel(), MoveType = Creature.MoveType.Walk }; List <Creature.MoveAction> subPath = ReconstructPath(cameFrom, first); subPath.Add(last); toReturn = subPath; return(true); } foreach (Creature.MoveAction n in neighbors) { if (closedSet.Contains(n.Voxel)) { continue; } float tenativeGScore = gScore[current] + GetDistance(current, n.Voxel, n.MoveType, chunks); if (openSet.Contains(n.Voxel) && !(tenativeGScore < gScore[n.Voxel])) { continue; } openSet.Add(n.Voxel); Creature.MoveAction cameAction = n; cameAction.Voxel = current; cameFrom[n.Voxel] = cameAction; gScore[n.Voxel] = tenativeGScore; fScore.Enqueue(n.Voxel, gScore[n.Voxel] + Heuristic(n.Voxel, goal.GetVoxel())); } if (numExpansions >= maxExpansions) { return(false); } } toReturn = null; return(false); }