unity – Pathfinding Only Working in Editor View

I am new to the a* method so still learning. I have two scripts, a Pathfinding script and a GridForPathfinding script. When I am not in the Editor view or looking at the DrawGizmos during play, the Pathfinding script is unable to find the path and I cannot figure out why. I think at this point I have been looking at this for so long and hoping it’s just something simple I am overlooking. It has something do with the DrawGizmos, but I am not sure why since it is just reading the path node list.

GridForPathfinding script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GridForPathfinding : MonoBehaviour
{
public static GridForPathfinding instance;
public List<Node> path;
public bool onlyDisplayPath;
private Node(,) grid;

private Vector2 gridWorldSize;
public float nodeRadius;
public float nodeDiameter;
private int gridSizeX, gridSizeY;
public LayerMask unWalkableMask;

private void Awake()
{
      instance = this;
}


private void OnDrawGizmos()
{
    gridWorldSize.x = Maze.instance.iSize.x;
    gridWorldSize.y = Maze.instance.iSize.z;
    Gizmos.DrawWireCube(Vector3.zero, new Vector3(gridWorldSize.x, 1, gridWorldSize.y));

    if (onlyDisplayPath)
    {
        if(path != null)
        {
            foreach(Node n in path)
            {
                Gizmos.color = new Color(0, 1, 0, 0.5f);
                Gizmos.DrawCube(n.worldPosition, Vector3.one * (nodeDiameter - 0.1f));
            }
        }
    }
    else
    {
        if (grid != null)
        {
            // have the pathfinding detect where the Player is
            // FORTESTING Node playerNode = NodeFromWorldPoint(playerPlaceholder.position);
            

            foreach (Node n in grid)
            {
                // visualize what is walkable 
                Gizmos.color = (n.walkable ? new Color(0, 0, 1, 0.5f) : new Color(1, 0, 0, 0.5f));
                if (path != null)
                {
                    if (path.Contains(n))
                    {
                        Gizmos.color = new Color(0, 1, 0, 0.5f);
                    }
                }

                // FORTESTING
                // make Player node a different color
                // if (playerNode == n)
                // {
                //     Gizmos.color = new Color(0, 1, 1, 0.5f);
                // }




                // so the cube is the size of the node, and leave a little room for the walls
                Gizmos.DrawCube(n.worldPosition, Vector3.one * (nodeDiameter - 0.1f));
            }
        }

    }

}

public Node NodeFromWorldPoint(Vector3 worldPos)
{
    // use a percentage to determine location on the grid
    // 0 = far left, 0.5 = center, 1 = right
    float percentX = (worldPos.x + gridWorldSize.x / 2) / gridWorldSize.x;
    float percentY = 1 -(((worldPos.z + gridWorldSize.y / 2) / gridWorldSize.y));
    // clamp between 0 and 1
    percentX = Mathf.Clamp01(percentX);
    percentY = Mathf.Clamp01(percentY);
    // need x and y indexes of grid array

    int x = Mathf.RoundToInt((gridSizeX - 1) * percentX);
    int y = Mathf.RoundToInt((gridSizeY - 1) * percentY);
    return grid(x, y);
}

private void OnEnable()
{
    CalculateGridSizeForPathfinding();
}

//calculates the grid size to be navigated
public void CalculateGridSizeForPathfinding()
{
    nodeDiameter = nodeRadius * 2;
    gridSizeX = Mathf.RoundToInt(Maze.instance.iSize.x/nodeDiameter);
    gridSizeY = Mathf.RoundToInt(Maze.instance.iSize.z/ nodeDiameter);
    CreateGrid();
}

private void CreateGrid()
{
    grid = new Node(gridSizeX, gridSizeY);

    Vector3 gridTopLeft = transform.position = new Vector3(-Maze.instance.iSize.x/2, 0.4f, Maze.instance.iSize.z / 2 );


    for (int x = 0; x < gridSizeX; x++)
    {
        for (int y = 0; y < gridSizeY; y++)
        {
            Vector3 worldPoint = gridTopLeft - Vector3.left * (x * nodeDiameter + nodeRadius) + -Vector3.forward * (y * nodeDiameter + nodeRadius);
            // make walkable if there are no collisions in the Unwalkable mask
            bool walkable = !(Physics.CheckSphere(worldPoint, nodeRadius, unWalkableMask));
            // pass this information into each individual node
            grid(x, y) = new Node(walkable, worldPoint, x, y);
        }

    }
}

public List<Node> GetNeighbors(Node node)
{
    List<Node> neighbors = new List<Node>();

    // search in a 3x3 block around the node
    // x = 0 && y = 0 means we are in the center of that (current Node position)
    for(int x = -1; x <= 1; x++)
    {
        for (int y = -1; y <= 1; y++)
        {
            if(x == 0 && y == 0)
                continue;
            
            int checkX = node.gridX + x;
            int checkY = node.gridY + y;

            // check if the node is inside of the grid and if so, add to list
            if (checkX >= 0 && checkX < gridSizeX && checkY >= 0 && checkY < gridSizeY)
            {
                neighbors.Add(grid(checkX,checkY));
            }
        }
    }

    return neighbors;

}

public int MaxSize
{
    get
    {
        return gridSizeX * gridSizeY;
    }
}

}

