/// <summary> /// Attach nodes which span from the given node to another ground node.</summary> /// <param name="origin">The point being searched from.</param> /// <param name="left">Indicates whether the path is to the left of origin; False indicates to right.</param> /// <param name="groundNodes">The set of path nodes along the ground.</param> private void AttachSpanNodes(LinkedPathNode origin, bool left, Dictionary<Point, LinkedPathNode> groundNodes) { // Attach span for a direct vertical 'pin drop' downwards this.AttachPinDropJump(origin, left, groundNodes); // Attach spans for parabolic jumps this.AttachParabolicJump(origin, left, groundNodes, 0.4f, 4); this.AttachParabolicJump(origin, left, groundNodes, 1, 2); this.AttachParabolicJump(origin, left, groundNodes, 1.5f, 1); }
/// <summary> /// Attach nodes which span from the given node as a vertical 'pin drop' downwards to another ground node. /// </summary> /// <param name="origin">The point being jumped down from.</param> /// <param name="left">Indicates whether the path is to the left of origin; False indicates to right.</param> /// <param name="groundNodes">The set of path nodes along the ground.</param> private void AttachPinDropJump(LinkedPathNode origin, bool left, Dictionary<Point, LinkedPathNode> groundNodes) { var spanNodes = new List<LinkedPathNode>(); // Set the x coordinate of the pin drop int x = left ? origin.Node.X - 1 : origin.Node.X + 1; // Add the first 2 points as they have already been tested spanNodes.Add(new LinkedPathNode(x, origin.Node.Y, PathNodeType.Jump)); spanNodes.Add(new LinkedPathNode(x, origin.Node.Y + 1, PathNodeType.Jump)); spanNodes[0].AddLink(spanNodes[1], LinkCostFourDirection); spanNodes[1].AddLink(spanNodes[0], LinkCostFourDirection); // Test the remaining points until ground is hit or max span length is reached LinkedPathNode groundBelow = null; LinkedPathNode prevNode = spanNodes[1]; for (int y = origin.Node.Y + 2; y <= origin.Node.Y + this.MaxSpanLength; y++) { var point = new Point(x, y); // Check that this point is passable if (!this.IsPassableTerrain(point)) { // This is a non-passable point so the path fails break; } // If the point below this one is non-passable terrain then the ground has been hit if (!this.IsPassableTerrain(new Point(point.X, point.Y + 1))) { // A ground node has been hit, so set this and break groundBelow = groundNodes[point]; break; } // The point is mid-air, so create the node and connect it to the previous node var node = new LinkedPathNode(point, PathNodeType.Jump); prevNode.AddLink(node, LinkCostFourDirection); node.AddLink(prevNode, LinkCostFourDirection); // Add the node to the list spanNodes.Add(node); // Set the previous node prevNode = node; } // If the path is complete, join this span to the ground nodes if (groundBelow != null && spanNodes.Count > 0) { // If it is a small drop, don't treat this as a jump if (spanNodes.Count < this.MinJumpHeight) { foreach (LinkedPathNode node in spanNodes) { node.Node = new PathNode(node.Node.X, node.Node.Y, PathNodeType.Normal); } } // Connect the first jump node to the origin origin.AddLink(spanNodes[0], LinkCostFourDirection); spanNodes[0].AddLink(origin, LinkCostFourDirection); // Connect the last jump node to the ground groundBelow.AddLink(spanNodes[spanNodes.Count - 1], LinkCostFourDirection); spanNodes[spanNodes.Count - 1].AddLink(groundBelow, LinkCostFourDirection); } }
/// <summary> /// Add a linked path node. /// </summary> /// <param name="node">The linked node.</param> /// <param name="cost">The movement cost of this link.</param> public void AddLink(LinkedPathNode node, int cost) { this.links.Add(new PathLink(node, cost)); }
/// <summary> /// Attach nodes which span from the given node as a parabolic jump to another ground node. /// </summary> /// <param name="origin">The point being jumped down from.</param> /// <param name="left">Indicates whether the path is to the left of origin; False indicates to right.</param> /// <param name="groundNodes">The set of path nodes along the ground.</param> /// <param name="coefficient">The parabolic coefficient.</param> /// <param name="peakOffset">The distance to peak upwards before spanning downwards.</param> private void AttachParabolicJump( LinkedPathNode origin, bool left, Dictionary<Point, LinkedPathNode> groundNodes, float coefficient, uint peakOffset) { var spanNodes = new List<LinkedPathNode>(); // Iterate along x values calculating the y coordinate for each step LinkedPathNode groundBelow = null; LinkedPathNode prevNode = null; int deltaX = 0; int jumpNodeCount = 0; bool terrainHit = false; while (groundBelow == null && jumpNodeCount < this.MaxSpanLength && !terrainHit) { // Increment/decrement the x delta deltaX = left ? deltaX - 1 : deltaX + 1; // Calculate the x and y point for this step int x = origin.Node.X + deltaX; double square = (coefficient * (left ? -1 : 1) * deltaX) - Math.Sqrt(peakOffset); int y = (int)Math.Round(origin.Node.Y - (-(square * square) + peakOffset)); // Calculate how many points of interpolation are required and the direction PathNode prevPoint = prevNode == null ? origin.Node : prevNode.Node; int interpolateLength = (int)Math.Abs(prevPoint.Y - y); bool up = prevPoint.Y > y; // Add the points (or just the one point if no interpolation is required) int deltaY = 0; while (interpolateLength == 0 || Math.Abs(deltaY) < interpolateLength) { // If interpolation is required, increment/decrement the delta y value if (interpolateLength > 0) { deltaY = up ? deltaY - 1 : deltaY + 1; } var point = new Point(x, prevPoint.Y + deltaY); // Check that this point is passable if (!this.IsPassableTerrain(point)) { // This is a non-passable point so the path fails terrainHit = true; break; } // If the point below this one is non-passable terrain then the ground has been hit if (!this.IsPassableTerrain(new Point(point.X, point.Y + 1))) { // A ground node has been hit, so set this and break groundBelow = groundNodes[point]; break; } // The point is mid-air, so create the node and connect it to the previous node var node = new LinkedPathNode(point, PathNodeType.Jump); if (prevNode != null) { int cost = this.CalculatePathCost(node.Node.Y - prevNode.Node.Y, node.Node.X - prevNode.Node.X); prevNode.AddLink(node, cost); node.AddLink(prevNode, cost); } // Add the node to the list spanNodes.Add(node); // Increment the jump nodes count and check whether the limit has been reached if (++jumpNodeCount >= this.MaxSpanLength) { break; } // Set the previous node prevNode = node; // If no interpolation is required do not perform any further iteration if (interpolateLength == 0) { break; } } } // If this path is complete, join this jump-segment to the dictionary nodes if (groundBelow != null && spanNodes.Count >= this.MinJumpHeight) { LinkedPathNode first = spanNodes[0]; LinkedPathNode last = spanNodes[spanNodes.Count - 1]; // Connect the first jump node to the origin int cost = this.CalculatePathCost(origin.Node.Y - first.Node.Y, origin.Node.X - first.Node.X); origin.AddLink(first, cost); first.AddLink(origin, cost); // Connect the last jump node to the ground cost = this.CalculatePathCost(groundBelow.Node.Y - last.Node.Y, groundBelow.Node.X - last.Node.X); groundBelow.AddLink(last, cost); last.AddLink(groundBelow, cost); } }
/// <summary> /// Indicates whether this node contains a link to the given node. /// </summary> /// <param name="node">The node.</param> /// <returns>True if the given node is linked to this node.</returns> public bool HasLinkedNode(LinkedPathNode node) { foreach (PathLink link in this.links) { if (link.Node.Equals(node)) { return true; } } return false; }