// Computes a (hopefully) efficient connection from one Platform start to Platform end private List <Waypoint> ComputeConnection(NavGraphNode start, NavGraphNode end) { List <Waypoint> res = new List <Waypoint>(); Collidable startC = start.Platform; Collidable endC = end.Platform; // Position of the transition waypoints between the two nodes Vector2 fst, snd; foreach (Tuple <NavGraphNode, Vector2, Vector2> t in start.ReachableNodes) { if (t.Item1 == end) { fst = t.Item2; // waypoint on start snd = t.Item3; // waypoint on end // if different platforms (but roughly on same height): ensure, that the waypoint clearly // lies on the target platform if (end.Platform != start.Platform && Math.Abs(fst.Y - snd.Y) <= AiConfig.DISTANCE_JUMP_VERT && Math.Abs(fst.X - snd.X) > AiConfig.DISTANCE_JUMP_MIN_HOR) { if (fst.X < snd.X) { snd.X += AiConfig.DISTANCE_OVERSHOOT_HOR; } else if (fst.X > snd.X) { snd.X -= AiConfig.DISTANCE_OVERSHOOT_HOR; } } // If target-waypoint is higher up (and only reachable by climbing): ensure that the // AI climbs high enough and doesn't get stuck at the top if (fst.Y > snd.Y && Math.Abs(fst.Y - snd.Y) > AiConfig.DISTANCE_JUMP_VERT) { fst.Y -= AiConfig.DISTANCE_OVERSHOOT_UP; snd.Y -= 0.8f * AiConfig.DISTANCE_OVERSHOOT_UP; } else if (fst.Y < snd.Y && Math.Abs(fst.Y - snd.Y) > AiConfig.DISTANCE_JUMP_VERT) { fst.Y -= AiConfig.DISTANCE_OVERSHOOT_UP; } // Add in the correct order res.Add(new Waypoint(new Vector3(fst.X, fst.Y, 0), startC)); res.Add(new Waypoint(new Vector3(snd.X, snd.Y, 0), endC)); return(res); } } return(res); }
// Compute waypoints from start to target via the paths defined with the pathMap<next, previous> private List <Waypoint> BacktrackPath(Dictionary <NavGraphNode, NavGraphNode> pathMap, NavGraphNode start, NavGraphNode target) { List <Waypoint> res = new List <Waypoint>(); NavGraphNode current = target; while (current != start) { NavGraphNode next = pathMap[current]; // Compute path from previous (next) to the current one (<- reverse order) res.InsertRange(0, ComputeConnection(next, current)); current = next; } return(res); }
public NavGraph(Scene scene) { this.scene = scene; NavGraphNodes = new NavGraphNode[scene.WalkableCollidables.Count]; CurrClimbGroup = new List <ClimbGroup>(); //Init a NavGraphNode for each walkable platform in the scene for (var i = 0; i < NavGraphNodes.Length; i++) { NavGraphNodes[i] = new NavGraphNode(scene.WalkableCollidables[i]); } InitClimbGroups(); InitClimbReachableNodes(); InitCloseReachableNodes(); scene.NavigationGraph = this; }
private List <Waypoint> ComputePath(Collidable currP, Collidable tgtP) { List <Waypoint> res = new List <Waypoint>(); // Map to remember last step of fastest connection to <key> Dictionary <NavGraphNode, NavGraphNode> pathMap = new Dictionary <NavGraphNode, NavGraphNode>(); NavGraphNode start = scene.NavigationGraph.findNode(currP); NavGraphNode target = null; // Do breadth-first-search on NavGraph (start by adding all neighboring nodes of the start node to "to visit") // <which new node to visit, last platform before this new one> List <Tuple <NavGraphNode, NavGraphNode> > toVisit = new List <Tuple <NavGraphNode, NavGraphNode> >(); foreach (Tuple <NavGraphNode, Vector2, Vector2> t in start.ReachableNodes) { if (!pathMap.ContainsKey(t.Item1)) { pathMap.Add(t.Item1, start); toVisit.Add(new Tuple <NavGraphNode, NavGraphNode>(t.Item1, start)); } } while (toVisit.Count > 0) { Tuple <NavGraphNode, NavGraphNode> t = toVisit.First(); NavGraphNode next = t.Item1; toVisit.RemoveAt(0); // Check if the currently visited node is the target platform if (next.Platform == tgtP) { // Found our target platform if (!pathMap.ContainsKey(next)) { pathMap.Add(next, t.Item2); } target = t.Item1; break; } // Not yet reached our target, check connections to other platforms (which not have been visited yet) foreach (Tuple <NavGraphNode, Vector2, Vector2> newConn in next.ReachableNodes) { if (!pathMap.ContainsKey(newConn.Item1)) { // Not yet visited if (!pathMap.ContainsKey(newConn.Item1)) { pathMap.Add(newConn.Item1, next); toVisit.Add(new Tuple <NavGraphNode, NavGraphNode>(newConn.Item1, next)); } } } } // Check if target platform has been found if (target != null) { // Compute final path res = BacktrackPath(pathMap, start, target); } return(res); }
private void InitCloseReachableNodes() { for (int i = 0; i < NavGraphNodes.Length; i++) { NavGraphNode n = NavGraphNodes[i]; for (int j = i; j < NavGraphNodes.Length; j++) { NavGraphNode o = NavGraphNodes[j]; // Check if vertically close enough if (Math.Abs(n.Platform.MinHeight - o.Platform.MinHeight) <= AiConfig.DISTANCE_JUMP_VERT) { // Check horizontal displacement Vector2 nMm = n.Platform.WalkableMinMax; Vector2 oMm = o.Platform.WalkableMinMax; float leftDis = nMm.X - oMm.Y; float rightDis = oMm.X - nMm.Y; if (leftDis <= AiConfig.DISTANCE_JUMP_MAX_HOR && leftDis >= -AiConfig.DISTANCE_VALID_OVERLAP) { // Horizontally reachable (both ways) on the left side of n Vector2 wpN = new Vector2(nMm.X + AiConfig.DISTANCE_OFFS_DROP_HOR, n.Platform.MinHeight - AiConfig.DISTANCE_OFFS_DROP_HOR); Vector2 wpO = new Vector2(oMm.Y - AiConfig.DISTANCE_OFFS_DROP_HOR, o.Platform.MinHeight - AiConfig.DISTANCE_OFFS_DROP_HOR); Vector2 diff = wpN - wpO; if (!IsWithinPlatform(wpN - 0.3f * diff) && !IsWithinPlatform(wpO + 0.3f * diff) && !IsWithinPlatform(wpN) && !IsWithinPlatform(wpO)) { n.ReachableNodes.Add(new Tuple <NavGraphNode, Vector2, Vector2>(o, wpN, wpO)); o.ReachableNodes.Add(new Tuple <NavGraphNode, Vector2, Vector2>(n, wpO, wpN)); } } else if (rightDis <= AiConfig.DISTANCE_JUMP_MAX_HOR && rightDis >= -AiConfig.DISTANCE_VALID_OVERLAP) { // Horizontally reachable (both ways) on the right side of n Vector2 wpN = new Vector2(nMm.Y - AiConfig.DISTANCE_OFFS_DROP_HOR, n.Platform.MinHeight - AiConfig.DISTANCE_OFFS_DROP_HOR); Vector2 wpO = new Vector2(oMm.X + AiConfig.DISTANCE_OFFS_DROP_HOR, o.Platform.MinHeight - AiConfig.DISTANCE_OFFS_DROP_HOR); Vector2 diff = wpN - wpO; if (!IsWithinPlatform(wpN - 0.3f * diff) && !IsWithinPlatform(wpO + 0.3f * diff) && !IsWithinPlatform(wpN) && !IsWithinPlatform(wpO)) { n.ReachableNodes.Add(new Tuple <NavGraphNode, Vector2, Vector2>(o, wpN, wpO)); o.ReachableNodes.Add(new Tuple <NavGraphNode, Vector2, Vector2>(n, wpO, wpN)); } } } else { NavGraphNode above, below; if (n.Platform.MinHeight < o.Platform.MinHeight) { // N is higher then O above = n; below = o; } else { // O is higher then N above = o; below = n; } // Check horizontal overlap of the platforms Vector2 abMm = above.Platform.WalkableMinMax; Vector2 belMm = above.Platform.WalkableMinMax; if (abMm.X >= belMm.X) { // Can drop from platform above to below (left side) Vector2 wpAbove = new Vector2(abMm.X - AiConfig.DISTANCE_OFFS_DROP_COLL, above.Platform.MinHeight - AiConfig.DISTANCE_OFFS_DROP_COLL_VERT); Vector2 wpBelow = new Vector2(abMm.X - AiConfig.DISTANCE_OFFS_DROP_COLL, below.Platform.MinHeight - 5); if (!IntersectsWithCollidable(wpAbove, wpBelow - wpAbove)) { wpAbove += new Vector2(-AiConfig.DISTANCE_OFFS_DROP_HOR, AiConfig.DISTANCE_OFFS_DROP_COLL_VERT - AiConfig.DISTANCE_OFFS_DROP_HOR); wpBelow += new Vector2(-AiConfig.DISTANCE_OFFS_DROP_HOR, 0); above.ReachableNodes.Add(new Tuple <NavGraphNode, Vector2, Vector2>(below, wpAbove, wpBelow)); } } else if (abMm.Y <= belMm.Y) { // Can drop from platform above to below (right side) Vector2 wpAbove = new Vector2(abMm.X + AiConfig.DISTANCE_OFFS_DROP_COLL, above.Platform.MinHeight - AiConfig.DISTANCE_OFFS_DROP_COLL_VERT); Vector2 wpBelow = new Vector2(abMm.X + AiConfig.DISTANCE_OFFS_DROP_COLL, below.Platform.MinHeight - 5); if (!IntersectsWithCollidable(wpAbove, wpBelow - wpAbove)) { wpAbove += new Vector2(AiConfig.DISTANCE_OFFS_DROP_HOR, AiConfig.DISTANCE_OFFS_DROP_COLL_VERT - AiConfig.DISTANCE_OFFS_DROP_HOR); wpBelow += new Vector2(AiConfig.DISTANCE_OFFS_DROP_HOR, 0); above.ReachableNodes.Add(new Tuple <NavGraphNode, Vector2, Vector2>(below, wpAbove, wpBelow)); } } } } } }