Pathfinding script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Diagnostics;

public class PathFinding : MonoBehaviour
{
public static PathFinding instance;
public GameObject seeker, target;


private void Awake()
{
    instance = this;
}


private void Update()
{
    if (seeker != null && target != null)
    {
        StartFindPath();
    }
    if (Input.GetKeyDown(KeyCode.G))
    {
        StartFindPath();
    }

}

public void StartFindPath()
{
    FindPath(seeker.transform.position, target.transform.position);
}


private void FindPath(Vector3 startPos, Vector3 goalPos)
{
     Stopwatch stopWatch = new Stopwatch();
     stopWatch.Start();
    
     Node startNode = GridForPathfinding.instance.NodeFromWorldPoint(startPos);
     Node targetNode = GridForPathfinding.instance.NodeFromWorldPoint(goalPos);

     // list of nodes for Open set
     // **I did not implement the Heap because it kept making my editor crash
     List<Node> openSet = new List<Node>();
     HashSet<Node> closedSet = new HashSet<Node>();

    // add the start node to the open set
    openSet.Add(startNode);

    while(openSet.Count > 0)
    {
        // need to find the node with the lowest f value
        // want to optimize from this nested structure later
        Node currentNode = openSet(0);
        for (int i = 1; i < openSet.Count; i++)
        {
            // move to node with lowest f cost
            // if fCosts are equal, move to the node with the lowest hCost
            if (openSet(i).fCost < currentNode.fCost || openSet(i).fCost == currentNode.fCost && openSet(i).hCost < currentNode.hCost)
            {
                currentNode = openSet(i);
            }
        }
        openSet.Remove(currentNode);
        closedSet.Add(currentNode);

        // if we have found the path
        if(currentNode == targetNode)
        {
            stopWatch.Stop();
            print("path found in: " + stopWatch.ElapsedMilliseconds + " ms");
            RetracePath(startNode, targetNode);
            return;
        }

        foreach(Node neighbor in GridForPathfinding.instance.GetNeighbors(currentNode))
        {
            // if neighbor is not traversable or neighbor is in CLOSED list, skip to next neighbor
            if(!neighbor.walkable || closedSet.Contains(neighbor))
            {
                continue;
            }

            int newMovementCostToNeighbor = currentNode.gCost + GetDistance(currentNode, neighbor);
           
            // if the gCost is lower than the current node and is not already in the Neighbor list
            if (newMovementCostToNeighbor < neighbor.gCost || !openSet.Contains(neighbor)) 
            {
                neighbor.gCost = newMovementCostToNeighbor;
                // hCost is the distnace from this node to the Goal or Target Node
                neighbor.hCost = GetDistance(neighbor, targetNode);
                // set parent of neighbor to currentNode so it can point toward it's origin
                neighbor.parent = currentNode;

                // check if the neighbor is not in the open set, then add
                if (!openSet.Contains(neighbor))
                {
                    openSet.Add(neighbor);
                }
            }
           
        }
    }
}

// get path from Start node to Goal/Target node
private void RetracePath(Node startNode, Node endNode)
{
    List<Node> path = new List<Node>();
    Node currentNode = endNode;

    // retrace steps until we reach the start node
    while (currentNode != startNode)
    {
        path.Add(currentNode);
        currentNode = currentNode.parent;
    }

    // ^^ path is currently in reverse, we need to invert that
    path.Reverse();

    GridForPathfinding.instance.path = path;
    
}

public void VisualizePath()
{
    List<Node> _path = GridForPathfinding.instance.path;

    if (_path != null)
    {
        foreach (Node n in _path)
        {
            Instantiate(GameManager.instance.waypointDot, n.worldPosition, Quaternion.identity);
        }
    }
    else
    {
        GameManager.instance.ShowWarning();
    }
}


int GetDistance(Node nodeA, Node nodeB)
{
    int distX = Mathf.Abs(nodeA.gridX - nodeB.gridX);
    int distY = Mathf.Abs(nodeA.gridY - nodeB.gridY);

    if(distX > distY)
    {
        // formula for getting the shortest path between 2 points
        return 14 * distY + 10 * (distX - distY);
    }
    else
    {
        return 14 * distX + 10 * (distY - distX);

    }
}

}

