Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_astar.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
37#include "nilorea/n_astar.h"
38#include <string.h>
39#include <math.h>
40
41/* Internal helpers */
42
44static inline int grid_index(const ASTAR_GRID* grid, int x, int y, int z) {
45 return (z * grid->height + y) * grid->width + x;
46}
47
49static inline int in_bounds(const ASTAR_GRID* grid, int x, int y, int z) {
50 return x >= 0 && x < grid->width &&
51 y >= 0 && y < grid->height &&
52 z >= 0 && z < grid->depth;
53}
54
61typedef struct ASTAR_BOX {
62 int minx, miny, minz;
63 int bw, bh, bd;
64} ASTAR_BOX;
65
67static inline int box_index(const ASTAR_BOX* b, int x, int y, int z) {
68 return ((z - b->minz) * b->bh + (y - b->miny)) * b->bw + (x - b->minx);
69}
70
72static inline int in_box(const ASTAR_BOX* b, int x, int y, int z) {
73 return x >= b->minx && x < b->minx + b->bw &&
74 y >= b->miny && y < b->miny + b->bh &&
75 z >= b->minz && z < b->minz + b->bd;
76}
77
79static inline int iabs(int v) {
80 return v < 0 ? -v : v;
81}
82
83/* Binary Min-Heap (priority queue) */
84
85static ASTAR_HEAP* heap_new(int capacity) {
86 ASTAR_HEAP* h = (ASTAR_HEAP*)calloc(1, sizeof(ASTAR_HEAP));
87 if (!h) return NULL;
88
89 h->data = (ASTAR_HEAP_NODE*)malloc((size_t)capacity * sizeof(ASTAR_HEAP_NODE));
90 if (!h->data) {
91 free(h);
92 return NULL;
93 }
94 h->size = 0;
95 h->capacity = capacity;
96 return h;
97}
98
99static void heap_free(ASTAR_HEAP* h) {
100 if (!h) return;
101 free(h->data);
102 free(h);
103}
104
105static int heap_grow(ASTAR_HEAP* h) {
106 int new_cap = h->capacity * 2;
107 ASTAR_HEAP_NODE* new_data = (ASTAR_HEAP_NODE*)realloc(
108 h->data, (size_t)new_cap * sizeof(ASTAR_HEAP_NODE));
109 if (!new_data) return 0;
110 h->data = new_data;
111 h->capacity = new_cap;
112 return 1;
113}
114
116 ASTAR_HEAP_NODE tmp = *a;
117 *a = *b;
118 *b = tmp;
119}
120
121static void heap_push(ASTAR_HEAP* h, int x, int y, int z, int f) {
122 if (h->size >= h->capacity) {
123 if (!heap_grow(h)) return;
124 }
125
126 int i = h->size++;
127 h->data[i].x = x;
128 h->data[i].y = y;
129 h->data[i].z = z;
130 h->data[i].f = f;
131
132 /* Sift up */
133 while (i > 0) {
134 int parent = (i - 1) / 2;
135 if (h->data[i].f < h->data[parent].f) {
136 heap_swap(&h->data[i], &h->data[parent]);
137 i = parent;
138 } else {
139 break;
140 }
141 }
142}
143
145 ASTAR_HEAP_NODE top = h->data[0];
146 h->data[0] = h->data[--h->size];
147
148 /* Sift down */
149 int i = 0;
150 for (;;) {
151 int left = 2 * i + 1;
152 int right = 2 * i + 2;
153 int smallest = i;
154
155 if (left < h->size && h->data[left].f < h->data[smallest].f)
156 smallest = left;
157 if (right < h->size && h->data[right].f < h->data[smallest].f)
158 smallest = right;
159
160 if (smallest != i) {
161 heap_swap(&h->data[i], &h->data[smallest]);
162 i = smallest;
163 } else {
164 break;
165 }
166 }
167
168 return top;
169}
170
171/* Heuristic Functions */
172
184int n_astar_heuristic(int x1, int y1, int z1, int x2, int y2, int z2, ASTAR_HEURISTIC heuristic) {
185 int dx = iabs(x2 - x1);
186 int dy = iabs(y2 - y1);
187 int dz = iabs(z2 - z1);
188
189 switch (heuristic) {
191 return (dx + dy + dz) * ASTAR_COST_CARDINAL;
192
194 double dist = sqrt((double)(dx * dx + dy * dy + dz * dz));
195 return (int)(dist * ASTAR_COST_CARDINAL);
196 }
197
199 default: {
200 int vals[3] = {dx, dy, dz};
201 /* Sort ascending */
202 if (vals[0] > vals[1]) {
203 int t = vals[0];
204 vals[0] = vals[1];
205 vals[1] = t;
206 }
207 if (vals[1] > vals[2]) {
208 int t = vals[1];
209 vals[1] = vals[2];
210 vals[2] = t;
211 }
212 if (vals[0] > vals[1]) {
213 int t = vals[0];
214 vals[0] = vals[1];
215 vals[1] = t;
216 }
217
218 int cost = vals[2] * ASTAR_COST_CARDINAL;
219 cost += vals[0] * (ASTAR_COST_DIAGONAL3D - ASTAR_COST_CARDINAL);
220 cost += (vals[1] - vals[0]) * (ASTAR_COST_DIAGONAL - ASTAR_COST_CARDINAL);
221 return cost;
222 }
223 }
224}
225
226/* Grid Management */
227
235ASTAR_GRID* n_astar_grid_new(int width, int height, int depth) {
236 if (width <= 0 || height <= 0 || depth <= 0) return NULL;
237
238 ASTAR_GRID* grid = (ASTAR_GRID*)calloc(1, sizeof(ASTAR_GRID));
239 if (!grid) return NULL;
240
241 grid->width = width;
242 grid->height = height;
243 grid->depth = depth;
244
245 size_t total = (size_t)width * (size_t)height * (size_t)depth;
246
247 grid->walkable = (uint8_t*)malloc(total);
248 if (!grid->walkable) {
249 free(grid);
250 return NULL;
251 }
252 memset(grid->walkable, 1, total);
253
254 grid->cost = (int*)malloc(total * sizeof(int));
255 if (!grid->cost) {
256 free(grid->walkable);
257 free(grid);
258 return NULL;
259 }
260 for (size_t i = 0; i < total; i++) {
261 grid->cost[i] = ASTAR_COST_CARDINAL;
262 }
263
264 return grid;
265}
266
272 if (!grid) return;
273 free(grid->walkable);
274 free(grid->cost);
275 free(grid);
276}
277
286void n_astar_grid_set_walkable(ASTAR_GRID* grid, int x, int y, int z, uint8_t walkable) {
287 if (!grid || !in_bounds(grid, x, y, z)) return;
288 grid->walkable[grid_index(grid, x, y, z)] = walkable;
289}
290
299uint8_t n_astar_grid_get_walkable(const ASTAR_GRID* grid, int x, int y, int z) {
300 if (!grid || !in_bounds(grid, x, y, z)) return 0;
301 return grid->walkable[grid_index(grid, x, y, z)];
302}
303
312void n_astar_grid_set_cost(ASTAR_GRID* grid, int x, int y, int z, int cost) {
313 if (!grid || !in_bounds(grid, x, y, z)) return;
314 grid->cost[grid_index(grid, x, y, z)] = cost;
315}
316
325int n_astar_grid_get_cost(const ASTAR_GRID* grid, int x, int y, int z) {
326 if (!grid || !in_bounds(grid, x, y, z)) return 0;
327 return grid->cost[grid_index(grid, x, y, z)];
328}
329
341 int x1,
342 int y1,
343 int z1,
344 int x2,
345 int y2,
346 int z2) {
347 if (!grid) return;
348 if (x1 > x2) {
349 int t = x1;
350 x1 = x2;
351 x2 = t;
352 }
353 if (y1 > y2) {
354 int t = y1;
355 y1 = y2;
356 y2 = t;
357 }
358 if (z1 > z2) {
359 int t = z1;
360 z1 = z2;
361 z2 = t;
362 }
363
364 for (int z = z1; z <= z2; z++)
365 for (int y = y1; y <= y2; y++)
366 for (int x = x1; x <= x2; x++)
367 n_astar_grid_set_walkable(grid, x, y, z, 0);
368}
369
370/* Path Reconstruction */
371
373static ASTAR_PATH* reconstruct_path(ASTAR_CELL* cells, const ASTAR_BOX* box, int sx, int sy, int sz, int gx, int gy, int gz) {
374 /* Count path length */
375 int length = 0;
376 int cx = gx, cy = gy, cz = gz;
377 while (cx != sx || cy != sy || cz != sz) {
378 length++;
379 int idx = box_index(box, cx, cy, cz);
380 int px = cells[idx].parent_x;
381 int py = cells[idx].parent_y;
382 int pz = cells[idx].parent_z;
383 cx = px;
384 cy = py;
385 cz = pz;
386 if (length > box->bw * box->bh * box->bd) {
387 return NULL;
388 }
389 }
390 length++;
391
392 ASTAR_PATH* path = (ASTAR_PATH*)calloc(1, sizeof(ASTAR_PATH));
393 if (!path) return NULL;
394
395 path->nodes = (ASTAR_NODE*)malloc((size_t)length * sizeof(ASTAR_NODE));
396 if (!path->nodes) {
397 free(path);
398 return NULL;
399 }
400 path->length = length;
401 path->cost = cells[box_index(box, gx, gy, gz)].g;
402
403 int i = length - 1;
404 cx = gx;
405 cy = gy;
406 cz = gz;
407 while (i >= 0) {
408 path->nodes[i].x = cx;
409 path->nodes[i].y = cy;
410 path->nodes[i].z = cz;
411 if (cx == sx && cy == sy && cz == sz) break;
412 int idx = box_index(box, cx, cy, cz);
413 int px = cells[idx].parent_x;
414 int py = cells[idx].parent_y;
415 int pz = cells[idx].parent_z;
416 cx = px;
417 cy = py;
418 cz = pz;
419 i--;
420 }
421
422 return path;
423}
424
425/* 2D Neighbor Directions */
426
427static const int dir2d_cardinal[][2] = {
428 {0, -1},
429 {0, 1},
430 {1, 0},
431 {-1, 0}};
432static const int dir2d_diagonal[][2] = {
433 {1, -1},
434 {-1, -1},
435 {1, 1},
436 {-1, 1}};
437
438/* 3D Neighbor Directions */
439
440static const int dir3d_cardinal[][3] = {
441 {1, 0, 0},
442 {-1, 0, 0},
443 {0, 1, 0},
444 {0, -1, 0},
445 {0, 0, 1},
446 {0, 0, -1}};
447static const int dir3d_diagonal[][3] = {
448 {1, 1, 0},
449 {1, -1, 0},
450 {-1, 1, 0},
451 {-1, -1, 0},
452 {1, 0, 1},
453 {1, 0, -1},
454 {-1, 0, 1},
455 {-1, 0, -1},
456 {0, 1, 1},
457 {0, 1, -1},
458 {0, -1, 1},
459 {0, -1, -1},
460 {1, 1, 1},
461 {1, 1, -1},
462 {1, -1, 1},
463 {1, -1, -1},
464 {-1, 1, 1},
465 {-1, 1, -1},
466 {-1, -1, 1},
467 {-1, -1, -1}};
468
469/* A* Core Algorithm */
470
486 int sx,
487 int sy,
488 int sz,
489 int gx,
490 int gy,
491 int gz,
492 int diagonal,
493 ASTAR_HEURISTIC heuristic) {
494 if (!grid) return NULL;
495 if (!in_bounds(grid, sx, sy, sz) || !in_bounds(grid, gx, gy, gz)) return NULL;
496 if (!grid->walkable[grid_index(grid, sx, sy, sz)]) return NULL;
497 if (!grid->walkable[grid_index(grid, gx, gy, gz)]) return NULL;
498
499 /* Trivial case */
500 if (sx == gx && sy == gy && sz == gz) {
501 ASTAR_PATH* path = (ASTAR_PATH*)calloc(1, sizeof(ASTAR_PATH));
502 if (!path) return NULL;
503 path->nodes = (ASTAR_NODE*)malloc(sizeof(ASTAR_NODE));
504 if (!path->nodes) {
505 free(path);
506 return NULL;
507 }
508 path->nodes[0].x = sx;
509 path->nodes[0].y = sy;
510 path->nodes[0].z = sz;
511 path->length = 1;
512 path->cost = 0;
513 return path;
514 }
515
516 int is_3d = (grid->depth > 1);
517
518 /* Window the per-call working set to the bounding box of (start, goal)
519 * expanded by ASTAR_SEARCH_MARGIN and clamped to the grid, instead of
520 * allocating one ASTAR_CELL per map tile. A 2400x2400 grid is 5.76M
521 * cells (~161 MB) per calloc/free — fired on every chase repath, this
522 * was the cause of multi-hundred-ms tick spikes. The grid's persistent
523 * walkable[] / cost[] planes stay full-size and keep using the absolute
524 * grid_index(); only the transient `cells` scratch is windowed, indexed
525 * by box_index(). So per-call cost scales with the path length, not the
526 * map size: a few KB for a short path. Trade-off: an optimal route that
527 * detours more than ASTAR_SEARCH_MARGIN tiles outside the start-goal box
528 * is not found through this window (returns NULL). For bounded game
529 * paths that never happens; raise ASTAR_SEARCH_MARGIN if a caller needs
530 * wider detours.
531 *
532 * calloc keeps zero-initialised cells (ASTAR_NODE_NONE == 0 == the
533 * "unvisited" state); the search only touches the handful of cells it
534 * explores, and reconstruct_path walks parents it has already written. */
535 ASTAR_BOX box;
536 {
537 int lo = (sx < gx) ? sx : gx, hi = (sx > gx) ? sx : gx;
538 box.minx = lo - ASTAR_SEARCH_MARGIN;
539 if (box.minx < 0) box.minx = 0;
541 if (hi > grid->width - 1) hi = grid->width - 1;
542 box.bw = hi - box.minx + 1;
543
544 lo = (sy < gy) ? sy : gy;
545 hi = (sy > gy) ? sy : gy;
546 box.miny = lo - ASTAR_SEARCH_MARGIN;
547 if (box.miny < 0) box.miny = 0;
549 if (hi > grid->height - 1) hi = grid->height - 1;
550 box.bh = hi - box.miny + 1;
551
552 if (is_3d) {
553 lo = (sz < gz) ? sz : gz;
554 hi = (sz > gz) ? sz : gz;
555 box.minz = lo - ASTAR_SEARCH_MARGIN;
556 if (box.minz < 0) box.minz = 0;
558 if (hi > grid->depth - 1) hi = grid->depth - 1;
559 box.bd = hi - box.minz + 1;
560 } else {
561 box.minz = 0;
562 box.bd = 1;
563 }
564 }
565
566 size_t btotal = (size_t)box.bw * (size_t)box.bh * (size_t)box.bd;
567 ASTAR_CELL* cells = (ASTAR_CELL*)calloc(btotal, sizeof(ASTAR_CELL));
568 if (!cells) return NULL;
569
570 ASTAR_HEAP* open = heap_new(256);
571 if (!open) {
572 free(cells);
573 return NULL;
574 }
575
576 int si = box_index(&box, sx, sy, sz);
577 cells[si].g = 0;
578 cells[si].h = n_astar_heuristic(sx, sy, sz, gx, gy, gz, heuristic);
579 cells[si].f = cells[si].h;
580 cells[si].status = ASTAR_NODE_OPEN;
581 heap_push(open, sx, sy, sz, cells[si].f);
582
583 ASTAR_PATH* result = NULL;
584
585 while (open->size > 0) {
586 ASTAR_HEAP_NODE current = heap_pop(open);
587 int cx = current.x, cy = current.y, cz = current.z;
588 int ci = grid_index(grid, cx, cy, cz); /* absolute: cost[] */
589 int lci = box_index(&box, cx, cy, cz); /* window-local: cells[] */
590
591 if (cells[lci].status == ASTAR_NODE_CLOSED)
592 continue;
593
594 if (cx == gx && cy == gy && cz == gz) {
595 result = reconstruct_path(cells, &box, sx, sy, sz, gx, gy, gz);
596 break;
597 }
598
599 cells[lci].status = ASTAR_NODE_CLOSED;
600
601 if (is_3d) {
602 /* 3D: 6 cardinal directions */
603 for (int d = 0; d < 6; d++) {
604 int nx = cx + dir3d_cardinal[d][0];
605 int ny = cy + dir3d_cardinal[d][1];
606 int nz = cz + dir3d_cardinal[d][2];
607 if (!in_box(&box, nx, ny, nz)) continue;
608
609 int ni = grid_index(grid, nx, ny, nz);
610 int lni = box_index(&box, nx, ny, nz);
611 if (!grid->walkable[ni] || cells[lni].status == ASTAR_NODE_CLOSED)
612 continue;
613
614 int move_cost = (grid->cost[ci] + grid->cost[ni]) / 2;
615 int tentative_g = cells[lci].g + move_cost;
616
617 if (cells[lni].status == ASTAR_NODE_NONE || tentative_g < cells[lni].g) {
618 cells[lni].g = tentative_g;
619 cells[lni].h = n_astar_heuristic(nx, ny, nz, gx, gy, gz, heuristic);
620 cells[lni].f = tentative_g + cells[lni].h;
621 cells[lni].parent_x = cx;
622 cells[lni].parent_y = cy;
623 cells[lni].parent_z = cz;
624 cells[lni].status = ASTAR_NODE_OPEN;
625 heap_push(open, nx, ny, nz, cells[lni].f);
626 }
627 }
628
629 /* 3D: 20 diagonal directions */
630 if (diagonal) {
631 for (int d = 0; d < 20; d++) {
632 int ddx = dir3d_diagonal[d][0];
633 int ddy = dir3d_diagonal[d][1];
634 int ddz = dir3d_diagonal[d][2];
635 int nx = cx + ddx;
636 int ny = cy + ddy;
637 int nz = cz + ddz;
638 if (!in_box(&box, nx, ny, nz)) continue;
639
640 int ni = grid_index(grid, nx, ny, nz);
641 int lni = box_index(&box, nx, ny, nz);
642 if (!grid->walkable[ni] || cells[lni].status == ASTAR_NODE_CLOSED)
643 continue;
644
645 /* Corner-cutting check */
646 int blocked = 0;
647 if (ddx != 0 && ddy != 0 && !grid->walkable[grid_index(grid, cx + ddx, cy, cz)])
648 blocked = 1;
649 if (ddx != 0 && ddy != 0 && !grid->walkable[grid_index(grid, cx, cy + ddy, cz)])
650 blocked = 1;
651 if (ddx != 0 && ddz != 0 && !grid->walkable[grid_index(grid, cx + ddx, cy, cz)])
652 blocked = 1;
653 if (ddx != 0 && ddz != 0 && !grid->walkable[grid_index(grid, cx, cy, cz + ddz)])
654 blocked = 1;
655 if (ddy != 0 && ddz != 0 && !grid->walkable[grid_index(grid, cx, cy + ddy, cz)])
656 blocked = 1;
657 if (ddy != 0 && ddz != 0 && !grid->walkable[grid_index(grid, cx, cy, cz + ddz)])
658 blocked = 1;
659 if (blocked) continue;
660
661 int axes = (ddx != 0) + (ddy != 0) + (ddz != 0);
662 int base_cost;
663 if (axes == 3)
664 base_cost = ASTAR_COST_DIAGONAL3D;
665 else
666 base_cost = ASTAR_COST_DIAGONAL;
667
668 int cell_cost = (grid->cost[ci] + grid->cost[ni]) / 2;
669 int move_cost = (base_cost * cell_cost) / ASTAR_COST_CARDINAL;
670 int tentative_g = cells[lci].g + move_cost;
671
672 if (cells[lni].status == ASTAR_NODE_NONE || tentative_g < cells[lni].g) {
673 cells[lni].g = tentative_g;
674 cells[lni].h = n_astar_heuristic(nx, ny, nz, gx, gy, gz, heuristic);
675 cells[lni].f = tentative_g + cells[lni].h;
676 cells[lni].parent_x = cx;
677 cells[lni].parent_y = cy;
678 cells[lni].parent_z = cz;
679 cells[lni].status = ASTAR_NODE_OPEN;
680 heap_push(open, nx, ny, nz, cells[lni].f);
681 }
682 }
683 }
684 } else {
685 /* 2D: 4 cardinal directions */
686 for (int d = 0; d < 4; d++) {
687 int nx = cx + dir2d_cardinal[d][0];
688 int ny = cy + dir2d_cardinal[d][1];
689 if (!in_box(&box, nx, ny, 0)) continue;
690
691 int ni = grid_index(grid, nx, ny, 0);
692 int lni = box_index(&box, nx, ny, 0);
693 if (!grid->walkable[ni] || cells[lni].status == ASTAR_NODE_CLOSED)
694 continue;
695
696 int move_cost = (grid->cost[ci] + grid->cost[ni]) / 2;
697 int tentative_g = cells[lci].g + move_cost;
698
699 if (cells[lni].status == ASTAR_NODE_NONE || tentative_g < cells[lni].g) {
700 cells[lni].g = tentative_g;
701 cells[lni].h = n_astar_heuristic(nx, ny, 0, gx, gy, 0, heuristic);
702 cells[lni].f = tentative_g + cells[lni].h;
703 cells[lni].parent_x = cx;
704 cells[lni].parent_y = cy;
705 cells[lni].parent_z = 0;
706 cells[lni].status = ASTAR_NODE_OPEN;
707 heap_push(open, nx, ny, 0, cells[lni].f);
708 }
709 }
710
711 /* 2D: 4 diagonal directions */
712 if (diagonal) {
713 for (int d = 0; d < 4; d++) {
714 int ddx = dir2d_diagonal[d][0];
715 int ddy = dir2d_diagonal[d][1];
716 int nx = cx + ddx;
717 int ny = cy + ddy;
718 if (!in_box(&box, nx, ny, 0)) continue;
719
720 int ni = grid_index(grid, nx, ny, 0);
721 int lni = box_index(&box, nx, ny, 0);
722 if (!grid->walkable[ni] || cells[lni].status == ASTAR_NODE_CLOSED)
723 continue;
724
725 if (!grid->walkable[grid_index(grid, cx + ddx, cy, 0)] ||
726 !grid->walkable[grid_index(grid, cx, cy + ddy, 0)])
727 continue;
728
729 int cell_cost = (grid->cost[ci] + grid->cost[ni]) / 2;
730 int move_cost = (ASTAR_COST_DIAGONAL * cell_cost) / ASTAR_COST_CARDINAL;
731 int tentative_g = cells[lci].g + move_cost;
732
733 if (cells[lni].status == ASTAR_NODE_NONE || tentative_g < cells[lni].g) {
734 cells[lni].g = tentative_g;
735 cells[lni].h = n_astar_heuristic(nx, ny, 0, gx, gy, 0, heuristic);
736 cells[lni].f = tentative_g + cells[lni].h;
737 cells[lni].parent_x = cx;
738 cells[lni].parent_y = cy;
739 cells[lni].parent_z = 0;
740 cells[lni].status = ASTAR_NODE_OPEN;
741 heap_push(open, nx, ny, 0, cells[lni].f);
742 }
743 }
744 }
745 }
746 }
747
748 heap_free(open);
749 free(cells);
750 return result;
751}
752
753/* Path Cleanup */
754
760 if (!path) return;
761 free(path->nodes);
762 free(path);
763}
int f
priority (f = g + h)
Definition n_astar.h:142
int capacity
allocated capacity
Definition n_astar.h:149
int parent_z
parent cell Z
Definition n_astar.h:135
int parent_x
parent cell X (-1 if none)
Definition n_astar.h:133
int x
grid X coordinate
Definition n_astar.h:116
int depth
grid depth (Z axis, 1 for 2D)
Definition n_astar.h:156
int cost
total path cost (x1000 fixed-point)
Definition n_astar.h:125
uint8_t status
ASTAR_NODE_NONE / OPEN / CLOSED.
Definition n_astar.h:136
int width
grid width (X axis)
Definition n_astar.h:154
int f
g + h
Definition n_astar.h:132
ASTAR_NODE * nodes
array of path nodes from start to goal
Definition n_astar.h:123
int y
grid Y coordinate
Definition n_astar.h:117
int height
grid height (Y axis)
Definition n_astar.h:155
int g
cost from start to this cell
Definition n_astar.h:130
int z
grid coordinates
Definition n_astar.h:141
uint8_t * walkable
walkability map: 1=passable, 0=blocked
Definition n_astar.h:157
int * cost
per-cell movement cost multiplier (x1000)
Definition n_astar.h:158
ASTAR_HEAP_NODE * data
heap array
Definition n_astar.h:147
int size
current number of elements
Definition n_astar.h:148
int h
heuristic estimate to goal
Definition n_astar.h:131
int parent_y
parent cell Y
Definition n_astar.h:134
int z
grid Z coordinate (0 for 2D)
Definition n_astar.h:118
int length
number of nodes in the path
Definition n_astar.h:124
#define ASTAR_NODE_OPEN
Node is in the open list.
Definition n_astar.h:88
int n_astar_grid_get_cost(const ASTAR_GRID *grid, int x, int y, int z)
Get a cell's movement cost multiplier.
Definition n_astar.c:325
#define ASTAR_NODE_CLOSED
Node has been fully evaluated.
Definition n_astar.h:90
ASTAR_PATH * n_astar_find_path(const ASTAR_GRID *grid, int sx, int sy, int sz, int gx, int gy, int gz, int diagonal, ASTAR_HEURISTIC heuristic)
Find a path using A* search.
Definition n_astar.c:485
void n_astar_grid_set_cost(ASTAR_GRID *grid, int x, int y, int z, int cost)
Set a cell's movement cost multiplier.
Definition n_astar.c:312
uint8_t n_astar_grid_get_walkable(const ASTAR_GRID *grid, int x, int y, int z)
Get a cell's walkability.
Definition n_astar.c:299
ASTAR_HEURISTIC
Heuristic function selection for h(n) estimation.
Definition n_astar.h:106
void n_astar_grid_free(ASTAR_GRID *grid)
Free a grid and all its internal data.
Definition n_astar.c:271
#define ASTAR_COST_CARDINAL
Default cost for straight movement (fixed-point x1000)
Definition n_astar.h:79
int n_astar_heuristic(int x1, int y1, int z1, int x2, int y2, int z2, ASTAR_HEURISTIC heuristic)
Compute heuristic distance between two 3D points.
Definition n_astar.c:184
void n_astar_grid_set_rect_blocked(ASTAR_GRID *grid, int x1, int y1, int z1, int x2, int y2, int z2)
Set a rectangular region as blocked (wall)
Definition n_astar.c:340
void n_astar_path_free(ASTAR_PATH *path)
Free a path returned by n_astar_find_path.
Definition n_astar.c:759
#define ASTAR_SEARCH_MARGIN
Search-window margin (in cells) added around the bounding box of the start and goal.
Definition n_astar.h:100
#define ASTAR_COST_DIAGONAL3D
Default cost for 3D diagonal movement (sqrt(3)*1000)
Definition n_astar.h:83
#define ASTAR_COST_DIAGONAL
Default cost for 2D diagonal movement (sqrt(2)*1000)
Definition n_astar.h:81
void n_astar_grid_set_walkable(ASTAR_GRID *grid, int x, int y, int z, uint8_t walkable)
Set a cell's walkability.
Definition n_astar.c:286
ASTAR_GRID * n_astar_grid_new(int width, int height, int depth)
Create a new grid for A* pathfinding.
Definition n_astar.c:235
#define ASTAR_NODE_NONE
Node has not been visited.
Definition n_astar.h:86
@ ASTAR_HEURISTIC_EUCLIDEAN
straight-line distance
Definition n_astar.h:108
@ ASTAR_HEURISTIC_CHEBYSHEV
max of axis deltas (optimal for 8-dir)
Definition n_astar.h:109
@ ASTAR_HEURISTIC_MANHATTAN
sum of axis deltas (optimal for 4-dir)
Definition n_astar.h:107
Internal node data used during pathfinding.
Definition n_astar.h:129
Grid structure holding walkability, costs, and dimensions.
Definition n_astar.h:153
Binary min-heap (priority queue) for the open list.
Definition n_astar.h:146
Min-heap entry for the open list priority queue.
Definition n_astar.h:140
A single node in the resulting path.
Definition n_astar.h:115
The computed path result.
Definition n_astar.h:122
int bd
window dimensions in cells
Definition n_astar.c:63
static int box_index(const ASTAR_BOX *b, int x, int y, int z)
Flat index into the window-local ASTAR_CELL array.
Definition n_astar.c:67
static void heap_swap(ASTAR_HEAP_NODE *a, ASTAR_HEAP_NODE *b)
Definition n_astar.c:115
static int in_box(const ASTAR_BOX *b, int x, int y, int z)
Check if coordinates fall inside the search window.
Definition n_astar.c:72
static int heap_grow(ASTAR_HEAP *h)
Definition n_astar.c:105
static void heap_free(ASTAR_HEAP *h)
Definition n_astar.c:99
static int grid_index(const ASTAR_GRID *grid, int x, int y, int z)
Convert 3D coordinates to flat array index.
Definition n_astar.c:44
static const int dir2d_diagonal[][2]
Definition n_astar.c:432
static int iabs(int v)
Absolute value for integers.
Definition n_astar.c:79
static const int dir3d_cardinal[][3]
Definition n_astar.c:440
int minx
Definition n_astar.c:62
static const int dir3d_diagonal[][3]
Definition n_astar.c:447
int bw
Definition n_astar.c:63
static ASTAR_HEAP * heap_new(int capacity)
Definition n_astar.c:85
static ASTAR_PATH * reconstruct_path(ASTAR_CELL *cells, const ASTAR_BOX *box, int sx, int sy, int sz, int gx, int gy, int gz)
Build path from goal back to start following parent pointers.
Definition n_astar.c:373
static void heap_push(ASTAR_HEAP *h, int x, int y, int z, int f)
Definition n_astar.c:121
static ASTAR_HEAP_NODE heap_pop(ASTAR_HEAP *h)
Definition n_astar.c:144
int bh
Definition n_astar.c:63
static const int dir2d_cardinal[][2]
Definition n_astar.c:427
int minz
window origin (absolute grid coords)
Definition n_astar.c:62
static int in_bounds(const ASTAR_GRID *grid, int x, int y, int z)
Check if coordinates are within grid bounds.
Definition n_astar.c:49
int miny
Definition n_astar.c:62
Search window: the bounding box of (start, goal) expanded by ASTAR_SEARCH_MARGIN and clamped to the g...
Definition n_astar.c:61
A* Pathfinding API for 2D and 3D grids.