├── 2DTest.php ├── README ├── libNodeGraph.2D.php ├── libNodeGraph.Waypoints.php ├── libNodeGraph.php └── libPathfinding.php /2DTest.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 56 | /s/ervices 57 | 58 | 59 | 60 | Set(Array( 67 | Array(20, 4, 255), 68 | Array(20, 5, 255), 69 | Array(20, 6, 255), 70 | Array(20, 7, 255), 71 | Array(20, 8, 255), 72 | Array(20, 10, 255), 73 | Array(20, 12, 255), 74 | Array(21, 4, 255), 75 | Array(21, 10, 255), 76 | Array(21, 12, 255), 77 | Array(22, 4, 255), 78 | Array(22, 5, 255), 79 | Array(22, 6, 255), 80 | Array(22, 7, 255), 81 | Array(22, 9, 255), 82 | Array(22, 10, 255), 83 | Array(22, 12, 255), 84 | Array(23, 4, 255), 85 | Array(23, 10, 255), 86 | Array(23, 12, 255), 87 | Array(24, 4, 255), 88 | Array(24, 10, 255), 89 | Array(24, 12, 255), 90 | Array(25, 4, 255), 91 | Array(25, 5, 255), 92 | Array(25, 6, 255), 93 | Array(25, 7, 255), 94 | Array(25, 8, 255), 95 | Array(25, 9, 255), 96 | Array(25, 10, 255), 97 | Array(25, 12, 255), 98 | Array(25, 13, 255), 99 | Array(25, 14, 255), 100 | Array(25, 15, 255), 101 | Array(25, 16, 255), 102 | Array(25, 17, 255), 103 | Array(25, 18, 255), 104 | Array(25, 19, 255), 105 | Array(25, 20, 255), 106 | )); 107 | 108 | $Start = $NodeGraph->Random(); 109 | $End = $NodeGraph->Random(); 110 | while($NodeGraph->H($Start, $End) < 25) 111 | $End = $NodeGraph->Random(); 112 | 113 | 114 | $t0 = microtime(true); 115 | $PathFinder = new PathFinding($NodeGraph); 116 | $t1 = microtime(true); 117 | $Path = $PathFinder->Find($Start, $End); 118 | $t2 = microtime(true); 119 | 120 | 121 | if($Path) { 122 | $Rendered = Array(); 123 | $Cache = $PathFinder->Cache; 124 | foreach($Cache as $Node => $Caching) { 125 | list($X, $Y) = $NodeGraph->Node2XY($Node); 126 | if($Caching['Status'] == 1) 127 | $Rendered[$X][$Y] = 'Open Dir'.$NodeGraph->Direction($Caching['Parent'], $Node); 128 | else if($Caching['Status'] == 2) 129 | $Rendered[$X][$Y] = 'Close Dir'.$NodeGraph->Direction($Caching['Parent'], $Node); 130 | } 131 | 132 | foreach($Path as $Node) { 133 | list($X, $Y) = $NodeGraph->Node2XY($Node); 134 | $Rendered[$X][$Y] = 'Path Dir'.$NodeGraph->Direction($PathFinder->Cache[$Node]['Parent'], $Node); 135 | } 136 | 137 | list($X, $Y) = $NodeGraph->Node2XY($Start); 138 | $Rendered[$X][$Y] = 'Start'; 139 | list($X, $Y) = $NodeGraph->Node2XY($End); 140 | $Rendered[$X][$Y] = 'End Dir'.$NodeGraph->Direction($PathFinder->Cache[$Node]['Parent'], $End); 141 | 142 | ?>Tiles; 145 | foreach($Tiles as $X => $Rows) { 146 | foreach($Rows as $Y => $Value) { 147 | if(isset($Rendered[$X][$Y])) 148 | $Table[$Y][$X] = $Rendered[$X][$Y]; 149 | else $Table[$Y][$X] = $Value == 255 ? 'Wall' : 'Floor'; 150 | } 151 | } 152 | 153 | foreach($Table as $Y => $Cols) { 154 | ?> $Class) 156 | ?>
Pathfinding in ".round(($t2-$t1)*1000)."msec


"; 163 | 164 | foreach($Path as &$P) $P = implode(' x ',$NodeGraph->Node2XY($P));// For easier debugging. 165 | echo '
Path = '.print_r($Path, TRUE).'

'; 166 | //echo '
$PathFinder = '.print_r($PathFinder->Cache, TRUE).'

'; 167 | } else { 168 | ?>

Path not found.


Debug?>

'.print_r($PathFinder, TRUE).''; 170 | } 171 | ?> 172 | 173 | 174 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | This is a pathfinding library that uses a flexible design so that it can be run with any navigation graph desirable, e.g. 2D tile grid, navigation meshes, etc. 3 | 4 | Waypoint and 2D grid graphs are provided. 5 | 6 | The pathfinding is the A* algorithm. 7 | 8 | 9 | 10 | It is licensed under a modified MIT License (the clause for requiring license to be included in all copies was removed) -------------------------------------------------------------------------------- /libNodeGraph.2D.php: -------------------------------------------------------------------------------- 1 | SX = $SizeX; 33 | $this->SY = $SizeY; 34 | $this->Tiles = array_fill(0, $SizeX, array_fill(0, $SizeY, 0.0)); 35 | } 36 | 37 | function XY2Node($X, $Y) { 38 | return ($Y * $this->SX) + $X; 39 | } 40 | 41 | function Node2XY($Lookup) { 42 | $X = $Lookup % $this->SX; 43 | $Y = (int)($Lookup / $this->SX); 44 | return Array($X, $Y); 45 | } 46 | 47 | 48 | 49 | /// Methods for debugging-ish 50 | function Set($NewTiles) { 51 | foreach($NewTiles as $NewTile) 52 | $this->Tiles[$NewTile[0]][$NewTile[1]] = $NewTile[2]; 53 | } 54 | 55 | function Random() { 56 | $X = rand(1, $this->SX-1); 57 | $Y = rand(1, $this->SY-1); 58 | if($this->Tiles[$X][$Y] == 255) return $this->Random(); 59 | return $this->XY2Node($X, $Y); 60 | } 61 | 62 | function Direction($NodeFrom, $NodeTo) { 63 | list($FX, $FY) = $this->Node2XY($NodeFrom); 64 | list($TX, $TY) = $this->Node2XY($NodeTo); 65 | $Dirs = Array( 66 | -1 => Array(-1 => 7, 0 => 3, 1 => 6), 67 | 0 => Array(-1 => 0, 0 => 'C', 1 => 2), 68 | 1 => Array(-1 => 4, 0 => 1, 1 => 5) 69 | ); 70 | return $Dirs[$TX-$FX][$TY-$FY]; 71 | } 72 | 73 | 74 | 75 | /// Pathfinding-related stuff 76 | function Neighbours($Node) { 77 | list($X, $Y) = $this->Node2XY($Node); 78 | 79 | $Neighbours = Array(); 80 | for($i = 0; $i < 8; ++$i) { 81 | $NX = $X + $this->Directions[$i][0]; 82 | $NY = $Y + $this->Directions[$i][1]; 83 | 84 | if($NX < 0 || $NY < 0 || $NX >= $this->SX || $NY >= $this->SY) 85 | continue; 86 | 87 | if($this->Tiles[$NX][$NY] == 255) 88 | continue; 89 | 90 | $Neighbours[] = ($NY * $this->SX) + $NX; 91 | } 92 | 93 | return $Neighbours; 94 | } 95 | 96 | function G($NodeFrom, $NodeTo) { 97 | // Assumes we are being given a neighbour, as expected. 98 | list($FX, $FY) = $this->Node2XY($NodeFrom); 99 | list($TX, $TY) = $this->Node2XY($NodeTo); 100 | $G = $this->Tiles[$TX][$TY]; 101 | if($FX == $TX || $FY == $TY) 102 | $G += $this->Movement_Horizontally; 103 | else $G += $this->Movement_Diagonally; 104 | return $G; 105 | } 106 | 107 | function H($NodeFrom, $NodeTo) { 108 | list($FX, $FY) = $this->Node2XY($NodeFrom); 109 | list($TX, $TY) = $this->Node2XY($NodeTo); 110 | return sqrt(pow($TX - $FX, 2) + pow($TY - $FY, 2)); 111 | } 112 | } 113 | ?> -------------------------------------------------------------------------------- /libNodeGraph.Waypoints.php: -------------------------------------------------------------------------------- 1 | Nodes = $Nodes; 18 | } 19 | 20 | 21 | /// Other Methods 22 | function Random() { 23 | $Node = array_rand($this->Nodes); 24 | return $Node; 25 | } 26 | 27 | function Pos($Node) { 28 | return $this->Nodes[$Node]['Pos']; 29 | } 30 | 31 | function Closest($PosFrom) { 32 | $Distance = 99999999.; 33 | $Closest = NULL; 34 | foreach($this->Nodes as $Node => $Contents) { 35 | $PosTo = $Contents['Pos']; 36 | $D = $this->VecDist($PosFrom, $PosTo); 37 | if($D < $Distance) { 38 | $Closest = $Node; 39 | $Distance = $D; 40 | } 41 | } 42 | return $Closest; 43 | } 44 | 45 | function VecDist($a, $b) { 46 | return sqrt(pow($b[0] - $a[0], 2) + pow($b[1] - $a[1], 2) + pow($b[2] - $a[2], 2)); 47 | } 48 | 49 | 50 | /// Pathfinding-related stuff 51 | function Neighbours($Node) { 52 | return $this->Nodes[$Node]['Neighbours']; 53 | } 54 | 55 | function G($NodeFrom, $NodeTo) { 56 | $PosFrom = $this->Nodes[$NodeFrom]['Pos']; 57 | $PosTo = $this->Nodes[$NodeTo]['Pos']; 58 | return $this->VecDist($PosFrom, $PosTo); 59 | } 60 | 61 | function H($NodeFrom, $NodeTo) { 62 | $PosFrom = $this->Nodes[$NodeFrom]['Pos']; 63 | $PosTo = $this->Nodes[$NodeTo]['Pos']; 64 | return $this->VecDist($PosFrom, $PosTo); 65 | } 66 | } 67 | ?> -------------------------------------------------------------------------------- /libNodeGraph.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libPathfinding.php: -------------------------------------------------------------------------------- 1 | $b) return -1; 10 | return 0; 11 | } 12 | } 13 | 14 | 15 | define('STATUS_UNTOUCHED', 0); 16 | define('STATUS_OPEN', 1); 17 | define('STATUS_CLOSED', 2); 18 | 19 | 20 | class PathFinding { 21 | public $Graph; 22 | public $Limit = 750; 23 | public $Cache; 24 | public $Debug; 25 | 26 | function __construct(&$Graph) { 27 | $this->Graph = &$Graph; 28 | } 29 | 30 | function Find($NodeStart, $NodeEnd) { 31 | $Queue = new PriorityQueue(); // Open Nodes ordered based on F cost 32 | $Queue->setExtractFlags(PriorityQueue::EXTR_DATA); 33 | 34 | $Closed = 0; 35 | $Found = FALSE; 36 | $this->Debug = ''; 37 | 38 | $this->Cache = Array( // Open and Closed Nodes. Stores calculated costs and parent nodes. 39 | $NodeStart => Array( 40 | 'G' => 0, 41 | 'F' => 0, 42 | 'Parent' => $NodeStart, 43 | 'Status' => STATUS_OPEN 44 | ) 45 | ); 46 | $Queue->insert($NodeStart, $this->Cache[$NodeStart]['F']); 47 | 48 | while(!$Queue->isEmpty()) { 49 | $Node = $Queue->extract(); 50 | 51 | if($this->Cache[$Node]['Status'] == STATUS_CLOSED) 52 | continue; 53 | 54 | if($Node == $NodeEnd) { 55 | $this->Cache[$Node]['Status'] = STATUS_CLOSED; 56 | $Found = TRUE; 57 | break; 58 | } 59 | 60 | if($Closed > $this->Limit) { 61 | $this->Debug = 'Hit limit. ('.$this->Limit.')'; 62 | return NULL; 63 | } 64 | 65 | $Neighbours = $this->Graph->Neighbours($Node); 66 | foreach($Neighbours as $Neighbour) { 67 | $G = $this->Cache[$Node]['G'] + $this->Graph->G($Node, $Neighbour); 68 | 69 | if( isset($this->Cache[$Neighbour]) 70 | && $this->Cache[$Neighbour]['Status'] 71 | && $this->Cache[$Neighbour]['G'] <= $G 72 | ) continue; 73 | 74 | $F = $G + $this->Graph->H($Neighbour, $NodeEnd); 75 | 76 | $this->Cache[$Neighbour] = Array( 77 | 'G' => $G, 78 | 'F' => $F, 79 | 'Parent' => $Node, 80 | 'Status' => STATUS_OPEN 81 | ); 82 | $Queue->insert($Neighbour, $F); 83 | } 84 | ++$Closed; 85 | $this->Cache[$Node]['Status'] = STATUS_CLOSED; 86 | } 87 | 88 | if($Found) { 89 | $Path = Array(); 90 | $Node = $NodeEnd; 91 | while($NodeStart != $Node) { 92 | $Path[] = $Node; 93 | $Node = $this->Cache[$Node]['Parent']; 94 | } 95 | $Path[] = $Node; 96 | return array_reverse($Path); 97 | } 98 | $this->Debug = 'Path not found, ran out of open nodes.'; 99 | return NULL; 100 | } 101 | } 102 | ?> --------------------------------------------------------------------------------