unity – How would I incorporate gravity and gravity affecting obstacles into a* pathfinding in a 2d side-scrolling game?

I want to be able to modify the A* pathfinding in a way that I can get my NPC from his current position to any point that’s within reach. The tricky part is that this takes place in a side-scrolling world where gravity is an issue. There’s workarounds to that. My goal is to not only make the pathfinding clean, but be able to add other obstacles and movement aiding objects like jump pads.

enter image description here

In that image I have my NPC and his target position. Between them is a jump pad. When stepping on a jump pad, it launches him into the air high enough to reach his goal.This is what it looks like with a relatively unmodified a* script:

enter image description here

and that’s how it should look. But that’s just lucky jump pad placement. The NPC doesn’t perceive it in any way. Here it looks a lot different just by having the starting position changed:

enter image description here

what I’m trying to do is have a way for the NPC to recognize that the jump pad will take him to his target. Or on the other hand:

enter image description here

What if the best way to get up there is a different jump pad on the opposite side?

Or if the best way is to go down then back up

enter image description here

and would it be any more difficult to add them back to back?

enter image description here

I watched the brackey’s video so I know about making a “custom” a* script. I just can’t find anywhere how I’d introduce both gravity to it, and my jump pads. Any help is MASSIVELY appreciated.

ai – Sparse voxel octree for 3d pathfinding and Manhattan distance

I’ve been learning about using sparse voxel octree for 3d pathfinding based on Daniel Brewer’s chapter in Game AI and his talk at GDC 2015.

One aspect that I’m really confused about is exactly what is considered to be a neightbor of a cell in the tree, and whether using Manhattan distance is alright or not. Let’s assume that all the cells are of the same size and consider some cell C. How many neightbors will it have – 6, one for each side of the cube, or 27-1=26, all the cubes “touching” our chosen cell at least in one vertex?

It seems the former 6, but I don’t see how that doesn’t cause problems if we calculate distances between centers of the cells – it induces a manhattan norm, and there’s lots of situations where that fails. I have some intuition why it might not be an issue if you do some type of “post processing” on the found path, but it could simply fail quite badly in finding a good path.

Brewer has mentioned using Manhattan distance throughout the talk as well to allow for easier computations, but: 1, it seems like a very problematic thing to me and 2, it seems to me like Manhattan distance is already being used when moving across neightboring cells of same size, which I would assume where the bulk of the expensive computation happens.

path finding – Moving walkway in Three.js – pathfinding algo or vector math ala Nature of Code?

I am trying to develop a moving walkway for a 3D virtual space made in Three.js link here. Right now guests navigate using WASD or the arrow keys and the mouse to look around.

My goal is to have guests would navigate themselves onto the walkway and be moved along a path through the space, like a high speed lazy river. They could hop off any time once they reach their destination.

I was planning on using vectors and adapting some steering behaviors code from The Nature of Code series (which is based on Craig Reynolds’ steering behaviors). I would accelerate the user once they’re on the path and keep them on the path using steering, until they move themselves off.

I am curious if there is a better way to do this and determine the pathway. I checked out Don McCurdy’s Three.js pathfinding work, but it requires that you build an external mesh in something like Blender and import that into Three.js, which I haven’t been able to do yet. A lot of Blender’s options have also changed since he wrote the tutorials, so it’s hard to map.

I’m all ears if anyone has suggestions –

Billy

javascript – How to inform pathfinding of solid objects blocking path?

I’m sure this has been asked before, but I couldn’t find the thread. Let me know if you can locate the duplicate.

