Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
99.66% |
291 / 292 |
|
91.67% |
11 / 12 |
CRAP | |
0.00% |
0 / 1 |
JumpPointSearch | |
99.66% |
291 / 292 |
|
91.67% |
11 / 12 |
174 | |
0.00% |
0 / 1 |
findPath | |
96.00% |
24 / 25 |
|
0.00% |
0 / 1 |
7 | |||
identifySuccessors | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
8 | |||
findNeighbors | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
findNeighborsStraight | |
100.00% |
24 / 24 |
|
100.00% |
1 / 1 |
10 | |||
findNeighborsDiagonal | |
100.00% |
34 / 34 |
|
100.00% |
1 / 1 |
16 | |||
findNeighborsDiagonalOneObstacle | |
100.00% |
34 / 34 |
|
100.00% |
1 / 1 |
19 | |||
findNeighborsDiagonalNoObstacle | |
100.00% |
45 / 45 |
|
100.00% |
1 / 1 |
22 | |||
jump | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
jumpStraight | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
17 | |||
jumpDiagonal | |
100.00% |
24 / 24 |
|
100.00% |
1 / 1 |
22 | |||
jumpDiagonalOneObstacle | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
24 | |||
jumpDiagonalNoObstacle | |
100.00% |
24 / 24 |
|
100.00% |
1 / 1 |
21 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package phpOMS\Algorithm\PathFinding |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | * |
13 | * Extended based on: |
14 | * MIT License |
15 | * (c) 2011-2012 Xueqiao Xu <xueqiaoxu@gmail.com> |
16 | * (c) PathFinding.js |
17 | */ |
18 | declare(strict_types=1); |
19 | |
20 | namespace phpOMS\Algorithm\PathFinding; |
21 | |
22 | use phpOMS\Stdlib\Base\Heap; |
23 | |
24 | /** |
25 | * Perform path finding. |
26 | * |
27 | * @package phpOMS\Algorithm\PathFinding |
28 | * @license OMS License 2.0 |
29 | * @link https://jingga.app |
30 | * @since 1.0.0 |
31 | */ |
32 | final class JumpPointSearch implements PathFinderInterface |
33 | { |
34 | /** |
35 | * {@inheritdoc} |
36 | */ |
37 | public static function findPath( |
38 | int $startX, int $startY, |
39 | int $endX, int $endY, |
40 | Grid $grid, |
41 | int $heuristic, int $movement |
42 | ) : Path |
43 | { |
44 | /** @var null|JumpPointNode $startNode */ |
45 | $startNode = $grid->getNode($startX, $startY); |
46 | /** @var null|JumpPointNode $endNode */ |
47 | $endNode = $grid->getNode($endX, $endY); |
48 | |
49 | if ($startNode === null || $endNode === null) { |
50 | return new Path($grid); |
51 | } |
52 | |
53 | $startNode->setG(0.0); |
54 | $startNode->setF(0.0); |
55 | $startNode->setOpened(true); |
56 | |
57 | $openList = new Heap(function($node1, $node2) { |
58 | return $node1->getF() - $node2->getF(); |
59 | }); |
60 | |
61 | $openList->push($startNode); |
62 | $node = null; |
63 | |
64 | while (!$openList->isEmpty()) { |
65 | $node = $openList->pop(); |
66 | if ($node === null) { |
67 | break; |
68 | } |
69 | |
70 | /** @var JumpPointNode $node */ |
71 | $node->setClosed(true); |
72 | |
73 | if ($node->isEqual($endNode)) { |
74 | break; |
75 | } |
76 | |
77 | $openList = self::identifySuccessors($node, $grid, $heuristic, $movement, $endNode, $openList); |
78 | } |
79 | |
80 | $path = new Path($grid); |
81 | |
82 | while ($node !== null) { |
83 | $path->addNode($node); |
84 | $node = $node->parent; |
85 | } |
86 | |
87 | return $path; |
88 | } |
89 | |
90 | /** |
91 | * Find possible successor jump points |
92 | * |
93 | * @param JumpPointNode $node Node to find successor for |
94 | * @param Grid $grid Grid of the nodes |
95 | * @param int $heuristic Heuristic/metrics type for the distance calculation |
96 | * @param int $movement Movement type |
97 | * @param JumpPointNode $endNode End node to find path to |
98 | * @param Heap $openList Heap of open nodes |
99 | * |
100 | * @return Heap |
101 | * |
102 | * @since 1.0.0 |
103 | */ |
104 | public static function identifySuccessors(JumpPointNode $node, Grid $grid, int $heuristic, int $movement, JumpPointNode $endNode, Heap $openList) : Heap |
105 | { |
106 | /** @var JumpPointNode[] $neighbors */ |
107 | $neighbors = self::findNeighbors($node, $movement, $grid); |
108 | $neighborsLength = \count($neighbors); |
109 | |
110 | for ($i = 0; $i < $neighborsLength; ++$i) { |
111 | $neighbor = $neighbors[$i]; |
112 | |
113 | if ($neighbor === null) { |
114 | continue; |
115 | } |
116 | |
117 | $jumpPoint = self::jump($neighbor, $node, $endNode, $movement, $grid); |
118 | |
119 | if ($jumpPoint === null || $jumpPoint->isClosed()) { |
120 | continue; |
121 | } |
122 | |
123 | $d = Heuristic::metric($node->getCoordinates(), $jumpPoint->getCoordinates(), HeuristicType::OCTILE); |
124 | $ng = $node->getG() + $d; |
125 | |
126 | if (!$jumpPoint->isOpened() || $ng < $jumpPoint->getG()) { |
127 | $jumpPoint->setG($ng); |
128 | $jumpPoint->setH($jumpPoint->getH() ?? Heuristic::metric($jumpPoint->getCoordinates(), $endNode->getCoordinates(), $heuristic)); |
129 | $jumpPoint->setF($jumpPoint->getG() + $jumpPoint->getH()); |
130 | $jumpPoint->parent = $node; |
131 | |
132 | if (!$jumpPoint->isOpened()) { |
133 | $openList->push($jumpPoint); |
134 | $jumpPoint->setOpened(true); |
135 | } else { |
136 | $openList->update($jumpPoint); |
137 | } |
138 | } |
139 | } |
140 | |
141 | return $openList; |
142 | } |
143 | |
144 | /** |
145 | * Find neighbor of node |
146 | * |
147 | * @param JumpPointNode $node Node to find successor for |
148 | * @param int $movement Movement type |
149 | * @param Grid $grid Grid of the nodes |
150 | * |
151 | * @return Node[] Neighbors of node |
152 | * |
153 | * @since 1.0.0 |
154 | */ |
155 | private static function findNeighbors(JumpPointNode $node, int $movement, Grid $grid) : array |
156 | { |
157 | if ($movement === MovementType::STRAIGHT) { |
158 | return self::findNeighborsStraight($node, $grid); |
159 | } elseif ($movement === MovementType::DIAGONAL) { |
160 | return self::findNeighborsDiagonal($node, $grid); |
161 | } elseif ($movement === MovementType::DIAGONAL_ONE_OBSTACLE) { |
162 | return self::findNeighborsDiagonalOneObstacle($node, $grid); |
163 | } |
164 | |
165 | return self::findNeighborsDiagonalNoObstacle($node, $grid); |
166 | } |
167 | |
168 | /** |
169 | * Find neighbor of node |
170 | * |
171 | * @param JumpPointNode $node Node to find successor for |
172 | * @param Grid $grid Grid of the nodes |
173 | * |
174 | * @return Node[] Neighbors of node |
175 | * |
176 | * @since 1.0.0 |
177 | */ |
178 | private static function findNeighborsStraight(JumpPointNode $node, Grid $grid) : array |
179 | { |
180 | if ($node->parent === null) { |
181 | return $grid->getNeighbors($node, MovementType::STRAIGHT); |
182 | } |
183 | |
184 | $x = $node->getX(); |
185 | $y = $node->getY(); |
186 | |
187 | $px = $node->parent->getX(); |
188 | $py = $node->parent->getY(); |
189 | |
190 | /** @var int $dx */ |
191 | $dx = ($x - $px) / \max(\abs($x - $px), 1); |
192 | /** @var int $dy */ |
193 | $dy = ($y - $py) / \max(\abs($y - $py), 1); |
194 | |
195 | $neighbors = []; |
196 | if ($dx !== 0) { |
197 | if ($grid->isWalkable($x, $y - 1)) { |
198 | $neighbors[] = $grid->getNode($x, $y - 1); |
199 | } |
200 | |
201 | if ($grid->isWalkable($x, $y + 1)) { |
202 | $neighbors[] = $grid->getNode($x, $y + 1); |
203 | } |
204 | |
205 | if ($grid->isWalkable($x + $dx, $y)) { |
206 | $neighbors[] = $grid->getNode($x + $dx, $y); |
207 | } |
208 | } elseif ($dy !== 0) { |
209 | if ($grid->isWalkable($x - 1, $y)) { |
210 | $neighbors[] = $grid->getNode($x - 1, $y); |
211 | } |
212 | |
213 | if ($grid->isWalkable($x + 1, $y)) { |
214 | $neighbors[] = $grid->getNode($x + 1, $y); |
215 | } |
216 | |
217 | if ($grid->isWalkable($x, $y + $dy)) { |
218 | $neighbors[] = $grid->getNode($x, $y + $dy); |
219 | } |
220 | } |
221 | |
222 | /** @var JumpPointNode[] $neighbors */ |
223 | return $neighbors; |
224 | } |
225 | |
226 | /** |
227 | * Find neighbor of node |
228 | * |
229 | * @param JumpPointNode $node Node to find successor for |
230 | * @param Grid $grid Grid of the nodes |
231 | * |
232 | * @return Node[] Neighbors of node |
233 | * |
234 | * @since 1.0.0 |
235 | */ |
236 | private static function findNeighborsDiagonal(JumpPointNode $node, Grid $grid) : array |
237 | { |
238 | if ($node->parent === null) { |
239 | return $grid->getNeighbors($node, MovementType::DIAGONAL); |
240 | } |
241 | |
242 | $x = $node->getX(); |
243 | $y = $node->getY(); |
244 | |
245 | $px = $node->parent->getX(); |
246 | $py = $node->parent->getY(); |
247 | |
248 | /** @var int $dx */ |
249 | $dx = ($x - $px) / \max(\abs($x - $px), 1); |
250 | /** @var int $dy */ |
251 | $dy = ($y - $py) / \max(\abs($y - $py), 1); |
252 | |
253 | $neighbors = []; |
254 | if ($dx !== 0 && $dy !== 0) { |
255 | if ($grid->isWalkable($x, $y + $dy)) { |
256 | $neighbors[] = $grid->getNode($x, $y + $dy); |
257 | } |
258 | |
259 | if ($grid->isWalkable($x + $dx, $y)) { |
260 | $neighbors[] = $grid->getNode($x + $dx, $y); |
261 | } |
262 | |
263 | if ($grid->isWalkable($x + $dx, $y + $dy)) { |
264 | $neighbors[] = $grid->getNode($x + $dx, $y + $dy); |
265 | } |
266 | |
267 | if (!$grid->isWalkable($x - $dx, $y)) { |
268 | $neighbors[] = $grid->getNode($x - $dx, $y + $dy); |
269 | } |
270 | |
271 | if (!$grid->isWalkable($x, $y - $dy)) { |
272 | $neighbors[] = $grid->getNode($x + $dx, $y - $dy); |
273 | } |
274 | } elseif ($dx === 0) { |
275 | if ($grid->isWalkable($x, $y + $dy)) { |
276 | $neighbors[] = $grid->getNode($x, $y + $dy); |
277 | } |
278 | |
279 | if (!$grid->isWalkable($x + 1, $y)) { |
280 | $neighbors[] = $grid->getNode($x + 1, $y + $dy); |
281 | } |
282 | |
283 | if (!$grid->isWalkable($x - 1, $y)) { |
284 | $neighbors[] = $grid->getNode($x - 1, $y + $dy); |
285 | } |
286 | } else { |
287 | if ($grid->isWalkable($x + $dx, $y)) { |
288 | $neighbors[] = $grid->getNode($x + $dx, $y); |
289 | } |
290 | |
291 | if (!$grid->isWalkable($x, $y + 1)) { |
292 | $neighbors[] = $grid->getNode($x + $dx, $y + 1); |
293 | } |
294 | |
295 | if (!$grid->isWalkable($x, $y - 1)) { |
296 | $neighbors[] = $grid->getNode($x + $dx, $y - 1); |
297 | } |
298 | } |
299 | |
300 | /** @var JumpPointNode[] $neighbors */ |
301 | return $neighbors; |
302 | } |
303 | |
304 | /** |
305 | * Find neighbor of node |
306 | * |
307 | * @param JumpPointNode $node Node to find successor for |
308 | * @param Grid $grid Grid of the nodes |
309 | * |
310 | * @return Node[] Neighbors of node |
311 | * |
312 | * @since 1.0.0 |
313 | */ |
314 | private static function findNeighborsDiagonalOneObstacle(JumpPointNode $node, Grid $grid) : array |
315 | { |
316 | if ($node->parent === null) { |
317 | return $grid->getNeighbors($node, MovementType::DIAGONAL_ONE_OBSTACLE); |
318 | } |
319 | |
320 | $x = $node->getX(); |
321 | $y = $node->getY(); |
322 | |
323 | $px = $node->parent->getX(); |
324 | $py = $node->parent->getY(); |
325 | |
326 | /** @var int $dx */ |
327 | $dx = ($x - $px) / \max(\abs($x - $px), 1); |
328 | /** @var int $dy */ |
329 | $dy = ($y - $py) / \max(\abs($y - $py), 1); |
330 | |
331 | $neighbors = []; |
332 | if ($dx !== 0 && $dy !== 0) { |
333 | if ($grid->isWalkable($x, $y + $dy)) { |
334 | $neighbors[] = $grid->getNode($x, $y + $dy); |
335 | } |
336 | |
337 | if ($grid->isWalkable($x + $dx, $y)) { |
338 | $neighbors[] = $grid->getNode($x + $dx, $y); |
339 | } |
340 | |
341 | if ($grid->isWalkable($x, $y + $dy) || $grid->isWalkable($x + $dx, $y)) { |
342 | $neighbors[] = $grid->getNode($x + $dx, $y + $dy); |
343 | } |
344 | |
345 | if (!$grid->isWalkable($x - $dx, $y) && $grid->isWalkable($x, $y + $dy)) { |
346 | $neighbors[] = $grid->getNode($x - $dx, $y + $dy); |
347 | } |
348 | |
349 | if (!$grid->isWalkable($x, $y - $dy) && $grid->isWalkable($x + $dx, $y)) { |
350 | $neighbors[] = $grid->getNode($x + $dx, $y - $dy); |
351 | } |
352 | } elseif ($dx === 0) { |
353 | if ($grid->isWalkable($x, $y + $dy)) { |
354 | $neighbors[] = $grid->getNode($x, $y + $dy); |
355 | if (!$grid->isWalkable($x + 1, $y)) { |
356 | $neighbors[] = $grid->getNode($x + 1, $y + $dy); |
357 | } |
358 | |
359 | if (!$grid->isWalkable($x - 1, $y)) { |
360 | $neighbors[] = $grid->getNode($x - 1, $y + $dy); |
361 | } |
362 | } |
363 | } elseif ($grid->isWalkable($x + $dx, $y)) { |
364 | $neighbors[] = $grid->getNode($x + $dx, $y); |
365 | if (!$grid->isWalkable($x, $y + 1)) { |
366 | $neighbors[] = $grid->getNode($x + $dx, $y + 1); |
367 | } |
368 | |
369 | if (!$grid->isWalkable($x, $y - 1)) { |
370 | $neighbors[] = $grid->getNode($x + $dx, $y - 1); |
371 | } |
372 | } |
373 | |
374 | /** @var JumpPointNode[] $neighbors */ |
375 | return $neighbors; |
376 | } |
377 | |
378 | /** |
379 | * Find neighbor of node |
380 | * |
381 | * @param JumpPointNode $node Node to find successor for |
382 | * @param Grid $grid Grid of the nodes |
383 | * |
384 | * @return Node[] Neighbors of node |
385 | * |
386 | * @since 1.0.0 |
387 | */ |
388 | private static function findNeighborsDiagonalNoObstacle(JumpPointNode $node, Grid $grid) : array |
389 | { |
390 | if ($node->parent === null) { |
391 | return $grid->getNeighbors($node, MovementType::DIAGONAL_NO_OBSTACLE); |
392 | } |
393 | |
394 | $x = $node->getX(); |
395 | $y = $node->getY(); |
396 | |
397 | $px = $node->parent->getX(); |
398 | $py = $node->parent->getY(); |
399 | |
400 | /** @var int $dx */ |
401 | $dx = ($x - $px) / \max(\abs($x - $px), 1); |
402 | /** @var int $dy */ |
403 | $dy = ($y - $py) / \max(\abs($y - $py), 1); |
404 | |
405 | $neighbors = []; |
406 | if ($dx !== 0 && $dy !== 0) { |
407 | if ($grid->isWalkable($x, $y + $dy)) { |
408 | $neighbors[] = $grid->getNode($x, $y + $dy); |
409 | } |
410 | |
411 | if ($grid->isWalkable($x + $dx, $y)) { |
412 | $neighbors[] = $grid->getNode($x + $dx, $y); |
413 | } |
414 | |
415 | if ($grid->isWalkable($x, $y + $dy) || $grid->isWalkable($x + $dx, $y)) { |
416 | $neighbors[] = $grid->getNode($x + $dx, $y + $dy); |
417 | } |
418 | } elseif ($dx !== 0 && $dy === 0) { |
419 | $isNextWalkable = $grid->isWalkable($x + $dx, $y); |
420 | $isTopWalkable = $grid->isWalkable($x, $y + 1); |
421 | $isBottomWalkable = $grid->isWalkable($x, $y - 1); |
422 | |
423 | if ($isNextWalkable) { |
424 | $neighbors[] = $grid->getNode($x + $dx, $y); |
425 | if ($isTopWalkable) { |
426 | $neighbors[] = $grid->getNode($x + $dx, $y + 1); |
427 | } |
428 | |
429 | if ($isBottomWalkable) { |
430 | $neighbors[] = $grid->getNode($x + $dx, $y - 1); |
431 | } |
432 | } |
433 | |
434 | if ($isTopWalkable) { |
435 | $neighbors[] = $grid->getNode($x, $y + 1); |
436 | } |
437 | |
438 | if ($isBottomWalkable) { |
439 | $neighbors[] = $grid->getNode($x, $y - 1); |
440 | } |
441 | } elseif ($dx === 0 && $dy !== 0) { |
442 | $isNextWalkable = $grid->isWalkable($x, $y + $dy); |
443 | $isRightWalkable = $grid->isWalkable($x + 1, $y); |
444 | $isLeftWalkable = $grid->isWalkable($x - 1, $y); |
445 | |
446 | if ($isNextWalkable) { |
447 | $neighbors[] = $grid->getNode($x, $y + $dy); |
448 | if ($isRightWalkable) { |
449 | $neighbors[] = $grid->getNode($x + 1, $y + $dy); |
450 | } |
451 | |
452 | if ($isLeftWalkable) { |
453 | $neighbors[] = $grid->getNode($x - 1, $y + $dy); |
454 | } |
455 | } |
456 | |
457 | if ($isRightWalkable) { |
458 | $neighbors[] = $grid->getNode($x + 1, $y); |
459 | } |
460 | |
461 | if ($isLeftWalkable) { |
462 | $neighbors[] = $grid->getNode($x - 1, $y); |
463 | } |
464 | } |
465 | |
466 | /** @var JumpPointNode[] $neighbors */ |
467 | return $neighbors; |
468 | } |
469 | |
470 | /** |
471 | * Find next jump point |
472 | * |
473 | * @param null|JumpPointNode $node Node to find jump point from |
474 | * @param null|JumpPointNode $pNode Parent node |
475 | * @param JumpPointNode $endNode End node to find path to |
476 | * @param int $movement Movement type |
477 | * @param Grid $grid Grid of the nodes |
478 | * |
479 | * @return null|JumpPointNode |
480 | * |
481 | * @since 1.0.0 |
482 | */ |
483 | private static function jump(?JumpPointNode $node, ?JumpPointNode $pNode, JumpPointNode $endNode, int $movement, Grid $grid) : ?JumpPointNode |
484 | { |
485 | if ($movement === MovementType::STRAIGHT) { |
486 | return self::jumpStraight($node, $pNode, $endNode, $grid); |
487 | } elseif ($movement === MovementType::DIAGONAL) { |
488 | return self::jumpDiagonal($node, $pNode, $endNode, $grid); |
489 | } elseif ($movement === MovementType::DIAGONAL_ONE_OBSTACLE) { |
490 | return self::jumpDiagonalOneObstacle($node, $pNode, $endNode, $grid); |
491 | } |
492 | |
493 | return self::jumpDiagonalNoObstacle($node, $pNode, $endNode, $grid); |
494 | } |
495 | |
496 | /** |
497 | * Find next jump point |
498 | * |
499 | * @param null|JumpPointNode $node Node to find jump point from |
500 | * @param null|JumpPointNode $pNode Parent node |
501 | * @param JumpPointNode $endNode End node to find path to |
502 | * @param Grid $grid Grid of the nodes |
503 | * |
504 | * @return null|JumpPointNode |
505 | * |
506 | * @throws \Exception |
507 | * |
508 | * @since 1.0.0 |
509 | */ |
510 | private static function jumpStraight(?JumpPointNode $node, ?JumpPointNode $pNode, JumpPointNode $endNode, Grid $grid) : ?JumpPointNode |
511 | { |
512 | if ($node === null || $pNode === null || !$node->isWalkable) { |
513 | return null; |
514 | } |
515 | |
516 | $x = $node->getX(); |
517 | $y = $node->getY(); |
518 | |
519 | $dx = $x - $pNode->getX(); |
520 | $dy = $y - $pNode->getY(); |
521 | |
522 | // not always necessary but might be important for the future |
523 | $node->setTested(true); |
524 | |
525 | if ($node->isEqual($endNode)) { |
526 | return $node; |
527 | } |
528 | |
529 | if ($dx !== 0) { |
530 | if (($grid->isWalkable($x, $y - 1) && !$grid->isWalkable($x - $dx, $y - 1)) |
531 | || ($grid->isWalkable($x, $y + 1) && !$grid->isWalkable($x - $dx, $y + 1)) |
532 | ) { |
533 | return $node; |
534 | } |
535 | } elseif ($dy !== 0) { |
536 | if (($grid->isWalkable($x - 1, $y) && !$grid->isWalkable($x - 1, $y - $dy)) |
537 | || ($grid->isWalkable($x + 1, $y) && !$grid->isWalkable($x + 1, $y - $dy)) |
538 | ) { |
539 | return $node; |
540 | } |
541 | |
542 | if (self::jumpStraight($grid->getNode($x + 1, $y), $node, $endNode, $grid) !== null |
543 | || self::jumpStraight($grid->getNode($x - 1, $y), $node, $endNode, $grid) !== null |
544 | ) { |
545 | return $node; |
546 | } |
547 | } else { |
548 | throw new \Exception('invalid movement'); // @codeCoverageIgnore |
549 | } |
550 | |
551 | return self::jumpStraight($grid->getNode($x + $dx, $y + $dy), $node, $endNode, $grid); |
552 | } |
553 | |
554 | /** |
555 | * Find next jump point |
556 | * |
557 | * @param null|JumpPointNode $node Node to find jump point from |
558 | * @param null|JumpPointNode $pNode Parent node |
559 | * @param JumpPointNode $endNode End node to find path to |
560 | * @param Grid $grid Grid of the nodes |
561 | * |
562 | * @return null|JumpPointNode |
563 | * |
564 | * @since 1.0.0 |
565 | */ |
566 | private static function jumpDiagonal(?JumpPointNode $node, ?JumpPointNode $pNode, JumpPointNode $endNode, Grid $grid) : ?JumpPointNode |
567 | { |
568 | if ($node === null || $pNode === null || !$node->isWalkable) { |
569 | return null; |
570 | } |
571 | |
572 | $x = $node->getX(); |
573 | $y = $node->getY(); |
574 | |
575 | $dx = $x - $pNode->getX(); |
576 | $dy = $y - $pNode->getY(); |
577 | |
578 | // not always necessary but might be important for the future |
579 | $node->setTested(true); |
580 | |
581 | if ($node->isEqual($endNode)) { |
582 | return $node; |
583 | } |
584 | |
585 | if ($dx !== 0 && $dy !== 0) { |
586 | if (($grid->isWalkable($x - $dx, $y + $dy) && !$grid->isWalkable($x - $dx, $y)) |
587 | || ($grid->isWalkable($x + $dx, $y - $dy) && !$grid->isWalkable($x, $y - $dy)) |
588 | ) { |
589 | return $node; |
590 | } |
591 | |
592 | if (self::jumpDiagonal($grid->getNode($x + $dx, $y), $node, $endNode, $grid) !== null |
593 | || self::jumpDiagonal($grid->getNode($x, $y + $dy), $node, $endNode, $grid) !== null |
594 | ) { |
595 | return $node; |
596 | } |
597 | } elseif ($dx !== 0) { |
598 | if (($grid->isWalkable($x + $dx, $y + 1) && !$grid->isWalkable($x, $y + 1)) |
599 | || ($grid->isWalkable($x + $dx, $y - 1) && !$grid->isWalkable($x, $y - 1)) |
600 | ) { |
601 | return $node; |
602 | } |
603 | } elseif (($grid->isWalkable($x + 1, $y + $dy) && !$grid->isWalkable($x + 1, $y)) |
604 | || ($grid->isWalkable($x - 1, $y + $dy) && !$grid->isWalkable($x - 1, $y)) |
605 | ) { |
606 | return $node; |
607 | } |
608 | |
609 | return self::jumpDiagonal($grid->getNode($x + $dx, $y + $dy), $node, $endNode, $grid); |
610 | } |
611 | |
612 | /** |
613 | * Find next jump point |
614 | * |
615 | * @param null|JumpPointNode $node Node to find jump point from |
616 | * @param null|JumpPointNode $pNode Parent node |
617 | * @param JumpPointNode $endNode End node to find path to |
618 | * @param Grid $grid Grid of the nodes |
619 | * |
620 | * @return null|JumpPointNode |
621 | * |
622 | * @since 1.0.0 |
623 | */ |
624 | private static function jumpDiagonalOneObstacle(?JumpPointNode $node, ?JumpPointNode $pNode, JumpPointNode $endNode, Grid $grid) : ?JumpPointNode |
625 | { |
626 | if ($node === null || $pNode === null || !$node->isWalkable) { |
627 | return null; |
628 | } |
629 | |
630 | $x = $node->getX(); |
631 | $y = $node->getY(); |
632 | |
633 | $dx = $x - $pNode->getX(); |
634 | $dy = $y - $pNode->getY(); |
635 | |
636 | // not always necessary but might be important for the future |
637 | $node->setTested(true); |
638 | |
639 | if ($node->isEqual($endNode)) { |
640 | return $node; |
641 | } |
642 | |
643 | if ($dx !== 0 && $dy !== 0) { |
644 | if (($grid->isWalkable($x - $dx, $y + $dy) && !$grid->isWalkable($x - $dx, $y)) |
645 | || ($grid->isWalkable($x + $dx, $y - $dy) && !$grid->isWalkable($x, $y - $dy)) |
646 | ) { |
647 | return $node; |
648 | } |
649 | |
650 | if (self::jumpDiagonalOneObstacle($grid->getNode($x + $dx, $y), $node, $endNode, $grid) !== null |
651 | || self::jumpDiagonalOneObstacle($grid->getNode($x, $y + $dy), $node, $endNode, $grid) !== null |
652 | ) { |
653 | return $node; |
654 | } |
655 | } elseif ($dx !== 0) { |
656 | if (($grid->isWalkable($x + $dx, $y + 1) && !$grid->isWalkable($x, $y + 1)) |
657 | || ($grid->isWalkable($x + $dx, $y - 1) && !$grid->isWalkable($x, $y - 1)) |
658 | ) { |
659 | return $node; |
660 | } |
661 | } elseif (($grid->isWalkable($x + 1, $y + $dy) && !$grid->isWalkable($x + 1, $y)) |
662 | || ($grid->isWalkable($x - 1, $y + $dy) && !$grid->isWalkable($x - 1, $y)) |
663 | ) { |
664 | return $node; |
665 | } |
666 | |
667 | if ($grid->isWalkable($x + $dx, $y) || $grid->isWalkable($x, $y + $dy)) { |
668 | return self::jumpDiagonalOneObstacle($grid->getNode($x + $dx, $y + $dy), $node, $endNode, $grid); |
669 | } |
670 | |
671 | return null; |
672 | } |
673 | |
674 | /** |
675 | * Find next jump point |
676 | * |
677 | * @param null|JumpPointNode $node Node to find jump point from |
678 | * @param null|JumpPointNode $pNode Parent node |
679 | * @param JumpPointNode $endNode End node to find path to |
680 | * @param Grid $grid Grid of the nodes |
681 | * |
682 | * @return null|JumpPointNode |
683 | * |
684 | * @since 1.0.0 |
685 | */ |
686 | private static function jumpDiagonalNoObstacle(?JumpPointNode $node, ?JumpPointNode $pNode, JumpPointNode $endNode, Grid $grid) : ?JumpPointNode |
687 | { |
688 | if ($node === null || $pNode === null || !$node->isWalkable) { |
689 | return null; |
690 | } |
691 | |
692 | $x = $node->getX(); |
693 | $y = $node->getY(); |
694 | |
695 | $dx = $x - $pNode->getX(); |
696 | $dy = $y - $pNode->getY(); |
697 | |
698 | // not always necessary but might be important for the future |
699 | $node->setTested(true); |
700 | |
701 | if ($node->isEqual($endNode)) { |
702 | return $node; |
703 | } |
704 | |
705 | if ($dx !== 0 && $dy !== 0) { |
706 | if (self::jumpDiagonalNoObstacle($grid->getNode($x + $dx, $y), $node, $endNode, $grid) !== null |
707 | || self::jumpDiagonalNoObstacle($grid->getNode($x, $y + $dy), $node, $endNode, $grid) !== null |
708 | ) { |
709 | return $node; |
710 | } |
711 | } elseif ($dx !== 0) { |
712 | if (($grid->isWalkable($x, $y - 1) && !$grid->isWalkable($x - $dx, $y - 1)) |
713 | || ($grid->isWalkable($x, $y + 1) && !$grid->isWalkable($x - $dx, $y + 1)) |
714 | ) { |
715 | return $node; |
716 | } |
717 | } elseif ($dy !== 0) { |
718 | if (($grid->isWalkable($x - 1, $y) && !$grid->isWalkable($x - 1, $y - $dy)) |
719 | || ($grid->isWalkable($x + 1, $y) && !$grid->isWalkable($x + 1, $y - $dy)) |
720 | ) { |
721 | return $node; |
722 | } |
723 | } |
724 | |
725 | if ($grid->isWalkable($x + $dx, $y) || $grid->isWalkable($x, $y + $dy)) { |
726 | return self::jumpDiagonalNoObstacle($grid->getNode($x + $dx, $y + $dy), $node, $endNode, $grid); |
727 | } |
728 | |
729 | return null; |
730 | } |
731 | } |