So, I have a my level is an array of tiles, where a tile can either be solid or walkable. I then use A-star to path-find my enemies from one location of the floor to another.

The issue is that I also have solid entities. As an example, I may spawn 3 breakable vases at a certain location. They can be larger or smaller than a single tile, and they are just objects in the world, not tiles themselves.

Because they are not a part of the A-star grid (can they? they’re larger or smaller than a tile, and their position is not locked to the grid), the path-finding is not aware of their existence, since it only operates on its grid. As a result, sometimes the chosen path for the enemy to follow flows through the solid vases, so the enemy just gets stuck at that location.

Is there a solution that alleviates this problem? I want the enemies to path around the vases (until they’re destroyed, of course), instead of just getting stuck trying to unsuccessfully move through them.

typescript – Animating Pathfinding Generically

This is part of a project I built to visualise pathfinding algorithms. These files run the algorithms and gather a list of frames, which are used to show the animations.

algorithms.ts

export type Algorithm = (start: Coord, goal: Coord, walls: boolean()(), weights: number()(), heuristic: string) => GridFrame();

export function breadthFirstSearch(start: Coord, goal: Coord, walls: boolean()(), weights: number()(), _: string) {
    const queue = new Queue<Coord>();
    const distances = new HashMap<Coord, number>();

    return genericUnidirectionalSearch(start, goal, queue, distances, weights, walls);
}

export function depthFirstSearch(start: Coord, goal: Coord, walls: boolean()(), weights: number()(), _: string) {
    const stack = new Stack<Coord>();
    const distances = new HashMap<Coord, number>();

    return genericUnidirectionalSearch(start, goal, stack, distances, weights, walls);
}

export function bestFirstSearch(start: Coord, goal: Coord, walls: boolean()(), weights: number()(), heuristic: string) {
    const heuristicComparator = stringToHeuristic.get(heuristic)(goal);
    const priorityQueue = new PriorityQueue(heuristicComparator);
    const distances = new HashMap<Coord, number>();

    return genericUnidirectionalSearch(start, goal, priorityQueue, distances, weights, walls);
}

export function dijkstra(start: Coord, goal: Coord, walls: boolean()(), weights: number()(), _: string) {
    const distances = new HashMap<Coord, number>();
    const distanceComparator = generateDijkstraComparator(distances);
    const priorityQueue = new PriorityQueue(distanceComparator);

    return genericUnidirectionalSearch(start, goal, priorityQueue, distances, weights, walls);
}

export function aStar(start: Coord, goal: Coord, walls: boolean()(), weights: number()(), heuristic: string) {
    const distances = new HashMap<Coord, number>();
    const comparator = generateAStarComparator(goal, distances, heuristic);
    const priorityQueue = new PriorityQueue(comparator);

    return genericUnidirectionalSearch(start, goal, priorityQueue, distances, weights, walls);
}

export function randomSearch(start: Coord, goal: Coord, walls: boolean()(), weights: number()(), _: string) {
    const distances = new HashMap<Coord, number>();
    const comparator = generateRandomComparator();
    const priorityQueue = new PriorityQueue(comparator);

    return genericUnidirectionalSearch(start, goal, priorityQueue, distances, weights, walls);
}

export function bidirectionalBFS(start: Coord, goal: Coord, walls: boolean()(), weights: number()(), _: string) {
    const forwardsDistances = new HashMap<Coord, number>();
    const backwardsDistances = new HashMap<Coord, number>();
    const forwardQueue = new Queue<Coord>();
    const backwardQueue = new Queue<Coord>();

    return genericBidirectionalSearch(forwardQueue, backwardQueue, forwardsDistances, backwardsDistances, start, goal, weights, walls);
}

export function bidirectionalDFS(start: Coord, goal: Coord, walls: boolean()(), weights: number()(), _: string) {
    const forwardsDistances = new HashMap<Coord, number>();
    const backwardsDistances = new HashMap<Coord, number>();
    const forwardsStack = new Stack<Coord>();
    const backwardStack = new Stack<Coord>();

    return genericBidirectionalSearch(forwardsStack, backwardStack, forwardsDistances, backwardsDistances, start, goal, weights, walls);
}

export function bidirectionalGBFS(start: Coord, goal: Coord, walls: boolean()(), weights: number()(), heuristic: string) {
    const forwardsDistances = new HashMap<Coord, number>();
    const backwardsDistances = new HashMap<Coord, number>();
    const forwardsComparator = stringToHeuristic.get(heuristic)(goal);
    const backwardsComparator = stringToHeuristic.get(heuristic)(start);
    const forwardsQueue = new PriorityQueue<Coord>(forwardsComparator);
    const backwardQueue = new PriorityQueue<Coord>(backwardsComparator);

    return genericBidirectionalSearch(forwardsQueue, backwardQueue, forwardsDistances, backwardsDistances, start, goal, weights, walls);
}

export function bidirectionalDijkstra(start: Coord, goal: Coord, walls: boolean()(), weights: number()(), heuristic: string) {
    const forwardsDistances = new HashMap<Coord, number>();
    const backwardsDistances = new HashMap<Coord, number>();
    const forwardsComparator = generateDijkstraComparator(forwardsDistances);
    const backwardsComparator = generateDijkstraComparator(backwardsDistances);
    const forwardsQueue = new PriorityQueue<Coord>(forwardsComparator);
    const backwardQueue = new PriorityQueue<Coord>(backwardsComparator);

    return genericBidirectionalSearch(forwardsQueue, backwardQueue, forwardsDistances, backwardsDistances, start, goal, weights, walls);
}

export function bidirectionalAStar(start: Coord, goal: Coord, walls: boolean()(), weights: number()(), heuristic: string) {
    const forwardsDistances = new HashMap<Coord, number>();
    const backwardsDistances = new HashMap<Coord, number>();
    const forwardsComparator = generateAStarComparator(goal, forwardsDistances, heuristic);
    const backwardsComparator = generateAStarComparator(start, backwardsDistances, heuristic);
    const forwardsQueue = new PriorityQueue<Coord>(forwardsComparator);
    const backwardQueue = new PriorityQueue<Coord>(backwardsComparator);

    return genericBidirectionalSearch(forwardsQueue, backwardQueue, forwardsDistances, backwardsDistances, start, goal, weights, walls);
}

export function bidirectionalRandom(start: Coord, goal: Coord, walls: boolean()(), weights: number()(), heuristic: string) {
    const forwardsDistances = new HashMap<Coord, number>();
    const backwardsDistances = new HashMap<Coord, number>();
    const comparator = generateRandomComparator();
    const forwardsQueue = new PriorityQueue<Coord>(comparator);
    const backwardQueue = new PriorityQueue<Coord>(comparator);

    return genericBidirectionalSearch(forwardsQueue, backwardQueue, forwardsDistances, backwardsDistances, start, goal, weights, walls);
}

genericPathfinding.ts


// Generic pathfinding algo for searching from a source. Parameterized with the data structure used to make it generic
export function genericUnidirectionalSearch(
    start: Coord,
    goal: Coord,
    agenda: Collection<Coord>,
    distances: HashMap<Coord, number>,
    weights: number()(),
    walls: boolean()()) {

    const path = new HashMap<Coord, Coord>();
    const visited = initialseGridWith(false);
    const considered = initialseGridWith(false);
    const frames: GridFrame() = ();

    agenda.add(start);
    visited(start.row)(start.col) = true;
    distances.add(start, 0);

    while (!agenda.isEmpty()) {
        const isFound = considerNextNode(path, visited, agenda, goal, weights, considered, walls, distances, frames);

        if (isFound) {
            const finalPath = pathMapToGrid(path, goal);

            createFinalPathFrame(finalPath, visited, considered, frames);
            break;
        }
    }

    return frames;
}

// Generic pathfinding algo for searching from both the source and goal concurrently
export function genericBidirectionalSearch(
    forwardAgenda: Collection<Coord>,
    backwardAgenda: Collection<Coord>,
    forwardDistances: HashMap<Coord, number>,
    backwardDistances: HashMap<Coord, number>,
    start: Coord,
    goal: Coord,
    weights: number()(),
    walls: boolean()()) {

    const forwardPath = new HashMap<Coord, Coord>();
    const backwardPath = new HashMap<Coord, Coord>();

    const forwardVisited = initialseGridWith(false);
    const backwardVisited = initialseGridWith(false);

    const forwardConsidered = initialseGridWith(false);
    const backwardConsidered = initialseGridWith(false);

    const frames: GridFrame() = ();

    forwardDistances.add(start, 0);
    backwardDistances.add(goal, 0);

    forwardAgenda.add(start);
    backwardAgenda.add(goal);

    forwardVisited(start.row)(start.col) = true;
    backwardVisited(goal.row)(goal.col) = true;

    while (!forwardAgenda.isEmpty() && !backwardAgenda.isEmpty()) {
        const foundForwards = considerNextNode(forwardPath, forwardVisited, forwardAgenda, goal, weights, forwardConsidered, walls, forwardDistances, frames);
        const foundBackwards = considerNextNode(backwardPath, backwardVisited, backwardAgenda, start, weights, backwardConsidered, walls, backwardDistances, frames);

        mergeFrames(frames);

        if (foundForwards) {
            const finalPath = pathMapToGrid(forwardPath, goal);
            const mergedVisited = mergeGrids(forwardVisited, backwardVisited);
            const mergedConsidered = mergeGrids(forwardConsidered, backwardConsidered);

            createFinalPathFrame(finalPath, mergedVisited, mergedConsidered, frames);
            break;
        } else if (foundBackwards) {
            const finalPath = pathMapToGrid(backwardPath, start);
            const mergedVisited = mergeGrids(forwardVisited, backwardVisited);
            const mergedConsidered = mergeGrids(forwardConsidered, backwardConsidered);

            createFinalPathFrame(finalPath, mergedVisited, mergedConsidered, frames);
            break;
        } else if (hasIntersection(forwardConsidered, backwardConsidered)) {
            const intersection = getIntersection(forwardConsidered, backwardConsidered);
            const finalPath = mergeGrids(pathMapToGrid(forwardPath, intersection), pathMapToGrid(backwardPath, intersection));
            const mergedVisited = mergeGrids(forwardVisited, backwardVisited);
            const mergedConsidered = mergeGrids(forwardConsidered, backwardConsidered);

            createFinalPathFrame(finalPath, mergedVisited, mergedConsidered, frames);
            break;
        }
    }

    return frames;
}

// Generic function for making one step in a pathfinding algorithm
function considerNextNode(
    path: HashMap<Coord, Coord>,
    visited: boolean()(),
    agenda: Collection<Coord>,
    goal: Coord,
    weights: number()(),
    considered: boolean()(),
    walls: boolean()(),
    distances: HashMap<Coord, number>,
    frames: GridFrame()) {

    const currentPos = agenda.remove();
    const currentNeighbours = generateNeighbours(currentPos);

    considered(currentPos.row)(currentPos.col) = true;
    frames.push(generateGridFrame(visited, considered, ()));

    if (isSameCoord(currentPos, goal)) {
        return true;
    }

    currentNeighbours.forEach(neighbour => {
        if (!isOutOfBounds(neighbour) && !walls(neighbour.row)(neighbour.col)) {
            const neighbourDistance = distances.get(currentPos) + weights(neighbour.row)(neighbour.col);

            if (!visited(neighbour.row)(neighbour.col)) {
                distances.add(neighbour, neighbourDistance);
                agenda.add(neighbour);
                visited(neighbour.row)(neighbour.col) = true;
                path.add(neighbour, currentPos);
            }
        }
    });

    return false;
}

// Given two boolean grids, for all tiles, take the logical OR and return a new grid
function mergeGrids(grid: boolean()(), matrix: boolean()()) {
    const mergedGrid = initialseGridWith(false);

    for (let row = 0; row < HEIGHT; row++) {
        for (let col = 0; col < WIDTH; col++) {
            mergedGrid(row)(col) = grid(row)(col) || matrix(row)(col);
        }
    }

    return mergedGrid;
}

// Merge two animation frames to produce a frame that contains the information encoded in both
function mergeFrames(frames: GridFrame()) {
    const backwardsFrame = frames.pop();
    const forwardsFrame = frames.pop();
    const mergedFrame = initialseGridWith(TileFrame.Blank);

    for (let row = 0; row < HEIGHT; row++) {
        for (let col = 0; col < WIDTH; col++) {
            if (forwardsFrame(row)(col) === TileFrame.Searching || backwardsFrame(row)(col) === TileFrame.Searching) {
                mergedFrame(row)(col) = TileFrame.Searching;
            } else if (forwardsFrame(row)(col) === TileFrame.Frontier || backwardsFrame(row)(col) === TileFrame.Frontier) {
                mergedFrame(row)(col) = TileFrame.Frontier;
            }
        }
    }

    frames.push(mergedFrame);
}

// Add a frame containing the final path to the list
function createFinalPathFrame(path: boolean()(), visited: boolean()(), considered: boolean()(), frames: GridFrame()) {
    frames.push(generateGridFrame(visited, considered, path));
}

// Convert the hashmap pointer based path to a boolean grid based path
function pathMapToGrid(pathMap: HashMap<Coord, Coord>, goal: Coord) {
    const path = initialseGridWith(false);
    let pos = goal;

    while (pathMap.get(pos) !== undefined) {
        path(pos.row)(pos.col) = true;

        pos = pathMap.get(pos);
    }

    return path;
}

// Encode the state of a pathfinding algorithm into a frame 
function generateGridFrame(visited: boolean()(), considered: boolean()(), path: boolean()()) {
    const grid = initialseGridWith(TileFrame.Blank);

    for (let row = 0; row < HEIGHT; row++) {
        for (let col = 0; col < WIDTH; col++) {
            if (path.length > 0 && path(row)(col)) {
                grid(row)(col) = TileFrame.Path;
            } else if (considered(row)(col)) {
                grid(row)(col) = TileFrame.Searching;
            } else if (visited(row)(col)) {
                grid(row)(col) = TileFrame.Frontier;
            }
        }
    }

    return grid;
}

Comparators.ts


type Heuristic = (goal: Coord) => (c1: Coord, c2: Coord) => number;

// Map a heuristics JSX representation onto its implementation 
export const stringToHeuristic = new Map<string, Heuristic>((
    ("manhattan", generateManhattanComparator),
    ("euclidean", generateEuclideanComparator),
    ("chebyshev", generateChebyshevComparator)
));

// Return a comparator that compares by adding the distance from the starting tile and estimated distance from the goal
export function generateAStarComparator(goal: Coord, distances: HashMap<Coord, number>, heuristic: string) {
    return (c1: Coord, c2: Coord) => {
        const heuristicGenerator = stringToHeuristic.get(heuristic);
        const dijkstraComparison = generateDijkstraComparator(distances)(c1, c2);
        const heuristicComparison = heuristicGenerator(goal)(c1, c2);

        return dijkstraComparison + heuristicComparison;
    }
}

// Returns a comparator that compares using the distance from the starting tile
export function generateDijkstraComparator(distances: HashMap<Coord, number>) {
    return (c1: Coord, c2: Coord) =>
        distances.get(c2) - distances.get(c1);
}

// Returns a comparator that selects an item randomly
export function generateRandomComparator() {
    return (c1: Coord, c2: Coord) => Math.random() - 0.6;
}

// Given two coordinates (p1, p2) and (g1, g2), return comparator that compares using formula max(abs(p1 - g1), abs(p2 - g2))
function generateChebyshevComparator({ row: goalRow, col: goalCol }: Coord) {
    return ({ row: r1, col: c1 }: Coord, { row: r2, col: c2 }: Coord) =>
        Math.max(Math.abs(r2 - goalRow), Math.abs(c2 - goalCol)) - Math.max(Math.abs(r1 - goalRow), Math.abs(c1 - goalCol));

}

// Given two coordinates (p1, p2) and (g1, g2), return comparator that compares using formula sqrt((p1 - g1)^2 + abs(p2 - g2)^2)
function generateEuclideanComparator({ row: goalRow, col: goalCol }: Coord) {
    return ({ row: r1, col: c1 }: Coord, { row: r2, col: c2 }: Coord) => {
        const r1Dist = Math.sqrt(Math.pow(r1 - goalRow, 2) + Math.pow(c1 - goalCol, 2));
        const r2Dist = Math.sqrt(Math.pow(r2 - goalRow, 2) + Math.pow(c2 - goalCol, 2));

        return r2Dist - r1Dist;
    }
}

// Given two coordinates (p1, p2) and (g1, g2), return comparator that compares using formula abs(p1 - g1) + abs(p2 - g2) 
function generateManhattanComparator({ row: goalRow, col: goalCol }: Coord) {
    return ({ row: r1, col: c1 }: Coord, { row: r2, col: c2 }: Coord) =>
        (Math.abs(r2 - goalRow) + Math.abs(c2 - goalCol)) - (Math.abs(r1 - goalRow) + Math.abs(c1 - goalCol));

}

How to perform right-angle pathfinding?

I’m developing a procedural dungeon generator using pre-built rooms and currently I’m using A* pathing to create corridors to connect one room’s door to another room’s door.

The problem with this is that the path is very jagged, like this:

enter image description here

whereas I’d prefer it to look like this:

enter image description here

It seems simple enough if only one turn is required, but say it has to connect to the bottom side of the 2nd rectangle, then another turn will be required. Is there any way to do some sort of pathfinding that prefers as straight of lines as possible?

shaders – Using Jump Flooding Alghorithm for pathfinding?

I am creating a distance-to-river texture. I am using the Jump-Flood Alghorithm in a compute shader to do this using simple distance() and it works well. See code below.
I want to augment this by adding blockers/walls so raw distance-to-seed might be blocked by pixels inbetween. I need to adapt it to a more pathfinding alghorithm.

Is this possible to do with JFA? Or do I need some other solution?

cbuffer JFAConstants : register( CBUFFER_REGISTER_EXTRA )
{
    int2 gSampleOffset;
}

RWTexture2D<uint2> gJumpFloodRiverMap : register( UAV_REGISTER_0 );

void UpdateClosest( inout int2 currentValue, int2 currentTexcoord, int2 jumpTexcoord )
{
    int2 jumpValue = gJumpFloodRiverMap( jumpTexcoord );
    if ( IsZero( jumpValue ) )
    {
        return;
    }

    float jumpDistance = distance( (float2)currentTexcoord, (float2)jumpValue );
    if ( IsZero( currentValue ) )
    {
        gJumpFloodRiverMap( currentTexcoord ) = jumpValue;
        currentValue = jumpValue;
        return;
    }

    float currentDistance = distance( (float2)currentTexcoord, (float2)currentValue );
    if ( jumpDistance < currentDistance )
    {
        gJumpFloodRiverMap( currentTexcoord ) = jumpValue;
        currentValue = jumpValue;
    }
}

(numthreads(TERRAIN_NORMAL_THREADS_AXIS, TERRAIN_NORMAL_THREADS_AXIS, 1))
void cs_main(uint3 groupID : SV_GroupID, uint3 dispatchTID : SV_DispatchThreadID, uint3 groupTID : SV_GroupThreadID, uint groupIndex : SV_GroupIndex)
{
    int2 texCoord = dispatchTID.xy;
    int2 currentValue = gJumpFloodRiverMap( texCoord );

    int2 texCoordT = texCoord + int2( 0, gSampleOffset.y );
    int2 texCoordTR = texCoord + gSampleOffset;
    int2 texCoordR = texCoord + int2( gSampleOffset.x, 0 );
    int2 texCoordBR = texCoord + int2( gSampleOffset.x, -gSampleOffset.y );
    int2 texCoordB = texCoord + int2( 0, -gSampleOffset.y );
    int2 texCoordBL = texCoord - gSampleOffset;
    int2 texCoordL = texCoord + int2( -gSampleOffset.x, 0 );
    int2 texCoordTL = texCoord + int2( -gSampleOffset.x, gSampleOffset.y );

    UpdateClosest( currentValue, texCoord, texCoordT );
    UpdateClosest( currentValue, texCoord, texCoordTR );
    UpdateClosest( currentValue, texCoord, texCoordR );
    UpdateClosest( currentValue, texCoord, texCoordBR );
    UpdateClosest( currentValue, texCoord, texCoordB );
    UpdateClosest( currentValue, texCoord, texCoordBL );
    UpdateClosest( currentValue, texCoord, texCoordL );
    UpdateClosest( currentValue, texCoord, texCoordTL );
}

path finding – Flowfield pathfinding behind obstacles on symmetrical grid

I use Fast iterative method to create my flow field. However when an obstacle is on the same axis as the destination, the vectors behind the obstacle point directly to the destination and ignore the obstacle. I think the following image describes the problem best:

Blue is the destination
Red rectangle is an obstacle (black dots mark blocked cells)

On the middle left you can see how the flow field points directly to the obstacle.

enter image description here

I think this is due to the ‘nature’ of the eikonal equation, creating the same distance for upward/downward direction in the middle behind the obstacle. I calculate the vector by:

x = T(x – 1) – T(x + 1)
y = T(y – 1) – T(y + 1)

I currently solve it by post-processing the field and fix cells behind obstacles that point to the destination. However I’d like to know if there is a better method.